diff --git a/.eslintrc b/.eslintrc index 75fcbf094f..a656e80d38 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,14 +16,11 @@ "_": true, "andThen": true, "asyncRender": true, - "asyncTestDiscourse": true, "Blob": true, "bootbox": true, "click": true, "waitUntil": true, "getSettledState": true, - "collapseSelectKit": true, - "controllerFor": true, "count": true, "currentPath": true, "currentRouteName": true, @@ -32,11 +29,9 @@ "Discourse": true, "Ember": true, "exists": true, - "expandSelectKit": true, "File": true, "fillIn": true, "find": true, - "fixture": true, "Handlebars": true, "hasModule": true, "I18n": true, @@ -53,14 +48,6 @@ "requirejs": true, "RSVP": true, "sandbox": true, - "selectKit": true, - "selectKitFillInFilter": true, - "selectKitSelectNoneRow": true, - "selectKitSelectRowByIndex": true, - "selectKitSelectRowByName": true, - "selectKitSelectRowByValue": true, - "setTextareaSelection": true, - "getTextareaSelection": true, "sinon": true, "test": true, "triggerEvent": true, diff --git a/Gemfile b/Gemfile index 9e1e63ea2b..19deecf88f 100644 --- a/Gemfile +++ b/Gemfile @@ -46,7 +46,7 @@ gem 'redis-namespace' gem 'active_model_serializers', '~> 0.8.3' -gem 'onebox', '1.8.90' +gem 'onebox', '1.8.92' gem 'http_accept_language', '~>2.0.5', require: false @@ -89,8 +89,7 @@ gem 'omniauth-github' gem 'omniauth-oauth2', require: false -# pinned until we test verified email change in the gem -gem 'omniauth-google-oauth2', '0.6.0' +gem 'omniauth-google-oauth2' gem 'oj' gem 'pg' @@ -145,6 +144,7 @@ group :test, :development do gem 'byebug', require: ENV['RM_INFO'].nil? gem 'rubocop', require: false gem 'parallel_tests' + gem 'diffy', require: false end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 0f52f73de6..0fa35801f0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -90,6 +90,7 @@ GEM crass (1.0.4) debug_inspector (0.0.3) diff-lcs (1.3) + diffy (3.3.0) discourse-ember-source (3.8.0.1) discourse_image_optim (0.26.2) exifr (~> 1.2, >= 1.2.2) @@ -151,7 +152,7 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.2.0) - jwt (2.1.0) + jwt (2.2.1) kgio (2.11.2) libv8 (7.3.492.27.1) listen (3.1.5) @@ -196,7 +197,7 @@ GEM msgpack (1.2.10) multi_json (1.13.1) multi_xml (0.6.0) - multipart-post (2.0.0) + multipart-post (2.1.1) mustache (1.1.0) nokogiri (1.10.3) mini_portile2 (~> 2.4.0) @@ -218,7 +219,7 @@ GEM omniauth-github (1.3.0) omniauth (~> 1.5) omniauth-oauth2 (>= 1.4.0, < 2.0) - omniauth-google-oauth2 (0.6.0) + omniauth-google-oauth2 (0.7.0) jwt (>= 2.0) omniauth (>= 1.1.1) omniauth-oauth2 (>= 1.5) @@ -237,7 +238,7 @@ GEM omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.90) + onebox (1.8.92) htmlentities (~> 4.3) moneta (~> 1.0) multi_json (~> 1.11) @@ -435,6 +436,7 @@ DEPENDENCIES certified colored2 cppjieba_rb + diffy discourse-ember-source (~> 3.8.0) discourse_image_optim email_reply_trimmer (~> 0.1) @@ -479,12 +481,12 @@ DEPENDENCIES omniauth omniauth-facebook omniauth-github - omniauth-google-oauth2 (= 0.6.0) + omniauth-google-oauth2 omniauth-instagram omniauth-oauth2 omniauth-openid omniauth-twitter - onebox (= 1.8.90) + onebox (= 1.8.92) openid-redis-store parallel_tests pg diff --git a/app/assets/javascripts/admin/components/admin-report-table.js.es6 b/app/assets/javascripts/admin/components/admin-report-table.js.es6 index 0b52599a9b..6e1e22c172 100644 --- a/app/assets/javascripts/admin/components/admin-report-table.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-table.js.es6 @@ -31,6 +31,21 @@ export default Ember.Component.extend({ return reportTotal && total && twoColumns; }, + @computed("model.{average,data}", "totalsForSample.1.value", "twoColumns") + showAverage(model, sampleTotalValue, hasTwoColumns) { + return ( + model.average && + model.data.length > 0 && + sampleTotalValue && + hasTwoColumns + ); + }, + + @computed("totalsForSample.1.value", "model.data.length") + averageForSample(totals, count) { + return (totals / count).toFixed(0); + }, + @computed("model.data.length") showSortingUI(dataLength) { return dataLength >= 5; diff --git a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 index b17b5a2345..c1cd77b63b 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 @@ -1,51 +1,63 @@ import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; import StaffActionLog from "admin/models/staff-action-log"; -import computed from "ember-addons/ember-computed-decorators"; +import { + default as computed, + on +} from "ember-addons/ember-computed-decorators"; export default Ember.Controller.extend({ loading: false, filters: null, + userHistoryActions: [], + model: null, + nextPage: 0, + lastPage: null, + filtersExists: Ember.computed.gt("filterCount", 0), - - init() { - this._super(...arguments); - - this.userHistoryActions = []; - }, - - filterActionIdChanged: function() { - const filterActionId = this.filterActionId; - if (filterActionId) { - this._changeFilters({ - action_name: filterActionId, - action_id: this.userHistoryActions.findBy("id", filterActionId) - .action_id - }); - } - }.observes("filterActionId"), + showTable: Ember.computed.gt("model.length", 0), @computed("filters.action_name") actionFilter(name) { - if (name) { - return I18n.t("admin.logs.staff_actions.actions." + name); - } else { - return null; - } + return name ? I18n.t("admin.logs.staff_actions.actions." + name) : null; }, - showInstructions: Ember.computed.gt("model.length", 0), + @on("init") + resetFilters() { + this.setProperties({ + filters: Ember.Object.create(), + model: [], + nextPage: 0, + lastPage: null + }); + this.scheduleRefresh(); + }, + + _changeFilters(props) { + this.filters.setProperties(props); + this.setProperties({ + model: [], + nextPage: 0, + lastPage: null + }); + this.scheduleRefresh(); + }, _refresh() { + if (this.lastPage && this.nextPage >= this.lastPage) { + return; + } + this.set("loading", true); - var filters = this.filters, - params = {}, - count = 0; + const page = this.nextPage; + let filters = this.filters; + let params = { page }; + let count = 0; // Don't send null values - Object.keys(filters).forEach(function(k) { - var val = filters.get(k); + Object.keys(filters).forEach(k => { + let val = filters.get(k); if (val) { params[k] = val; count += 1; @@ -55,42 +67,49 @@ export default Ember.Controller.extend({ StaffActionLog.findAll(params) .then(result => { - this.set("model", result.staff_action_logs); + this.setProperties({ + model: this.model.concat(result.staff_action_logs), + nextPage: page + 1 + }); + + if (result.staff_action_logs.length === 0) { + this.set("lastPage", page); + } + if (this.userHistoryActions.length === 0) { - let actionTypes = result.user_history_actions.map(action => { - return { - id: action.id, - action_id: action.action_id, - name: I18n.t("admin.logs.staff_actions.actions." + action.id), - name_raw: action.id - }; - }); - actionTypes = _.sortBy(actionTypes, row => row.name); - this.set("userHistoryActions", actionTypes); + this.set( + "userHistoryActions", + result.user_history_actions + .map(action => ({ + id: action.id, + action_id: action.action_id, + name: I18n.t("admin.logs.staff_actions.actions." + action.id), + name_raw: action.id + })) + .sort((a, b) => (a.name > b.name ? 1 : -1)) + ); } }) - .finally(() => { - this.set("loading", false); - }); + .finally(() => this.set("loading", false)); }, scheduleRefresh() { Ember.run.scheduleOnce("afterRender", this, this._refresh); }, - resetFilters: function() { - this.set("filters", Ember.Object.create()); - this.scheduleRefresh(); - }.on("init"), - - _changeFilters: function(props) { - this.filters.setProperties(props); - this.scheduleRefresh(); - }, - actions: { - clearFilter: function(key) { - var changed = {}; + filterActionIdChanged(filterActionId) { + if (filterActionId) { + this._changeFilters({ + action_name: filterActionId, + action_id: this.userHistoryActions.findBy("id", filterActionId) + .action_id + }); + } + }, + + clearFilter(key) { + let changed = {}; // Special case, clear all action related stuff if (key === "actionFilter") { @@ -109,7 +128,7 @@ export default Ember.Controller.extend({ this.resetFilters(); }, - filterByAction: function(logItem) { + filterByAction(logItem) { this._changeFilters({ action_name: logItem.get("action_name"), action_id: logItem.get("action"), @@ -117,20 +136,24 @@ export default Ember.Controller.extend({ }); }, - filterByStaffUser: function(acting_user) { + filterByStaffUser(acting_user) { this._changeFilters({ acting_user: acting_user.username }); }, - filterByTargetUser: function(target_user) { + filterByTargetUser(target_user) { this._changeFilters({ target_user: target_user.username }); }, - filterBySubject: function(subject) { + filterBySubject(subject) { this._changeFilters({ subject: subject }); }, - exportStaffActionLogs: function() { + exportStaffActionLogs() { exportEntity("staff_action").then(outputExportResult); + }, + + loadMore() { + this._refresh(); } } }); diff --git a/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 index 15688a166a..a81e8caef7 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 @@ -6,104 +6,10 @@ import { observes } from "ember-addons/ember-computed-decorators"; import { THEMES, COMPONENTS } from "admin/models/theme"; +import { POPULAR_THEMES } from "discourse-common/helpers/popular-themes"; const MIN_NAME_LENGTH = 4; -// TODO: use a central repository for themes/components -const POPULAR_THEMES = [ - { - name: "Graceful", - value: "https://github.com/discourse/graceful", - preview: "https://theme-creator.discourse.org/theme/awesomerobot/graceful", - description: "A light and graceful theme for Discourse.", - meta_url: - "https://meta.discourse.org/t/a-graceful-theme-for-discourse/93040" - }, - { - name: "Material Design Theme", - value: "https://github.com/discourse/material-design-stock-theme", - preview: "https://newmaterial.trydiscourse.com", - description: - "Inspired by Material Design, this theme comes with several color palettes (incl. a dark one).", - meta_url: "https://meta.discourse.org/t/material-design-stock-theme/47142" - }, - { - name: "Minima", - value: "https://github.com/discourse/minima", - preview: "https://theme-creator.discourse.org/theme/awesomerobot/minima", - description: "A minimal theme with reduced UI elements and focus on text.", - meta_url: - "https://meta.discourse.org/t/minima-a-minimal-theme-for-discourse/108178" - }, - { - name: "Sam's Simple Theme", - value: "https://github.com/discourse/discourse-simple-theme", - preview: "https://theme-creator.discourse.org/theme/sam/simple", - description: - "Simplified front page design with classic colors and typography.", - meta_url: - "https://meta.discourse.org/t/sams-personal-minimal-topic-list-design/23552" - }, - { - name: "Vincent", - value: "https://github.com/discourse/discourse-vincent-theme", - preview: "https://theme-creator.discourse.org/theme/awesomerobot/vincent", - description: "An elegant dark theme with a few color palettes.", - meta_url: "https://meta.discourse.org/t/discourse-vincent-theme/76662" - }, - { - name: "Alternative Logos", - value: "https://github.com/discourse/discourse-alt-logo", - description: "Add alternative logos for dark / light themes.", - meta_url: - "https://meta.discourse.org/t/alternative-logo-for-dark-themes/88502", - component: true - }, - { - name: "Brand Header Theme Component", - value: "https://github.com/discourse/discourse-brand-header", - description: - "Add an extra top header with your logo, navigation links and social icons.", - meta_url: "https://meta.discourse.org/t/brand-header-theme-component/77977", - component: true - }, - { - name: "Custom Header Links", - value: "https://github.com/discourse/discourse-custom-header-links", - preview: - "https://theme-creator.discourse.org/theme/Johani/custom-header-links", - description: "Easily add custom text-based links to the header.", - meta_url: "https://meta.discourse.org/t/custom-header-links/90588", - component: true - }, - { - name: "Category Banners", - value: "https://github.com/discourse/discourse-category-banners", - preview: - "https://theme-creator.discourse.org/theme/awesomerobot/discourse-category-banners", - description: - "Show banners on category pages using your existing category details.", - meta_url: "https://meta.discourse.org/t/discourse-category-banners/86241", - component: true - }, - { - name: "Hamburger Theme Selector", - value: "https://github.com/discourse/discourse-hamburger-theme-selector", - description: - "Displays a theme selector in the hamburger menu provided there is more than one user-selectable theme.", - meta_url: "https://meta.discourse.org/t/hamburger-theme-selector/61210", - component: true - }, - { - name: "Header submenus", - value: "https://github.com/discourse/discourse-header-submenus", - preview: "https://theme-creator.discourse.org/theme/Johani/header-submenus", - description: "Lets you build a header menu with submenus (dropdowns).", - meta_url: "https://meta.discourse.org/t/header-submenus/94584", - component: true - } -]; - export default Ember.Controller.extend(ModalFunctionality, { popular: Ember.computed.equal("selection", "popular"), local: Ember.computed.equal("selection", "local"), diff --git a/app/assets/javascripts/admin/templates/components/admin-report-table.hbs b/app/assets/javascripts/admin/templates/components/admin-report-table.hbs index 6011407673..6c7838e872 100644 --- a/app/assets/javascripts/admin/templates/components/admin-report-table.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-report-table.hbs @@ -48,6 +48,18 @@ {{number model.total}} {{/if}} + + {{#if showAverage}} + + + {{i18n 'admin.dashboard.reports.average_for_sample'}} + + + + — + {{number averageForSample}} + + {{/if}} diff --git a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs index dc7438e1ec..d072bfec45 100644 --- a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs +++ b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs @@ -30,7 +30,7 @@ {{/if}} {{else}} - {{i18n "admin.logs.staff_actions.filter"}} {{combo-box content=userHistoryActions value=filterActionId none="admin.logs.staff_actions.all"}} + {{i18n "admin.logs.staff_actions.filter"}} {{combo-box content=userHistoryActions value=filterActionId none="admin.logs.staff_actions.all" onSelect=(action "filterActionIdChanged")}} {{/if}} {{d-button class="btn-default" action=(action "exportStaffActionLogs") label="admin.export_csv.button_text" icon="download"}} @@ -38,67 +38,71 @@
{{#staff-actions}} - {{#conditional-loading-spinner condition=loading}} - +{{#load-more selector=".staff-logs tr" action=(action "loadMore")}} + {{#if showTable}} +
- - - - - - - - + + + + + + + + - + + {{#each model as |item|}} + + + + - - - + + + + + {{/each}} + - {{#if item.target_user}} - {{#link-to 'adminUser' item.target_user}}{{avatar item.target_user imageSize="tiny"}}{{/link-to}} - {{item.target_user.username}} - {{/if}} - {{#if item.subject}} - {{item.subject}} - {{/if}} - - - - - - - {{else}} - {{i18n 'search.no_results'}} - {{/each}} - -
{{i18n 'admin.logs.staff_actions.staff_user'}}{{i18n 'admin.logs.action'}}{{i18n 'admin.logs.staff_actions.subject'}}{{i18n 'admin.logs.staff_actions.when'}}{{i18n 'admin.logs.staff_actions.details'}}{{i18n 'admin.logs.staff_actions.context'}}
{{i18n 'admin.logs.staff_actions.staff_user'}}{{i18n 'admin.logs.action'}}{{i18n 'admin.logs.staff_actions.subject'}}{{i18n 'admin.logs.staff_actions.when'}}{{i18n 'admin.logs.staff_actions.details'}}{{i18n 'admin.logs.staff_actions.context'}}
+
+ {{#if item.acting_user}} + {{#link-to 'adminUser' item.acting_user}}{{avatar item.acting_user imageSize="tiny"}}{{/link-to}} + {{item.acting_user.username}} + {{else}} + + {{d-icon "far-trash-alt"}} + + {{/if}} +
+
+ {{item.actionName}} + +
- {{#each model as |item|}} -
-
- {{#if item.acting_user}} - {{#link-to 'adminUser' item.acting_user}}{{avatar item.acting_user imageSize="tiny"}}{{/link-to}} - {{item.acting_user.username}} - {{else}} - - {{d-icon "far-trash-alt"}} - - {{/if}} -
-
- {{item.actionName}} - -
+ {{#if item.target_user}} + {{#link-to 'adminUser' item.target_user}}{{avatar item.target_user imageSize="tiny"}}{{/link-to}} + {{item.target_user.username}} + {{/if}} + {{#if item.subject}} + {{item.subject}} + {{/if}} +
+
{{age-with-tooltip item.created_at}} + {{{item.formattedDetails}}} + {{#if item.useCustomModalForDetails}} + {{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}} + {{/if}} + {{#if item.useModalForDetails}} + {{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}} + {{/if}} + {{item.context}}
{{age-with-tooltip item.created_at}} - {{{item.formattedDetails}}} - {{#if item.useCustomModalForDetails}} - {{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}} - {{/if}} - {{#if item.useModalForDetails}} - {{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}} - {{/if}} - {{item.context}}
- {{/conditional-loading-spinner}} + + {{else}} + {{i18n 'search.no_results'}} + {{/if}} + + {{conditional-loading-spinner condition=loading}} +{{/load-more}} {{/staff-actions}} diff --git a/app/assets/javascripts/discourse-common/helpers/popular-themes.js.es6 b/app/assets/javascripts/discourse-common/helpers/popular-themes.js.es6 new file mode 100644 index 0000000000..50d475e566 --- /dev/null +++ b/app/assets/javascripts/discourse-common/helpers/popular-themes.js.es6 @@ -0,0 +1,119 @@ +export const POPULAR_THEMES = [ + { + name: "Graceful", + value: "https://github.com/discourse/graceful", + preview: "https://theme-creator.discourse.org/theme/awesomerobot/graceful", + description: "A light and graceful theme for Discourse.", + meta_url: + "https://meta.discourse.org/t/a-graceful-theme-for-discourse/93040" + }, + { + name: "Material Design Theme", + value: "https://github.com/discourse/material-design-stock-theme", + preview: "https://newmaterial.trydiscourse.com", + description: + "Inspired by Material Design, this theme comes with several color palettes (incl. a dark one).", + meta_url: "https://meta.discourse.org/t/material-design-stock-theme/47142" + }, + { + name: "Minima", + value: "https://github.com/discourse/minima", + preview: "https://theme-creator.discourse.org/theme/awesomerobot/minima", + description: "A minimal theme with reduced UI elements and focus on text.", + meta_url: + "https://meta.discourse.org/t/minima-a-minimal-theme-for-discourse/108178" + }, + { + name: "Sam's Simple Theme", + value: "https://github.com/discourse/discourse-simple-theme", + preview: "https://theme-creator.discourse.org/theme/sam/simple", + description: + "Simplified front page design with classic colors and typography.", + meta_url: + "https://meta.discourse.org/t/sams-personal-minimal-topic-list-design/23552" + }, + { + name: "Vincent", + value: "https://github.com/discourse/discourse-vincent-theme", + preview: "https://theme-creator.discourse.org/theme/awesomerobot/vincent", + description: "An elegant dark theme with a few color palettes.", + meta_url: "https://meta.discourse.org/t/discourse-vincent-theme/76662" + }, + { + name: "Brand Header", + value: "https://github.com/discourse/discourse-brand-header", + description: + "Add an extra top header with your logo, navigation links and social icons.", + meta_url: "https://meta.discourse.org/t/brand-header-theme-component/77977", + component: true + }, + { + name: "Custom Header Links", + value: "https://github.com/discourse/discourse-custom-header-links", + preview: + "https://theme-creator.discourse.org/theme/Johani/custom-header-links", + description: "Easily add custom text-based links to the header.", + meta_url: "https://meta.discourse.org/t/custom-header-links/90588", + component: true + }, + { + name: "Category Banners", + value: "https://github.com/discourse/discourse-category-banners", + preview: + "https://theme-creator.discourse.org/theme/awesomerobot/discourse-category-banners", + description: + "Show banners on category pages using your existing category details.", + meta_url: "https://meta.discourse.org/t/discourse-category-banners/86241", + component: true + }, + { + name: "Kanban Board", + value: "https://github.com/discourse/discourse-kanban-theme", + preview: "https://theme-creator.discourse.org/theme/david/kanban", + description: "Display and organize topics using a Kanban board interface.", + meta_url: + "https://meta.discourse.org/t/kanban-board-theme-component/118164", + component: true + }, + { + name: "Hamburger Theme Selector", + value: "https://github.com/discourse/discourse-hamburger-theme-selector", + description: + "Displays a theme selector in the hamburger menu provided there is more than one user-selectable theme.", + meta_url: "https://meta.discourse.org/t/hamburger-theme-selector/61210", + component: true + }, + { + name: "Header Submenus", + value: "https://github.com/discourse/discourse-header-submenus", + preview: "https://theme-creator.discourse.org/theme/Johani/header-submenus", + description: "Lets you build a header menu with submenus (dropdowns).", + meta_url: "https://meta.discourse.org/t/header-submenus/94584", + component: true + }, + { + name: "Alternative Logos", + value: "https://github.com/discourse/discourse-alt-logo", + description: "Add alternative logos for dark / light themes.", + meta_url: + "https://meta.discourse.org/t/alternative-logo-for-dark-themes/88502", + component: true + }, + { + name: "Automatic Table of Contents", + value: "https://github.com/discourse/DiscoTOC", + description: + "Generates an interactive table of contents on the sidebar of your topic with a simple click in the composer.", + meta_url: + "https://meta.discourse.org/t/discotoc-automatic-table-of-contents/111143", + component: true + }, + { + name: "Easy Responsive Footer", + value: "https://github.com/discourse/Discourse-easy-footer", + preview: "https://theme-creator.discourse.org/theme/Johani/easy-footer", + description: "Add a fully responsive footer without writing any HTML.", + meta_url: "https://meta.discourse.org/t/easy-responsive-footer/95818", + component: true + } +]; diff --git a/app/assets/javascripts/discourse/components/badge-selector.js.es6 b/app/assets/javascripts/discourse/components/badge-selector.js.es6 index b794984aec..53ed8e385a 100644 --- a/app/assets/javascripts/discourse/components/badge-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/badge-selector.js.es6 @@ -4,6 +4,7 @@ import { default as computed } from "ember-addons/ember-computed-decorators"; import { findRawTemplate } from "discourse/lib/raw-templates"; +const { makeArray } = Ember; export default Ember.Component.extend({ @computed("placeholderKey") @@ -13,43 +14,40 @@ export default Ember.Component.extend({ @observes("badgeNames") _update() { - if (this.canReceiveUpdates === "true") + if (this.canReceiveUpdates === "true") { this._initializeAutocomplete({ updateData: true }); + } }, @on("didInsertElement") _initializeAutocomplete(opts) { - var self = this; - var selectedBadges; + let selectedBadges; - self.$("input").autocomplete({ + $(this.element.querySelector("input")).autocomplete({ allowAny: false, - items: _.isArray(this.badgeNames) ? this.badgeNames : [this.badgeNames], + items: makeArray(this.badgeNames), single: this.single, updateData: opts && opts.updateData ? opts.updateData : false, - onChangeItems: function(items) { + template: findRawTemplate("badge-selector-autocomplete"), + + onChangeItems(items) { selectedBadges = items; - self.set("badgeNames", items.join(",")); + this.set("badgeNames", items.join(",")); }, - transformComplete: function(g) { + + transformComplete(g) { return g.name; }, - dataSource: function(term) { - return self - .get("badgeFinder")(term) - .then(function(badges) { - if (!selectedBadges) { - return badges; - } - return badges.filter(function(badge) { - return !selectedBadges.any(function(s) { - return s === badge.name; - }); - }); - }); - }, - template: findRawTemplate("badge-selector-autocomplete") + dataSource(term) { + return this.badgeFinder(term).then(badges => { + if (!selectedBadges) return badges; + + return badges.filter( + badge => !selectedBadges.any(s => s === badge.name) + ); + }); + } }); } }); diff --git a/app/assets/javascripts/discourse/components/badge-title.js.es6 b/app/assets/javascripts/discourse/components/badge-title.js.es6 index ff20079380..117b1cc490 100644 --- a/app/assets/javascripts/discourse/components/badge-title.js.es6 +++ b/app/assets/javascripts/discourse/components/badge-title.js.es6 @@ -13,16 +13,19 @@ export default Ember.Component.extend(BadgeSelectController, { const badge_id = this.selectedUserBadgeId || 0; - ajax(this.get("user.path") + "/preferences/badge_title", { + ajax(this.currentUser.path + "/preferences/badge_title", { type: "PUT", data: { user_badge_id: badge_id } }).then( () => { this.setProperties({ saved: true, - saving: false, - "user.title": this.get("selectedUserBadge.badge.name") + saving: false }); + this.currentUser.set( + "title", + this.get("selectedUserBadge.badge.name") + ); }, () => { bootbox.alert(I18n.t("generic_error")); diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index 3a39a14fd2..347b939dbe 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -694,7 +694,7 @@ export default Ember.Component.extend({ const matchingHandler = uploadHandlers.find(matcher); if (data.files.length === 1 && matchingHandler) { - if (!matchingHandler.method(data.files[0])) { + if (!matchingHandler.method(data.files[0], this)) { return false; } } diff --git a/app/assets/javascripts/discourse/components/d-modal.js.es6 b/app/assets/javascripts/discourse/components/d-modal.js.es6 index 4f1d879ddb..cdf5fff878 100644 --- a/app/assets/javascripts/discourse/components/d-modal.js.es6 +++ b/app/assets/javascripts/discourse/components/d-modal.js.es6 @@ -35,42 +35,13 @@ export default Ember.Component.extend({ } }); - this.appEvents.on("modal:body-shown", data => { - if (this.isDestroying || this.isDestroyed) { - return; - } - - if (data.fixed) { - this.$().removeClass("hidden"); - } - - if (data.title) { - this.set("title", I18n.t(data.title)); - } else if (data.rawTitle) { - this.set("title", data.rawTitle); - } - - if (data.subtitle) { - this.set("subtitle", I18n.t(data.subtitle)); - } else if (data.rawSubtitle) { - this.set("subtitle", data.rawSubtitle); - } else { - // if no subtitle provided, makes sure the previous subtitle - // of another modal is not used - this.set("subtitle", null); - } - - if ("dismissable" in data) { - this.set("dismissable", data.dismissable); - } else { - this.set("dismissable", true); - } - }); + this.appEvents.on("modal:body-shown", this, "_modalBodyShown"); }, @on("willDestroyElement") cleanUp() { $("html").off("keydown.discourse-modal"); + this.appEvents.off("modal:body-shown", this, "_modalBodyShown"); }, mouseDown(e) { @@ -87,5 +58,37 @@ export default Ember.Component.extend({ // the backdrop and makes it unclickable. $(".modal-header a.close").click(); } + }, + + _modalBodyShown(data) { + if (this.isDestroying || this.isDestroyed) { + return; + } + + if (data.fixed) { + this.$().removeClass("hidden"); + } + + if (data.title) { + this.set("title", I18n.t(data.title)); + } else if (data.rawTitle) { + this.set("title", data.rawTitle); + } + + if (data.subtitle) { + this.set("subtitle", I18n.t(data.subtitle)); + } else if (data.rawSubtitle) { + this.set("subtitle", data.rawSubtitle); + } else { + // if no subtitle provided, makes sure the previous subtitle + // of another modal is not used + this.set("subtitle", null); + } + + if ("dismissable" in data) { + this.set("dismissable", data.dismissable); + } else { + this.set("dismissable", true); + } } }); diff --git a/app/assets/javascripts/discourse/components/date-picker.js.es6 b/app/assets/javascripts/discourse/components/date-picker.js.es6 index 8350c2dfe0..24ae9a6ad3 100644 --- a/app/assets/javascripts/discourse/components/date-picker.js.es6 +++ b/app/assets/javascripts/discourse/components/date-picker.js.es6 @@ -8,18 +8,31 @@ import { export default Ember.Component.extend({ classNames: ["date-picker-wrapper"], _picker: null, + value: null, + + @computed("site.mobileView") + inputType(mobileView) { + return mobileView ? "date" : "text"; + }, @on("didInsertElement") _loadDatePicker() { - const input = this.$(".date-picker")[0]; - const container = $("#" + this.containerId)[0]; + const container = this.element.querySelector(`#${this.containerId}`); + if (this.site.mobileView) { + this._loadNativePicker(container); + } else { + this._loadPikadayPicker(container); + } + }, + + _loadPikadayPicker(container) { loadScript("/javascripts/pikaday.js").then(() => { Ember.run.next(() => { - let default_opts = { - field: input, - container: container || this.$()[0], - bound: container === undefined, + const default_opts = { + field: this.element.querySelector(".date-picker"), + container: container || this.element, + bound: container === null, format: "YYYY-MM-DD", firstDay: 1, i18n: { @@ -29,24 +42,39 @@ export default Ember.Component.extend({ weekdays: moment.weekdays(), weekdaysShort: moment.weekdaysShort() }, - onSelect: date => { - const formattedDate = moment(date).format("YYYY-MM-DD"); - - if (this.attrs.onSelect) { - this.attrs.onSelect(formattedDate); - } - - if (!this.element || this.isDestroying || this.isDestroyed) return; - - this.set("value", formattedDate); - } + onSelect: date => this._handleSelection(date) }; - this._picker = new Pikaday(_.merge(default_opts, this._opts())); + this._picker = new Pikaday(Object.assign(default_opts, this._opts())); }); }); }, + _loadNativePicker(container) { + const wrapper = container || this.element; + const picker = wrapper.querySelector("input.date-picker"); + picker.onchange = () => this._handleSelection(picker.value); + picker.hide = () => { + /* do nothing for native */ + }; + picker.destroy = () => { + /* do nothing for native */ + }; + this._picker = picker; + }, + + _handleSelection(value) { + const formattedDate = moment(value).format("YYYY-MM-DD"); + + if (!this.element || this.isDestroying || this.isDestroyed) return; + + this._picker && this._picker.hide(); + + if (this.onSelect) { + this.onSelect(formattedDate); + } + }, + @on("willDestroyElement") _destroy() { if (this._picker) { diff --git a/app/assets/javascripts/discourse/components/future-date-input.js.es6 b/app/assets/javascripts/discourse/components/future-date-input.js.es6 index 45221ac719..9386c1b3f7 100644 --- a/app/assets/javascripts/discourse/components/future-date-input.js.es6 +++ b/app/assets/javascripts/discourse/components/future-date-input.js.es6 @@ -3,7 +3,6 @@ import { observes } from "ember-addons/ember-computed-decorators"; import { FORMAT } from "select-kit/components/future-date-input-selector"; - import { PUBLISH_TO_CATEGORY_STATUS_TYPE } from "discourse/controllers/edit-topic-timer"; export default Ember.Component.extend({ @@ -22,26 +21,37 @@ export default Ember.Component.extend({ init() { this._super(...arguments); - const input = this.input; - - if (input) { + if (this.input) { if (this.basedOnLastPost) { this.set("selection", "set_based_on_last_post"); } else { - this.set("selection", "pick_date_and_time"); - const datetime = moment(input); - this.set("date", datetime.toDate()); - this.set("time", datetime.format("HH:mm")); + const datetime = moment(this.input); + this.setProperties({ + selection: "pick_date_and_time", + date: datetime.format("YYYY-MM-DD"), + time: datetime.format("HH:mm") + }); this._updateInput(); } } }, + timeInputDisabled: Ember.computed.empty("date"), + @observes("date", "time") _updateInput() { - const date = moment(this.date).format("YYYY-MM-DD"); - const time = (this.time && ` ${this.time}`) || ""; - this.set("input", moment(`${date}${time}`).format(FORMAT)); + if (!this.date) { + this.set("time", null); + } + + const time = this.time ? ` ${this.time}` : ""; + const dateTime = moment(`${this.date}${time}`); + + if (dateTime.isValid()) { + this.set("input", dateTime.format(FORMAT)); + } else { + this.set("input", null); + } }, @observes("isBasedOnLastPost") @@ -72,6 +82,8 @@ export default Ember.Component.extend({ }, didReceiveAttrs() { + this._super(...arguments); + if (this.label) this.set("displayLabel", I18n.t(this.label)); }, diff --git a/app/assets/javascripts/discourse/components/global-notice.js.es6 b/app/assets/javascripts/discourse/components/global-notice.js.es6 index 851233564a..3d8c46679b 100644 --- a/app/assets/javascripts/discourse/components/global-notice.js.es6 +++ b/app/assets/javascripts/discourse/components/global-notice.js.es6 @@ -87,18 +87,36 @@ export default Ember.Component.extend( @on("didInsertElement") _setupLogsNotice() { - LogsNotice.current().addObserver("hidden", () => { - this.rerenderBuffer(); - }); + this._boundRerenderBuffer = Ember.run.bind(this, this.rerenderBuffer); + LogsNotice.current().addObserver("hidden", this._boundRerenderBuffer); - this.$().on("click.global-notice", ".alert-logs-notice .close", () => { - LogsNotice.currentProp("text", ""); - }); + this._boundResetCurrentProp = Ember.run.bind( + this, + this._resetCurrentProp + ); + $(this.element).on( + "click.global-notice", + ".alert-logs-notice .close", + this._boundResetCurrentProp + ); }, @on("willDestroyElement") _teardownLogsNotice() { - this.$().off("click.global-notice"); + if (this._boundResetCurrentProp) { + $(this.element).off("click.global-notice", this._boundResetCurrentProp); + } + + if (this._boundRerenderBuffer) { + LogsNotice.current().removeObserver( + "hidden", + this._boundRerenderBuffer + ); + } + }, + + _resetCurrentProp() { + LogsNotice.currentProp("text", ""); } }) ); diff --git a/app/assets/javascripts/discourse/components/mount-widget.js.es6 b/app/assets/javascripts/discourse/components/mount-widget.js.es6 index eb67d3e5e5..171ab375a5 100644 --- a/app/assets/javascripts/discourse/components/mount-widget.js.es6 +++ b/app/assets/javascripts/discourse/components/mount-widget.js.es6 @@ -61,7 +61,7 @@ export default Ember.Component.extend({ willDestroyElement() { this._dispatched.forEach(evt => { const [eventName, caller] = evt; - this.appEvents.off(eventName, caller); + this.appEvents.off(eventName, this, caller); }); Ember.run.cancel(this._timeout); }, @@ -84,7 +84,7 @@ export default Ember.Component.extend({ const caller = refreshArg => this.eventDispatched(eventName, key, refreshArg); this._dispatched.push([eventName, caller]); - this.appEvents.on(eventName, caller); + this.appEvents.on(eventName, this, caller); }, queueRerender(callback) { diff --git a/app/assets/javascripts/discourse/components/search-text-field.js.es6 b/app/assets/javascripts/discourse/components/search-text-field.js.es6 index cf442f869c..e2629bd812 100644 --- a/app/assets/javascripts/discourse/components/search-text-field.js.es6 +++ b/app/assets/javascripts/discourse/components/search-text-field.js.es6 @@ -4,6 +4,8 @@ import TextField from "discourse/components/text-field"; import { applySearchAutocomplete } from "discourse/lib/search"; export default TextField.extend({ + autocomplete: "discourse", + @computed("searchService.searchContextEnabled") placeholder(searchContextEnabled) { return searchContextEnabled ? "" : I18n.t("search.full_page_title"); diff --git a/app/assets/javascripts/discourse/components/share-popup.js.es6 b/app/assets/javascripts/discourse/components/share-popup.js.es6 index 669f0c9597..eb878763b7 100644 --- a/app/assets/javascripts/discourse/components/share-popup.js.es6 +++ b/app/assets/javascripts/discourse/components/share-popup.js.es6 @@ -1,6 +1,9 @@ import { wantsNewWindow } from "discourse/lib/intercept-click"; import { longDateNoYear } from "discourse/lib/formatter"; -import computed from "ember-addons/ember-computed-decorators"; +import { + default as computed, + on +} from "ember-addons/ember-computed-decorators"; import Sharing from "discourse/lib/sharing"; import { nativeShare } from "discourse/lib/pwa-utils"; @@ -148,19 +151,24 @@ export default Ember.Component.extend({ this._showUrl($target, url); }, + @on("init") + _setupHandlers() { + this._boundMouseDownHandler = Ember.run.bind(this, this._mouseDownHandler); + this._boundClickHandler = Ember.run.bind(this, this._clickHandler); + this._boundKeydownHandler = Ember.run.bind(this, this._keydownHandler); + }, + didInsertElement() { this._super(...arguments); - const $html = $("html"); - $html.on("mousedown.outside-share-link", this._mouseDownHandler.bind(this)); - - $html.on( - "click.discourse-share-link", - "button[data-share-url], .post-info .post-date[data-share-url]", - this._clickHandler.bind(this) - ); - - $html.on("keydown.share-view", this._keydownHandler); + $("html") + .on("mousedown.outside-share-link", this._boundMouseDownHandler) + .on( + "click.discourse-share-link", + "button[data-share-url], .post-info .post-date[data-share-url]", + this._boundClickHandler + ) + .on("keydown.share-view", this._boundKeydownHandler); this.appEvents.on("share:url", this._shareUrlHandler); }, @@ -169,9 +177,9 @@ export default Ember.Component.extend({ this._super(...arguments); $("html") - .off("click.discourse-share-link", this._clickHandler) - .off("mousedown.outside-share-link", this._mouseDownHandler) - .off("keydown.share-view", this._keydownHandler); + .off("click.discourse-share-link", this._boundClickHandler) + .off("mousedown.outside-share-link", this._boundMouseDownHandler) + .off("keydown.share-view", this._boundKeydownHandler); this.appEvents.off("share:url", this._shareUrlHandler); }, diff --git a/app/assets/javascripts/discourse/components/topic-entrance.js.es6 b/app/assets/javascripts/discourse/components/topic-entrance.js.es6 index fa25a528a7..e90ccd5857 100644 --- a/app/assets/javascripts/discourse/components/topic-entrance.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-entrance.js.es6 @@ -58,7 +58,7 @@ export default Ember.Component.extend(CleansUp, { _setCSS() { const pos = this._position; - const $self = this.$(); + const $self = $(this.element); const width = $self.width(); const height = $self.height(); pos.left = parseInt(pos.left) - width / 2; @@ -74,8 +74,7 @@ export default Ember.Component.extend(CleansUp, { _show(data) { this._position = data.position; - this.set("topic", data.topic); - this.set("visible", true); + this.setProperties({ topic: data.topic, visible: true }); Ember.run.scheduleOnce("afterRender", this, this._setCSS); @@ -85,7 +84,7 @@ export default Ember.Component.extend(CleansUp, { const $target = $(e.target); if ( $target.prop("id") === "topic-entrance" || - this.$().has($target).length !== 0 + $(this.element).has($target).length !== 0 ) { return; } @@ -94,8 +93,7 @@ export default Ember.Component.extend(CleansUp, { }, cleanUp() { - this.set("topic", null); - this.set("visible", false); + this.setProperties({ topic: null, visible: false }); $("html").off("mousedown.topic-entrance"); }, diff --git a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 index 0087226900..0c487e7353 100644 --- a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 @@ -32,6 +32,8 @@ export default Ember.Component.extend({ canInviteTo: Ember.computed.alias("topic.details.can_invite_to"), + canDefer: Ember.computed.alias("currentUser.enable_defer"), + inviteDisabled: Ember.computed.or( "topic.archived", "topic.closed", diff --git a/app/assets/javascripts/discourse/controllers/exception.js.es6 b/app/assets/javascripts/discourse/controllers/exception.js.es6 index 6a45998e5a..1f4c454da4 100644 --- a/app/assets/javascripts/discourse/controllers/exception.js.es6 +++ b/app/assets/javascripts/discourse/controllers/exception.js.es6 @@ -1,6 +1,9 @@ -import computed from "ember-addons/ember-computed-decorators"; +import { + on, + default as computed +} from "ember-addons/ember-computed-decorators"; -var ButtonBackBright = { +const ButtonBackBright = { classes: "btn-primary", action: "back", key: "errors.buttons.back" @@ -28,11 +31,13 @@ export default Ember.Controller.extend({ lastTransition: null, @computed - isNetwork: function() { + isNetwork() { // never made it on the wire if (this.get("thrown.readyState") === 0) return true; + // timed out if (this.get("thrown.jqTextStatus") === "timeout") return true; + return false; }, @@ -47,9 +52,10 @@ export default Ember.Controller.extend({ networkFixed: false, loading: false, - _init: function() { + @on("init") + _init() { this.set("loading", false); - }.on("init"), + }, @computed("isNetwork", "isServer", "isUnknown") reason() { @@ -99,16 +105,16 @@ export default Ember.Controller.extend({ }, actions: { - back: function() { + back() { window.history.back(); }, - tryLoading: function() { + tryLoading() { this.set("loading", true); - var self = this; - Ember.run.schedule("afterRender", function() { - self.get("lastTransition").retry(); - self.set("loading", false); + + Ember.run.schedule("afterRender", () => { + this.lastTransition.retry(); + this.set("loading", false); }); } } diff --git a/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 index c0a7fc4feb..ac56e0ec37 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 @@ -2,7 +2,7 @@ import { iconHTML } from "discourse-common/lib/icon-library"; import CanCheckEmails from "discourse/mixins/can-check-emails"; import { default as computed } from "ember-addons/ember-computed-decorators"; import PreferencesTabController from "discourse/mixins/preferences-tab-controller"; -import { setting } from "discourse/lib/computed"; +import { propertyNotEqual, setting } from "discourse/lib/computed"; import { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; import { findAll } from "discourse/models/login-method"; @@ -40,9 +40,7 @@ export default Ember.Controller.extend( ), reset() { - this.setProperties({ - passwordProgress: null - }); + this.set("passwordProgress", null); }, @computed() @@ -54,10 +52,7 @@ export default Ember.Controller.extend( ); }, - @computed("model.availableTitles") - canSelectTitle(availableTitles) { - return availableTitles.length > 0; - }, + canSelectTitle: Ember.computed.gt("model.availableTitles.length", 0), @computed("model.is_anonymous") canChangePassword(isAnonymous) { @@ -86,15 +81,10 @@ export default Ember.Controller.extend( }; }); - return result.filter(value => { - return value.account || value.method.get("can_connect"); - }); + return result.filter(value => value.account || value.method.can_connect); }, - @computed("model.id") - disableConnectButtons(userId) { - return userId !== this.get("currentUser.id"); - }, + disableConnectButtons: propertyNotEqual("model.id", "currentUser.id"), @computed( "model.second_factor_enabled", @@ -129,25 +119,23 @@ export default Ember.Controller.extend( : tokens.slice(0, DEFAULT_AUTH_TOKENS_COUNT); }, - @computed("model.user_auth_tokens") - canShowAllAuthTokens(tokens) { - return tokens.length > DEFAULT_AUTH_TOKENS_COUNT; - }, + canShowAllAuthTokens: Ember.computed.gt( + "model.user_auth_tokens.length", + DEFAULT_AUTH_TOKENS_COUNT + ), actions: { save() { this.set("saved", false); - const model = this.model; + this.model.setProperties({ + name: this.newNameInput, + title: this.newTitleInput + }); - model.set("name", this.newNameInput); - model.set("title", this.newTitleInput); - - return model + return this.model .save(this.saveAttrNames) - .then(() => { - this.set("saved", true); - }) + .then(() => this.set("saved", true)) .catch(popupAjaxError); }, @@ -178,17 +166,14 @@ export default Ember.Controller.extend( delete() { this.set("deleting", true); - const self = this, - message = I18n.t("user.delete_account_confirm"), + const message = I18n.t("user.delete_account_confirm"), model = this.model, buttons = [ { label: I18n.t("cancel"), class: "d-modal-cancel", link: true, - callback: () => { - this.set("deleting", false); - } + callback: () => this.set("deleting", false) }, { label: @@ -197,14 +182,15 @@ export default Ember.Controller.extend( class: "btn btn-danger", callback() { model.delete().then( - function() { - bootbox.alert(I18n.t("user.deleted_yourself"), function() { - window.location.pathname = Discourse.getURL("/"); - }); + () => { + bootbox.alert( + I18n.t("user.deleted_yourself"), + () => (window.location.pathname = Discourse.getURL("/")) + ); }, - function() { + () => { bootbox.alert(I18n.t("user.delete_yourself_not_allowed")); - self.set("deleting", false); + this.set("deleting", false); } ); } @@ -214,25 +200,23 @@ export default Ember.Controller.extend( }, revokeAccount(account) { - const model = this.model; this.set("revoking", true); - model + + this.model .revokeAssociatedAccount(account.name) .then(result => { if (result.success) { - model.get("associated_accounts").removeObject(account); + this.model.associated_accounts.removeObject(account); } else { bootbox.alert(result.message); } }) .catch(popupAjaxError) - .finally(() => { - this.set("revoking", false); - }); + .finally(() => this.set("revoking", false)); }, toggleShowAllAuthTokens() { - this.set("showAllAuthTokens", !this.showAllAuthTokens); + this.toggleProperty("showAllAuthTokens"); }, revokeAuthToken(token) { diff --git a/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 index 0be1508dae..d45e3fad89 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 @@ -29,6 +29,11 @@ export default Ember.Controller.extend(PreferencesTabController, { return this.get("currentUser.id") === this.get("model.id"); }, + @computed("siteSettings.remove_muted_tags_from_latest") + hideMutedTags() { + return this.siteSettings.remove_muted_tags_from_latest !== "never"; + }, + canSave: Ember.computed.or("canSee", "currentUser.admin"), actions: { diff --git a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 index 915db2d165..072c42c3b6 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 @@ -31,6 +31,7 @@ export default Ember.Controller.extend(PreferencesTabController, { "external_links_in_new_tab", "dynamic_favicon", "enable_quoting", + "enable_defer", "automatically_unpin_topics", "allow_private_messages", "homepage_id", diff --git a/app/assets/javascripts/discourse/controllers/review-index.js.es6 b/app/assets/javascripts/discourse/controllers/review-index.js.es6 index 5c950982b6..9b88af4e01 100644 --- a/app/assets/javascripts/discourse/controllers/review-index.js.es6 +++ b/app/assets/javascripts/discourse/controllers/review-index.js.es6 @@ -7,7 +7,8 @@ export default Ember.Controller.extend({ "status", "category_id", "topic_id", - "username" + "username", + "sort_order" ], type: null, status: "pending", @@ -17,6 +18,7 @@ export default Ember.Controller.extend({ topic_id: null, filtersExpanded: false, username: "", + sort_order: "priority", init(...args) { this._super(...args); @@ -44,6 +46,18 @@ export default Ember.Controller.extend({ }); }, + @computed + sortOrders() { + return ["priority", "priority_asc", "created_at", "created_at_asc"].map( + order => { + return { + id: order, + name: I18n.t(`review.filters.orders.${order}`) + }; + } + ); + }, + @computed statuses() { return [ @@ -86,7 +100,8 @@ export default Ember.Controller.extend({ priority: this.filterPriority, status: this.filterStatus, category_id: this.filterCategoryId, - username: this.filterUsername + username: this.filterUsername, + sort_order: this.filterSortOrder }); this.send("refreshRoute"); }, diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 1ec3b6cf30..27d1dc3698 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -108,22 +108,32 @@ export default Ember.Controller.extend(bufferedProperty("model"), { init() { this._super(...arguments); - this.appEvents.on("post:show-revision", (postNumber, revision) => { - const post = this.model.get("postStream").postForPostNumber(postNumber); - if (!post) { - return; - } - Ember.run.scheduleOnce("afterRender", () => { - this.send("showHistory", post, revision); - }); - }); + this.appEvents.on("post:show-revision", this, "_showRevision"); + this.setProperties({ selectedPostIds: [], quoteState: new QuoteState() }); }, + willDestroy() { + this._super(...arguments); + + this.appEvents.off("post:show-revision", this, "_showRevision"); + }, + + _showRevision(postNumber, revision) { + const post = this.model.get("postStream").postForPostNumber(postNumber); + if (!post) { + return; + } + + Ember.run.scheduleOnce("afterRender", () => { + this.send("showHistory", post, revision); + }); + }, + showCategoryChooser: Ember.computed.not("model.isPrivateMessage"), gotoInbox(name) { @@ -405,6 +415,27 @@ export default Ember.Controller.extend(bufferedProperty("model"), { } }, + deferTopic() { + const screenTrack = Discourse.__container__.lookup("screen-track:main"); + const currentUser = this.currentUser; + const topic = this.model; + + screenTrack.reset(); + screenTrack.stop(); + const goToPath = topic.get("isPrivateMessage") + ? currentUser.pmPath(topic) + : "/"; + ajax("/t/" + topic.get("id") + "/timings.json?last=1", { type: "DELETE" }) + .then(() => { + const highestSeenByTopic = Discourse.Session.currentProp( + "highestSeenByTopic" + ); + highestSeenByTopic[topic.get("id")] = null; + DiscourseURL.routeTo(goToPath); + }) + .catch(popupAjaxError); + }, + editFirstPost() { const postStream = this.get("model.postStream"); let firstPost = postStream.get("posts.firstObject"); diff --git a/app/assets/javascripts/discourse/initializers/avatar-select.js.es6 b/app/assets/javascripts/discourse/initializers/avatar-select.js.es6 index 2f9ac0c061..3b72cc520a 100644 --- a/app/assets/javascripts/discourse/initializers/avatar-select.js.es6 +++ b/app/assets/javascripts/discourse/initializers/avatar-select.js.es6 @@ -5,27 +5,32 @@ export default { name: "avatar-select", initialize(container) { - const siteSettings = container.lookup("site-settings:main"); - const appEvents = container.lookup("app-events:main"); + this.selectAvatarsEnabled = container.lookup( + "site-settings:main" + ).select_avatars_enabled; - appEvents.on("show-avatar-select", user => { - const avatarTemplate = user.get("avatar_template"); - let selected = "uploaded"; + container + .lookup("app-events:main") + .on("show-avatar-select", this, "_showAvatarSelect"); + }, - if (avatarTemplate === user.get("system_avatar_template")) { - selected = "system"; - } else if (avatarTemplate === user.get("gravatar_avatar_template")) { - selected = "gravatar"; - } + _showAvatarSelect(user) { + const avatarTemplate = user.avatar_template; + let selected = "uploaded"; - const modal = showModal("avatar-selector"); - modal.setProperties({ user, selected }); + if (avatarTemplate === user.system_avatar_template) { + selected = "system"; + } else if (avatarTemplate === user.gravatar_avatar_template) { + selected = "gravatar"; + } - if (siteSettings.selectable_avatars_enabled) { - ajax("/site/selectable-avatars.json").then(avatars => - modal.set("selectableAvatars", avatars) - ); - } - }); + const modal = showModal("avatar-selector"); + modal.setProperties({ user, selected }); + + if (this.selectAvatarsEnabled) { + ajax("/site/selectable-avatars.json").then(avatars => + modal.set("selectableAvatars", avatars) + ); + } } }; diff --git a/app/assets/javascripts/discourse/initializers/badging.js.es6 b/app/assets/javascripts/discourse/initializers/badging.js.es6 index 75672ec6b9..b80416c231 100644 --- a/app/assets/javascripts/discourse/initializers/badging.js.es6 +++ b/app/assets/javascripts/discourse/initializers/badging.js.es6 @@ -4,16 +4,20 @@ export default { after: "message-bus", initialize(container) { - const appEvents = container.lookup("app-events:main"); - const user = container.lookup("current-user:main"); - - if (!user) return; // must be logged in if (!window.ExperimentalBadge) return; // must have the Badging API - appEvents.on("notifications:changed", () => { - let notifications = - user.get("unread_notifications") + user.get("unread_private_messages"); - window.ExperimentalBadge.set(notifications); - }); + const user = container.lookup("current-user:main"); + if (!user) return; // must be logged in + + this.notifications = + user.unread_notifications + user.unread_private_messages; + + container + .lookup("app-events:main") + .on("notifications:changed", this, "_updateBadge"); + }, + + _updateBadge() { + window.ExperimentalBadge.set(this.notifications); } }; diff --git a/app/assets/javascripts/discourse/initializers/title-notifications.js.es6 b/app/assets/javascripts/discourse/initializers/title-notifications.js.es6 index d84cfc1cc4..ba24755d23 100644 --- a/app/assets/javascripts/discourse/initializers/title-notifications.js.es6 +++ b/app/assets/javascripts/discourse/initializers/title-notifications.js.es6 @@ -3,16 +3,18 @@ export default { after: "message-bus", initialize(container) { - const appEvents = container.lookup("app-events:main"); const user = container.lookup("current-user:main"); - if (!user) return; // must be logged in - appEvents.on("notifications:changed", () => { - let notifications = - user.get("unread_notifications") + user.get("unread_private_messages"); + this.notifications = + user.unread_notifications + user.unread_private_messages; - Discourse.updateNotificationCount(notifications); - }); + container + .lookup("app-events:main") + .on("notifications:changed", this, "_updateTitle"); + }, + + _updateTitle() { + Discourse.updateNotificationCount(this.notifications); } }; diff --git a/app/assets/javascripts/discourse/initializers/topic-footer-buttons.js.es6 b/app/assets/javascripts/discourse/initializers/topic-footer-buttons.js.es6 index ccb2e2e9d1..3cd0827a6f 100644 --- a/app/assets/javascripts/discourse/initializers/topic-footer-buttons.js.es6 +++ b/app/assets/javascripts/discourse/initializers/topic-footer-buttons.js.es6 @@ -130,6 +130,9 @@ export default { "archiveTitle", "toggleArchiveMessage" ], + dropdown() { + return this.site.mobileView; + }, displayed() { return this.canArchive; } @@ -148,5 +151,20 @@ export default { return this.showEditOnFooter; } }); + + registerTopicFooterButton({ + id: "defer", + icon: "circle", + priority: 300, + label: "topic.defer.title", + title: "topic.defer.help", + action: "deferTopic", + displayed() { + return this.canDefer; + }, + dropdown() { + return this.site.mobileView; + } + }); } }; diff --git a/app/assets/javascripts/discourse/lib/computed.js.es6 b/app/assets/javascripts/discourse/lib/computed.js.es6 index c97c0d0ec1..2683d944fe 100644 --- a/app/assets/javascripts/discourse/lib/computed.js.es6 +++ b/app/assets/javascripts/discourse/lib/computed.js.es6 @@ -103,17 +103,13 @@ export function endWith() { const args = Array.prototype.slice.call(arguments, 0); const substring = args.pop(); const computed = Ember.computed(function() { - const self = this; - return _.every( - args.map(function(a) { - return self.get(a); - }), - function(s) { + return args + .map(a => this.get(a)) + .every(s => { const position = s.length - substring.length, lastIndex = s.lastIndexOf(substring); return lastIndex !== -1 && lastIndex === position; - } - ); + }); }); return computed.property.apply(computed, args); } @@ -128,5 +124,5 @@ export function endWith() { export function setting(name) { return Ember.computed(function() { return Discourse.SiteSettings[name]; - }).property(); + }); } diff --git a/app/assets/javascripts/discourse/lib/discourse-location.js.es6 b/app/assets/javascripts/discourse/lib/discourse-location.js.es6 index f2585af057..4fd1c6628e 100644 --- a/app/assets/javascripts/discourse/lib/discourse-location.js.es6 +++ b/app/assets/javascripts/discourse/lib/discourse-location.js.es6 @@ -1,14 +1,6 @@ import { defaultHomepage } from "discourse/lib/utilities"; - -/** -@module Discourse -*/ - -const get = Ember.get, - set = Ember.set; let popstateFired = false; const supportsHistoryState = window.history && "state" in window.history; - const popstateCallbacks = []; /** @@ -21,7 +13,9 @@ const popstateCallbacks = []; */ const DiscourseLocation = Ember.Object.extend({ init() { - set(this, "location", get(this, "location") || window.location); + this._super(...arguments); + + this.set("location", this.location || window.location); this.initState(); }, @@ -33,18 +27,17 @@ const DiscourseLocation = Ember.Object.extend({ @method initState */ initState() { - const history = get(this, "history") || window.history; + const history = this.history || window.history; if (history && history.scrollRestoration) { history.scrollRestoration = "manual"; } - set(this, "history", history); + this.set("history", history); let url = this.formatURL(this.getURL()); - const loc = get(this, "location"); - if (loc && loc.hash) { - url += loc.hash; + if (this.location && this.location.hash) { + url += this.location.hash; } this.replaceState(url); @@ -66,12 +59,11 @@ const DiscourseLocation = Ember.Object.extend({ @method getURL */ getURL() { - const location = get(this, "location"); - let url = location.pathname; + let url = this.location.pathname; url = url.replace(new RegExp(`^${Discourse.BaseUri}`), ""); - const search = location.search || ""; + const search = this.location.search || ""; url += search; return url; }, @@ -124,9 +116,7 @@ const DiscourseLocation = Ember.Object.extend({ @method getState */ getState() { - return supportsHistoryState - ? get(this, "history").state - : this._historyState; + return supportsHistoryState ? this.history.state : this._historyState; }, /** @@ -138,13 +128,13 @@ const DiscourseLocation = Ember.Object.extend({ @param path {String} */ pushState(path) { - const state = { path: path }; + const state = { path }; // store state if browser doesn't support `history.state` if (!supportsHistoryState) { this._historyState = state; } else { - get(this, "history").pushState(state, null, path); + this.history.pushState(state, null, path); } // used for webkit workaround @@ -160,13 +150,13 @@ const DiscourseLocation = Ember.Object.extend({ @param path {String} */ replaceState(path) { - const state = { path: path }; + const state = { path }; // store state if browser doesn't support `history.state` if (!supportsHistoryState) { this._historyState = state; } else { - get(this, "history").replaceState(state, null, path); + this.history.replaceState(state, null, path); } // used for webkit workaround @@ -183,21 +173,18 @@ const DiscourseLocation = Ember.Object.extend({ @param callback {Function} */ onUpdateURL(callback) { - const guid = Ember.guidFor(this), - self = this; + const guid = Ember.guidFor(this); + + $(window).on(`popstate.ember-location-${guid}`, () => { + const url = this.getURL(); - Ember.$(window).on("popstate.ember-location-" + guid, function() { // Ignore initial page load popstate event in Chrome if (!popstateFired) { popstateFired = true; - if (self.getURL() === self._previousURL) { - return; - } + if (url === this._previousURL) return; } - const url = self.getURL(); - popstateCallbacks.forEach(function(cb) { - cb(url); - }); + + popstateCallbacks.forEach(cb => cb(url)); callback(url); }); }, @@ -211,7 +198,7 @@ const DiscourseLocation = Ember.Object.extend({ @param url {String} */ formatURL(url) { - let rootURL = get(this, "rootURL"); + let rootURL = this.rootURL; if (url !== "") { rootURL = rootURL.replace(/\/$/, ""); @@ -225,9 +212,10 @@ const DiscourseLocation = Ember.Object.extend({ }, willDestroy() { - const guid = Ember.guidFor(this); + this._super(...arguments); - Ember.$(window).off("popstate.ember-location-" + guid); + const guid = Ember.guidFor(this); + $(window).off(`popstate.ember-location-${guid}`); } }); diff --git a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 index cd0c8e7da2..035ccf572f 100644 --- a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 +++ b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 @@ -53,10 +53,21 @@ function show(image) { copyImg.style.position = "absolute"; copyImg.style.top = `${image.offsetTop}px`; copyImg.style.left = `${image.offsetLeft}px`; - copyImg.style.width = imageData.width; - copyImg.style.height = imageData.height; copyImg.className = imageData.className; + let inOnebox = false; + for (let element = image; element; element = element.parentElement) { + if (element.classList.contains("onebox")) { + inOnebox = true; + break; + } + } + + if (!inOnebox) { + copyImg.style.width = `${imageData.width}px`; + copyImg.style.height = `${imageData.height}px`; + } + image.parentNode.insertBefore(copyImg, image); } else { image.classList.remove("d-lazyload-hidden"); diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index 7c84799121..7b682b6f6e 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -44,7 +44,7 @@ import { addComposerUploadHandler } from "discourse/components/composer-editor"; import { addCategorySortCriteria } from "discourse/components/edit-category-settings"; // If you add any methods to the API ensure you bump up this number -const PLUGIN_API_VERSION = "0.8.30"; +const PLUGIN_API_VERSION = "0.8.31"; class PluginApi { constructor(version, container) { @@ -809,7 +809,7 @@ class PluginApi { * * Example: * - * addComposerUploadHandler(["mp4", "mov"], (file) => { + * addComposerUploadHandler(["mp4", "mov"], (file, editor) => { * console.log("Handling upload for", file.name); * }) */ diff --git a/app/assets/javascripts/discourse/lib/screen-track.js.es6 b/app/assets/javascripts/discourse/lib/screen-track.js.es6 index 363454e1e9..c7a451356d 100644 --- a/app/assets/javascripts/discourse/lib/screen-track.js.es6 +++ b/app/assets/javascripts/discourse/lib/screen-track.js.es6 @@ -26,7 +26,8 @@ export default class { // Create an interval timer if we don't have one. if (!this._interval) { this._interval = setInterval(() => this.tick(), 1000); - $(window).on("scroll.screentrack", this.scrolled.bind(this)); + this._boundScrolled = Ember.run.bind(this, this.scrolled); + $(window).on("scroll.screentrack", this._boundScrolled); } this._topicId = topicId; @@ -39,7 +40,10 @@ export default class { return; } - $(window).off("scroll.screentrack", this.scrolled); + if (this._boundScrolled) { + $(window).off("scroll.screentrack", this._boundScrolled); + } + this.tick(); this.flush(); this.reset(); diff --git a/app/assets/javascripts/discourse/lib/to-markdown.js.es6 b/app/assets/javascripts/discourse/lib/to-markdown.js.es6 index 8e4eaa4883..b4fc915023 100644 --- a/app/assets/javascripts/discourse/lib/to-markdown.js.es6 +++ b/app/assets/javascripts/discourse/lib/to-markdown.js.es6 @@ -241,6 +241,7 @@ export class Tag { let alt = attr.alt || pAttr.alt || ""; const width = attr.width || pAttr.width; const height = attr.height || pAttr.height; + const title = attr.title; if (width && height) { const pipe = this.element.parentNames.includes("table") @@ -249,7 +250,7 @@ export class Tag { alt = `${alt}${pipe}${width}x${height}`; } - return "![" + alt + "](" + src + ")"; + return `![${alt}](${src}${title ? ` "${title}"` : ""})`; } return ""; diff --git a/app/assets/javascripts/discourse/lib/transform-post.js.es6 b/app/assets/javascripts/discourse/lib/transform-post.js.es6 index 4aad29a302..7a1872b4fe 100644 --- a/app/assets/javascripts/discourse/lib/transform-post.js.es6 +++ b/app/assets/javascripts/discourse/lib/transform-post.js.es6 @@ -55,7 +55,7 @@ export function transformBasicPost(post) { reviewableScorePendingCount: post.reviewable_score_pending_count, version: post.version, canRecoverTopic: false, - canDeletedTopic: false, + canDeleteTopic: false, canViewEditHistory: post.can_view_edit_history, canWiki: post.can_wiki, showLike: false, @@ -234,12 +234,10 @@ export default function transformPost( // Show a "Flag to delete" message if not staff and you can't // otherwise delete it. - postAtts.showFlagDelete = ( + postAtts.showFlagDelete = !postAtts.canDelete && postAtts.yours && - (currentUser && !currentUser.staff) - ); - + (currentUser && !currentUser.staff); } else { postAtts.canRecover = postAtts.isDeleted && postAtts.canRecover; postAtts.canDelete = diff --git a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 index 383d5989f8..f81a573891 100644 --- a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 +++ b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 @@ -127,10 +127,16 @@ export default Ember.Mixin.create({ this.appEvents.on(previewClickEvent, this, "_previewClick"); - this.appEvents.on(`topic-header:trigger-${id}`, (username, $target) => { - this.setProperties({ isFixed: true, isDocked: true }); - return this._show(username, $target); - }); + this.appEvents.on( + `topic-header:trigger-${id}`, + this, + "_topicHeaderTrigger" + ); + }, + + _topicHeaderTrigger(username, $target) { + this.setProperties({ isFixed: true, isDocked: true }); + return this._show(username, $target); }, _bindMobileScroll() { @@ -281,7 +287,14 @@ export default Ember.Mixin.create({ $("#main") .off(clickDataExpand) .off(clickMention); + this.appEvents.off(previewClickEvent, this, "_previewClick"); + + this.appEvents.off( + `topic-header:trigger-${this.elementId}`, + this, + "_topicHeaderTrigger" + ); }, keyUp(e) { diff --git a/app/assets/javascripts/discourse/mixins/username-validation.js.es6 b/app/assets/javascripts/discourse/mixins/username-validation.js.es6 index 646f7f6ba9..892f587b85 100644 --- a/app/assets/javascripts/discourse/mixins/username-validation.js.es6 +++ b/app/assets/javascripts/discourse/mixins/username-validation.js.es6 @@ -7,20 +7,20 @@ export default Ember.Mixin.create({ uniqueUsernameValidation: null, maxUsernameLength: setting("max_username_length"), + minUsernameLength: setting("min_username_length"), fetchExistingUsername: debounce(function() { - const self = this; - Discourse.User.checkUsername(null, this.accountEmail).then(function( - result - ) { + Discourse.User.checkUsername(null, this.accountEmail).then(result => { if ( result.suggestion && - (Ember.isEmpty(self.get("accountUsername")) || - self.get("accountUsername") === self.get("authOptions.username")) + (Ember.isEmpty(this.accountUsername) || + this.accountUsername === this.get("authOptions.username")) ) { - self.set("accountUsername", result.suggestion); - self.set("prefilledUsername", result.suggestion); + this.setProperties({ + accountUsername: result.suggestion, + prefilledUsername: result.suggestion + }); } }); }, 500), @@ -38,9 +38,7 @@ export default Ember.Mixin.create({ // If blank, fail without a reason if (Ember.isEmpty(accountUsername)) { - return InputValidation.create({ - failed: true - }); + return InputValidation.create({ failed: true }); } // If too short @@ -67,7 +65,7 @@ export default Ember.Mixin.create({ }); }, - shouldCheckUsernameAvailability: function() { + shouldCheckUsernameAvailability() { return ( !Ember.isEmpty(this.accountUsername) && this.accountUsername.length >= this.minUsernameLength diff --git a/app/assets/javascripts/discourse/models/badge.js.es6 b/app/assets/javascripts/discourse/models/badge.js.es6 index aa3aaa50d9..d70a5fbbe6 100644 --- a/app/assets/javascripts/discourse/models/badge.js.es6 +++ b/app/assets/javascripts/discourse/models/badge.js.es6 @@ -11,23 +11,14 @@ const Badge = RestModel.extend({ return Discourse.getURL(`/badges/${this.id}/${this.slug}`); }, - /** - Update this badge with the response returned by the server on save. - - @method updateFromJson - @param {Object} json The JSON response returned by the server - **/ - updateFromJson: function(json) { - const self = this; + updateFromJson(json) { if (json.badge) { - Object.keys(json.badge).forEach(function(key) { - self.set(key, json.badge[key]); - }); + Object.keys(json.badge).forEach(key => this.set(key, json.badge[key])); } if (json.badge_types) { - json.badge_types.forEach(function(badgeType) { - if (badgeType.id === self.get("badge_type_id")) { - self.set("badge_type", Object.create(badgeType)); + json.badge_types.forEach(badgeType => { + if (badgeType.id === this.badge_type_id) { + this.set("badge_type", Object.create(badgeType)); } }); } @@ -36,77 +27,57 @@ const Badge = RestModel.extend({ @computed("badge_type.name") badgeTypeClassName(type) { type = type || ""; - return "badge-type-" + type.toLowerCase(); + return `badge-type-${type.toLowerCase()}`; }, - /** - Save and update the badge from the server's response. - - @method save - @returns {Promise} A promise that resolves to the updated `Badge` - **/ - save: function(data) { + save(data) { let url = "/admin/badges", - requestType = "POST"; - const self = this; + type = "POST"; if (this.id) { // We are updating an existing badge. - url += "/" + this.id; - requestType = "PUT"; + url += `/${this.id}`; + type = "PUT"; } - return ajax(url, { - type: requestType, - data: data - }) - .then(function(json) { - self.updateFromJson(json); - return self; + return ajax(url, { type, data }) + .then(json => { + this.updateFromJson(json); + return this; }) - .catch(function(error) { + .catch(error => { throw new Error(error); }); }, - /** - Destroy the badge. - - @method destroy - @returns {Promise} A promise that resolves to the server response - **/ - destroy: function() { + destroy() { if (this.newBadge) return Ember.RSVP.resolve(); - return ajax("/admin/badges/" + this.id, { + + return ajax(`/admin/badges/${this.id}`, { type: "DELETE" }); } }); Badge.reopenClass({ - /** - Create `Badge` instances from the server JSON response. - - @method createFromJson - @param {Object} json The JSON returned by the server - @returns Array or instance of `Badge` depending on the input JSON - **/ - createFromJson: function(json) { + createFromJson(json) { // Create BadgeType objects. const badgeTypes = {}; if ("badge_types" in json) { - json.badge_types.forEach(function(badgeTypeJson) { - badgeTypes[badgeTypeJson.id] = Ember.Object.create(badgeTypeJson); - }); + json.badge_types.forEach( + badgeTypeJson => + (badgeTypes[badgeTypeJson.id] = Ember.Object.create(badgeTypeJson)) + ); } const badgeGroupings = {}; if ("badge_groupings" in json) { - json.badge_groupings.forEach(function(badgeGroupingJson) { - badgeGroupings[badgeGroupingJson.id] = BadgeGrouping.create( - badgeGroupingJson - ); - }); + json.badge_groupings.forEach( + badgeGroupingJson => + (badgeGroupings[badgeGroupingJson.id] = BadgeGrouping.create( + badgeGroupingJson + )) + ); } // Create Badge objects. @@ -116,13 +87,12 @@ Badge.reopenClass({ } else if (json.badges) { badges = json.badges; } - badges = badges.map(function(badgeJson) { + badges = badges.map(badgeJson => { const badge = Badge.create(badgeJson); - badge.set("badge_type", badgeTypes[badge.get("badge_type_id")]); - badge.set( - "badge_grouping", - badgeGroupings[badge.get("badge_grouping_id")] - ); + badge.setProperties({ + badge_type: badgeTypes[badge.badge_type_id], + badge_grouping: badgeGroupings[badge.badge_grouping_id] + }); return badge; }); @@ -133,35 +103,21 @@ Badge.reopenClass({ } }, - /** - Find all `Badge` instances that have been defined. - - @method findAll - @returns {Promise} a promise that resolves to an array of `Badge` - **/ - findAll: function(opts) { + findAll(opts) { let listable = ""; if (opts && opts.onlyListable) { listable = "?only_listable=true"; } - return ajax("/badges.json" + listable, { data: opts }).then(function( - badgesJson - ) { - return Badge.createFromJson(badgesJson); - }); + + return ajax(`/badges.json${listable}`, { data: opts }).then(badgesJson => + Badge.createFromJson(badgesJson) + ); }, - /** - Returns a `Badge` that has the given ID. - - @method findById - @param {Number} id ID of the badge - @returns {Promise} a promise that resolves to a `Badge` - **/ - findById: function(id) { - return ajax("/badges/" + id).then(function(badgeJson) { - return Badge.createFromJson(badgeJson); - }); + findById(id) { + return ajax(`/badges/${id}`).then(badgeJson => + Badge.createFromJson(badgeJson) + ); } }); diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6 index c40fd0f684..c25f0ce8d5 100644 --- a/app/assets/javascripts/discourse/models/group.js.es6 +++ b/app/assets/javascripts/discourse/models/group.js.es6 @@ -18,7 +18,7 @@ const Group = RestModel.extend({ init() { this._super(...arguments); - this.owners = []; + this.set("owners", []); }, hasOwners: Ember.computed.notEmpty("owners"), @@ -50,7 +50,7 @@ const Group = RestModel.extend({ return Group.loadMembers(this.name, offset, this.limit, params).then( result => { - var ownerIds = {}; + const ownerIds = {}; result.owners.forEach(owner => (ownerIds[owner.id] = true)); this.setProperties({ @@ -70,29 +70,26 @@ const Group = RestModel.extend({ }, removeOwner(member) { - var self = this; - return ajax("/admin/groups/" + this.id + "/owners.json", { + return ajax(`/admin/groups/${this.id}/owners.json`, { type: "DELETE", - data: { user_id: member.get("id") } - }).then(function() { + data: { user_id: member.id } + }).then(() => { // reload member list - self.findMembers(); + this.findMembers(); }); }, removeMember(member, params) { - return ajax("/groups/" + this.id + "/members.json", { + return ajax(`/groups/${this.id}/members.json`, { type: "DELETE", - data: { user_id: member.get("id") } - }).then(() => { - this.findMembers(params); - }); + data: { user_id: member.id } + }).then(() => this.findMembers(params)); }, addMembers(usernames, filter) { - return ajax("/groups/" + this.id + "/members.json", { + return ajax(`/groups/${this.id}/members.json`, { type: "PUT", - data: { usernames: usernames } + data: { usernames } }).then(response => { if (filter) { this._filterMembers(response); @@ -105,7 +102,7 @@ const Group = RestModel.extend({ addOwners(usernames, filter) { return ajax(`/admin/groups/${this.id}/owners.json`, { type: "PUT", - data: { group: { usernames: usernames } } + data: { group: { usernames } } }).then(response => { if (filter) { this._filterMembers(response); @@ -125,30 +122,27 @@ const Group = RestModel.extend({ }, @computed("flair_bg_color") - flairBackgroundHexColor() { - return this.flair_bg_color - ? this.flair_bg_color.replace(new RegExp("[^0-9a-fA-F]", "g"), "") + flairBackgroundHexColor(flairBgColor) { + return flairBgColor + ? flairBgColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "") : null; }, @computed("flair_color") - flairHexColor() { - return this.flair_color - ? this.flair_color.replace(new RegExp("[^0-9a-fA-F]", "g"), "") + flairHexColor(flairColor) { + return flairColor + ? flairColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "") : null; }, - @computed("mentionable_level") - canEveryoneMention(mentionableLevel) { - return mentionableLevel === "99"; - }, + canEveryoneMention: Ember.computed.equal("mentionable_level", 99), @computed("visibility_level") isPrivate(visibilityLevel) { return visibilityLevel !== 0; }, - @observes("visibility_level", "canEveryoneMention") + @observes("isPrivate", "canEveryoneMention") _updateAllowMembershipRequests() { if (this.isPrivate || !this.canEveryoneMention) { this.set("allow_membership_requests", false); @@ -158,8 +152,7 @@ const Group = RestModel.extend({ @observes("visibility_level") _updatePublic() { if (this.isPrivate) { - this.set("public", false); - this.set("allow_membership_requests", false); + this.setProperties({ public: false, allow_membership_requests: false }); } }, @@ -170,9 +163,7 @@ const Group = RestModel.extend({ messageable_level: this.messageable_level, visibility_level: this.visibility_level, automatic_membership_email_domains: this.emailDomains, - automatic_membership_retroactive: !!this.get( - "automatic_membership_retroactive" - ), + automatic_membership_retroactive: !!this.automatic_membership_retroactive, title: this.title, primary_group: !!this.primary_group, grant_trust_level: this.grant_trust_level, @@ -223,7 +214,7 @@ const Group = RestModel.extend({ if (!this.id) { return; } - return ajax("/admin/groups/" + this.id, { type: "DELETE" }); + return ajax(`/admin/groups/${this.id}`, { type: "DELETE" }); }, findLogs(offset, filters) { @@ -239,13 +230,13 @@ const Group = RestModel.extend({ findPosts(opts) { opts = opts || {}; - const type = opts.type || "posts"; + const data = {}; - var data = {}; if (opts.beforePostId) { data.before_post_id = opts.beforePostId; } + if (opts.categoryId) { data.category_id = parseInt(opts.categoryId); } @@ -271,21 +262,21 @@ const Group = RestModel.extend({ requestMembership(reason) { return ajax(`/groups/${this.name}/request_membership`, { type: "POST", - data: { reason: reason } + data: { reason } }); } }); Group.reopenClass({ findAll(opts) { - return ajax("/groups/search.json", { data: opts }).then(groups => { - return groups.map(g => Group.create(g)); - }); + return ajax("/groups/search.json", { data: opts }).then(groups => + groups.map(g => Group.create(g)) + ); }, loadMembers(name, offset, limit, params) { - return ajax("/groups/" + name + "/members.json", { - data: _.extend( + return ajax(`/groups/${name}/members.json`, { + data: Object.assign( { limit: limit || 50, offset: offset || 0 diff --git a/app/assets/javascripts/discourse/models/invite.js.es6 b/app/assets/javascripts/discourse/models/invite.js.es6 index c5ab2d50a7..842a04a324 100644 --- a/app/assets/javascripts/discourse/models/invite.js.es6 +++ b/app/assets/javascripts/discourse/models/invite.js.es6 @@ -12,21 +12,18 @@ const Invite = Discourse.Model.extend({ }, reinvite() { - const self = this; return ajax("/invites/reinvite", { type: "POST", data: { email: this.email } }) - .then(function() { - self.set("reinvited", true); - }) + .then(() => this.set("reinvited", true)) .catch(popupAjaxError); } }); Invite.reopenClass({ create() { - var result = this._super.apply(this, arguments); + const result = this._super.apply(this, arguments); if (result.user) { result.user = Discourse.User.create(result.user); } @@ -34,37 +31,27 @@ Invite.reopenClass({ }, findInvitedBy(user, filter, search, offset) { - if (!user) { - return Ember.RSVP.resolve(); - } + if (!user) Ember.RSVP.resolve(); - var data = {}; - if (!Ember.isNone(filter)) { - data.filter = filter; - } - if (!Ember.isNone(search)) { - data.search = search; - } + const data = {}; + if (!Ember.isNone(filter)) data.filter = filter; + if (!Ember.isNone(search)) data.search = search; data.offset = offset || 0; - return ajax(userPath(user.get("username_lower") + "/invited.json"), { + return ajax(userPath(`${user.username_lower}/invited.json`), { data - }).then(function(result) { - result.invites = result.invites.map(function(i) { - return Invite.create(i); - }); - + }).then(result => { + result.invites = result.invites.map(i => Invite.create(i)); return Ember.Object.create(result); }); }, findInvitedCount(user) { - if (!user) { - return Ember.RSVP.resolve(); - } - return ajax( - userPath(user.get("username_lower") + "/invited_count.json") - ).then(result => Ember.Object.create(result.counts)); + if (!user) Ember.RSVP.resolve(); + + return ajax(userPath(`${user.username_lower}/invited_count.json`)).then( + result => Ember.Object.create(result.counts) + ); }, reinviteAll() { diff --git a/app/assets/javascripts/discourse/models/login-method.js.es6 b/app/assets/javascripts/discourse/models/login-method.js.es6 index f79644ee71..244e75b625 100644 --- a/app/assets/javascripts/discourse/models/login-method.js.es6 +++ b/app/assets/javascripts/discourse/models/login-method.js.es6 @@ -13,7 +13,7 @@ const LoginMethod = Ember.Object.extend({ @computed message() { - return this.message_override || I18n.t("login." + this.name + ".message"); + return this.message_override || I18n.t(`login.${this.name}.message`); }, doLogin({ reconnect = false, fullScreenLogin = true } = {}) { @@ -23,7 +23,7 @@ const LoginMethod = Ember.Object.extend({ if (customLogin) { customLogin(); } else { - let authUrl = this.custom_url || Discourse.getURL("/auth/" + name); + let authUrl = this.custom_url || Discourse.getURL(`/auth/${name}`); if (reconnect) { authUrl += "?reconnect=true"; @@ -45,7 +45,7 @@ const LoginMethod = Ember.Object.extend({ authUrl += "display=popup"; } - const w = window.open( + const windowState = window.open( authUrl, "_blank", "menubar=no,status=no,height=" + @@ -57,11 +57,11 @@ const LoginMethod = Ember.Object.extend({ ",top=" + top ); - const self = this; - const timer = setInterval(function() { - if (!w || w.closed) { + + const timer = setInterval(() => { + if (!windowState || windowState.closed) { clearInterval(timer); - self.set("authenticate", null); + this.set("authenticate", null); } }, 1000); } @@ -72,18 +72,16 @@ const LoginMethod = Ember.Object.extend({ let methods; export function findAll() { - if (methods) { - return methods; - } + if (methods) return methods; methods = []; - Discourse.Site.currentProp("auth_providers").forEach(provider => { - methods.pushObject(LoginMethod.create(provider)); - }); + Discourse.Site.currentProp("auth_providers").forEach(provider => + methods.pushObject(LoginMethod.create(provider)) + ); // exclude FA icon for Google, uses custom SVG - methods.forEach(m => m.set("isGoogle", m.get("name") === "google_oauth2")); + methods.forEach(m => m.set("isGoogle", m.name === "google_oauth2")); return methods; } diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6 index 5ce71ab8b5..00f60bb358 100644 --- a/app/assets/javascripts/discourse/models/post.js.es6 +++ b/app/assets/javascripts/discourse/models/post.js.es6 @@ -20,7 +20,7 @@ const Post = RestModel.extend({ @computed("url") shareUrl(url) { const user = Discourse.User.current(); - const userSuffix = user ? "?u=" + user.get("username_lower") : ""; + const userSuffix = user ? `?u=${user.username_lower}` : ""; if (this.firstPost) { return this.get("topic.url") + userSuffix; @@ -55,24 +55,21 @@ const Post = RestModel.extend({ }, @computed("post_number", "topic_id", "topic.slug") - url(postNr, topicId, slug) { + url(post_number, topic_id, topicSlug) { return postUrl( - slug || this.topic_slug, - topicId || this.get("topic.id"), - postNr + topicSlug || this.topic_slug, + topic_id || this.get("topic.id"), + post_number ); }, // Don't drop the /1 @computed("post_number", "url") urlWithNumber(postNumber, baseUrl) { - return postNumber === 1 ? baseUrl + "/1" : baseUrl; + return postNumber === 1 ? `${baseUrl}/1` : baseUrl; }, - @computed("username") - usernameUrl(username) { - return userPath(username); - }, + @computed("username") usernameUrl: userPath, topicOwner: propertyEqual("topic.details.created_by.id", "user_id"), @@ -81,9 +78,7 @@ const Post = RestModel.extend({ data[field] = value; return ajax(`/posts/${this.id}/${field}`, { type: "PUT", data }) - .then(() => { - this.set(field, value); - }) + .then(() => this.set(field, value)) .catch(popupAjaxError); }, @@ -102,9 +97,9 @@ const Post = RestModel.extend({ return []; } - return this.site.get("flagTypes").filter(item => { - return this.get(`actionByName.${item.get("name_key")}.can_act`); - }); + return this.site.flagTypes.filter(item => + this.get(`actionByName.${item.name_key}.can_act`) + ); }, afterUpdate(res) { @@ -131,9 +126,9 @@ const Post = RestModel.extend({ // Put the metaData into the request if (metaData) { data.meta_data = {}; - Object.keys(metaData).forEach(function(key) { - data.meta_data[key] = metaData.get(key); - }); + Object.keys(metaData).forEach( + key => (data.meta_data[key] = metaData[key]) + ); } return data; @@ -194,7 +189,7 @@ const Post = RestModel.extend({ // Moderators can delete posts. Users can only trigger a deleted at message, unless delete_removed_posts_after is 0. if ( - deletedBy.get("staff") || + deletedBy.staff || Discourse.SiteSettings.delete_removed_posts_after === 0 ) { this.setProperties({ @@ -260,10 +255,9 @@ const Post = RestModel.extend({ is already found in an identity map. **/ updateFromPost(otherPost) { - const self = this; - Object.keys(otherPost).forEach(function(key) { + Object.keys(otherPost).forEach(key => { let value = otherPost[key], - oldValue = self[key]; + oldValue = this[key]; if (!value) { value = null; @@ -282,28 +276,27 @@ const Post = RestModel.extend({ } if (!skip) { - self.set(key, value); + this.set(key, value); } } }); }, expandHidden() { - return ajax("/posts/" + this.id + "/cooked.json").then(result => { + return ajax(`/posts/${this.id}/cooked.json`).then(result => { this.setProperties({ cooked: result.cooked, cooked_hidden: false }); }); }, rebake() { - return ajax("/posts/" + this.id + "/rebake", { type: "PUT" }); + return ajax(`/posts/${this.id}/rebake`, { type: "PUT" }); }, unhide() { - return ajax("/posts/" + this.id + "/unhide", { type: "PUT" }); + return ajax(`/posts/${this.id}/unhide`, { type: "PUT" }); }, toggleBookmark() { - const self = this; let bookmarkedTopic; this.toggleProperty("bookmarked"); @@ -316,13 +309,11 @@ const Post = RestModel.extend({ // need to wait to hear back from server (stuff may not be loaded) return Discourse.Post.updateBookmark(this.id, this.bookmarked) - .then(function(result) { - self.set("topic.bookmarked", result.topic_bookmarked); - }) - .catch(function(error) { - self.toggleProperty("bookmarked"); + .then(result => this.set("topic.bookmarked", result.topic_bookmarked)) + .catch(error => { + this.toggleProperty("bookmarked"); if (bookmarkedTopic) { - self.set("topic.bookmarked", false); + this.set("topic.bookmarked", false); } throw new Error(error); }); @@ -348,7 +339,7 @@ Post.reopenClass({ const lookup = Ember.Object.create(); // this area should be optimized, it is creating way too many objects per post - json.actions_summary = json.actions_summary.map(function(a) { + json.actions_summary = json.actions_summary.map(a => { a.actionType = Discourse.Site.current().postActionTypeById(a.id); a.count = a.count || 0; const actionSummary = ActionSummary.create(a); @@ -366,13 +357,14 @@ Post.reopenClass({ if (json && json.reply_to_user) { json.reply_to_user = Discourse.User.create(json.reply_to_user); } + return json; }, updateBookmark(postId, bookmarked) { - return ajax("/posts/" + postId + "/bookmark", { + return ajax(`/posts/${postId}/bookmark`, { type: "PUT", - data: { bookmarked: bookmarked } + data: { bookmarked } }); }, @@ -391,27 +383,27 @@ Post.reopenClass({ }, loadRevision(postId, version) { - return ajax("/posts/" + postId + "/revisions/" + version + ".json").then( - result => Ember.Object.create(result) + return ajax(`/posts/${postId}/revisions/${version}.json`).then(result => + Ember.Object.create(result) ); }, hideRevision(postId, version) { - return ajax("/posts/" + postId + "/revisions/" + version + "/hide", { + return ajax(`/posts/${postId}/revisions/${version}/hide`, { type: "PUT" }); }, showRevision(postId, version) { - return ajax("/posts/" + postId + "/revisions/" + version + "/show", { + return ajax(`/posts/${postId}/revisions/${version}/show`, { type: "PUT" }); }, loadQuote(postId) { - return ajax("/posts/" + postId + ".json").then(result => { + return ajax(`/posts/${postId}.json`).then(result => { const post = Discourse.Post.create(result); - return Quote.build(post, post.get("raw"), { raw: true, full: true }); + return Quote.build(post, post.raw, { raw: true, full: true }); }); }, diff --git a/app/assets/javascripts/discourse/models/reviewable.js.es6 b/app/assets/javascripts/discourse/models/reviewable.js.es6 index 3c1869f0e4..25fc62f63d 100644 --- a/app/assets/javascripts/discourse/models/reviewable.js.es6 +++ b/app/assets/javascripts/discourse/models/reviewable.js.es6 @@ -10,8 +10,13 @@ export const IGNORED = 3; export const DELETED = 4; export default RestModel.extend({ - @computed("type") - humanType(type) { + @computed("type", "topic") + humanType(type, topic) { + // Display "Queued Topic" if the post will create a topic + if (type === "ReviewableQueuedPost" && !topic) { + type = "ReviewableQueuedTopic"; + } + return I18n.t(`review.types.${type.underscore()}.title`, { defaultValue: "" }); diff --git a/app/assets/javascripts/discourse/models/store.js.es6 b/app/assets/javascripts/discourse/models/store.js.es6 index 4e6e68dab6..96a96f502f 100644 --- a/app/assets/javascripts/discourse/models/store.js.es6 +++ b/app/assets/javascripts/discourse/models/store.js.es6 @@ -312,7 +312,7 @@ export default Ember.Object.extend({ obj[subType] = hydrated; delete obj[k]; } else { - obj[subType] = null; + Ember.set(obj, subType, null); } } } diff --git a/app/assets/javascripts/discourse/models/topic-list.js.es6 b/app/assets/javascripts/discourse/models/topic-list.js.es6 index 9ed73dadca..e12a90e77e 100644 --- a/app/assets/javascripts/discourse/models/topic-list.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-list.js.es6 @@ -1,15 +1,17 @@ import { ajax } from "discourse/lib/ajax"; import RestModel from "discourse/models/rest"; import Model from "discourse/models/model"; +import { getOwner } from "discourse-common/lib/get-owner"; // Whether to show the category badge in topic lists function displayCategoryInList(site, category) { if (category) { - if (category.get("has_children")) { + if (category.has_children) { return true; } - let draftCategoryId = site.get("shared_drafts_category_id"); - if (draftCategoryId && category.get("id") === draftCategoryId) { + + const draftCategoryId = site.shared_drafts_category_id; + if (draftCategoryId && category.id === draftCategoryId) { return true; } @@ -25,7 +27,7 @@ const TopicList = RestModel.extend({ forEachNew(topics, callback) { const topicIds = []; - this.topics.forEach(topic => (topicIds[topic.get("id")] = true)); + this.topics.forEach(topic => (topicIds[topic.id] = true)); topics.forEach(topic => { if (!topicIds[topic.id]) { @@ -68,30 +70,27 @@ const TopicList = RestModel.extend({ moreUrl += "?" + params; } - const self = this; this.set("loadingMore", true); - const store = this.store; - return ajax({ url: moreUrl }).then(function(result) { + return ajax({ url: moreUrl }).then(result => { let topicsAdded = 0; if (result) { // the new topics loaded from the server - const newTopics = TopicList.topicsFrom(store, result); - const topics = self.get("topics"); + const newTopics = TopicList.topicsFrom(this.store, result); - self.forEachNew(newTopics, function(t) { + this.forEachNew(newTopics, t => { t.set("highlight", topicsAdded++ === 0); - topics.pushObject(t); + this.topics.pushObject(t); }); - self.setProperties({ + this.setProperties({ loadingMore: false, more_topics_url: result.topic_list.more_topics_url }); - Discourse.Session.currentProp("topicList", self); - return self.get("more_topics_url"); + Discourse.Session.currentProp("topicList", this); + return this.more_topics_url; } }); } else { @@ -102,37 +101,32 @@ const TopicList = RestModel.extend({ // loads topics with these ids "before" the current topics loadBefore(topic_ids, storeInSession) { - const topicList = this, - topics = this.topics; - // refresh dupes - topics.removeObjects( - topics.filter(topic => topic_ids.indexOf(topic.get("id")) >= 0) + this.topics.removeObjects( + this.topics.filter(topic => topic_ids.indexOf(topic.id) >= 0) ); - const url = `${Discourse.getURL("/")}${this.get( - "filter" - )}.json?topic_ids=${topic_ids.join(",")}`; - const store = this.store; + const url = `${Discourse.getURL("/")}${ + this.filter + }.json?topic_ids=${topic_ids.join(",")}`; return ajax({ url, data: this.params }).then(result => { let i = 0; - topicList.forEachNew(TopicList.topicsFrom(store, result), function(t) { + this.forEachNew(TopicList.topicsFrom(this.store, result), t => { // highlight the first of the new topics so we can get a visual feedback t.set("highlight", true); - topics.insertAt(i, t); + this.topics.insertAt(i, t); i++; }); - if (storeInSession) Discourse.Session.currentProp("topicList", topicList); + + if (storeInSession) Discourse.Session.currentProp("topicList", this); }); } }); TopicList.reopenClass({ topicsFrom(store, result, opts) { - if (!result) { - return; - } + if (!result) return; opts = opts || {}; let listKey = opts.listKey || "topics"; @@ -143,9 +137,9 @@ TopicList.reopenClass({ users = Model.extractByKey(result.users, Discourse.User), groups = Model.extractByKey(result.primary_groups, Ember.Object); - return result.topic_list[listKey].map(function(t) { + return result.topic_list[listKey].map(t => { t.category = categories.findBy("id", t.category_id); - t.posters.forEach(function(p) { + t.posters.forEach(p => { p.user = users[p.user_id]; p.extraClasses = p.extras; if (p.primary_group_id) { @@ -157,11 +151,11 @@ TopicList.reopenClass({ } } }); + if (t.participants) { - t.participants.forEach(function(p) { - p.user = users[p.user_id]; - }); + t.participants.forEach(p => (p.user = users[p.user_id])); } + return store.createRecord("topic", t); }); }, @@ -188,7 +182,7 @@ TopicList.reopenClass({ }, find(filter, params) { - const store = Discourse.__container__.lookup("service:store"); + const store = getOwner(this).lookup("service:store"); return store.findFiltered("topicList", { filter, params }); }, diff --git a/app/assets/javascripts/discourse/models/user-posts-stream.js.es6 b/app/assets/javascripts/discourse/models/user-posts-stream.js.es6 index a106b28fb3..7521b82dce 100644 --- a/app/assets/javascripts/discourse/models/user-posts-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/user-posts-stream.js.es6 @@ -1,3 +1,4 @@ +import { on } from "ember-addons/ember-computed-decorators"; import { ajax } from "discourse/lib/ajax"; import { url } from "discourse/lib/computed"; import UserAction from "discourse/models/user-action"; @@ -5,13 +6,14 @@ import UserAction from "discourse/models/user-action"; export default Discourse.Model.extend({ loaded: false, - _initialize: function() { + @on("init") + _initialize() { this.setProperties({ itemsLoaded: 0, canLoadMore: true, content: [] }); - }.on("init"), + }, url: url( "user.username_lower", @@ -40,7 +42,6 @@ export default Discourse.Model.extend({ }, findItems() { - const self = this; if (this.loading || !this.canLoadMore) { return Ember.RSVP.reject(); } @@ -48,21 +49,17 @@ export default Discourse.Model.extend({ this.set("loading", true); return ajax(this.url, { cache: false }) - .then(function(result) { + .then(result => { if (result) { - const posts = result.map(function(post) { - return UserAction.create(post); - }); - self.get("content").pushObjects(posts); - self.setProperties({ + const posts = result.map(post => UserAction.create(post)); + this.content.pushObjects(posts); + this.setProperties({ loaded: true, - itemsLoaded: self.get("itemsLoaded") + posts.length, + itemsLoaded: this.itemsLoaded + posts.length, canLoadMore: posts.length > 0 }); } }) - .finally(function() { - self.set("loading", false); - }); + .finally(() => this.set("loading", false)); } }); diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 90b47dc083..b9064d13f6 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -271,6 +271,7 @@ const User = RestModel.extend({ "email_previous_replies", "dynamic_favicon", "enable_quoting", + "enable_defer", "automatically_unpin_topics", "digest_after_minutes", "new_topic_duration_minutes", @@ -338,6 +339,7 @@ const User = RestModel.extend({ const userProps = Ember.getProperties( this.user_option, "enable_quoting", + "enable_defer", "external_links_in_new_tab", "dynamic_favicon" ); diff --git a/app/assets/javascripts/discourse/routes/badges-show.js.es6 b/app/assets/javascripts/discourse/routes/badges-show.js.es6 index 7a5b7f7830..3507beac99 100644 --- a/app/assets/javascripts/discourse/routes/badges-show.js.es6 +++ b/app/assets/javascripts/discourse/routes/badges-show.js.es6 @@ -30,15 +30,16 @@ export default Discourse.Route.extend({ }, afterModel(model, transition) { - const username = + const usernameFromParams = transition.to.queryParams && transition.to.queryParams.username; const userBadgesGrant = UserBadge.findByBadgeId(model.get("id"), { - username + username: usernameFromParams }).then(userBadges => { this.userBadgesGrant = userBadges; }); + const username = this.currentUser && this.currentUser.username_lower; const userBadgesAll = UserBadge.findByUsername(username).then( userBadges => { this.userBadgesAll = userBadges; diff --git a/app/assets/javascripts/discourse/routes/discovery.js.es6 b/app/assets/javascripts/discourse/routes/discovery.js.es6 index aa71f49ece..32926561b6 100644 --- a/app/assets/javascripts/discourse/routes/discovery.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery.js.es6 @@ -11,16 +11,16 @@ export default Discourse.Route.extend(OpenComposer, { }, beforeModel(transition) { + const user = Discourse.User; + const url = transition.intent.url; + if ( - (transition.intent.url === "/" || - transition.intent.url === "/latest" || - transition.intent.url === "/categories") && + (url === "/" || url === "/latest" || url === "/categories") && transition.targetName.indexOf("discovery.top") === -1 && - Discourse.User.currentProp("should_be_redirected_to_top") + user.currentProp("should_be_redirected_to_top") ) { - Discourse.User.currentProp("should_be_redirected_to_top", false); - const period = - Discourse.User.currentProp("redirect_to_top.period") || "all"; + user.currentProp("should_be_redirected_to_top", false); + const period = user.currentProp("redirected_to_top.period") || "all"; this.replaceWith(`discovery.top${period.capitalize()}`); } }, diff --git a/app/assets/javascripts/discourse/routes/review-index.js.es6 b/app/assets/javascripts/discourse/routes/review-index.js.es6 index 01ff4e588d..b6ba62ab45 100644 --- a/app/assets/javascripts/discourse/routes/review-index.js.es6 +++ b/app/assets/javascripts/discourse/routes/review-index.js.es6 @@ -20,7 +20,8 @@ export default Discourse.Route.extend({ filterCategoryId: meta.category_id, filterPriority: meta.priority, reviewableTypes: meta.reviewable_types, - filterUsername: meta.username + filterUsername: meta.username, + filterSortOrder: meta.sort_order }); }, diff --git a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 index ebb7532673..63a0ebcfb2 100644 --- a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 @@ -17,9 +17,8 @@ export default Discourse.Route.extend({ params = params || {}; params.track_visit = true; - const self = this, - topic = this.modelFor("topic"), - postStream = topic.get("postStream"), + const topic = this.modelFor("topic"), + postStream = topic.postStream, topicController = this.controllerFor("topic"), composerController = this.controllerFor("composer"); @@ -32,7 +31,7 @@ export default Discourse.Route.extend({ postStream .refresh(params) - .then(function() { + .then(() => { // TODO we are seeing errors where closest post is null and this is exploding // we need better handling and logging for this condition. @@ -40,22 +39,20 @@ export default Discourse.Route.extend({ const closestPost = postStream.closestPostForPostNumber( params.nearPost || 1 ); - const closest = closestPost.get("post_number"); + const closest = closestPost.post_number; topicController.setProperties({ "model.currentPost": closest, - enteredIndex: topic - .get("postStream") - .progressIndexOfPost(closestPost), + enteredIndex: topic.postStream.progressIndexOfPost(closestPost), enteredAt: new Date().getTime().toString() }); topicController.subscribe(); // Highlight our post after the next render - Ember.run.scheduleOnce("afterRender", function() { - self.appEvents.trigger("post:highlight", closest); - }); + Ember.run.scheduleOnce("afterRender", () => + this.appEvents.trigger("post:highlight", closest) + ); const opts = {}; if (document.location.hash && document.location.hash.length) { @@ -63,13 +60,13 @@ export default Discourse.Route.extend({ } DiscourseURL.jumpToPost(closest, opts); - if (!Ember.isEmpty(topic.get("draft"))) { + if (!Ember.isEmpty(topic.draft)) { composerController.open({ - draft: Draft.getLocal(topic.get("draft_key"), topic.get("draft")), - draftKey: topic.get("draft_key"), - draftSequence: topic.get("draft_sequence"), - topic: topic, - ignoreIfChanged: true + draft: Draft.getLocal(topic.draft_key, topic.draft), + draftKey: topic.draft_key, + draftSequence: topic.draft_sequence, + ignoreIfChanged: true, + topic }); } }) diff --git a/app/assets/javascripts/discourse/routes/user.js.es6 b/app/assets/javascripts/discourse/routes/user.js.es6 index c90353c754..97d9bff7b9 100644 --- a/app/assets/javascripts/discourse/routes/user.js.es6 +++ b/app/assets/javascripts/discourse/routes/user.js.es6 @@ -1,6 +1,6 @@ export default Discourse.Route.extend({ titleToken() { - const username = this.modelFor("user").get("username"); + const username = this.modelFor("user").username; if (username) { return [I18n.t("user.profile"), username]; } @@ -32,12 +32,11 @@ export default Discourse.Route.extend({ model(params) { // If we're viewing the currently logged in user, return that object instead - const currentUser = this.currentUser; if ( - currentUser && - params.username.toLowerCase() === currentUser.get("username_lower") + this.currentUser && + params.username.toLowerCase() === this.currentUser.username_lower ) { - return currentUser; + return this.currentUser; } return Discourse.User.create({ username: params.username }); @@ -45,43 +44,38 @@ export default Discourse.Route.extend({ afterModel() { const user = this.modelFor("user"); - const self = this; return user .findDetails() - .then(function() { - return user.findStaffInfo(); - }) - .catch(function() { - return self.replaceWith("/404"); - }); + .then(() => user.findStaffInfo()) + .catch(() => this.replaceWith("/404")); }, serialize(model) { if (!model) return {}; - return { username: (Ember.get(model, "username") || "").toLowerCase() }; + + return { username: (model.username || "").toLowerCase() }; }, setupController(controller, user) { controller.set("model", user); - this.searchService.set("searchContext", user.get("searchContext")); + this.searchService.set("searchContext", user.searchContext); }, activate() { this._super(...arguments); + const user = this.modelFor("user"); - this.messageBus.subscribe("/u/" + user.get("username_lower"), function( - data - ) { - user.loadUserAction(data); - }); + this.messageBus.subscribe(`/u/${user.username_lower}`, data => + user.loadUserAction(data) + ); }, deactivate() { this._super(...arguments); - this.messageBus.unsubscribe( - "/u/" + this.modelFor("user").get("username_lower") - ); + + const user = this.modelFor("user"); + this.messageBus.unsubscribe(`/u/${user.username_lower}`); // Remove the search context this.searchService.set("searchContext", null); diff --git a/app/assets/javascripts/discourse/templates/components/badge-title.hbs b/app/assets/javascripts/discourse/templates/components/badge-title.hbs index 0ac803fdad..23b1f4e22d 100644 --- a/app/assets/javascripts/discourse/templates/components/badge-title.hbs +++ b/app/assets/javascripts/discourse/templates/components/badge-title.hbs @@ -8,7 +8,6 @@
-
{{combo-box value=selectedUserBadgeId diff --git a/app/assets/javascripts/discourse/templates/components/date-picker.hbs b/app/assets/javascripts/discourse/templates/components/date-picker.hbs index 6e6054c2ad..e29eb0e73a 100644 --- a/app/assets/javascripts/discourse/templates/components/date-picker.hbs +++ b/app/assets/javascripts/discourse/templates/components/date-picker.hbs @@ -1 +1,5 @@ -{{input type="text" class="date-picker" placeholder=placeholder value=value}} +{{input + type=inputType + class="date-picker" + placeholder=placeholder + value=value}} diff --git a/app/assets/javascripts/discourse/templates/components/future-date-input.hbs b/app/assets/javascripts/discourse/templates/components/future-date-input.hbs index 336fabec67..d1e9bce807 100644 --- a/app/assets/javascripts/discourse/templates/components/future-date-input.hbs +++ b/app/assets/javascripts/discourse/templates/components/future-date-input.hbs @@ -21,7 +21,7 @@
{{d-icon "far-clock"}} - {{input type="time" value=time}} + {{input placeholder="--:--" type="time" value=time disabled=timeInputDisabled}}
{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/groups-form-membership-fields.hbs b/app/assets/javascripts/discourse/templates/components/groups-form-membership-fields.hbs index 03f5755d8f..f2c6ca7593 100644 --- a/app/assets/javascripts/discourse/templates/components/groups-form-membership-fields.hbs +++ b/app/assets/javascripts/discourse/templates/components/groups-form-membership-fields.hbs @@ -27,6 +27,9 @@
+ {{plugin-outlet name="groups-form-membership-below-automatic" + args=(hash model=model)}} +
diff --git a/app/assets/javascripts/discourse/templates/components/reviewable-field.hbs b/app/assets/javascripts/discourse/templates/components/reviewable-field.hbs new file mode 100644 index 0000000000..cf9e69ed6d --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/reviewable-field.hbs @@ -0,0 +1,6 @@ +{{#if value }} +
+
{{name}}
+
{{value}}
+
+{{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/templates/components/reviewable-flagged-post.hbs b/app/assets/javascripts/discourse/templates/components/reviewable-flagged-post.hbs index be4407ffc9..22b5ceb385 100644 --- a/app/assets/javascripts/discourse/templates/components/reviewable-flagged-post.hbs +++ b/app/assets/javascripts/discourse/templates/components/reviewable-flagged-post.hbs @@ -12,9 +12,8 @@
{{reviewable-created-by user=reviewable.target_created_by tagName=''}}
- {{reviewable-created-by-name user=reviewable.target_created_by tagName=''}} + {{reviewable-post-header reviewable=reviewable createdBy=reviewable.target_created_by tagName=''}}
- {{#if reviewable.blank_post}}

{{i18n "review.deleted_post"}}

{{else}} diff --git a/app/assets/javascripts/discourse/templates/components/reviewable-post-header.hbs b/app/assets/javascripts/discourse/templates/components/reviewable-post-header.hbs new file mode 100644 index 0000000000..70b7d32673 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/reviewable-post-header.hbs @@ -0,0 +1,9 @@ +
+ {{reviewable-created-by-name user=createdBy tagName=''}} + {{#if reviewable.reply_to_post_number}} + + {{d-icon "share"}} + {{i18n "review.in_reply_to"}} + + {{/if}} +
diff --git a/app/assets/javascripts/discourse/templates/components/reviewable-queued-post.hbs b/app/assets/javascripts/discourse/templates/components/reviewable-queued-post.hbs index 1a02f4d962..fb54fe9763 100644 --- a/app/assets/javascripts/discourse/templates/components/reviewable-queued-post.hbs +++ b/app/assets/javascripts/discourse/templates/components/reviewable-queued-post.hbs @@ -1,5 +1,6 @@ {{#reviewable-topic-link reviewable=reviewable tagName=''}} -
{{i18n "review.new_topic"}} +
+ {{d-icon "plus-square" title="review.new_topic"}} {{reviewable.payload.title}}
{{category-badge reviewable.category}} @@ -10,7 +11,7 @@ {{reviewable-created-by user=reviewable.created_by tagName=''}}
- {{reviewable-created-by-name user=reviewable.created_by tagName=''}} + {{reviewable-post-header reviewable=reviewable createdBy=reviewable.created_by tagName=''}}
{{cook-text reviewable.payload.raw}} diff --git a/app/assets/javascripts/discourse/templates/components/reviewable-topic-link.hbs b/app/assets/javascripts/discourse/templates/components/reviewable-topic-link.hbs index 30d2dc8c64..4b1704aec4 100644 --- a/app/assets/javascripts/discourse/templates/components/reviewable-topic-link.hbs +++ b/app/assets/javascripts/discourse/templates/components/reviewable-topic-link.hbs @@ -1,7 +1,7 @@
{{#if reviewable.topic}} {{topic-status topic=reviewable.topic}} - {{reviewable.topic.title}} + {{reviewable.topic.title}} {{category-badge reviewable.category}} {{reviewable-tags tags=reviewable.topic_tags tagName=''}} {{else if (has-block)}} diff --git a/app/assets/javascripts/discourse/templates/components/reviewable-user.hbs b/app/assets/javascripts/discourse/templates/components/reviewable-user.hbs index f55516363d..96977b4b53 100644 --- a/app/assets/javascripts/discourse/templates/components/reviewable-user.hbs +++ b/app/assets/javascripts/discourse/templates/components/reviewable-user.hbs @@ -12,21 +12,19 @@ {{/if}}
- {{#if reviewable.payload.name}} -
-
{{i18n "review.user.name"}}
-
{{reviewable.payload.name}}
-
- {{/if}} - + + {{reviewable-field classes='reviewable-user-details name' + name=(i18n 'review.user.name') + value=reviewable.payload.name}} + + {{reviewable-field classes='reviewable-user-details email' + name=(i18n 'review.user.email') + value=reviewable.payload.email}} + {{#each userFields as |f|}} -
-
{{f.name}}
-
{{f.value}}
-
+ {{reviewable-field classes='reviewable-user-details user-field' + name=f.name + value=f.value}} {{/each}}
diff --git a/app/assets/javascripts/discourse/templates/group-index.hbs b/app/assets/javascripts/discourse/templates/group-index.hbs index 583e827abe..397cf7808e 100644 --- a/app/assets/javascripts/discourse/templates/group-index.hbs +++ b/app/assets/javascripts/discourse/templates/group-index.hbs @@ -64,8 +64,10 @@ removeMember=(action "removeMember") makeOwner=(action "makeOwner") removeOwner=(action "removeOwner") - member=m}} + member=m + group=model}} {{/if}} + {{!-- group parameter is used by plugins --}} {{/each}} diff --git a/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs b/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs index 1e82906f4d..2423725abf 100644 --- a/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs +++ b/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs @@ -22,7 +22,7 @@

{{i18n "topic.feature_topic.pin_note"}}

{{/if}}

{{{unPinMessage}}}

-

{{d-button action=(action "unpin") icon="thumb-tack" label="topic.feature.unpin" class="btn-primary"}}

+

{{d-button action=(action "unpin") icon="thumbtack" label="topic.feature.unpin" class="btn-primary"}}

{{else}} @@ -61,7 +61,7 @@

{{/if}}

- {{d-button action=(action "pin") icon="thumb-tack" label="topic.feature.pin" class="btn-primary"}} + {{d-button action=(action "pin") icon="thumbtack" label="topic.feature.pin" class="btn-primary"}}

@@ -105,7 +105,7 @@

{{/if}}

- {{d-button action=(action "pinGlobally") icon="thumb-tack" label="topic.feature.pin_globally" class="btn-primary"}} + {{d-button action=(action "pinGlobally") icon="thumbtack" label="topic.feature.pin_globally" class="btn-primary"}}

@@ -135,9 +135,9 @@

{{#if model.isBanner}} - {{d-button action=(action "removeBanner") icon="thumb-tack" label="topic.feature.remove_banner" class="btn-primary"}} + {{d-button action=(action "removeBanner") icon="thumbtack" label="topic.feature.remove_banner" class="btn-primary"}} {{else}} - {{d-button action=(action "makeBanner") icon="thumb-tack" label="topic.feature.make_banner" class="btn-primary"}} + {{d-button action=(action "makeBanner") icon="thumbtack" label="topic.feature.make_banner" class="btn-primary"}} {{/if}}

diff --git a/app/assets/javascripts/discourse/templates/preferences/categories.hbs b/app/assets/javascripts/discourse/templates/preferences/categories.hbs index f76b48e827..aaf5572a22 100644 --- a/app/assets/javascripts/discourse/templates/preferences/categories.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/categories.hbs @@ -33,7 +33,7 @@ {{category-selector categories=model.mutedCategories blacklist=selectedCategories}} -
{{i18n 'user.muted_categories_instructions'}}
+
{{i18n (if hideMutedTags 'user.muted_categories_instructions' 'user.muted_categories_instructions_dont_hide')}}
{{#if canSee}}
{{i18n 'user.muted_topics_link'}} diff --git a/app/assets/javascripts/discourse/templates/preferences/interface.hbs b/app/assets/javascripts/discourse/templates/preferences/interface.hbs index e1192d127d..24d245b8c0 100644 --- a/app/assets/javascripts/discourse/templates/preferences/interface.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/interface.hbs @@ -47,17 +47,18 @@
- {{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.user_option.external_links_in_new_tab}} - {{preference-checkbox labelKey="user.enable_quoting" checked=model.user_option.enable_quoting}} + {{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.user_option.external_links_in_new_tab class="pref-external-links"}} + {{preference-checkbox labelKey="user.enable_quoting" checked=model.user_option.enable_quoting class="pref-enable-quoting"}} + {{preference-checkbox labelKey="user.enable_defer" checked=model.user_option.enable_defer class="pref-defer-undread"}} {{#if siteSettings.automatically_unpin_topics}} - {{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.user_option.automatically_unpin_topics}} + {{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.user_option.automatically_unpin_topics class="pref-auto-unpin"}} {{/if}} - {{preference-checkbox labelKey="user.hide_profile_and_presence" checked=model.user_option.hide_profile_and_presence}} + {{preference-checkbox labelKey="user.hide_profile_and_presence" checked=model.user_option.hide_profile_and_presence class="pref-hide-profile"}} {{#if isiPad}} - {{preference-checkbox labelKey="user.enable_physical_keyboard" checked=disableSafariHacks}} + {{preference-checkbox labelKey="user.enable_physical_keyboard" checked=disableSafariHacks class="pref-safari-hacks"}} {{/if}} - {{preference-checkbox labelKey="user.dynamic_favicon" checked=model.user_option.dynamic_favicon}} -
+ {{preference-checkbox labelKey="user.dynamic_favicon" checked=model.user_option.dynamic_favicon class="pref-dynamic-favicon"}} +
{{combo-box valueAttribute="value" content=titleCountModes diff --git a/app/assets/javascripts/discourse/templates/preferences/users.hbs b/app/assets/javascripts/discourse/templates/preferences/users.hbs index 6d77e2bd98..ad39b51a9c 100644 --- a/app/assets/javascripts/discourse/templates/preferences/users.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/users.hbs @@ -1,5 +1,5 @@ -
+
{{#if ignoredEnabled}} -
-
+
+
{{ignored-user-list model=model items=model.ignored_usernames saving=saved}}
diff --git a/app/assets/javascripts/discourse/templates/review-index.hbs b/app/assets/javascripts/discourse/templates/review-index.hbs index b4770800db..a7cd4c95a7 100644 --- a/app/assets/javascripts/discourse/templates/review-index.hbs +++ b/app/assets/javascripts/discourse/templates/review-index.hbs @@ -55,6 +55,11 @@ {{d-button label="review.show_all_topics" icon="times" action=(action "resetTopic")}}
{{/if}} + +
+ {{i18n "review.order_by"}} + {{combo-box value=filterSortOrder content=sortOrders}} +
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index b47f7935cc..f58cc7fd56 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -301,6 +301,7 @@ showFlagTopic=(route-action "showFlagTopic") toggleArchiveMessage=(action "toggleArchiveMessage") editFirstPost=(action "editFirstPost") + deferTopic=(action "deferTopic") replyToPost=(action "replyToPost")}} {{else}} diff --git a/app/views/list/list.rss.erb b/app/views/list/list.rss.erb index 69799bc593..8824eeb86b 100644 --- a/app/views/list/list.rss.erb +++ b/app/views/list/list.rss.erb @@ -23,7 +23,9 @@

<%= t('author_wrote', author: link_to("@#{username}", "#{Discourse.base_url}/u/#{topic.user.username_lower}")).html_safe %>

<% end %>
- <%= topic.posts.first.cooked.html_safe %> + <%- if first_post = topic.ordered_posts.first %> + <%= first_post.cooked.html_safe %> + <%- end %>

<%= t 'num_posts' %> <%= topic.posts_count %>

<%= t 'num_participants' %> <%= topic.participant_count %>

diff --git a/app/views/tags/_tag.html.erb b/app/views/tags/_tag.html.erb new file mode 100644 index 0000000000..b8258f9a12 --- /dev/null +++ b/app/views/tags/_tag.html.erb @@ -0,0 +1,6 @@ +
+ <%= tag[:text] %> + <% if tag[:count] && tag[:count] > 0 %> + x <%= tag[:count] %> + <% end %> +
diff --git a/app/views/tags/index.html.erb b/app/views/tags/index.html.erb index cc250d4684..823b146de0 100644 --- a/app/views/tags/index.html.erb +++ b/app/views/tags/index.html.erb @@ -1,3 +1,45 @@ <% content_for :head do %> <%= raw crawlable_meta_data(title: @title, description: @description_meta) %> <% end %> + +
+ + <% if @extras[:categories] %> + <% @extras[:categories].each do |category| %> +
+ <% if category[:name] %> +

<%= category[:name] %>

+ <% end %> + <% category[:tags].each do |tag| %> + <%= render "tag", tag: tag %> + <% end %> +
+
+ <% end %> + <% end %> + + <% if @extras[:tag_groups] %> + <% @extras[:tag_groups].each do |tag_group| %> +
+ <% if tag_group[:name] %> +

<%= tag_group[:name] %>

+ <% end %> + <% tag_group[:tags].each do |tag| %> + <%= render "tag", tag: tag %> + <% end %> +
+
+ <% end %> + <% end %> + + <% if @tags.present? %> +
+

<%= t 'js.tagging.other_tags' %>

+ <% @tags.each do |tag| %> + <%= render "tag", tag: tag %> + <% end %> +
+
+ <% end %> + +
diff --git a/app/views/topics/show.html.erb b/app/views/topics/show.html.erb index 019c451c5c..9d921c58bc 100644 --- a/app/views/topics/show.html.erb +++ b/app/views/topics/show.html.erb @@ -1,4 +1,4 @@ -

+

<%= render_topic_title(@topic_view.topic) %>

@@ -22,12 +22,11 @@ <% if SiteSetting.tagging_enabled %> <% @tags = @topic_view.topic.tags %> <% if @tags.present? %> -
- <% @tags.each do |tag| %> -
- - - <%= tag.name -%> +
+ <% @tags.each_with_index do |tag, i| %> + <% end %> @@ -35,7 +34,6 @@ <% end %> <% end %> - <%= server_plugin_outlet "topic_header" %> <%- if include_crawler_content? %> @@ -43,16 +41,20 @@ <% @topic_view.posts.each do |post| %>
<% if (u = post.user) %> -
- - +
<%= post.hidden ? t('flagging.user_must_edit').html_safe : post.cooked.html_safe %>
- - + -
- <%= post.like_count > 0 ? t('post.has_likes', count: post.like_count) : '' %> -
- <% if @topic_view.link_counts[post.id] && @topic_view.link_counts[post.id].length > 0 %> - <% @topic_view.link_counts[post.id].each do |link| %> - <% if link[:reflection] && link[:title].present? %> - <%=link[:title]%> -
- <% end %> - <% end %> - <% end %> + +
+ + + <%= post.like_count > 0 ? t('post.has_likes', count: post.like_count) : '' %> +
+ +
+ + +
+ + <% if @topic_view.link_counts[post.id] && @topic_view.link_counts[post.id].length > 0 %> +
+ <% @topic_view.link_counts[post.id].each_with_index do |link, i| %> + <% if link[:reflection] && link[:title].present? %> + + <% end %> + <% end %> +
+ <% end %> + <% end %>
<% end %> diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf index bf4e8e3fa7..e66ed464b2 100644 --- a/config/discourse_defaults.conf +++ b/config/discourse_defaults.conf @@ -239,3 +239,13 @@ refresh_maxmind_db_during_precompile_days = 2 # backup path containing maxmind db files maxmind_backup_path = +# when enabled the following headers will be added to every response: +# (note, if measurements do not exist for the header they will be omitted) +# +# X-Redis-Calls: 10 +# X-Redis-Time: 1.02 +# X-Sql-Calls: 102 +# X-Sql-Time: 1.02 +# X-Queue-Time: 1.01 +enable_performance_http_headers = false + diff --git a/config/initializers/014-track-setting-changes.rb b/config/initializers/014-track-setting-changes.rb index 9720c1e7d8..bf7d998bf6 100644 --- a/config/initializers/014-track-setting-changes.rb +++ b/config/initializers/014-track-setting-changes.rb @@ -34,6 +34,8 @@ DiscourseEvent.on(:site_setting_changed) do |name, old_value, new_value| Jobs.enqueue(:update_s3_inventory) if [:s3_inventory, :s3_upload_bucket].include?(name) + Jobs.enqueue(:update_private_uploads_acl) if name == :prevent_anons_from_downloading_files + SvgSprite.expire_cache if name.to_s.include?("_icon") if SiteIconManager::WATCHED_SETTINGS.include?(name) diff --git a/config/initializers/200-message_bus_request_tracker.rb b/config/initializers/200-message_bus_request_tracker.rb index 1026912c49..c0ebcfcb1b 100644 --- a/config/initializers/200-message_bus_request_tracker.rb +++ b/config/initializers/200-message_bus_request_tracker.rb @@ -16,4 +16,8 @@ Rails.configuration.middleware = Rails.configuration.middleware + session_operat if Rails.env != 'development' || ENV['TRACK_REQUESTS'] require 'middleware/request_tracker' Rails.configuration.middleware.unshift Middleware::RequestTracker + + if GlobalSetting.enable_performance_http_headers + MethodProfiler.ensure_discourse_instrumentation! + end end diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index e26ab853a1..71e66c08cb 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -1332,6 +1332,8 @@ ar: replied: 'ردّ {{username}} عليك في "{{topic}}" - {{site_title}}' posted: 'نشر {{username}} في "{{topic}}" - {{site_title}}' linked: '{{username}} وضع رابطا لمنشورك في "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "موضوع جديد" upload_selector: title: "أضف صورة" title_with_attachments: "أضف صورة أو ملفّ" @@ -1489,6 +1491,8 @@ ar: help: "انقل الرسالة للبريد الوارد" edit_message: title: "تحرير رسالة" + defer: + title: "تأجيل" list: "الموضوعات" new: "موضوع جديد" unread: "غير مقروء" @@ -2517,6 +2521,10 @@ ar: admin: title: "مدير المجتمع" moderator: "مشرف" + tags: + remove_muted_tags_from_latest: + always: "دائما" + never: "أبدا" dashboard: title: "لوحة التحكم" version: "الإصدار" diff --git a/config/locales/client.bg.yml b/config/locales/client.bg.yml index 1524c1c2af..858eb56d91 100644 --- a/config/locales/client.bg.yml +++ b/config/locales/client.bg.yml @@ -1074,6 +1074,9 @@ bg: linked: '{{username}} прикачи вашата публикация от "{{topic}}" - {{site_title}}' confirm_title: "Включени известявания - %{site_title}" confirm_body: "Успех! Известяването е включено." + titles: + watching_first_post: "нова тема" + post_approved: "публикацията е одобрена" upload_selector: title: "Добавете изображение" title_with_attachments: "Добавете изображение или файл" @@ -1204,6 +1207,8 @@ bg: move_to_inbox: title: "Премести във входящи" help: "Премести съобщението обратно във входящи" + defer: + title: "Отложи " list: "Теми" new: "нова тема" unread: "непрочетено" @@ -1954,6 +1959,10 @@ bg: admin: title: "Discourse Админ" moderator: "Модератор" + tags: + remove_muted_tags_from_latest: + always: "винаги" + never: "никога" dashboard: title: "Работен плот" version: "Версия" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index 08728537d7..6f53626534 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -1332,6 +1332,8 @@ bs_BA: linked: '{{username}} je linkao/la vašu objavu "{{topic}}" - {{site_title}}' confirm_title: "Obavijesti uključene - %{site_title}" confirm_body: "Uspješno! Obavijesti su sada uključene." + titles: + watching_first_post: "nova tema" upload_selector: title: "Dodaj sliku" title_with_attachments: "Dodaj sliku ili fajl" @@ -1498,6 +1500,8 @@ bs_BA: edit_message: help: "Izmijeni prvu objavu ove poruke" title: "Izmijeni poruku" + defer: + title: "Defer" list: "Teme" new: "nova tema" unread: "nepročitana" @@ -2236,6 +2240,10 @@ bs_BA: admin: title: "Discourse Admin" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "uvijek" + never: "nikad" dashboard: title: "Dashboard" version: "Version" diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml index 74c26061b6..45afb90073 100644 --- a/config/locales/client.ca.yml +++ b/config/locales/client.ca.yml @@ -109,10 +109,15 @@ ca: next_month: "Següent mes" placeholder: data share: + topic_html: 'Tema: %{topicTitle}' post: "publica #%{postNumber}" close: "tanca" + twitter: "Comparteix aquest enllaç a Twitter" + facebook: "Comparteix aquest enllaç a Facebook" + email: "Envia aquest enllaç a un correu electrònic" action_codes: public_topic: "va fer públic aquest tema %{when}" + private_topic: "es va convertir aquest tema en un missatge personal %{when}" split_topic: "va dividir aquest tema %{when}" invited_user: "va invitar %{who} %{when}" invited_group: "va invitar %{who} %{when}" @@ -158,6 +163,7 @@ ca: eu_west_1: "EU (Irlanda)" eu_west_2: "EU (Londres)" eu_west_3: "UE (Paris)" + sa_east_1: "Sud-amèrica (São Paulo)" us_east_1: "US East (N. Virginia)" us_east_2: "EEUU Est (Ohio)" us_west_1: "US West (N. California)" @@ -1176,6 +1182,8 @@ ca: replied: '{{username}} t''ha respost a "{{topic}}" - {{site_title}}' posted: '{{username}} publicat a "{{topic}}" - {{site_title}}' linked: '{{username}} ha enllaçat la teva entrada en "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "nou tema" upload_selector: title: "Afegeix una imatge" title_with_attachments: "Afegeix una imatge o arxiu" @@ -1314,6 +1322,8 @@ ca: move_to_inbox: title: "Mou a la safata d'entrada" help: "Torna el missatge a la safata d'entrada" + defer: + title: "Aplaça" list: "Temes" new: "nou tema" unread: "sense llegir" @@ -2129,6 +2139,10 @@ ca: admin: title: "Administració de Discourse" moderator: "Moderació" + tags: + remove_muted_tags_from_latest: + always: "sempre" + never: "mai" dashboard: title: "Tauler de control" version: "Versió" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 0305274cb8..31b44864a0 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -337,6 +337,7 @@ cs: title: placeholder: "sem napište název tématu" review: + in_reply_to: "v odpovědi na" delete: "Smazat" settings: save_changes: "Uložit změny" @@ -1459,6 +1460,8 @@ cs: linked: '{{username}} odkázal na vás příspěvek v "{{topic}}" - {{site_title}}' confirm_title: "Upozornění zapnuta - %{site_title}" confirm_body: "Upozornění úspěšně zapnuta." + titles: + watching_first_post: "nové téma" upload_selector: title: "Vložit obrázek" title_with_attachments: "Nahrát obrázek nebo soubor" @@ -1628,6 +1631,8 @@ cs: edit_message: help: "Upravit první příspěvek zprávy" title: "upravit zprávu" + defer: + title: "Odložit" list: "Témata" new: "nové téma" unread: "nepřečtený" @@ -2666,6 +2671,10 @@ cs: admin: title: "Administrátor" moderator: "Moderátor" + tags: + remove_muted_tags_from_latest: + always: "vždy" + never: "nikdy" reports: title: "Seznam dostupných reportů" dashboard: diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 76610a7015..200d6cf962 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -253,6 +253,7 @@ da: title: placeholder: "indtast emnets titel her" review: + in_reply_to: "som svar til" delete: "Slet" settings: save_changes: "Gem ændringer" @@ -1117,6 +1118,8 @@ da: replied: '{{username}} svarede dig i "{{topic}}" - {{site_title}}' posted: '{{username}} skrev i "{{topic}}" - {{site_title}}' linked: '{{username}} linkede til dit indlæg fra "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "nyt emne" upload_selector: title: "Indsæt billede" title_with_attachments: "Tilføj et billede eller en fil" @@ -1256,6 +1259,8 @@ da: help: "Flyt beskeder tilbage til Indbakke" edit_message: title: "Rediger Besked" + defer: + title: "Udsæt" list: "Emner" new: "nyt emne" unread: "ulæste" @@ -2113,6 +2118,10 @@ da: admin: title: "Discourse Admin" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "altid" + never: "aldrig" dashboard: title: "Dashboard" version: "Installeret version" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index bce3459428..d6b0cc5dd9 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -153,6 +153,7 @@ de: bootstrap_mode_disabled: "Starthilfe-Modus wird in den nächsten 24 Stunden deaktiviert." themes: default_description: "Standard" + broken_theme_alert: "Deine Seite funktioniert vielleicht nicht, weil Theme/Komponent %{theme} Fehler hat. Deaktiviere es in %{path}." s3: regions: ap_northeast_1: "Asien-Pazifik (Tokio)" @@ -307,6 +308,8 @@ de: search: "Suche eine Nachricht anhand der Überschrift:" placeholder: "Gib hier die Überschrift der Nachricht ein" review: + order_by: "beauftragt von" + in_reply_to: "Antwort auf" claim_help: optional: "Du kannst dieses Element reservieren, damit andere es nicht überprüfen." required: "Du musst Elemente reservieren, bevor du sie überprüfen kannst." @@ -382,7 +385,13 @@ de: refresh: "Aktualisieren" status: "Status" category: "Kategorie" + orders: + priority: "Priorität" + priority_asc: "Priorität (umgekehrt)" + created_at: "Erstellt am" + created_at_asc: "Erstellt am (umgekehrt)" priority: + title: "Minimale Priorität" low: "Niedrig" medium: "Mittel" high: "Hoch" @@ -415,6 +424,8 @@ de: reviewable_flagged_post: title: "Gemeldeter Beitrag" flagged_by: "Gemeldet von" + reviewable_queued_topic: + title: "Thema in der Warteschlange" reviewable_queued_post: title: "Beitrag in der Warteschlange" reviewable_user: @@ -727,6 +738,7 @@ de: allow_private_messages: "Anderen Benutzern erlauben, mir persönliche Nachrichten zu schicken" external_links_in_new_tab: "Öffne alle externen Links in einem neuen Tab" enable_quoting: "Aktiviere Zitatantwort mit dem hervorgehobenen Text" + enable_defer: "Aktiviere Verzögerung für das ungelesen Markieren von Themen" change: "ändern" moderator: "{{user}} ist ein Moderator" admin: "{{user}} ist ein Administrator" @@ -766,6 +778,7 @@ de: watched_first_post_tags_instructions: "Du erhältst eine Benachrichtigung für den ersten Beitrag in jedem neuen Thema mit diesen Schlagwörtern." muted_categories: "Stummgeschaltet" muted_categories_instructions: "Du erhältst keine Benachrichtigungen über neue Themen in dieser Kategorie und die Themen werden auch nicht in der Liste der Kategorien oder der aktuellen Themen erscheinen." + muted_categories_instructions_dont_hide: "Du bekommst keine Benachrichtigung über irgendetwas an neuen Themen in diesen Kategorien." no_category_access: "Moderaturen haben eingeschränkte Kategorien-Berechtigungen, Speichern ist nicht verfügbar." delete_account: "Lösche mein Benutzerkonto" delete_account_confirm: "Möchtest du wirklich dein Benutzerkonto permanent löschen? Diese Aktion kann nicht rückgängig gemacht werden!" @@ -1161,6 +1174,11 @@ de: too_few_topics_and_posts_notice: "Lass' die Diskussionen starten! Es existieren bisher %{currentTopics} von %{requiredTopics} benötigten Themen und %{currentPosts} von %{requiredPosts} benötigten Beiträgen. Neue Besucher benötigen bestehende Konversationen, die sie lesen und auf die sie antworten können." too_few_topics_notice: "Lass' die Diskussionen starten! Es existieren bisher %{currentTopics} von %{requiredTopics} benötigten Themen. Neue Besucher benötigen bestehende Konversationen, die sie lesen und auf die sie antworten können." too_few_posts_notice: "Lass' die Diskussionen starten! Es existieren bisher %{currentPosts} von %{requiredPosts} benötigten Beiträgen. Neue Besucher benötigen bestehende Konversationen, die sie lesen und auf die sie antworten können." + logs_error_rate_notice: + reached_hour_MF: "{relativeAge}{rate, plural, one {# Fehler/Stunde} other {# errors/hour}} hat die Grenze der Webseiten-Einstellung von {limit, plural, one {# Fehler/Stunde} other {# Fehler/Stunde}} erreicht." + reached_minute_MF: "{relativeAge}{rate, plural, one {# Fehler/Minute} other {# Fehler/Minute}} hat die Grenze der Webseiten-Einstellung von {limit, plural, one {# Fehler/Minute} other {# Fehler/Minute}} erreicht." + exceeded_hour_MF: "{relativeAge}{rate, plural, one {# Fehler/Stunde} other {# Fehler/Stunde}} hat die Grenze der Webseiten-Einstellung von {limit, plural, one {# Fehler/Stunde} other {# Fehler/Stunde}} überschritten." + exceeded_minute_MF: "{relativeAge}{rate, plural, one {# Fehler/Minute} other {# Fehler/Minute}} hat die Grenze der Webseiten-Einstellung von {limit, plural, one {# Fehler/Minute} other {# Fehler/Minute}} überschritten." learn_more: "mehr erfahren…" all_time: "gesamt" all_time_desc: "Erstellte Themen" @@ -1553,6 +1571,26 @@ de: confirm_title: "Benachrichtigungen aktiviert – %{site_title}" confirm_body: "Erfolgreich! Benachrichtigungen wurden aktiviert." custom: "Benachrichtigung von {{username}} auf %{site_title}" + titles: + mentioned: "erwähnte" + replied: "neue Antwort" + quoted: "zitiert" + edited: "bearbeitet" + liked: "neue „Gefällt mir“-Angabe" + private_message: "neue private Nachricht" + invited_to_private_message: "zu einer privaten Nachricht eingeladen" + invitee_accepted: "Einladung angenommen" + posted: "neuer Beitrag" + moved_post: "Beitrag verschoben" + linked: "verknüpft" + granted_badge: "Abzeichen gewährt" + invited_to_topic: "zum Thema eingeladen" + group_mentioned: "Gruppe erwähnt" + group_message_summary: "neue Gruppen-Nachrichten" + watching_first_post: "neues Thema" + topic_reminder: "Themen-Erinnerung" + liked_consolidated: "neue „Gefällt mir“-Angaben" + post_approved: "Beitrag genehmigt" upload_selector: title: "Ein Bild hinzufügen" title_with_attachments: "Ein Bild oder eine Datei hinzufügen" @@ -1716,6 +1754,9 @@ de: edit_message: help: "Bearbeite ersten Beitrag dieser Nachricht" title: "Nachricht bearbeiten" + defer: + help: "Als ungelesen markieren" + title: "Ignorieren" list: "Themen" new: "neues Thema" unread: "ungelesen" @@ -2741,6 +2782,11 @@ de: admin: title: "Discourse-Administrator" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "immer" + only_muted: "wenn allein oder mit anderen stummgeschalteten Tags verwendet" + never: "nie" reports: title: "Verfügbare Berichte" dashboard: @@ -2815,6 +2861,7 @@ de: groups: "Alle Gruppen" disabled: "Dieser Bericht ist deaktiviert" totals_for_sample: "Insgesamt für Stichprobe" + average_for_sample: "Durchschnitt für Beispiel" total: "Gesamt aller Zeiten" no_data: "Keine Daten anzuzeigen." trending_search: @@ -3599,6 +3646,7 @@ de: delete_posts_failed: "Es gab ein Problem beim Löschen der Beiträge." penalty_post_actions: "Was möchtest du mit dem zugehörigen Beitrag machen?" penalty_post_delete: "Beitrag löschen" + penalty_post_delete_replies: "Den Beitrag und etwaige Antworten löschen" penalty_post_edit: "Beitrag bearbeiten" penalty_post_none: "Nichts tun" penalty_count: "Anzahl der Strafen" diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml index 49b2236923..ff809ac930 100644 --- a/config/locales/client.el.yml +++ b/config/locales/client.el.yml @@ -23,20 +23,20 @@ el: mb: MB tb: TB short: - thousands: "{{number}}k" - millions: "{{number}}M" + thousands: "{{number}}χιλ." + millions: "{{number}}εκατ." dates: time: "ΗΗ:mm" timeline_date: "MMM YYYY" long_no_year: "DD MMM HH:mm" long_no_year_no_time: "DD MMM" full_no_year_no_time: "Do MMMM" - long_with_year: "DD MMM YYYY h:mm a" - long_with_year_no_time: "D, MMM YYYY" - full_with_year_no_time: "Do MMMM, YYYY" - long_date_with_year: "DD, MMM 'YY HH:mm" + long_with_year: "D MMM YYYY HH:mm" + long_with_year_no_time: "D MMM YYYY" + full_with_year_no_time: "D MMMM YYYY" + long_date_with_year: "D MMM 'YY LT" long_date_without_year: "MMM D, LT" - long_date_with_year_without_time: "D MMM, 'YY" + long_date_with_year_without_time: "MMM D, 'YY" long_date_without_year_with_linebreak: "MMM D
LT" long_date_with_year_with_linebreak: "MMM D, 'YY
LT" wrap_ago: "%{date} πριν" @@ -1167,6 +1167,8 @@ el: replied: '{{username}} σου απάντησε στο "{{topic}}" - {{site_title}}' posted: '{{username}} ανάρτησε στο "{{topic}}" - {{site_title}}' linked: '{{username}} έκανε μια σύνδεση στην ανάρτηση που έκανες στο νήμα "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "νέο νήμα" upload_selector: title: "Προσθήκη εικόνας" title_with_attachments: "Προσθήκη εικόνας ή αρχείου" @@ -1311,6 +1313,8 @@ el: move_to_inbox: title: "Μετακίνηση στα Εισερχόμενα" help: "Μετακίνηση μηνύματος πίσω στα Εισερχόμενα" + defer: + title: "Αναβολή" list: "Νήματα" new: "νέο νήμα" unread: "αδιάβαστο" @@ -2208,6 +2212,10 @@ el: admin: title: "Διαχειριστής" moderator: "Συντονιστής" + tags: + remove_muted_tags_from_latest: + always: "πάντα" + never: "ποτέ" dashboard: title: "Πίνακας ελέγχου" version: "Έκδοση" diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 844b204ac7..24f5a293c6 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -363,6 +363,8 @@ en: placeholder: "type the message title here" review: + order_by: "Order by" + in_reply_to: "in reply to" claim_help: optional: "You can claim this item to prevent others from reviewing it." required: "You must claim items before you can review them." @@ -431,7 +433,7 @@ en: edit: "Edit" save: "Save" cancel: "Cancel" - new_topic: "New Topic:" + new_topic: "Approving this item will create a new topic" filters: type: @@ -441,6 +443,12 @@ en: refresh: "Refresh" status: "Status" category: "Category" + orders: + priority: "Priority" + priority_asc: "Priority (reverse)" + created_at: "Created At" + created_at_asc: "Created At (reverse)" + priority: title: "Minimum Priority" low: "Low" @@ -478,6 +486,8 @@ en: reviewable_flagged_post: title: "Flagged Post" flagged_by: "Flagged By" + reviewable_queued_topic: + title: "Queued Topic" reviewable_queued_post: title: "Queued Post" reviewable_user: @@ -803,6 +813,7 @@ en: allow_private_messages: "Allow other users to send me personal messages" external_links_in_new_tab: "Open all external links in a new tab" enable_quoting: "Enable quote reply for highlighted text" + enable_defer: "Enable defer to mark topics unread" change: "change" moderator: "{{user}} is a moderator" admin: "{{user}} is an admin" @@ -843,6 +854,7 @@ en: muted_categories: "Muted" muted_categories_instructions: "You will not be notified of anything about new topics in these categories, and they will not appear on the categories or latest pages." + muted_categories_instructions_dont_hide: "You will not be notified of anything about new topics in these categories." no_category_access: "As a moderator you have limited category access, save is disabled." delete_account: "Delete My Account" delete_account_confirm: "Are you sure you want to permanently delete your account? This action cannot be undone!" @@ -1273,9 +1285,13 @@ en: too_few_topics_notice: "Let's get this discussion started! There are currently %{currentTopics} / %{requiredTopics} topics. New visitors need some conversations to read and respond to." too_few_posts_notice: "Let's get this discussion started! There are currently %{currentPosts} / %{requiredPosts} posts. New visitors need some conversations to read and respond to." logs_error_rate_notice: + # keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details reached_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} other {# errors/hour}} reached site setting limit of {limit, plural, one {# error/hour} other {# errors/hour}}." + # keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details reached_minute_MF: "{relativeAge}{rate, plural, one {# error/minute} other {# errors/minute}} reached site setting limit of {limit, plural, one {# error/minute} other {# errors/minute}}." + # keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details exceeded_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} other {# errors/hour}} exceeded site setting limit of {limit, plural, one {# error/hour} other {# errors/hour}}." + # keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details exceeded_minute_MF: "{relativeAge}{rate, plural, one {# error/minute} other {# errors/minute}} exceeded site setting limit of {limit, plural, one {# error/minute} other {# errors/minute}}." learn_more: "learn more..." @@ -1706,6 +1722,27 @@ en: confirm_body: "Success! Notifications have been enabled." custom: "Notification from {{username}} on %{site_title}" + titles: + mentioned: "mentioned" + replied: "new reply" + quoted: "quoted" + edited: "edited" + liked: "new like" + private_message: "new private message" + invited_to_private_message: "invited to private message" + invitee_accepted: "invite accepted" + posted: "new post" + moved_post: "post moved" + linked: "linked" + granted_badge: "badge granted" + invited_to_topic: "invited to topic" + group_mentioned: "group mentioned" + group_message_summary: "new group messages" + watching_first_post: "new topic" + topic_reminder: "topic reminder" + liked_consolidated: "new likes" + post_approved: "post approved" + upload_selector: title: "Add an image" title_with_attachments: "Add an image or a file" @@ -1876,6 +1913,9 @@ en: edit_message: help: "Edit first post of the message" title: "Edit Message" + defer: + help: "Mark as unread" + title: "Defer" list: "Topics" new: "new topic" unread: "unread" @@ -2999,6 +3039,12 @@ en: title: "Discourse Admin" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "always" + only_muted: "when used alone or with other muted tags" + never: "never" + reports: title: "List of available reports" @@ -3075,6 +3121,7 @@ en: groups: "All groups" disabled: "This report is disabled" totals_for_sample: "Totals for sample" + average_for_sample: "Average for sample" total: "All time total" no_data: "No data to display." trending_search: diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index 8eb01a7b58..1c95677847 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -26,15 +26,15 @@ es: thousands: "{{number}}k" millions: "{{number}}M" dates: - time: "h:mm a" + time: "HH:mm" timeline_date: "MMM YYYY" - long_no_year: "MMM D h:mm a" - long_no_year_no_time: "MMM D" - full_no_year_no_time: "MMMM Do" - long_with_year: "MMM D, YYYY h:mm a" - long_with_year_no_time: "MMM D, YYYY" - full_with_year_no_time: "MMMM Do, YYYY" - long_date_with_year: "D MMM, 'YY LT" + long_no_year: "D MMM HH:mm" + long_no_year_no_time: "D MMM" + full_no_year_no_time: "Do MMMM" + long_with_year: "D MMM YYYY HH:mm" + long_with_year_no_time: "D MMM YYYY" + full_with_year_no_time: "D MMMM YYYY" + long_date_with_year: "D MMM 'YY LT" long_date_without_year: "D MMM, LT" long_date_with_year_without_time: "D MMM, 'YY" long_date_without_year_with_linebreak: "D MMM
LT" @@ -153,6 +153,7 @@ es: bootstrap_mode_disabled: "El modo de arranque se desactivará dentro de 24 horas." themes: default_description: "Por defecto" + broken_theme_alert: "Tu sitio puede no funcionar porque el theme / componente %{theme} tiene errores. Desactivarlo en %{path}." s3: regions: ap_northeast_1: "Asia Pacific (Tokyo)" @@ -172,6 +173,7 @@ es: us_east_1: "US East (N. Virginia)" us_east_2: "US East (Ohio)" us_gov_east_1: "AWS GovCloud (US-East)" + us_gov_west_1: "AWS GovCloud (US-West)" us_west_1: "US West (N. California)" us_west_2: "US West (Oregon)" edit: "editar el título y la categoría de este tema" @@ -306,6 +308,8 @@ es: search: "Buscar un mensaje por título:" placeholder: "escribe el título del mensaje aquí" review: + order_by: "Ordenar por" + in_reply_to: "en respuesta a" claim_help: optional: "Puedes reclamar este artículo para evitar que otros lo revisen." required: "Debes reclamar los artículos antes de poder revisarlos." @@ -321,6 +325,8 @@ es: saved: "Guardado" save_changes: "Guardar cambios" title: "Ajustes" + priorities: + title: "Prioridades Revisables" moderation_history: "Historial de moderación" view_all: "Ver todo" grouped_by_topic: "Agrupados por tema" @@ -379,6 +385,11 @@ es: refresh: "Actualizar" status: "Estado" category: "Categoría" + orders: + priority: "Prioridad" + priority_asc: "Prioridad (inverso)" + created_at: "Creado el" + created_at_asc: "Creado el (inverso)" priority: title: "Prioridad mínima" low: "Bajo" @@ -413,6 +424,8 @@ es: reviewable_flagged_post: title: "Publicación reportada" flagged_by: "Reportado por" + reviewable_queued_topic: + title: "Tema en cola" reviewable_queued_post: title: "Publicación en cola" reviewable_user: @@ -725,6 +738,7 @@ es: allow_private_messages: "Permitir que otros usuarios me envíen mensajes privados" external_links_in_new_tab: "Abrir todos los enlaces externos en una nueva pestaña" enable_quoting: "Activar respuesta citando el texto resaltado" + enable_defer: "Habilitar diferir para marcar temas no leídos" change: "cambio" moderator: "{{user}} es un moderador" admin: "{{user}} es un administrador" @@ -764,6 +778,7 @@ es: watched_first_post_tags_instructions: "Se te notificará del primer post en cada nuevo tema con estas etiquetas." muted_categories: "Silenciado" muted_categories_instructions: "No serás notificado de ningún tema en estas categorías, y no aparecerán en la página de categorías o mensajes recientes." + muted_categories_instructions_dont_hide: "No se le notificará nada sobre nuevos temas en estas categorías." no_category_access: "Como un moderador, tienes acceso limitado a categorías, guardar está deshabilitado." delete_account: "Borrar Mi Cuenta" delete_account_confirm: "¿Estás seguro que quieres borrar permanentemente tu cuenta? ¡Esta acción no puede ser revertida!" @@ -840,6 +855,8 @@ es: description: "Cada uno de estos códigos de respaldo puede ser usado una única vez. Mantén los códigos en un lugar seguro, pero accesible." second_factor: title: "Autenticación Dos Factores" + disable: "Inhabilitar Autenticación Dos Factores" + enable: "Habilitar Autenticación Dos Factores" confirm_password_description: "Por favor confirma tu contraseña para continuar" label: "Código" rate_limit: "Por favor, espera antes de volver a intentar otro código de autenticación." @@ -847,6 +864,8 @@ es: Escanea este código QR en una aplicación que lo soporte (AndroidiOS) e introduce el código de verificación. disable_description: "Por favor ingrese el código de autenticación desde su aplicación" show_key_description: "Ingrese Manualmente" + short_description: | + Protege tu cuenta con el uso de un único código de seguridad. extended_description: | La verificación en dos pasos añade una capa extra de seguridad requiriendo un token por única vez además de tu contraseña. Los códigos (token) se generan en dispositivos Android e iOS oauth_enabled_warning: "Por favor ten en cuenta que los accesos a través de redes sociales serán inhabilitados si habilitas el factor de autenticación en dos pasos de tu cuenta." @@ -1155,6 +1174,11 @@ es: too_few_topics_and_posts_notice: "¡Vamos a dar por comenzada la comunidad! Hay %{currentTopics} / %{requiredTopics} temas y %{currentPosts} / %{requiredPosts} posts. Los nuevos visitantes necesitan algo que leer y a lo que responder." too_few_topics_notice: "¡Vamos a dar por comenzada la comunidad! Hay %{currentTopics} / %{requiredTopics} temas. Los nuevos visitantes necesitan algo que leer y a lo que responder." too_few_posts_notice: "¡Vamos a dar por empezada la comunidad! Hay %{currentPosts} / %{requiredPosts} posts. Los nuevos visitantes necesitan algo que leer y a lo que responder." + logs_error_rate_notice: + reached_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} otros {# errors/hour}} alcanzó el límite de la configuración del sitio del {limit, plural, one {# error/hour} otros {# errors/hour}}." + reached_minute_MF: "{relativeAge}{rate, plural, one {# error/minute} otros {# errors/minute}} alcanzó el límite de la configuración del sitio del {limit, plural, one {# error/minute} otros {# errors/minute}}." + exceeded_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} otros {# errors/hour}} excedió el límite de la configuración del sitio del {limit, plural, one {# error/hour} otros {# errors/hour}}." + exceeded_minute_MF: "{relativeAge}{rate, plural, one {# error/minute} otros {# errors/minute}} excedió el límite de la configuración del sitio del {limit, plural, one {# error/minute} otros {# errors/minute}}." learn_more: "saber más..." all_time: "total" all_time_desc: "temas creados total" @@ -1210,6 +1234,7 @@ es: trust_level: "Nivel de Confianza" search_hint: "usuario, email o dirección IP" create_account: + disclaimer: "Al registrarte aceptas la política de privacidad y los términos del servicio." title: "Crear Cuenta Nueva" failed: "Algo ha salido mal, tal vez este e-mail ya fue registrado, intenta con el enlace 'olvidé la contraseña'" forgot_password: @@ -1248,6 +1273,7 @@ es: email_placeholder: "dirección de e-mail o nombre de usuario" caps_lock_warning: "Está activado Bloqueo de Mayúsculas" error: "Error desconocido" + cookies_error: "Su navegador parece tener las cookies deshabilitadas. Es posible que no pueda iniciar sesión sin habilitarlos primero." rate_limit: "Por favor, espera un poco antes de volver a intentar iniciar sesión." blank_username: "Por favor ingresa tu email o nombre de usuario." blank_username_or_password: "Por favor, introducir tu e-mail o usuario, y tu contraseña." @@ -1325,6 +1351,7 @@ es: shift: "Shift" ctrl: "Ctrl" alt: "Alt" + enter: "Intro" conditional_loading_section: loading: Cargando... category_row: @@ -1544,6 +1571,26 @@ es: confirm_title: "Notificaciones habilitadas - %{site_title}" confirm_body: "¡Éxito! Las notificaciones han sido habilitadas." custom: "Notificación de {{username}} en %{site_title}" + titles: + mentioned: "mencionado" + replied: "nueva respuesta" + quoted: "citado" + edited: "editado" + liked: "nuevo me gusta" + private_message: "nuevo mensaje privado" + invited_to_private_message: "invitado a mensaje privado" + invitee_accepted: "invitación aceptada" + posted: "nueva publicación" + moved_post: "publicación movida" + linked: "gustado" + granted_badge: "medalla concedida" + invited_to_topic: "invitado al tema" + group_mentioned: "grupo mencionado" + group_message_summary: "nuevo mensaje grupal" + watching_first_post: "nuevo tema" + topic_reminder: "recordatorio de tema" + liked_consolidated: "nuevos me gusta" + post_approved: "post aprobado" upload_selector: title: "Añadir imagen" title_with_attachments: "Añadir una imagen o archivo" @@ -1707,6 +1754,9 @@ es: edit_message: help: "Editar el primer post del mensaje" title: "Editar Mensaje" + defer: + help: "Marcar como no leído" + title: "Aplazar" list: "Temas" new: "nuevo tema" unread: "sin leer" @@ -2736,6 +2786,11 @@ es: admin: title: "Administrador de Discourse" moderator: "Moderador" + tags: + remove_muted_tags_from_latest: + always: "siempre" + only_muted: "cuando es usado solo o con otras etiquetas silenciadas" + never: "nunca" reports: title: "Lista de informes disponibles" dashboard: @@ -2810,6 +2865,7 @@ es: groups: "Todos los grupos" disabled: "Este reporte está inhabilitado" totals_for_sample: "Total para la muestra" + average_for_sample: "Promedio por muestra" total: "Total de siempre" no_data: "Ningún dato a mostrar." trending_search: @@ -3594,6 +3650,7 @@ es: delete_posts_failed: "Ha habido un problema borrando los mensajes." penalty_post_actions: "¿Qué te gustaría hacer con la publicación asociada?" penalty_post_delete: "Borrar el post" + penalty_post_delete_replies: "Eliminar el post + sus respuesta" penalty_post_edit: "Editar el post" penalty_post_none: "Nada" penalty_count: "Contador de Faltas" diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml index 86de3015c2..5cdea52c3e 100644 --- a/config/locales/client.et.yml +++ b/config/locales/client.et.yml @@ -714,6 +714,7 @@ et: primary: "Peamine e-post" secondary: "Teine e-post" no_secondary: "Teist e-posti pole" + instructions: "Ära näita kunagi avalikkusele." ok: "Saadame sulle kinnituseks meili" invalid: "Sisesta palun korrektne meiliaadress" authenticated: "Sinu meil on autenditud {{provider}} poolt" @@ -1019,6 +1020,7 @@ et: button_ok: "OK" button_help: "Abi" email_login: + button_label: "e-postiga" complete_username_not_found: "Kasutajanimele %{username} ei vasta ükski konto" complete_email_not_found: "Meiliaadressile %{email} ei vasta ükski konto" login: @@ -1118,6 +1120,7 @@ et: medium_dark_tone: Keskmiselt tume nahatoon dark_tone: Tume nahatoon shared_drafts: + destination_category: "Sihtkategooria" publishing: "Teema avaldamine..." composer: emoji: "Emoji :)" @@ -1255,6 +1258,8 @@ et: replied: '{{username}} vastas Sulle teemas "{{topic}}" - {{site_title}}' posted: '{{username}} postitas teemasse "{{topic}}" - {{site_title}}' linked: '{{username}} viitas Sinu postitusele teemas "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "uus teema" upload_selector: title: "Lisa pilt" title_with_attachments: "Lisa pilt või fail" @@ -1403,6 +1408,8 @@ et: help: "Liiguta sõnum tagasi Postkasti" edit_message: title: "Muuda sõnumit" + defer: + title: "Lükka edasi" list: "Teemad" new: "uus teema" unread: "lugemata" @@ -1649,6 +1656,7 @@ et: split_topic: title: "Liiguta uue teema alla" action: "liiguta uue teema alla" + topic_name: "Uue teema pealkiri" radio_label: "Uus teema" error: "Postituste uude teemasse liigutamisel tekkis viga." instructions: @@ -2022,6 +2030,7 @@ et: history: "Ajalugu" changed_by: "autor {{author}}" raw_email: + title: "Sissetulevad e-kirjad" not_available: "Pole saadaval!" categories_list: "Foorumite loend" filters: @@ -2190,6 +2199,7 @@ et:

tagging: all_tags: "Kõik sildid" + other_tags: "Muud sildid" selector_all_tags: "kõik sildid" selector_no_tags: "sildid puuduvad" changed: "muudetud sildid:" @@ -2262,8 +2272,13 @@ et: admin: title: "Discourse Admin" moderator: "Moderaator" + tags: + remove_muted_tags_from_latest: + always: "alati" + never: "mitte kunagi" dashboard: title: "Armatuurlaud" + last_updated: "Töölauda on uuendatud:" version: "Versioon" up_to_date: "Oled aja tasemel!" critical_available: "Kriitiline uuendus on saadaval." @@ -2573,6 +2588,8 @@ et: settings: "Seaded" preview: "Eelvaade" is_default: "Kujundus on vaikimisi sisse lülitatud" + user_selectable: "Kasutajad saavad teemat valida" + color_scheme: "Värvipalett" theme_components: "Kujunduse osad" convert: "Konverdi" collapse: Ahenda @@ -2580,12 +2597,14 @@ et: add_upload: "Lisa üleslaadimine" upload: "Laadi üles" select_component: "Vali komponent..." + edit_css_html: "Muuda CSS-i/HTML-i" installed: "Paigaldatud" install_popular: "Populaarsed" about_theme: "Teave" license: "Litsents" updating: "Uuendamine..." add: "Lisa" + theme_settings: "Teema seaded" scss: text: "CSS" header: @@ -2602,9 +2621,14 @@ et: body_tag: text: "" title: "HTML, mida lisatakse enne silti" + yaml: + text: "YAML" colors: title: "Värvid" + edit: "Muuda värvipalette" + long_title: "Värvipaletid" copy_name_prefix: "Koopia sellest" + delete_confirm: "Kas kustutada see vävripalett?" undo: "ennista" undo_title: "Ennista selle värvistiku muudatused alates viimasest salvestamisest." revert: "võta tagasi" @@ -2822,6 +2846,9 @@ et: title: "Vaadatud sõnad" search: "otsi" clear_filter: "Tühjenda" + word_count: + one: "%{count} sõna" + other: "%{count} sõna" actions: block: "Blokeeri" censor: "Tsenseeri" @@ -2844,6 +2871,7 @@ et: last_emailed: "Viimati meilitud" not_found: "Vabanda, sellist kasutajanime ei eksisteeri meie süsteemis." id_not_found: "Vabanda, sellist kasutajatunnust ei eksisteeri meie süsteemis." + active: "Aktiveeritud" show_emails: "Näita meile" nav: new: "Uus" @@ -2885,6 +2913,8 @@ et: silence_modal_title: "Vaigista kasutaja" silence_reason_placeholder: "Vaigistamise põhjus" delete_all_posts: "Kustuta kõik postitused" + penalty_post_edit: "Muuda postitust" + penalty_post_none: "Ära tee midagi" delete_all_posts_confirm_MF: "Sa oled kustutamas {POSTS, plural, one {1 post} other {# posts}} and {TOPICS, plural, one {1 topic} other {# topics}}. Oled Sa kindel?" silence: "Vaigista" unsilence: "Eemalda vaigistud" @@ -2951,6 +2981,7 @@ et: activate_failed: "Viga kasutaja aktiveerimisel." deactivate_account: "Deaktiveeri konto" deactivate_failed: "Viga kasutaja deaktiveerimisel." + silence_accept: "Jah, vaigista see kasutaja" bounce_score: "Väljaviskamiste skoor" reset_bounce_score: label: "Lähtesta" @@ -2990,6 +3021,7 @@ et: likes_received: "Meeldimisi saadud" likes_received_days: "Meeldimisi saadud: unikaalseid päevi" likes_received_users: "Meeldimisi saadud: unikaalseid kasutajaid" + silenced: "Vaigistatud (kogu aeg)" qualifies: "Kvalifitseerub usaldustasemele 3." does_not_qualify: "Ei kvalifitseeru usaldustasemele 3." will_be_promoted: "Edutatakse varsti." diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 7d9172b92f..e71cf77070 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -153,6 +153,7 @@ fa_IR: bootstrap_mode_disabled: "حالت خود راه انداز در 24 ساعت آینده غیر‌فعال خواهد شد." themes: default_description: "پیش‌فرض" + broken_theme_alert: "ممکن است سایت شما به دلیل خطا در قالب %{theme} کار نکند. آن را در مسیر زیر از کار بیندازید: %{path}" s3: regions: ap_northeast_1: "آسیا و اقیانوسیه (توکیو)" @@ -182,6 +183,7 @@ fa_IR: submit: "ثبت" generic_error: "متأسفیم، خطایی روی داده." generic_error_with_reason: "خطایی روی داد: %{error}" + go_ahead: "ادامه دهید" sign_up: "ثبت نام" log_in: "ورود" age: "سن" @@ -198,6 +200,8 @@ fa_IR: privacy_policy: "سیاست حفظ حریم خصوصی" privacy: "حریم خصوصی" tos: "شرایط استفاده از خدمات" + rules: "قوانین" + conduct: "دستورالعمل" mobile_view: "نمایش برای موبایل " desktop_view: "نمایش برای کامپیوتر" you: "شما" @@ -211,11 +215,15 @@ fa_IR: every_hour: "هر ساعت" daily: "روزانه" weekly: "هفتگی" + every_month: "هر ماه" + every_six_months: "هر شش ماه" max_of_count: "حداکثر {{count}}" alternation: "یا" character_count: one: "{{count}} نویسه" other: "{{count}} نویسه" + related_messages: + title: "پیام‌های مرتبط" suggested_topics: title: "موضوعات پیشنهادی" pm_title: "پیام‌های پیشنهادی" @@ -244,10 +252,17 @@ fa_IR: unbookmark: "برای حذف تمام نشانک های این موضوع کلیک کنید" bookmarks: created: "شما روی این نوشته‌ها نشانک گذاشته‌اید" + not_bookmarked: "این نوشته را نشانک گذاری کنید." remove: "پاک کردن نشانک" + confirm_clear: "آیا مطمئنید می‌خواهید همه‌ی نشانک های خود را از این مبحث پاک کنید؟" drafts: + resume: "از سر گیری" remove: "پاک کردن" + new_topic: "درفت جدید برای مبحث" + new_private_message: "درفت جدید برای پیام" + topic_reply: "پاسخ درفت" abandon: + confirm: "شما پیش از این یک پیش نویس در این مبحث باز کرده اید. مطمئنید میخواهید آن را حذف کنید؟" yes_value: "بله، رها کن" no_value: "خیر، نگه دار" topic_count_latest: @@ -266,6 +281,8 @@ fa_IR: saved: "ذخیره شد!" upload: "بارگذاری" uploading: "در حال بارگذاری..." + uploading_filename: "بارگذاری {{filename}}..." + clipboard: "کلیپ بورد" uploaded: "بارگذاری شد!" pasting: "چسباندن..." enable: "فعال کردن" @@ -282,42 +299,142 @@ fa_IR: choose_topic: none_found: "موضوعی یافت نشد." title: + search: "جستجوی یک مبحث با عنوان، آی دی و یا لینک " placeholder: "عنوان موضوع را اینجا بنویسید" + choose_message: + none_found: "پیامی پیدا نشد" + title: + search: "جستجوی یک پیام با عنوان" + placeholder: "عنوان پیام را اینجا وارد کنید" review: + order_by: "به ترتیب" + in_reply_to: "در پاسخ به" + claim_help: + optional: "می‌توانید این مورد را درخواست کنید تا دیگران را از بازنگری آن بازنگه دارید." + required: "شما قبل از بازنگری موارد باید آن‌ها را درخواست دهید ." + claimed_by_you: "می‌توانید این مورد را درخواست کنید و آن را بازنگری کنید." + claimed_by_other: "این مورد فقط توسط کاربر{{username}} قابل بازنگری است." + claim: + title: "درخواست این مبحث" + unclaim: + help: "حذف درخواست" + awaiting_approval: "منتظر تایید" delete: "حذف" settings: + saved: "ذخیره شد" save_changes: "ذخیره تغییرات" title: "تنظیمات" + priorities: + title: "اولویت های بازنگری" + moderation_history: "تاریخچه مدیریت" + view_all: "نمایش همه" + grouped_by_topic: "دسته بندی مبحث ها" + none: "هیچ موردی برای بازنگری وجود ندارد" + view_pending: "نمایش صف انتظار" + topic_has_pending: + one: "این مبحث %{count}نوشته در حال انتظار دارد" + other: "این مبحث {{count}}نوشته در حال انتظار دارد." + title: "بازنگری" topic: "موضوع:" + filtered_topic: "شما یک مبحث را بر حسب محتوای قابل بازنگری فیلتر کرده‌اید" filtered_user: "کاربر" + show_all_topics: "نمایش همه ی مباحث" + deleted_post: "(نوشته حذف شده)" + deleted_user: "(کاربر حذف شده)" user: username: "نام‌کاربری" email: "ایمیل" name: "نام" + fields: "فیلدها" + user_percentage: + summary: + one: "{{agreed}}، {{disagreed}}، {{ignored}}({{count}}پرچم)" + other: "{{agreed}}، {{disagreed}}، {{ignored}} ({{count}}پرچم)" + agreed: + one: "{{count}}% موافقت شده" + other: "{{count}}% موافقت شده" + disagreed: + one: "{{count}}% موافقت نشده" + other: "{{count}}% موافقت نشده" + ignored: + one: "{{count}}% صرف نظر شده" + other: "{{count}}% صرف نظر شده" topics: topic: "موضوعات" + reviewable_count: "تعداد" + reported_by: "گزارش شده توسط" + deleted: "[مبحث حذف شده]" + original: "(مبحث اصلی)" + details: "جزئیات" + unique_users: + one: "%{count}کاربر" + other: "{{count}}کاربر" + replies: + one: "%{count}پاسخ" + other: "{{count}}پاسخ" edit: "ویرایش" save: "ذخیره" cancel: "لغو کردن" + new_topic: "مبحث جدید:" filters: type: title: "نوع" + all: "(همه نوع)" + minimum_score: "حداقل امتیاز :" refresh: "تازه کردن" + status: "وضعیت" category: "دسته‌بندی" + orders: + priority: "اولویت" + priority_asc: "اولویت (برعکس)" + created_at: "ساخته شده" + created_at_asc: "ساخته شده (برعکس)" + priority: + title: "کمترین اولویت" + low: "کم" + medium: "متوسط" + high: "بالا" + conversation: + view_full: "نمایش همه ی مباحث" scores: + about: "این امتیاز بر اساس سطح اعتماد گزارش دهنده، دقت پرچم‌های قبلی ایشان، و اولویت مورد گزارش شده حساب میشود، " + score: "امتیاز" date: "تاریخ" type: "نوع" + status: "وضعیت" + submitted_by: "ارسال شده توسط" + reviewed_by: "بازنگری شده توسط" statuses: pending: title: "در انتظار" + approved: + title: "پذیرفته شده" rejected: title: "رد شده" + ignored: + title: "صرف نظر شده" + deleted: + title: "حذف شده" + reviewed: + title: "(همه بازنگری ها)" + all: + title: "(همه چیز)" types: + reviewable_flagged_post: + title: "نوشته پرچم شده" + flagged_by: "پرچم شده توسط" + reviewable_queued_topic: + title: "مبحث های در صف" + reviewable_queued_post: + title: "نوشته در صف" reviewable_user: title: "کاربر" approval: title: "نوشته نیاز به تایید دارد" description: "ما نوشته شما را دریافت کرده ایم ولی قبل از نمایش نیاز به تایید آن توسط یکی از مدیران است. لطفا صبر داشته باشید." + pending_posts: + one: "شما %{count} نوشته در انتظار دارید" + other: "شما {{count}} نوشته در انتظار دارید" ok: "باشه" user_action: user_posted_topic: "{{user}} یک موضوع ایجاد کرد" @@ -361,13 +478,20 @@ fa_IR: make_user_group_owner: "مدیر کردن" remove_user_as_group_owner: "لغو مالکیت" groups: + member_added: "اضافه شده" + member_requested: "درخواست شده در" add_members: title: "افزودن اعضا" description: "مدیریت عضویت کاربران در این گروه" usernames: "نام های کاربری" requests: + title: "درخواست ها" reason: "دلیل" + accept: "پذیرفتن" accepted: "تأیدد شده" + deny: "رد کردن" + denied: "رد شده" + undone: "بازگرداندن درخواست" manage: title: "مدیریت" name: "نام" @@ -398,6 +522,7 @@ fa_IR: empty: posts: "در این گروه هیچ نوشته ای توسط کاربران ارسال نشده." members: "این گروه هیچ عضوی ندارد" + requests: "هیچ درخواست عضویتی برای این گروه وجود ندارد" mentions: "هیچ اشاره ای به این گروه وجود ندارد." messages: "هیچ پیامی برای این گروه وجود ندارد." topics: "در این گروه هیچ موضوعی توسط اعضای آن ایجاد نشده." @@ -425,8 +550,11 @@ fa_IR: all: "همه گروهها" empty: "هیچ گروه قابل نمایشی وجود ندارد." filter: "فیلتر با نوع گروه" + owner_groups: "گروه های تحت مالکیت من" + close_groups: "گروه های بسته" automatic_groups: "گروههای خودکار" automatic: "خودکار" + closed: "بسته" public: "عمومی" private: "خصوصی" public_groups: "گروههای عمومی" @@ -448,6 +576,8 @@ fa_IR: remove_member_description: "اخراج %{username} از این گروه" make_owner: "اعطای مالکیت" make_owner_description: "دادن مالکیت این گروه به %{username}" + remove_owner: "حذف توسط مالک" + remove_owner_description: "حذف کاربر %{username} به عنوان مالک این گروه" owner: "مالک" topics: "موضوعات" posts: "نوشته‌ها" @@ -468,6 +598,7 @@ fa_IR: description: "در صورت ارسال شدن پست جدید در هر پیام یک اعلان برای شما ارسال می‌شود و تعداد پاسخ‌های جدید در آن نمایش داده می‌شود." watching_first_post: title: "در حال مشاهده نوشته اول" + description: "شما از پیام های جدید این گروه آگاه می‌شوید ولی برای پاسخ به پیام ها آگاه سازی نمی‌گیرید." tracking: title: "پیگیری" description: "در صورت اشاره شدن به @نام شما توسط اشخاص دیگر و یا دریافت پاسخ، اعلانی برای شما ارسال می‌شود و تعداد پاسخ‌های جدید نمایش داده می‌شود." @@ -476,8 +607,10 @@ fa_IR: description: "در صورتی که به @نام شما اشاره شود و یا پاسخی دریافت کنید اعلانی برای شما ارسال می‌شود." muted: title: "بی صدا شد" + description: "شما برای هر اتفاقی در ارتباط با پیام های این گروه آگاه سازی می‌گیرید." flair_url: "تصویر آواتار" flair_url_placeholder: "(اختیاری) URL تصویر و یا کلاس Font Awesome" + flair_url_description: 'از تصاویر مربعی بزرگتر از ۲۰پیکسل × ۲۰ پیکسل و یا ایکون های فونت آسام استفاده کنید (فرمت های قابل قبول: "fa-icon", "far fa-icon", "fab fa-icon" ).' flair_bg_color: "رنگ پس زمینه آواتار" flair_bg_color_placeholder: "(اختیاری) کد HEX رنگ" flair_color: "رنگ آواتار" @@ -497,6 +630,7 @@ fa_IR: "12": "موارد ارسال شده" "13": "صندوق دریافت" "14": "در انتظار" + "15": "درفت" categories: all: "همه‌ی دسته‌بندی‌ها" all_subcategories: "همه" @@ -518,6 +652,12 @@ fa_IR: topic_sentence: one: "%{count} موضوع" other: "%{count} موضوع" + topic_stat_sentence_week: + one: "%{count} مبحث جدید در هفته ی گذشته" + other: "%{count} مبحث جدید در هفته ی گذشته" + topic_stat_sentence_month: + one: "%{count} مبحث جدید در ماه گذشته" + other: "%{count} مبحث جدید در ماه گذشته" n_more: "دسته‌بندی‌ها ( %{count}مورد دیگر)" ip_lookup: title: جستجوی نشانی IP @@ -534,6 +674,8 @@ fa_IR: topics_entered: "موضوعات وارد شده" post_count: "# نوشته" confirm_delete_other_accounts: "آیا مطمئن هستید که می خواهید این حساب کاربری را حذف نمایید؟" + powered_by: "استفاده از MaxMindDB" + copied: "کپی شده" user_fields: none: "(یک گزینه انتخاب کنید)" user: @@ -550,12 +692,23 @@ fa_IR: private_message: "پیام" private_messages: "پیام‌ها" user_notifications: + ignore_duration_title: "از زمانسنج صرف نظر کن" ignore_duration_username: "نام‌کاربری" + ignore_duration_when: "مدت زمان:" ignore_duration_save: "چشم پوشی" + ignore_duration_note: "توجه کنید همه ی صرف نظر شده ها بعد از انقضای زمان صرف نظر برداشته میشود." + ignore_duration_time_frame_required: "یک بازه ی زمانی انتخاب کنید" + ignore_no_users: "هیچ کاربر صرف نظر شده ای ندارید" + ignore_option: "صرف نظر شده" + ignore_option_title: "هیچ آگاه سازی از این کاربر دریافت نمی‌کنید و مبحث ها یا نوشته های ایشان برای شما نمایش داده نخواهد شد." + add_ignored_user: "اضافه کردن ..." mute_option: "بی صدا شد" + mute_option_title: "هیچ اگاه سازی از این کاربر دریافت نمیکنید" normal_option: "معمولی" + normal_option_title: "اگر این کاربر به شما پاسخ دهد، از شما نقل قول کند و یا شما را صدا کند، اگاه سازی دریافت میکنید" activity_stream: "فعالیت" preferences: "تنظیمات" + profile_hidden: "صفحه نمایه این کاربر مخفی است" expand_profile: "باز کردن" collapse_profile: "جمع کردن" bookmarks: "نشانک‌ها" @@ -565,6 +718,7 @@ fa_IR: notifications: "آگاه‌سازی‌ها" statistics: "وضعیت" desktop_notifications: + label: "اگاه سازی زنده" not_supported: "اعلانات بر روی این مرورگر پشتیبانی نمیشوند. با عرض پوزش." perm_default: "فعال کردن اعلانات" perm_denied_btn: "دسترسی رد شد" @@ -572,13 +726,18 @@ fa_IR: disable: "غیرفعال کردن اعلانات" enable: "فعال کردن اعلانات" each_browser_note: "نکته: شما باید این تنظیمات را در هر مرورگری که استفاده میکنید تغییر دهید." + consent_prompt: "ایا مایلید وقتی دیگران به شما پاسخ میدهند، اگاه سازی زنده دریافت کنید؟" dismiss: "نخواستیم" dismiss_notifications: "پنهان کردن همه" dismiss_notifications_tooltip: "علامت گذاری همه اطلاعیه های خوانده نشده به عنوان خوانده شده" first_notification: "اولین پیام اطلاع‌رسانی شما! برای شروع آن‌را انتخاب کنید." + dynamic_favicon: "نمایش تعداد در ایکون بروسر" + theme_default_on_all_devices: "این قالب، حالت پیش فرض در همه ی دستگاه های من باشد" + text_size_default_on_all_devices: "این سایز قلم حالت پیش فرض در تمام دستگاه های من باشد" allow_private_messages: "به دیگر کاربران اجازه بده به من پیام شخصی بفرستند" external_links_in_new_tab: "همهٔ پیوندهای خروجی را در یک تب جدید باز کن" enable_quoting: "فعال کردن نقل قول گرفتن از متن انتخاب شده" + enable_defer: "تاخیر را برای علامت زدن مباحث به عنوان خوانده نشده فعال کن" change: "تغییر" moderator: "{{user}} یک مدیر است" admin: "{{user}} یک مدیر ارشد است" @@ -600,6 +759,7 @@ fa_IR: individual_no_echo: "ارسال ایمیل برای هر پست جدید، به جز پست‌های خودم" many_per_day: "برای هر پست جدید یک ایمیل برای من بفرست. (حدود {{dailyEmailEstimate}} در روز)" few_per_day: "ارسال یک ایمیل برای هر پست جدید (حدود 2 ایمیل در روز)" + warning: "حالت ایمیل فعال شد. تنظیمات اگاه سازی ایمیلی بازنویسی میشود." tag_settings: "برچسب‌ها" watched_tags: "تماشا شده" watched_tags_instructions: "تمام موضوعاتی که دارای این برچسب هستند مشاهده خواهید کرد و گزارش و تعداد نوشته‌ها و موضوعات را کنار موضوع مشاهده خواهید کرد." @@ -624,6 +784,7 @@ fa_IR: users: "کاربران" muted_users: "بی صدا شده" muted_users_instructions: "متوقف کردن تمام اطلاعیه ها از طرف این کاربران." + ignored_users: "صرف نظر شده" muted_topics_link: "نمایش موضوعات بی‌صدا شده" watched_topics_link: "نمایش موضوعات مشاهده شده" tracked_topics_link: "نمایش موضوعات پی‌گیری شده" @@ -678,11 +839,27 @@ fa_IR: copy_to_clipboard_error: "خطا در کپی اطلاعات" second_factor: title: "احراز هویت دو مرحله ای" + disable: "احراز هویت دوعامله را از کار بینداز" + enable: "احراز هویت دو عامله را فعال کن" + confirm_password_description: "لطفا رمز عبور خود را تایید کنید تا ادامه دهیم." + rate_limit: "لطفا قبل از اینکه کد احراز هویت دیگری را تست کنید کمی صبر کنید" + enable_description: | + این کد QR را در یک اپلیکیشن مورد تایید (آندروید -- IOS) و کد احراز هویت خود را وارد کنید. + disable_description: "لطفا کد احراز هویت را از اپلیکیشن خود وارد کنید" + show_key_description: "دستی وارد کنید" + short_description: | + حساب کاربری خود را با کد امنیتی یک بار مصرف محافظت کنید + extended_description: | + احراز هویت دو عامله امنیت بیشتری به حساب کاربری شما میدهد، چرا که یک توکن یک بار مصرف علاوه بر رمز عبور خود خواهید داشت. توکن ها در ابزارهای اندروید و IOS قابل تولید هستند. + oauth_enabled_warning: "دقت کنید وقتی احراز هویت دوعامله فعال شود، ورود با حساب شبکه های اجتماعی به حساب کاربری شما از کار میفتد." + use: "از اپلیکیشن احراز هویت استفاده کنید" + enforced_notice: "شما باید احراز هویت دو عامله را قبل از دسترسی به سایت فعال کنید" change_about: title: "تغییر «درباره‌ی من»" error: "در فرآیند تغییر این مقدار خطایی روی داد." change_username: title: "تغییر نام‌کاربری" + confirm: "مطمئنید میخواهید نام کاربری خود را تغییر دهید؟" taken: "متأسفیم، آن نام کاربری قبلا گرفته شده است." invalid: "آن نام کاربری نامعتبر است. تنها باید عددها و حرف‌ها را در بر بگیرد." change_email: @@ -690,10 +867,12 @@ fa_IR: taken: "متأسفیم، آن ایمیل در دسترس نیست." error: "در تغییر ایمیلتان خطایی روی داد. شاید آن نشانی از پیش در حال استفاده است؟" success: "ما ایمیلی به آن نشانی فرستاده‌ایم. لطفاً دستورکار تأییده را در آن دنبال کنید." + success_staff: "یک ایمیل به آدرس کنونی شما ارسال کردیم. لطفا دستورالعمل آن را دنبال کنید." change_avatar: title: "عکس نمایه خود را تغییر دهید" gravatar: "بر‌اساس Gravatar" gravatar_title: "تصویرتان را در سایت Gravatar تغییر دهید" + gravatar_failed: "هیچ گراواتاری با این آدرس ایمیل پیدا نکردیم" refresh_gravatar_title: "تازه‌سازی Gravatar شما" letter_based: "سیستم تصویر پرفایل را اختصاص داده است" uploaded_avatar: "تصویر سفارشی" @@ -709,6 +888,11 @@ fa_IR: instructions: "تصاویر پس زمینه در مرکز قرار خواهند گرفت و عرض پیشفرض آن 590 پیکسل است" email: title: "ایمیل" + primary: "آدرس ایمیل اصلی" + secondary: "آدرس ایمیل ثانویه" + no_secondary: "بدون ایمیل ثانویه" + sso_override_instructions: "ایمیل را میتوانید از طریق SSO به روزرسانی کنید" + instructions: "هرگز به عموم نمایش داده نخواهد شد" ok: "برای تایید ایمیلی برایتان ارسال خواهیم کرد." invalid: "لطفا یک آدرس ایمیل معتبر وارد کنید" authenticated: "ایمیل شما توسط {{provider}} تصدیق شد" @@ -717,7 +901,10 @@ fa_IR: one: "ما فقط در صورتی برای شما ایمیل میفرستیم که در {{count}} دقیقه آخر شما را ندیده باشیم." other: "ما فقط در صورتی برای شما ایمیل میفرستیم که در {{count}} دقیقه آخر شما را ندیده باشیم." associated_accounts: + title: "حساب های مرتبط" + connect: "وصل شدن" revoke: "ابطال " + not_connected: "(وصل نشده)" name: title: "نام" instructions: "نام و نام‌خانوادگی شما (اختیاری)" @@ -743,8 +930,18 @@ fa_IR: password_confirmation: title: "رمز عبور را مجدد وارد نمایید" auth_tokens: + title: "ابزارهایی که اخیرا استفاده شدند" ip: "IP" details: "جزئیات" + log_out_all: "از همه خارج شو" + not_you: "شما نیستید؟" + show_all: "نمایش همه ({{count}})" + show_few: "کمتر نشان بده" + was_this_you: "آیا شما بودید؟" + was_this_you_description: "اگر شما نبودید، پیشنهاد می‌کنیم رمز عبور خود را تغییر دهید و از همه حساب ها خارج شوید" + browser_and_device: "{{browser}} در {{device}}" + secure_account: "از حساب کاربری من محافظت کن" + latest_post: "آخرین بار شما نوشتید ..." last_posted: "آخرین نوشته" last_emailed: "آخرین ایمیل فرستاده شده" last_seen: "مشاهده" @@ -753,8 +950,14 @@ fa_IR: location: "موقعیت" website: "تارنما" email_settings: "ایمیل" + hide_profile_and_presence: "نمایه کاربری و حضور من را مخفی کن" text_size: + smaller: "کوچکتر" normal: "معمولی" + larger: "بزرگتر" + largest: "بزرگترین" + title_count_mode: + notifications: "آگاهسازی جدید" like_notification_frequency: title: "وقتی پسندیده شد اعلام کن" always: "همیشه" @@ -771,6 +974,8 @@ fa_IR: every_hour: "هر ساعت" daily: "روزانه" weekly: "هفتگی" + every_month: "هر ماه" + every_six_months: "هر شش ماه" email_level: title: "وقتی کسی نوشته‌های من را نقل‌قول کرد، یا به من پاسخ داد، یا به @نام‌کاربری من اشاره کرد، یا به موضوعی دعوت کرد، ایمیل بفرست." always: "همیشه" @@ -1183,6 +1388,8 @@ fa_IR: replied: '{{username}} در "{{topic}}" - {{site_title}} به شما پاسخ داد' posted: '{{username}} در "{{topic}}" - {{site_title}} مطلبی نوشت' linked: '{{username}} در "{{topic}}" - {{site_title}} به نوشته‌ی شما پیوندی قرار داد' + titles: + watching_first_post: "موضوع جدید" upload_selector: title: "افزودن تصویر" title_with_attachments: "افزودن یک تصویر یا پرونده" @@ -1323,6 +1530,8 @@ fa_IR: move_to_inbox: title: "انتقال به صندوق دریافت" help: "بازگرداندن پیام به صندوق دریافتی" + defer: + title: " واگذار کردن" list: "موضوعات" new: "موضوع جدید" unread: "خوانده نشده" @@ -1374,6 +1583,7 @@ fa_IR: remove: "حذف زمان‌سنج" publish_to: "انتشار در:" when: "در زمان:" + time_frame_required: یک بازه ی زمانی انتخاب کنید auto_update_input: later_today: "امروز" tomorrow: "فردا" @@ -1821,6 +2031,8 @@ fa_IR: options: normal: "معمولی" ignore: "چشم پوشی" + low: "کم" + high: "بالا" sort_options: default: "پیش‌فرض" likes: "پسند‌ها" @@ -2125,6 +2337,14 @@ fa_IR: sort_by_name: "نام" manage_groups: "مدیریت گروه‌های برچسب" manage_groups_description: "تعریف گروه برای سازماندهی برچسب‌ها" + delete_unused_confirmation: + one: "%{count} برچسب حذف خواهد شد:%{tags}" + other: "%{count} برچسب حذف خواهد شد: %{tags}" + delete_unused_confirmation_more_tags: + one: "%{tags} و %{count} بیشتر" + other: "%{tags}و %{count} بیشتر" + delete_unused: "پاک کردن برچسب های استفاده نشده" + delete_unused_description: "پاک کردن همه ی برچسب هایی که به هیچ مبحث یا پیامی تعلق ندارند" cancel_delete_unused: "لغو کردن" filters: without_category: "%{filter} %{tag} موضوعات" @@ -2134,8 +2354,10 @@ fa_IR: notifications: watching: title: "در حال مشاهده" + description: "به شکل اتوماتیک همه ی مباحث با این برچسب را دنبال خواهید کرد. برای همه ی نوشته ها و مباحث اگاهسازی خواهید گرفت، و تعداد نوشته های جدید و خوانده نشده در اول مبحث نمایش داده خواهد شد." watching_first_post: title: "در حال مشاهده اولین نوشته" + description: "شما از مباحث جدید در این برچسب آگاهسازی می‌گیرید ولی برای نوشته ها نه." tracking: title: "پیگیری" regular: @@ -2184,6 +2406,10 @@ fa_IR: admin: title: "مدیر ارشد دیسکورس" moderator: "مدیر" + tags: + remove_muted_tags_from_latest: + always: "همیشه" + never: "هیچوقت" dashboard: title: "پیشخوان" version: "نسخه" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 21b4f80644..e15501f645 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -307,6 +307,7 @@ fi: search: "Etsi keskustelua otsikon perusteella:" placeholder: "kirjoita keskustelun otsikko tähän" review: + in_reply_to: "vastauksena" claim_help: optional: "Voit vaatia tämän itsellesi, jolloin muut eivät voi käsitellä sitä." required: "Sinun täytyy osoittaa asia itsellesi ennen kuin voit käsitellä sen." @@ -1550,6 +1551,9 @@ fi: confirm_title: "Ilmoitukset käytössä - %{site_title}" confirm_body: "Onnistui! Ilmoitukset ovat nyt käytössä." custom: "Ilmoitus käyttäjältä {{username}} sivustolla %{site_title}" + titles: + watching_first_post: "uusi ketju" + post_approved: "viesti hyväksytty" upload_selector: title: "Lisää kuva" title_with_attachments: "Lisää kuva tai tiedosto" @@ -1713,6 +1717,8 @@ fi: edit_message: help: "Muokkaa keskustelun ensimmäistä viestiä" title: "Muokkaa viestiä" + defer: + title: "Lykkää" list: "Ketjut" new: "uusi ketju" unread: "lukematta" @@ -2742,6 +2748,10 @@ fi: admin: title: "Discourse ylläpitäjä" moderator: "Valvoja" + tags: + remove_muted_tags_from_latest: + always: "aina" + never: "ei koskaan" reports: title: "Saatavilla olevat raportit" dashboard: diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index ef422606a9..e029a08e80 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -26,7 +26,7 @@ fr: thousands: "{{number}}k" millions: "{{number}}M" dates: - time: "H:mm" + time: "HH:mm" timeline_date: "MMM YYYY" long_no_year: "DD MMM H:mm" long_no_year_no_time: "D MMM" @@ -64,14 +64,14 @@ fr: one: "%{count} mois" other: "%{count} mois" about_x_years: - one: "%{count}a" - other: "%{count}a" + one: "%{count}an" + other: "%{count}ans" over_x_years: - one: "> %{count}a" - other: "> %{count}a" + one: "> %{count}an" + other: "> %{count}ans" almost_x_years: - one: "%{count}a" - other: "%{count}a" + one: "%{count}an" + other: "%{count}ans" date_month: "D MMM" date_year: "MMM 'YY" medium: @@ -153,6 +153,7 @@ fr: bootstrap_mode_disabled: "Le mode Bootstrap sera désactivé dans les prochaines 24 heures." themes: default_description: "Par défaut" + broken_theme_alert: "Votre site pourrait ne pas fonctionner parce que thème/composant %{theme} a des erreurs. Le désactiver ici : %{path}." s3: regions: ap_northeast_1: "Asie-Pacifique (Tokyo)" @@ -172,6 +173,7 @@ fr: us_east_1: "États-Unis est (Virginie)" us_east_2: "États-Unis est (Ohio)" us_gov_east_1: "AWS GovCloud (US-Est)" + us_gov_west_1: "AWS GovCloud (US-Ouest)" us_west_1: "États-Unis ouest (Californie)" us_west_2: "États-Unis ouest (Oregon)" edit: "modifier le titre et la catégorie de ce sujet" @@ -306,23 +308,45 @@ fr: search: "Rechercher messages par titre :" placeholder: "saisir le titre du message ici" review: + order_by: "Trier par" + in_reply_to: "en réponse à" + claim_help: + optional: "Vous pouvez réserver cet élément pour empêcher d'autres de le vérifier." + required: "Vous devez réserver des éléments avant des les vérifier." + claimed_by_you: "Vous avez réservé cet élément et pouvez le vérifier." + claimed_by_other: "Cet élément ne peut être vérifier que par {{username}}." + claim: + title: "réserver ce sujet" + unclaim: + help: "retirer cette réservation" + awaiting_approval: "En attente d'approbation" delete: "Supprimer" settings: + saved: "Sauvegardé" save_changes: "Sauvegarder" title: "Paramètres" + priorities: + title: "Priorités vérifications" moderation_history: "Historique de modération" + view_all: "Tout voir" + grouped_by_topic: "Groupé par sujet" none: "Il n'y a pas d'éléments à vérifier." view_pending: "voir les messages en attente" topic_has_pending: one: "Ce sujet a %{count} message en attente de validation" other: "Ce sujet a {{count}} messages en attente de validation" + title: "Vérification" topic: "Sujet :" + filtered_topic: "Vous avez restreint au contenu vérifiable dans un seul sujet." filtered_user: "Utilisateur" show_all_topics: "afficher tous les sujets" + deleted_post: "(message supprimé)" + deleted_user: "(utilisateur supprimé)" user: username: "Pseudo" email: "Courriel" name: "Nom" + fields: "Champs" user_percentage: summary: one: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} signalement total)" @@ -341,10 +365,14 @@ fr: reviewable_count: "Nombre" reported_by: "Signalé par" deleted: "[Sujet supprimé]" + original: "(sujet d'origine)" details: "détails" unique_users: one: "%{count} utilisateur" other: "{{count}} utilisateurs" + replies: + one: "%{count} réponse" + other: "{{count}} réponses" edit: "Modifier" save: "Sauvegarder" cancel: "Annuler" @@ -355,17 +383,28 @@ fr: all: "(tous les types)" minimum_score: "Score minimum :" refresh: "Rafraîchir" + status: "Etat" category: "Catégorie" + orders: + priority: "Priorité" + priority_asc: "Priorité (inverse)" + created_at: "Créé à" + created_at_asc: "Créé à (inverse)" priority: + title: "Priorité minimum" low: "Faible" + medium: "Moyenne" high: "Fort" conversation: view_full: "voir conversation entière" scores: + about: "Ce score est calculé en fonction du niveau de confiance de l'utilisateur qui a signalé, de l'exactitude de ses indicateurs précédents et de la priorité de l'élément déclaré." score: "Score" date: "Date" type: "Type" + status: "Etat" submitted_by: "Soumis par" + reviewed_by: "Vérifié par" statuses: pending: title: "En attente" @@ -385,6 +424,8 @@ fr: reviewable_flagged_post: title: "Message signalé" flagged_by: "Signalé par" + reviewable_queued_topic: + title: "Sujet en file d'attente" reviewable_queued_post: title: "Message en file d'attente" reviewable_user: @@ -658,8 +699,10 @@ fr: ignore_duration_save: "Ignorer" ignore_duration_note: "Noter que tous les 'ignorer' sans supprimés automatiquement après l'échéance de la durée d'ignore." ignore_duration_time_frame_required: "Veuillez sélectionner un intervalle de temps" + ignore_no_users: "Vous n'avez aucun utilisateur ignoré." ignore_option: "Ignoré" ignore_option_title: "Vous ne recevrez pas de notification en lien avec cet utilisateur et ses messages et réponses seront cachés." + add_ignored_user: "Ajouter..." mute_option: "Silencieux" mute_option_title: "Vous ne recevrez pas de notifications en lien avec cet utillisateur." normal_option: "Normal" @@ -689,11 +732,13 @@ fr: dismiss_notifications: "Tout ignorer" dismiss_notifications_tooltip: "Marquer les notifications comme lues" first_notification: "Votre première notification ! Cliquez-la pour démarrer." + dynamic_favicon: "Faire apparaître le nombre sur l'icône navigateur" theme_default_on_all_devices: "En faire mon thème par défaut sur tous mes périphériques" text_size_default_on_all_devices: "En faire la taille de texte par défaut sur tous mes périphériques" allow_private_messages: "Permettre aux autres utilisateurs de m’envoyer des messages directs" external_links_in_new_tab: "Ouvrir tous les liens externes dans un nouvel onglet" enable_quoting: "Proposer de citer le texte sélectionné" + enable_defer: "Activer reporter pour marquer des sujets non lu" change: "modifier" moderator: "{{user}} est un modérateur" admin: "{{user}} est un administrateur" @@ -735,6 +780,7 @@ fr: watched_first_post_tags_instructions: "Vous serez notifié du premier message de chaque sujet avec ces étiquettes." muted_categories: "Silencieuses" muted_categories_instructions: "Vous ne serez aucunement notifié à propos de nouveaux sujets dans ces catégories et ils n'apparaîtront pas sur les pages \"Catégories\" ou \"Dernier\"." + muted_categories_instructions_dont_hide: "Nous ne serez jamais notifié de quoi que ce soit à propos des nouveaux sujets dans cette catégorie." no_category_access: "En tant que modérateur votre accès à la catégorie est limitée, la sauvegarde est désactivée." delete_account: "Supprimer mon compte" delete_account_confirm: "Êtes-vous sûr de vouloir supprimer définitivement votre compte ? Cette action ne peut pas être annulée !" @@ -811,6 +857,8 @@ fr: description: "Chaque code de secours ne peut être utilisé qu'une seule fois. Garder les dans un endroit sûr mais accessible." second_factor: title: "Authentification à deux facteurs" + disable: "Désactiver l'authentification à deux facteurs" + enable: "Activer l'authentification à deux facteurs" confirm_password_description: "Merci de confirmer votre mot de passe pour continuer" label: "Code" rate_limit: "Veuillez patienter avant d'essayer un autre code d'identification." @@ -818,6 +866,8 @@ fr: Scannez ce code QR en utilisant une application supportée (AndroidiOS) et entrez votre code d'authentification. disable_description: "Veuillez saisir le code d'authentification de votre app" show_key_description: "Saisir manuellement" + short_description: | + Protéger votre compte avec des codes de sécurité à usage unique. extended_description: "L'authentification à deux facteurs ajoute une sécurité supplémentaire à votre compte en exigeant un jeton unique en \nplus de votre mot de passe. Les jetons peuvent être générés sur les appareils Android et iOS.\n" oauth_enabled_warning: "Veuillez noter que les connexions sociales seront désactivées une fois que l'authentification à deux facteurs aura été activée sur votre compte." use: "Utiliser l'application Authenticator" @@ -927,6 +977,10 @@ fr: normal: "Normal" larger: "Grand" largest: "Plus grand" + title_count_mode: + title: "Titre de page de fond affiche nombre de:" + notifications: "Nouvelles notifications" + contextual: "Nouveau contenu de page" like_notification_frequency: title: "Notifier lors d'un J'aime" always: "Toujours" @@ -1121,6 +1175,11 @@ fr: too_few_topics_and_posts_notice: "Faites partir la discussion ! Il y a actuellement %{currentTopics} / %{requiredTopics} sujets et %{currentPosts} / %{requiredPosts} messages. Les nouveaux utilisateurs ont besoin de discussions qu'ils peuvent lire et sur lesquelles ils peuvent répondre." too_few_topics_notice: "Démarrons cette discussion ! Il y a actuellement %{currentTopics} / %{requiredTopics} sujets. Les nouveaux visiteurs ont besoin de quelques conversations à lire et répondre." too_few_posts_notice: "Démarrons cette discussion ! Il y a actuellement %{currentPosts} / %{requiredPosts} messages. Les nouveaux visiteurs ont besoin de quelques conversations à lire et répondre." + logs_error_rate_notice: + reached_hour_MF: "{relativeAge} – {rate, plural, one {# erreur/heure} other {# erreurs/heure}} arrive à la limite paramétrée de {limit, plural, one {# erreur/heure} other {# erreurs/heure}}." + reached_minute_MF: "{relativeAge} – {rate, plural, one {# erreur/minute} other {# erreurs/minute}} arrive à la limite paramétrée de {limit, plural, one {# erreur/minute} other {# erreurs/minute}}." + exceeded_hour_MF: "{relativeAge} – {rate, plural, one {# erreur/heure} other {# erreurs/heure}} a dépassé la limite paramétrée de {limit, plural, one {# erreur/heure} other {# erreurs/heure}}." + exceeded_minute_MF: " {relativeAge} – {rate, plural, one {# erreur/heure} other {# erreurs/heure}} a dépassé la limite paramétrée de {limit, plural, one {# erreur/heure} other {# erreurs/heure}}." learn_more: "en savoir plus…" all_time: "total" all_time_desc: "nombre total de sujets créés" @@ -1176,6 +1235,7 @@ fr: trust_level: "Niveau de confiance" search_hint: "pseudo, courriel ou adresse IP" create_account: + disclaimer: "En vous s'inscrivant, vous acceptez la politique de confidentialité et les conditions générales d'utilisation." title: "Créer un nouveau compte" failed: "Quelque chose s'est mal passé, peut-être que cette adresse de courriel est déjà enregistrée, essayez le lien Mot de passe oublié." forgot_password: @@ -1214,6 +1274,7 @@ fr: email_placeholder: "courriel ou pseudo" caps_lock_warning: "Majuscules vérrouillées" error: "Erreur inconnue" + cookies_error: "Les cookies de votre navigateur semblent désactiver. Vous ne pourrez pas vous connecter sans les activer." rate_limit: "Merci de patienter avant de vous reconnecter." blank_username: "Merci de saisir votre courriel ou pseudo." blank_username_or_password: "Merci de saisir votre courriel ou pseudo, et mot de passe." @@ -1291,6 +1352,7 @@ fr: shift: "Maj" ctrl: "Ctrl" alt: "Alt" + enter: "Enter" conditional_loading_section: loading: Chargement… category_row: @@ -1386,6 +1448,7 @@ fr: remove_featured_link: "Retirer le lien du sujet" reply_placeholder: "Écrivez ici. Utilisez Markdown, BBCode, ou HTML pour mettre en forme. Glissez ou collez des images." reply_placeholder_no_images: "Écrivez ici. Utilisez Markdown, BBCode, ou HTML pour mettre en forme." + reply_placeholder_choose_category: "Sélectionner une catégorie avant de rédiger ici." view_new_post: "Voir votre nouveau message." saving: "Sauvegarde" saved: "Sauvegardé !" @@ -1468,6 +1531,8 @@ fr: none: "Impossible de charger les notifications pour le moment." empty: "Aucune notification trouvée." more: "voir les anciennes notifications" + post_approved: "Votre message a été approuvé" + reviewable_items: "éléments en attente de vérification" mentioned: "{{username}} {{description}}" group_mentioned: "{{username}} {{description}}" quoted: "{{username}} {{description}}" @@ -1506,6 +1571,27 @@ fr: watching_first_post: '{{username}} a crée un nouveau sujet « {{topic}} » - {{site_title}}' confirm_title: "Notifications activées - %{site_title}" confirm_body: "Les notifications ont été activées." + custom: "Notification de {{username}} sur %{site_title}" + titles: + mentioned: "mentionnés" + replied: "nouvelle réponse" + quoted: "cités" + edited: "modifiés" + liked: "nouveau j'aime" + private_message: "nouveau message direct" + invited_to_private_message: "invité au message privé" + invitee_accepted: "invitation acceptée" + posted: "nouveau message" + moved_post: "message déplacé" + linked: "linkés" + granted_badge: "badge attribué" + invited_to_topic: "invité à un sujet" + group_mentioned: "groupe mentionné" + group_message_summary: "nouveaux messages de groupe" + watching_first_post: "nouveau sujet" + topic_reminder: "rappel de sujet" + liked_consolidated: "nouveaux j'aime" + post_approved: "message approuvé" upload_selector: title: "Ajouter une image" title_with_attachments: "Ajouter une image ou un fichier" @@ -1669,6 +1755,9 @@ fr: edit_message: help: "Modifier premier message" title: "Modifier message" + defer: + help: "Marquer comme non-lu" + title: "Reporter" list: "Sujets" new: "nouveau sujet" unread: "non lus" @@ -1784,6 +1873,7 @@ fr: jump_bottom: "aller au dernier message" jump_prompt: "aller à…" jump_prompt_of: "de %{count} messages" + jump_prompt_long: "Aller à…" jump_bottom_with_number: "aller au message %{post_number}" jump_prompt_to_date: "à la date" jump_prompt_or: "ou" @@ -2017,6 +2107,7 @@ fr: quote_reply: "Citer" edit_reason: "Raison :" post_number: "message {{number}}" + ignored: "Contenu ignoré" wiki_last_edited_on: "Wiki édité pour la dernière fois" last_edited_on: "message édité pour la dernière fois" reply_as_new_topic: "Répondre par un nouveau sujet" @@ -2024,6 +2115,7 @@ fr: continue_discussion: "Suite du sujet {{postLink}} :" follow_quote: "aller au message cité" show_full: "Afficher le message complet" + show_hidden: "Visualiser contenu ignoré." deleted_by_author: one: "(message supprimé par son auteur, sera supprimé automatiquement dans %{count} heure à moins qu'il ne soit signalé)" other: "(message supprimé par son auteur, sera supprimé automatiquement dans %{count} heures à moins qu'il ne soit signalé)" @@ -2108,6 +2200,8 @@ fr: delete_topic_disallowed_modal: "Vous n'avez pas le permission de supprimer ce sujet. Si vous souhaitez vraiment le voir supprimé, signalez-le aux modérateurs avec une explication." delete_topic_disallowed: "vous n'avez pas la permission de supprimer ce sujet" delete_topic: "supprimer le sujet" + add_post_notice: "Ajouter note pour responsables" + remove_post_notice: "Retirer le note pour responsables" actions: flag: "Signaler" defer_flags: @@ -2191,9 +2285,13 @@ fr: settings: "Paramètres" topic_template: "Modèle de sujet" tags: "Étiquettes" + tags_allowed_tags: "Réserver l'utilisation de ces étiquettes à cette catégorie :" + tags_allowed_tag_groups: "Réserver l'utilisation de ces groupes d'étiquettes à cette catégorie :" tags_placeholder: "(Facultatif) liste des étiquettes autorisées" tags_tab_description: "Les étiquettes et les groupes d'étiquettes spécifiés ici ne seront disponibles que dans cette catégorie ainsi que dans toutes les catégories qui contiennent les mêmes spécifications. Ces étiquettes ne pourront pas être utilisées dans d'autres catégories." tag_groups_placeholder: "(Facultatif) liste des groupes de étiquettes autorisées" + manage_tag_groups_link: "Gérer les groupes d'étiquettes ici." + allow_global_tags_label: "Permettre aussi d'autres étiquettes" topic_featured_link_allowed: "Autoriser les liens à la une dans cette catégorie" delete: "Supprimer la catégorie" create: "Nouvelle catégorie" @@ -2240,10 +2338,12 @@ fr: default_top_period: "Période des meilleurs sujets par défaut :" allow_badges_label: "Autoriser les badges à être accordé dans cette catégorie" edit_permissions: "Modifier les permissions" + reviewable_by_group: "En plus des responsables, les messages et signalements dans cette catégorie peuvent aussi être vérifier par :" review_group_name: "nom du groupe" require_topic_approval: "Nécessiter l'approbation pour chaque nouveau sujet" require_reply_approval: "Nécessiter l'approbation pour chaque nouvelle réponse" this_year: "cette année" + position: "Position sur la page des catégories :" default_position: "Position par défaut" position_disabled: "Les catégories seront affichées dans l'ordre d'activité. Pour contrôler l'ordre des catégories dans la liste," position_disabled_click: 'activer le paramètre "fixed category positions"' @@ -2296,6 +2396,7 @@ fr: settings_sections: general: "Général" moderation: "Modération" + appearance: "Apparence" email: "Courriel" flagging: title: "Merci d'aider à garder notre communauté civilisée !" @@ -2482,7 +2583,18 @@ fr: readonly: "Voir" lightbox: download: "télécharger" + previous: "Précédent (touche flèche gauche)" + next: "Suivant (touche flèche droite)" + counter: "%curr% de %total%" + close: "Femer (Esc)" + content_load_error: 'Ce contenu ne pouvait pas être chargé.' + image_load_error: 'Cette image ne pouvait pas être chargé.' keyboard_shortcuts_help: + shortcut_key_delimiter_comma: ", " + shortcut_key_delimiter_plus: "+" + shortcut_delimiter_or: "%{shortcut1} ou %{shortcut2}" + shortcut_delimiter_slash: "%{shortcut1}/%{shortcut2}" + shortcut_delimiter_space: "%{shortcut1} %{shortcut2}" title: "Raccourcis clavier" jump_to: title: "Aller à" @@ -2688,10 +2800,17 @@ fr: admin: title: "Administrateur" moderator: "Modérateur" + tags: + remove_muted_tags_from_latest: + always: "toujours" + only_muted: "quand utilisé seul ou avec d'autres étiquettes mises sous silence" + never: "jamais" reports: title: "Liste de rapports disponibles" dashboard: title: "Tableau de bord" + last_updated: "Tableau de bord actualisé le :" + discourse_last_updated: "Discourse mis à jour le :" version: "Version" up_to_date: "À jour !" critical_available: "Une mise à jour critique est disponible." @@ -2760,12 +2879,15 @@ fr: groups: "Tous les groupes" disabled: "Ce rapport est désactivé" totals_for_sample: "Totaux pour l'échantillon" + average_for_sample: "Moyenne pour échantillon" total: "Total depuis toujours" no_data: "Aucune donnée à afficher." trending_search: more: 'Journal des recherches' disabled: 'Rapport sur les tendances de recherche désactivé. Activé journalisation des recherches pour collectioner les données.' filters: + file-extension: + label: Extension de fichier group: label: Groupe category: @@ -2903,6 +3025,9 @@ fr: queued_post_event: name: "Événement à l'approbation d'un message" details: "Quand un nouveau message est créé, approuvé ou rejeté" + reviewable_event: + name: "Evènement vérifiable" + details: "Quand un nouvel élément est prêt à être vérifié et quand son état est mis à jour." delivery_status: title: "État de l'envoi" inactive: "Inactif" @@ -3029,6 +3154,7 @@ fr: new_style: "Nouveau style" install: "Installer" delete: "Supprimer" + delete_confirm: 'Êtes-vous sûr de vouloir supprimer "%{theme_name}" ?' color: "Couleur" opacity: "Opacité" copy: "Copier" @@ -3062,11 +3188,14 @@ fr: long_title: "Modifier les couleurs, le CSS et le contenu HTML de votre site" edit: "Modifier" edit_confirm: "Ceci est un thème distant, si vous modifiez le CSS/HTML vos modifications seront écrasées la prochaine fois que vous le mettez à jour." + update_confirm: "Ces modifications locales seront supprimées par la mise à jour. Êtes-vous sûr de vouloir continuer ?" + update_confirm_yes: "Oui, poursuivre la mise à jour" common: "Général" desktop: "Bureau" mobile: "Mobile" settings: "Paramètres" translations: "Traductions" + extra_scss: "Extra SCSS" preview: "Prévisualiser" show_advanced: "Afficher les champs avancés" hide_advanced: "Cacher les champs avancés" @@ -3392,6 +3521,7 @@ fr: web_hook_create: "Créer un webhook" web_hook_update: "Mettre à jour le webhook" web_hook_destroy: "Détruire le webhook" + web_hook_deactivate: "désactiver webhook" embeddable_host_create: "créer hôte intégré" embeddable_host_update: "mettre à jour hôte intégré" embeddable_host_destroy: "détruire hôte intégré" @@ -3534,6 +3664,7 @@ fr: delete_posts_failed: "Il y a eu un problème lors de la suppression des messages." penalty_post_actions: "Qu'aimeriez-vous faire du message associé ?" penalty_post_delete: "Supprimer le message" + penalty_post_delete_replies: "Supprimer le message + réponses" penalty_post_edit: "Modifier le message" penalty_post_none: "Ne rien faire" penalty_count: "Décompte pénalités" @@ -3730,6 +3861,7 @@ fr: clear_filter: "Effacer" add_url: "ajouter URL" add_host: "ajouter hôte" + add_group: "ajouter groupe" uploaded_image_list: label: "Modifier la liste" empty: "Il n'y a aucune image pour le moment. Veuillez en envoyer une." @@ -3741,6 +3873,7 @@ fr: categories: all_results: "Toutes" required: "Requis" + branding: "Branding" basic: "Général" users: "Utilisateurs" posting: "Messages" @@ -3878,6 +4011,7 @@ fr: save: "Sauvegarder les paramètres d'intégration" permalink: title: "Permaliens" + description: "Veuillez noter que ceci s'applique uniquement aux sources externes, les liens postés sur votre forum ne seront pas redirigés." url: "URL" topic_id: "ID sujet" topic_title: "Sujet" diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml index a9257d0e11..3e1be788d0 100644 --- a/config/locales/client.gl.yml +++ b/config/locales/client.gl.yml @@ -913,6 +913,8 @@ gl: replied: '{{username}} respondeute en "{{topic}}" - {{site_title}}' posted: '{{username}} publicou en "{{topic}}" - {{site_title}}' linked: '{{username}} ligou a túa publicación desde "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "novo tema" upload_selector: title: "Engadir unha imaxe" title_with_attachments: "Engadir imaxe ou ficheiro" @@ -1007,6 +1009,8 @@ gl: move_to_inbox: title: "Mover á caixa de entrada" help: "Mover mensaxes á caixa de entrada" + defer: + title: "Pospor" list: "Temas" new: "novo tema" unread: "sen ler" @@ -1680,6 +1684,10 @@ gl: admin: title: "Administrador de Discourse" moderator: "Moderador" + tags: + remove_muted_tags_from_latest: + always: "sempre" + never: "nunca" dashboard: title: "Panel" version: "Versión" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 1d11e90619..776a546eae 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -358,6 +358,7 @@ he: search: "חפש הודעה לפי שם:" placeholder: "הקלידו את כותרת ההודעה כאן" review: + in_reply_to: "בתגובה ל" claim_help: optional: "באפשרותך לדרוש את הפריט כדי למנוע מאחרים לסקור אותו." required: "עליך לדרוש פריטים לפני שיתאפשר לך לסקור אותם." @@ -805,6 +806,7 @@ he: allow_private_messages: "אפשר למשתמשים אחרים לשלוח לי הודעות פרטיות" external_links_in_new_tab: "פתח את כל הקישורים החיצוניים בעמוד חדש" enable_quoting: "הפעלת תגובת ציטוט לטקסט מסומן" + enable_defer: "הפעלת דחייה כדי לסמן נושאים כלא נקראו" change: "שנה" moderator: "ל־{{user}} יש תפקיד פיקוח" admin: "{{user}} הוא מנהל מערכת" @@ -844,6 +846,7 @@ he: watched_first_post_tags_instructions: "אתם תיודעו לגבי הפוסט הראשון בכל נושא חדש בתגיות אלו." muted_categories: "מושתק" muted_categories_instructions: "לא תקבל הודעה בנוגע לנושאים חדשים בקטגוריות אלה, והם לא יופיעו בקטגוריות או בדפים האחרונים." + muted_categories_instructions_dont_hide: "לא תישלחנה אליך התראות על שום דבר בנוגע לנושאים בקטגוריות האלו." no_category_access: "בתור פיקוח יש לך גישה מוגבלת לקטגוריות, שמירה מנוטרלת." delete_account: "מחק את החשבון שלי" delete_account_confirm: "אתם בטוחים שברצונכם להסיר את החשבון? לא ניתן לבטל פעולה זו!" @@ -1259,6 +1262,11 @@ he: too_few_topics_and_posts_notice: "בואו נתניע את הדיון הזה! כרגע יש %{currentTopics} / %{requiredTopics} נושאים ו%{currentPosts} / %{requiredPosts} פרסומים. מבקרים חדשים צריכים כמה שיחות לקרוא ולהגיב אליהם." too_few_topics_notice: "בואו נתחיל את הדיון הזה! יש כרגע %{currentTopics} / %{requiredTopics} נושאים. אורחים חדשים צריכים כמה דיונים לקרוא ולהגיב אליהם." too_few_posts_notice: "בואו נתחיל את הדיון הזה! יש כרגע %{currentPosts} / %{requiredPosts} פרסומים. אורחים חדשים צריכים כמה דיונים לקרוא ולהגיב אליהם." + logs_error_rate_notice: + reached_hour_MF: "{relativeAge}{rate, plural, one {שגיאה אחת בשעה הגיעה} other {# שגיאות בשעה הגיעו}} למגבלת האתר שהיא {limit, plural, one {שגיאה אחת בשעה} other {# שגיאות בשעה}}." + reached_minute_MF: "{relativeAge}{rate, plural, one {שגיאה אחת בדקה הגיעה} other {# שגיאות בדקה הגיעו}} למגבלת האתר שהיא {limit, plural, one {שגיאה אחת בדקה} other {# שגיאות בדקה}}." + exceeded_hour_MF: "{relativeAge}{rate, plural, one {שגיאה אחת בשעה חרגה} other {# שגיאות בשעה חרגו}} ממגבלת האתר שהיא {limit, plural, one {שגיאה אחת בשעה} other {# שגיאות בשעה}}." + exceeded_minute_MF: "{relativeAge}{rate, plural, one {שגיאה אחת בדקה חרגה} other {# שגיאות בדקה חרגו}} ממגבלת האתר שהיא {limit, plural, one {שגיאה אחת בדקה} other {# שגיאות בדקה}}." learn_more: "למד עוד..." all_time: "סך הכל" all_time_desc: "כל הנושאים שנוצרו" @@ -1669,6 +1677,26 @@ he: 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: "מקושר" + granted_badge: "הוענק עיטור" + invited_to_topic: "הוזמן לנושא" + group_mentioned: "קבוצה אוזכרה" + group_message_summary: "הודעות קבוצתיות חדשות" + watching_first_post: "נושא חדש" + topic_reminder: "תזכורת נושא" + liked_consolidated: "לייקים חדשים" + post_approved: "פוסט אושר" upload_selector: title: "הוספת תמונה" title_with_attachments: "הוספת תמונה או קובץ" @@ -1840,6 +1868,9 @@ he: edit_message: help: "ערוך פוסט ראשון של ההודעה" title: "ערוך הודעה" + defer: + help: "סימון כלא נקראו" + title: "דחייה" list: "נושאים" new: "נושא חדש" unread: "לא נקראו" @@ -2969,6 +3000,11 @@ he: admin: title: "ניהול Discourse" moderator: "מפקח" + tags: + remove_muted_tags_from_latest: + always: "תמיד" + only_muted: "בעת שימוש בנפרד או עם תגיות מושתקות אחרות" + never: "אף פעם" reports: title: "רשימה של דיווחים זמינים" dashboard: @@ -3045,6 +3081,7 @@ he: groups: "כל הקבוצות" disabled: "דוח זה מנוטרל" totals_for_sample: "סיכומים לדוגמה" + average_for_sample: "ממוצע לדגימה" total: "סך הכול מאז ומתמיד" no_data: "אין נתונים להצגה." trending_search: diff --git a/config/locales/client.hu.yml b/config/locales/client.hu.yml index 0c55e8b891..b6ed2b5efe 100644 --- a/config/locales/client.hu.yml +++ b/config/locales/client.hu.yml @@ -1439,6 +1439,9 @@ hu: private_message: '{{username}} küldött egy személyes üzenetet itt: "{{topic}}" - {{site_title}}' confirm_title: "Értesítések bekapcsolva - %{site_title}" confirm_body: "Siker! Értesítések engedélyezve." + titles: + watching_first_post: "új téma" + post_approved: "bejegyzés jóváhagyva" upload_selector: title: "Kép hozzáadása" title_with_attachments: "Kép vagy file hozzáadása" @@ -2309,6 +2312,10 @@ hu: admin: title: "Discourse Admin" moderator: "Moderátor" + tags: + remove_muted_tags_from_latest: + always: "mindig" + never: "soha" reports: title: "Aktív bejelentések listája" dashboard: @@ -2876,7 +2883,7 @@ hu: users: title: "Felhasználók" create: "Admin felhasználó hozzáadása" - last_emailed: "Utoljára emailelt" + last_emailed: "Utolsó levélküldés" not_found: "Sajnos ez a felhasználónév nem létezik." id_not_found: "Sajnos ez a felhasználó id nem található a rendszerben." show_emails: "E-mailek mutatása" diff --git a/config/locales/client.hy.yml b/config/locales/client.hy.yml index b3186bd286..ea837347fd 100644 --- a/config/locales/client.hy.yml +++ b/config/locales/client.hy.yml @@ -1440,6 +1440,9 @@ hy: watching_first_post: '{{username}} -ը ստեղծել է նոր թեմա՝ "{{topic}}" - {{site_title}}' confirm_title: "Ծանուցումները միացված են. %{site_title}" confirm_body: "Հաջողվեց! Ծանուցումները միացված են:" + titles: + watching_first_post: "նոր թեմա" + post_approved: "գրառումը հաստատված է" upload_selector: title: "Ավելացնել նկար" title_with_attachments: "Ավելացնել նկար կամ ֆայլ" @@ -2604,6 +2607,10 @@ hy: admin: title: "Discourse Ադմին" moderator: "Մոդերատոր" + tags: + remove_muted_tags_from_latest: + always: "միշտ" + never: "երբեք" reports: title: "Հասանելի հաշվետվությունների ցանկը" dashboard: diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index fa77435720..dde7db5fc1 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -883,6 +883,8 @@ id: notifications: empty: "Tidak ada pemberitahuan." more: "lihat notifikasi sebelumnya" + titles: + watching_first_post: "topik baru" upload_selector: default_image_alt_text: gambar search: @@ -1098,6 +1100,10 @@ id: admin: title: "Admin Discourse" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "selalu" + never: "tidak pernah" dashboard: latest_version: "Terbaru" last_checked: "Terakhir dicek" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 03d59f8078..24f34ecb9a 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -153,6 +153,7 @@ it: bootstrap_mode_disabled: "La modalità bootstrap sarà disattivata entro 24 ore." themes: default_description: "Default" + broken_theme_alert: "Il tuo sito potrebbe non funzionare perché il tema / componente %{theme} contiene degli errori. Disabilitalo qui %{path}." s3: regions: ap_northeast_1: "Asia Pacifico (Tokyo)" @@ -172,6 +173,7 @@ it: us_east_1: "Stati Uniti Est (Virginia del Nord)" us_east_2: "USA Est (Ohio)" us_gov_east_1: "AWS GovCloud (USA Est)" + us_gov_west_1: "AWS GovCloud (US-West)" us_west_1: "Stati Uniti Ovest (California del Nord)" us_west_2: "Stati Uniti Ovest (Oregon)" edit: "modifica titolo e categoria dell'argomento" @@ -182,6 +184,7 @@ it: submit: "Invia" generic_error: "Spiacenti, c'è stato un problema." generic_error_with_reason: "Si è verificato un errore: %{error}" + go_ahead: "Continua" sign_up: "Iscriviti" log_in: "Connetti" age: "Età" @@ -303,46 +306,105 @@ it: none_found: "Nessun messaggio trovato." title: search: "Cerca un Messaggio per titolo:" + placeholder: "digita qui il titolo del messaggio" review: + order_by: "Ordina per" + in_reply_to: "in risposta a" + claim_help: + optional: "Puoi rivendicare questo elemento per prevenire che altri lo revisionino." + required: "Devi rivendicare questo elemento per poterlo revisionare." + claimed_by_you: "Hai rivendicato questo elemento e puoi revisionarlo." + claimed_by_other: "Questo elemento può essere revisionato esclusivamente da {{username}}." + claim: + title: "Rivendica questo Argomento" + unclaim: + help: "Rimuovi questa rivendicazione" + awaiting_approval: "In attesa di approvazione" delete: "Elimina" settings: + saved: "Salvato" save_changes: "Salva Modifiche" title: "Impostazioni" + priorities: + title: "Priorità" moderation_history: "Storico Moderazione" view_all: "Vedi tutti" + grouped_by_topic: "Raggruppati per Argomento" none: "Non ci sono elementi da revisionare." view_pending: "vedi in attesa" topic_has_pending: one: "Questo argomento ha %{count} messaggio in attesa di approvazione" other: "Questo Argomento ha {{count}} messaggi in attesa di approvazione" + title: "Revisiona" topic: "Argomento:" + filtered_topic: "Hai filtrato il contenuto revisionabile in un singolo Argomento" filtered_user: "Utente" + show_all_topics: "mostra tutti gli argomenti" + deleted_post: "(messaggio eliminato)" + deleted_user: "(utente eliminato)" user: username: "Nome utente" email: "Email" name: "Nome" + fields: "Campi" + user_percentage: + summary: + one: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} segnalazione in totale)" + other: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} segnalazioni in totale)" + agreed: + one: "{{count}}% a favore" + other: "{{count}}% a favore" + disagreed: + one: "{{count}}% contrario" + other: "{{count}}% contrari" + ignored: + one: "{{count}}% ignorato" + other: "{{count}}% ignorati" topics: topic: "Argomento" reviewable_count: "Conteggio" + reported_by: "Segnalato da" + deleted: "[Argomento eliminato]" + original: "(argomento originale)" details: "dettagli" + unique_users: + one: "%{count} utente" + other: "{{count}} utenti" replies: one: "%{count} risposta" other: "{{count}} risposte" edit: "Modifica" save: "Salva" cancel: "Annulla" + new_topic: "Nuovo Argomento:" filters: type: title: "Tipo" + all: "(tutti i tipi)" + minimum_score: "Punteggio minimo:" refresh: "Aggiorna" + status: "Stato" category: "Categoria" + orders: + priority: "Priorità" + priority_asc: "Priorità (inversa)" + created_at: "Creato il" + created_at_asc: "Data creazione (inversa)" priority: + title: "Priorità minima" low: "Bassa" + medium: "Media" high: "Alta" + conversation: + view_full: "vedi l'intera conversazione" scores: + about: "Questo punteggio è calcolato sulla base del Livello di Esperienza del segnalante, dell'accuratezza delle precedenti segnalazioni e della priorità dell'elemento segnalato." score: "Punteggio" date: "Data" type: "Tipo" + status: "Stato" + submitted_by: "Inviato da" + reviewed_by: "Revisionato da" statuses: pending: title: "In attesa" @@ -352,9 +414,20 @@ it: title: "Rifiutate" ignored: title: "Ignorato" + deleted: + title: "Eliminato" reviewed: title: "(Già revisionati)" + all: + title: "(Tutto)" types: + reviewable_flagged_post: + title: "Messaggio segnalato" + flagged_by: "Segnalato da" + reviewable_queued_topic: + title: "Argomenti in coda" + reviewable_queued_post: + title: "Messaggio accodato" reviewable_user: title: "Utente" approval: @@ -407,13 +480,19 @@ it: remove_user_as_group_owner: "Revoca proprietà" groups: member_added: "Aggiunto" + member_requested: "Richiesto alle" add_members: title: "Aggiungi Membri" description: "Gestisci le iscrizioni di questo gruppo" usernames: "Nomi Utente" requests: + title: "Richieste" reason: "Motivo" + accept: "Accetta" accepted: "accettata" + deny: "Nega" + denied: "negato" + undone: "richiesta annullata" manage: title: "Gestisci" name: "Nome" @@ -444,6 +523,7 @@ it: empty: posts: "Non ci sono messaggi da membri di questo gruppo." members: "Non ci sono membri in questo gruppo." + requests: "Non ci sono richieste di iscrizione a questo gruppo." mentions: "Questo gruppo non è stato menzionato." messages: "Non ci sono messaggi per questo gruppo." topics: "Non ci sono argomenti da membri di questo gruppo." @@ -613,12 +693,20 @@ it: private_message: "Messaggio" private_messages: "Messaggi" user_notifications: + ignore_duration_title: "Ignora Timer" ignore_duration_username: "Nome utente" + ignore_duration_when: "Durata:" ignore_duration_save: "Ignora" + ignore_duration_note: "Si prega di notare che tutti gli utenti ignorati saranno rimossi allo scadere del relativo periodo di attesa." ignore_duration_time_frame_required: "Per favore, seleziona un lasso di tempo" + ignore_no_users: "Non ci sono utenti ignorati." ignore_option: "Ignorato" + ignore_option_title: "Non riceverai più notifiche relative a questo utente, ed i suoi Argomenti e le sue risposte saranno nascoste." + add_ignored_user: "Aggiungi..." mute_option: "Silenziati" + mute_option_title: "Non riceverai più notifiche relative a questo utente." normal_option: "Normale" + normal_option_title: "Riceverai una notifica quando questo utente ti risponde, ti cita o ti menziona." activity_stream: "Attività" preferences: "Opzioni" profile_hidden: "Il profilo pubblico di questo utente è nascosto." @@ -644,6 +732,7 @@ it: dismiss_notifications: "Nascondi tutti" dismiss_notifications_tooltip: "Imposta tutte le notifiche non lette come lette " first_notification: "La tua prima notifica! Selezionala per iniziare." + dynamic_favicon: "Mostra il contatore sull'icona del browser" theme_default_on_all_devices: "Rendi questo il tema di default su tutti i miei dispositivi." text_size_default_on_all_devices: "Rendi questa dimensione del testo il default su tutti i miei dispositivi" allow_private_messages: "Consenti agli altri utenti di inviarmi messaggi privati" @@ -688,6 +777,7 @@ it: watched_first_post_tags_instructions: "Riceverai la notifica per il primo messaggio di ogni nuovo argomento con queste etichette." muted_categories: "Silenziate" muted_categories_instructions: "Non riceverai notifiche relative a nuovi Argomenti in queste Categorie, e non appariranno nella pagina delle Categorie o dei Recenti." + muted_categories_instructions_dont_hide: "Non riceverai alcuna notifica per i nuovi Argomenti creati in questa categoria." no_category_access: "Come moderatore hai accesso limitato alla categoria, il salvataggio è disabilitato." delete_account: "Cancella il mio account" delete_account_confirm: "Sei sicuro di voler cancellare il tuo account in modo permanente? Questa azione non può essere annullata!" @@ -758,19 +848,27 @@ it: copied_to_clipboard: "Copiato nella Clipboard" copy_to_clipboard_error: "Errore durante la copia nella Clipboard" remaining_codes: "Ti sono rimasti {{count}} codici di backup." + use: "Usa un codice di backup" codes: title: "Codici di backup generati" description: "Ciascuno di questi codici di backup può essere usato una sola volta. Conservali in un posto sicuro ma accessibile." second_factor: title: "Autenticazione a Due Fattori" + disable: "Disabilita l'autenticazione a due fattori" + enable: "Abilita l'autenticazione a due fattori" confirm_password_description: "Per favore conferma la tua password per continuare" label: "Codice" rate_limit: "Per favore, attendi prima di provare un altro codice di autenticazione." + enable_description: | + Scansiona il QR code con una delle app supportate (AndroidiOS e inserisci il tuo codice di autenticazione disable_description: "Inserisci il codice di autenticazione dalla tua app" show_key_description: "Inserisci manualmente" + short_description: | + Proteggi il tuo account con un codice di sicurezza usa e getta. extended_description: | L'autenticazione a due fattori aggiunge ulteriore sicurezza al tuo account attraverso la richiesta di un token usa e getta oltre alla tua password. I token possono essere generati su dispositivi Android e iOS . oauth_enabled_warning: "Tieni presente che gli accessi ai social network saranno disabilitati dopo aver attivato l'autenticazione a due fattori nel tuo account." + use: "Usa l'applicazione Authenticator " enforced_notice: "E' obbligatorio abilitare l'autenticazione a due fattori per accedere a questo sito." change_about: title: "Modifica i dati personali" @@ -809,6 +907,7 @@ it: primary: "Email principale" secondary: "Email secondaria" no_secondary: "Nessuna email secondaria" + sso_override_instructions: "L'e-mail può essere aggiornato dal provider SSO." instructions: "Mai mostrato pubblicamente" ok: "Ti invieremo una email di conferma" invalid: "Inserisci un indirizzo email valido" @@ -876,6 +975,9 @@ it: normal: "Normale" larger: "Più grande" largest: "Massima" + title_count_mode: + notifications: "Nuove notifiche" + contextual: "Nuovo contenuto nella pagina" like_notification_frequency: title: "Notifica alla ricezione di \"Mi piace\"." always: "Sempre" @@ -966,6 +1068,7 @@ it: text: "Invito di Massa da File" success: "Il file è stato caricato con successo, riceverai un messaggio di notifica quando il processo sarà completato." error: "Spiacenti, il file deve essere in formato CSV." + confirmation_message: "Stai per inviare un invito via email a tutti gli indirizzi inclusi nel file caricato." password: title: "Password" too_short: "La password è troppo breve." @@ -1066,6 +1169,9 @@ it: enabled: "Questo sito è in modalità di sola lettura. Puoi continuare a navigare nel sito, ma le risposte, i \"Mi piace\" e altre azioni sono per il momento disabilitate." login_disabled: "La connessione è disabilitata quando il sito è in modalità di sola lettura." logout_disabled: "La disconnessione è disabilitata quando il sito è in modalità di sola lettura." + too_few_topics_and_posts_notice: "Diamo il via alle discussionii! Al momento ci sono %{currentTopics} / %{requiredTopics} Argomenti e %{currentPosts} / %{requiredPosts} messaggi. I nuovi arrivati hanno bisogno di conversazioni da leggere e a cui rispondere." + too_few_topics_notice: "Diamo il via alle discussioni! Al momento ci sono %{currentTopics} / %{requiredTopics} Argomenti. I nuovi arrivati hanno bisogno di conversazioni da leggere e a cui rispondere." + too_few_posts_notice: "Diamo il via alle discussioni! Al momento ci sono %{currentPosts} / %{requiredPosts} messaggi. I nuovi arrivati hanno bisogno di conversazioni da leggere e a cui rispondere." learn_more: "per saperne di più..." all_time: "totale" all_time_desc: "totale argomenti creati" @@ -1121,6 +1227,7 @@ it: trust_level: "Livello Esperienza" search_hint: "nome utente, email o indirizzo IP" create_account: + disclaimer: "Registrandoti accetti il regolamento sulla Privacy e i termini del servizio." title: "Crea Nuovo Account" failed: "Qualcosa non ha funzionato. Forse questa email è già registrata, prova a usare il link di recupero password" forgot_password: @@ -1159,6 +1266,7 @@ it: email_placeholder: "email o nome utente" caps_lock_warning: "Il Blocco Maiuscole è attivo" error: "Errore sconosciuto" + cookies_error: "Sembra che il tuo browser abbia i cookie disabilitati. Potresti non riuscire a connetterti senza abilitarli." rate_limit: "Per favore attendi prima di provare nuovamente la connessione." blank_username: "Per favore, inserisci la tua email o il tuo nome utente." blank_username_or_password: "Per favore inserisci la tua email o il tuo nome utente, e la password." @@ -1236,8 +1344,11 @@ it: shift: "Maiusc" ctrl: "Ctrl" alt: "Alt" + enter: "Invio" conditional_loading_section: loading: In caricamento... + category_row: + topic_count: "{{count}} Argomenti in questa categoria" select_kit: default_header_text: Selezione... no_content: Nessun risultato trovato @@ -1252,7 +1363,14 @@ it: other: "Seleziona almeno {{count}} elementi." emoji_picker: filter_placeholder: Ricerca per emoji + smileys_&_emotion: Smileys ed Emotion + people_&_body: Persone e parti del corpo + animals_&_nature: Animali e Natura + food_&_drink: Cibo e Bevande + travel_&_places: Viaggi e Località + activities: Attività objects: Oggetti + symbols: Simboli flags: Segnalazioni custom: Emoji personalizzate recent: Usate recentemente @@ -1322,6 +1440,7 @@ it: remove_featured_link: "Rimuovi il collegamento dall'argomento." reply_placeholder: "Scrivi qui. Per formattare il testo usa Markdown, BBCode o HTML. Trascina o incolla le immagini." reply_placeholder_no_images: "Scrivi qui. Usa Markdown, BBcode o HTML per formattare." + reply_placeholder_choose_category: "Seleziona una categoria prima di scrivere qui." view_new_post: "Visualizza il tuo nuovo messaggio." saving: "Salvataggio" saved: "Salvato!" @@ -1354,7 +1473,10 @@ it: toggle_direction: "Commuta Direzione" help: "Aiuto Inserimento Markdown" collapse: "minimizza il pannello del composer" + open: "Apri il pannello del composer" abandon: "chiudi il composer e scarta la bozza" + enter_fullscreen: "Espandi il composer a tutto schermo" + exit_fullscreen: "Lascia il composer a tutto schermo" modal_ok: "OK" modal_cancel: "Annulla" cant_send_pm: "Spiacenti, non puoi inviare un messaggio a %{username}." @@ -1378,6 +1500,9 @@ it: reply_to_topic: label: Rispondi all'argomento desc: "Rispondi all'argomento, non a uno specifico messaggio" + toggle_whisper: + label: Commuta Sussurri + desc: I Sussurri sono visibili solo ai membri dello staff create_topic: label: "Nuovo Argomento" shared_draft: @@ -1398,6 +1523,8 @@ it: none: "Impossibile caricare le notifiche al momento." empty: "Nessuna notifica trovata." more: "visualizza le notifiche precedenti" + post_approved: "Il tuo messaggio è stato approvato" + reviewable_items: "elementi in attesa di revisione" mentioned: "{{username}} {{description}}" group_mentioned: "{{username}} {{description}}" quoted: "{{username}} {{description}}" @@ -1436,6 +1563,27 @@ it: watching_first_post: '{{username}} ha creato il nuovo argomento "{{topic}}" - {{site_title}}' confirm_title: "Notifiche abilitate - %{site_title}" confirm_body: "Successo! Le notifiche sono state abilitate." + custom: "Notifica di {{username}} su %{site_title}" + titles: + mentioned: "menzionato" + replied: "nuova risposta" + quoted: "citato" + edited: "modificato" + liked: "nuovo \"mi piace\"" + private_message: "nuovo messaggio privato" + invited_to_private_message: "invitato ad un Messaggio Privato" + invitee_accepted: "invito accettato" + posted: "nuovo messaggio" + moved_post: "messaggio spostato" + linked: "linkato" + granted_badge: "distintivo assegnato" + invited_to_topic: "invitato ad un Argomento" + group_mentioned: "gruppo menzionato" + group_message_summary: "nuovo messaggio di gruppo" + watching_first_post: "nuovo argomento" + topic_reminder: "promemoria Argomento" + liked_consolidated: "nuovi \"Mi piace\"" + post_approved: "messaggio approvato" upload_selector: title: "Aggiungi un'immagine" title_with_attachments: "Aggiungi un'immagine o un file" @@ -1461,6 +1609,9 @@ it: select_all: "Seleziona Tutto" clear_all: "Cancella Tutto" too_short: "La tua chiave di ricerca è troppo corta." + result_count: + one: "%{count} risultato per{{term}}" + other: "{{count}}{{plus}} risultati per{{term}}" title: "cerca argomenti, messaggi, utenti o categorie" full_page_title: "Cerca negli Argomenti o nei Messaggi" no_results: "Nessun risultato trovato." @@ -1596,6 +1747,9 @@ it: edit_message: help: "Modifica la prima versione del Messaggio" title: "Modifica Messaggio" + defer: + help: "Segna come non letto" + title: "Ignora" list: "Argomenti" new: "nuovo argomento" unread: "non letto" @@ -1660,7 +1814,9 @@ it: next_week: "La prossima settimana" two_weeks: "Due Settimane" next_month: "Il prossimo mese" + two_months: "Due mesi" three_months: "Tre Mesi" + four_months: "Quattro mesi" six_months: "Sei Mesi" one_year: "Un Anno" forever: "Per sempre" @@ -1709,7 +1865,9 @@ it: jump_bottom: "salta all'ultimo messaggio" jump_prompt: "vai a..." jump_prompt_of: "di %{count} messaggi" + jump_prompt_long: "Salta a..." jump_bottom_with_number: "Passa al messaggio %{post_number}" + jump_prompt_to_date: "ad oggi" jump_prompt_or: "o" total: totale messaggi current: messaggio corrente @@ -1934,10 +2092,14 @@ it: description: one: Hai selezionato %{count} messaggio. other: "Hai selezionato {{count}} messaggi." + deleted_by_author: + one: "(Argomento eliminato dall'autore, verrà automaticamente cancellato in %{count} ora, se non segnalato)" + other: "(Argomento eliminato dall'autore, verrà automaticamente cancellato in %{count} ore, se non segnalato)" post: quote_reply: "Cita" edit_reason: "Motivo:" post_number: "messaggio {{number}}" + ignored: "Contenuto ignorato" wiki_last_edited_on: "ultima modifica alla wiki" last_edited_on: "ultima modifica al messaggio:" reply_as_new_topic: "Rispondi come Argomento collegato" @@ -1945,6 +2107,7 @@ it: continue_discussion: "Continua la discussione da {{postLink}}:" follow_quote: "vai al messaggio citato" show_full: "Mostra Messaggio Completo" + show_hidden: "Visualizza contenuto ignorato" deleted_by_author: one: "(post eliminato dall'autore, sarà automaticamente cancellato in %{count} ore se non contrassegnato)" other: "(messaggio eliminato dall'autore, verrà automaticamente cancellato in %{count} ore se non segnalato)" @@ -2029,6 +2192,8 @@ it: delete_topic_disallowed_modal: "Non hai il permesso di eliminare questo Argomento. Se davvero lo vuoi eliminare, segnalalo all'attenzione dei moderatori spiegando le tue motivazioni." delete_topic_disallowed: "non hai il permesso di eliminare questo argomento" delete_topic: "elimina argomento" + add_post_notice: "Aggiungi Note Staff" + remove_post_notice: "Rimuovi Note Staff" actions: flag: "Segnala" defer_flags: @@ -2112,8 +2277,13 @@ it: settings: "Impostazioni" topic_template: "Modello di Argomento" tags: "Etichette" + tags_allowed_tags: "Limita queste Etichette a questa Categoria:" + tags_allowed_tag_groups: "Limita questi gruppi di Etichette a questa Categoria:" tags_placeholder: "Elenco (opzionale) delle etichette permesse" + tags_tab_description: "Le Etichette ed i gruppi di Etichette specificati di seguito saranno disponibili solamente in questa Categoria e nelle altre Categorie che li specificano. Non saranno disponibili per l'uso in altre categorie." tag_groups_placeholder: "Elenco (opzionale) dei gruppi di etichette permessi" + manage_tag_groups_link: "Gestisci qui i gruppi di Etichette." + allow_global_tags_label: "Consenti anche ulteriori Etichette" topic_featured_link_allowed: "Consenti collegamenti in primo piano in questa categoria" delete: "Elimina Categoria" create: "Crea Categoria" @@ -2141,6 +2311,7 @@ it: already_used: "Questo colore è già stato usato in un'altra categoria." security: "Sicurezza" special_warning: "Attenzione: questa è una categoria predefinita e le impostazioni di sicurezza ne vietano la modifica. Se non vuoi usare questa categoria, cancellala invece di modificarla." + uncategorized_security_warning: "Questa è una Categoria speciale. È utilizzata come area di parcheggio per gli Argomenti senza Categoria. Non può avere impostazioni di sicurezza." images: "Immagini" email_in: "Indirizzo email personalizzato:" email_in_allow_strangers: "Accetta email da utenti anonimi senza alcun account" @@ -2158,10 +2329,12 @@ it: default_top_period: "Periodo Predefinito Argomenti Di Punta:" allow_badges_label: "Permetti l'assegnazione di distintivi in questa categoria" edit_permissions: "Modifica Permessi" + reviewable_by_group: "Oltre che dallo Staff, i messaggi e le segnalazioni in questa Categoria possono essere revisionati da:" review_group_name: "nome gruppo" require_topic_approval: "Richiedi l'approvazione di un moderatore per tutti i nuovi argomenti" require_reply_approval: "Richiedi l'approvazione di un moderatore per tutti i nuovi argomenti" this_year: "quest'anno" + position: "Posizione nella pagina delle Categorie:" default_position: "Posizione di default" position_disabled: "Le categorie verranno mostrate in ordine d'attività. Per modificare l'ordinamento delle categorie nelle liste," position_disabled_click: 'attiva l''impostazione "posizione fissa delle categorie".' @@ -2214,6 +2387,7 @@ it: settings_sections: general: "Generale" moderation: "Moderazione" + appearance: "Aspetto" email: "Email" flagging: title: "Grazie per aiutarci a mantenere la nostra comunità civile!" @@ -2384,13 +2558,24 @@ it: this_week: "Settimana" today: "Oggi" other_periods: "vedi argomenti di punta" + browser_update: 'Sfortunatamente, il tuo browser è troppo obsoleto per funzionare con questo sito. Per favore, aggiorna il tuo browser.' permission_types: full: "Crea / Rispondi / Visualizza" create_post: "Rispondi / Visualizza" readonly: "Visualizza" lightbox: download: "scarica" + previous: "Precedente (tasto freccia a sinistra)" + next: "Successivo (tasto freccia a destra)" + close: "Chiudi (Esc)" + content_load_error: 'Caricamento del contenuto non riuscito.' + image_load_error: 'Caricamento dell''immagine non riuscito.' keyboard_shortcuts_help: + shortcut_key_delimiter_comma: ", " + shortcut_key_delimiter_plus: "+" + shortcut_delimiter_or: "%{shortcut1} o %{shortcut2}" + shortcut_delimiter_slash: "%{shortcut1}/%{shortcut2}" + shortcut_delimiter_space: "%{shortcut1} %{shortcut2}" title: "Scorciatoie Tastiera" jump_to: title: "Salta A" @@ -2425,6 +2610,8 @@ it: log_out: "%{shortcut} Disconnetti" composing: title: "Scrittura" + return: "%{shortcut} Ritorna al composer" + fullscreen: "%{shortcut} Composer a tutto schermo" actions: title: "Azioni" bookmark_topic: "%{shortcut} Aggiungi/togli argomento nei segnalibri" @@ -2465,6 +2652,7 @@ it: other: "%{count} assegnati" select_badge_for_title: Seleziona un distintivo da usare come tuo titolo none: "(nessuno)" + successfully_granted: "Distintivo %{badge} assegnato con successo a %{username}" badge_grouping: getting_started: name: Iniziali @@ -2509,6 +2697,12 @@ it: upload_description: "Carica un file csv per creare Etichette in modo massivo" upload_instructions: "Uno per riga, indicando opzionalmente un Gruppo di Etichette con il formato 'nome_etichetta,gruppo_etichetta'" upload_successful: "Etichette caricate con successo" + delete_unused_confirmation: + one: "%{count} Etichetta sarà eliminata: %{tags}" + other: "%{count} Etichette saranno eliminate: %{tags}" + delete_unused_confirmation_more_tags: + one: "%{tags} e %{count} altra" + other: "%{tags} e %{count} altre" delete_unused: "Elimina Etichette non utilizzate" delete_unused_description: "Elimina tutte le Etichette che non sono usate in alcun Argomento o Messaggio Personale" cancel_delete_unused: "Annulla" @@ -2520,16 +2714,19 @@ it: notifications: watching: title: "In osservazione" + description: "Visualizzerai automaticamente tutti gli Argomenti con questa Etichetta. Riceverai una notifica per tutti i nuovi messaggi e Argomenti. Inoltre, accanto all'Argomento apparirà il conteggio dei messaggi non letti e di quelli nuovi." watching_first_post: title: "Osservando Primo Messaggio" description: "Riceverai una notifica se saranno aperti nuovi Argomenti in questa Etichetta, ma non per le risposte agli Argomenti." tracking: title: "Seguiti" + description: "Seguirai automaticamente tutti gli argomenti con questa Etichetta. Accanto all'argomento apparirà un conteggio dei messaggi non letti e di quelli nuovi." regular: title: "Normale" description: "Riceverai una notifica se qualcuno menziona il tuo @nome o risponde al tuo messaggio." muted: title: "Silenziato" + description: "Non riceverai alcuna notifica per nuovi Argomenti con questa Etichetta, e non compariranno nella pagina dei messaggi non letti." groups: title: "Gruppi Etichette" about: "Aggiungi etichette a gruppi per poterle gestire più facilmente." @@ -2564,9 +2761,11 @@ it: top: "Non ci sono ulteriori argomenti popolari." bookmarks: "Non ci sono ulteriori argomenti nei segnalibri." invite: + custom_message: "Rendi il tuo l'invito un po' più personale scrivendo un messaggio personalizzato." custom_message_placeholder: "Inserisci il tuo messaggio personalizzato" custom_message_template_forum: "Ehi, unisciti a questo forum!" custom_message_template_topic: "Ehi, credo che questo argomento ti possa interessare!" + forced_anonymous: "A causa del carico eccessivo, il sito viene ora mostrato a tutti come lo vedrebbe un utente non connesso." safe_mode: enabled: "La modalità sicura è attiva, per disattivarla chiudi il browser" admin_js: @@ -2574,10 +2773,17 @@ it: admin: title: "Amministratore Discourse" moderator: "Moderatore" + tags: + remove_muted_tags_from_latest: + always: "sempre" + only_muted: "quando usato da solo o con altre Etichette silenziate" + never: "mai" reports: title: "Lista dei rapporti disponibili" dashboard: title: "Cruscotto" + last_updated: "Cruscotto aggiornato:" + discourse_last_updated: "Discourse aggiornato:" version: "Versione" up_to_date: "Sei aggiornato!" critical_available: "È disponibile un aggiornamento essenziale." @@ -2588,6 +2794,7 @@ it: version_check_pending: "Sembra che tu abbia aggiornato di recente. Ottimo!" installed_version: "Installata" latest_version: "Ultima" + problems_found: "Alcuni consigli basati sulle attuali impostazioni del sito" last_checked: "Ultimo controllo" refresh_problems: "Aggiorna" no_problems: "Nessun problema rilevato." @@ -2598,8 +2805,13 @@ it: private_messages_short: "MP" private_messages_title: "Messaggi" mobile_title: "Mobile" + space_used: "%{usedSize} usati" + space_used_and_free: "%{usedSize} (%{freeSize} liberi)" uploads: "Caricamenti" backups: "Backup" + backup_count: + one: "%{count} backup su %{location}" + other: "%{count} backup su %{location}" lastest_backup: "Recenti: %{date}" traffic_short: "Traffico" traffic: "Richieste web dell'applicazione" @@ -2618,6 +2830,8 @@ it: report_filter_any: "qualunque" disabled: Disabilitato timeout_error: "Spiacenti, la ricerca sta impiegando troppo tempo, per favore scegli un intervallo più breve" + exception_error: "Spiacenti, si è verificato un errore nell'esecuzione della query" + too_many_requests: Hai eseguito questa operazione troppe volte. Attendi qualche istante prima di riprovare. not_found_error: "Spiacenti, questo rapporto non esiste" filter_reports: Filtra rapporti reports: @@ -2636,6 +2850,7 @@ it: end_date: "Data Fine" groups: "Tutti i gruppi" disabled: "Il rapporto è disabilitato" + total: "Totale generale" no_data: "Non ci sono dati da mostrare." trending_search: more: 'Cerca nei log' @@ -2856,7 +3071,9 @@ it: title: "Ripristina il database a una versione funzionante precedente" confirm: "Sei sicuro di voler ripristinare il database alla versione funzionante precedente?" location: + local: "Salvataggio in locale" s3: "S3" + backup_storage_error: "Fallito accesso all'archivio dei backup: %{error_message}" export_csv: success: "Esportazione iniziata, verrai avvertito con un messaggio al termine del processo." failed: "Esportazione fallita. Controlla i log." @@ -2880,6 +3097,7 @@ it: save: "Salva" new: "Nuovo" new_style: "Nuovo Stile" + install: "Installa" delete: "Cancella" color: "Colore" opacity: "Opacità" @@ -2911,11 +3129,13 @@ it: long_title: "Modifica i colori, i CSS e il contenuto HTML del tuo sito" edit: "Modifica" edit_confirm: "Questo è un tema remoto, se modifichi il CSS o l'HTML i cambiamenti verranno cancellati la prossima volta che aggiorni il tema." + update_confirm_yes: "Sì, continua con l'aggiornamento" common: "Condiviso" desktop: "Desktop" mobile: "Mobile" settings: "Impostazioni" translations: "Traduzioni" + extra_scss: "Extra SCSS" preview: "Anteprima" show_advanced: "Mostra campi avanzati" hide_advanced: "Nascondi campi avanzati" @@ -2945,15 +3165,31 @@ it: variable_name: "Nome SCSS var:" upload: "Carica" unsaved_changes_alert: "Non hai ancora salvato le tue modifiche. Sei sicuro di volerle abbandonare e passare ad altro?" + discard: "Annulla" + stay: "Resta" css_html: "CSS/HTML personalizzato" edit_css_html: "Modifica CSS/HTML" edit_css_html_help: "Non hai modificato nessun CSS o HTML" delete_upload_confirm: "Elimina questo caricamento? (Il CSS del tema potrebbe non funzionare!)" import_web_tip: "Repository contenente il tema" + import_web_advanced: "Avanzate..." + is_private: "Il tema è in un repository git privato" + remote_branch: "Nome del branch (opzionale)" + install: "Installa" installed: "Installata" install_popular: "Di successo" + install_upload: "Dal tuo dispositivo" + install_git_repo: "Da un repository git" + install_create: "Crea nuovo" about_theme: "Informazioni su" license: "Licenza" + version: "Versione:" + authors: "Creato da:" + source_url: "Fonte" + required_version: + error: "Questo tema è stato disattivato automaticamente perché non compatibile con questa versione di Discourse." + minimum: "Richiede Discourse versione {{version}} o superiore." + maximum: "Richiede Discourse versione {{version}} or inferiore." component_of: "Componente di:" update_to_latest: "Aggiorna all'ultima versione" check_for_updates: "Controllo Aggiornamenti" @@ -2962,10 +3198,14 @@ it: add: "Aggiungi" theme_settings: "Impostazioni Tema" no_settings: "Questo tema non ha impostazioni." + theme_translations: "Traduzione Tema" empty: "Nessun elemento" commits_behind: one: "Il tema è indietro di %{count} aggiornamento!" other: "Il tema è indietro di {{count}} aggiornamenti!" + compare_commits: "(Vedi i nuovi commit)" + repo_unreachable: "Impossibile accedere al Git repository di questo tema. Messaggio di errore:" + imported_from_archive: "Questo tema è stato importato da un file .tar.gz" scss: text: "CSS" title: "Inserire il CSS personalizzato, accettiamo tutti gli stili validi di CSS e SCSSpersonalizzato" @@ -2991,11 +3231,20 @@ it: text: "YAML" title: "Definisci le impostazioni del tema in formato YAML" colors: + select_base: + title: "Seleziona la paletta dei colori di base" + description: "Paletta di base:" title: "Colori" + edit: "Modifica le palette dei colori" + long_title: "Palette dei colori" + about: "Modifica i colori usati dal tuo tema. Crea una nuova paletta dei colori, per iniziare." + new_name: "Nuova paletta dei colori" copy_name_prefix: "Copia di" + delete_confirm: "Eliminare questa paletta dei colori?" undo: "annulla" undo_title: "Annulla le modifiche effettuate a questo colore dall'ultimo salvataggio." revert: "ripristina" + revert_title: "Reimposta questo colore alla combinazione colori di default di Discourse." primary: name: "primario" description: "Per la maggior parte del testo, icone e bordi." @@ -3031,6 +3280,12 @@ it: settings: "Impostazioni" templates: "Modelli" preview_digest: "Anteprima Riepilogo" + advanced_test: + title: "Test avanzato" + email: "Messaggio originale" + run: "Esegui test" + text: "Seleziona il corpo del testo" + elided: "Testo omesso" sending_test: "Invio email di prova in corso..." error: "ERRORE - %{server_error}" test_error: "C'è stato un problema nell'invio dell'email di test. Controlla nuovamente le impostazioni email, verifica che il tuo host non blocchi le connessioni email e riprova." @@ -3089,12 +3344,15 @@ it: type_placeholder: "riepilogo, iscrizione..." reply_key_placeholder: "chiave di risposta" moderation_history: + performed_by: "Effettuato da" + no_results: "Lo storico della moderazione non è disponibile." actions: delete_user: "Utente Eliminato" suspend_user: "Utente Sospeso" silence_user: "Utente silenziato" delete_post: "Messaggio Eliminato" delete_topic: "Argomento Eliminato" + post_approved: "Messaggio approvato" logs: title: "Log" action: "Azione" @@ -3187,7 +3445,17 @@ it: change_badge: "cambia distintivo" delete_badge: "elimina distintivo" merge_user: "unisci utente" + entity_export: "esporta entità" change_name: "cambia nome" + topic_timestamps_changed: "Marche temprali dell'Argomento modificate" + approve_user: "utente approvato" + web_hook_create: "crea webhook" + web_hook_update: "aggiorna webhook" + web_hook_destroy: "distruggi webhook" + web_hook_deactivate: "disattiva webhook" + embeddable_host_create: "crea host incorporabile" + embeddable_host_update: "aggiorna host incorporabile" + embeddable_host_destroy: "distruggi host incorporabile" screened_emails: title: "Email Scansionate" description: "Quando qualcuno cerca di creare un nuovo account, verrando controllati i seguenti indirizzi email e la registrazione viene bloccata, o eseguita qualche altra azione." @@ -3222,6 +3490,7 @@ it: title: "Log Ricerca" term: "Termine" searches: "Ricerche" + click_through_rate: "CTR" types: all_search_types: "Tutti i tipi di ricerca" header: "Intestazione" @@ -3265,7 +3534,9 @@ it: last_emailed: "Ultima email inviata" not_found: "Spiacenti, questo nome utente non esiste nel sistema." id_not_found: "Spiacenti, nel nostro sistema non esiste questo id utente." + active: "Attivati" show_emails: "Mostra email" + hide_emails: "Nascondi email" nav: new: "Nuovi" active: "Attivi" @@ -3317,10 +3588,17 @@ it: suspended_until: "(fino %{until})" cant_suspend: "Questo utente non può essere sospeso." delete_all_posts: "Cancella tutti i messaggi" + delete_posts_progress: "Cancellazione messaggi..." + delete_posts_failed: "C'è stato un problema con la cancellazione dei messaggi." + penalty_post_actions: "Cosa vorresti fare con il messaggio associato?" penalty_post_delete: "Elimina il messaggio" + penalty_post_delete_replies: "Elimina il messaggio e tutte le sue risposte" penalty_post_edit: "Modifica il messaggio" penalty_post_none: "Non fare nulla" penalty_count: "Conteggio Penalità" + clear_penalty_history: + title: "Cancella la storia delle penalizzazioni" + description: "Utenti con penalizzazioni non possono accedere al Livello Esperienza 3" delete_all_posts_confirm_MF: "Stai per cancellare {POSTS, plural, one {1 messaggio} other {# messaggi}} e {TOPICS, plural, one {1 argomento} other {# argomenti}}. Sei sicuro?" silence: "Silenzio" unsilence: "Non in silenzio" @@ -3343,6 +3621,9 @@ it: grant_moderation: "Assegna diritti di moderazione" unsuspend: "Riabilita" suspend: "Sospendi" + show_flags_received: "Mostra le Segnalazioni ricevute" + flags_received_by: "Segnalazione ricevuta da %{username}" + flags_received_none: "Questo utente non ha ricevuto segnalazioni." reputation: Reputazione permissions: Permessi activity: Attività @@ -3436,6 +3717,8 @@ it: likes_received: "Mi piace - Ricevuti" likes_received_days: "\"Mi piace\" Ricevuti: singoli giorni" likes_received_users: "\"Mi piace\" Ricevuti: singoli utenti" + suspended: "Sospeso (da sempre)" + silenced: "Silenziati (da sempre)" qualifies: "Requisiti soddisfatti per il livello di esperienza 3." does_not_qualify: "Mancano i requisiti per il livello esperienza 3." will_be_promoted: "Verrà presto promosso." @@ -3494,6 +3777,7 @@ it: go_back: "Torna alla Ricerca" recommended: "Consigliamo di personalizzare il seguente testo per tue adattarlo alle necessità:" show_overriden: "Mostra solo le opzioni sovrascritte" + more_than_50_results: "Ci sono più di 50 risultati. Per favore, raffina la tua ricerca." settings: show_overriden: "Mostra solo i sovrascritti" reset: "reimposta" @@ -3505,6 +3789,7 @@ it: clear_filter: "Pulisci" add_url: "aggiungi URL" add_host: "aggiungi host" + add_group: "aggiungi gruppo" uploaded_image_list: label: "Modifica lista" empty: "Ancora non ci sono immagini. Per favore, caricane una." @@ -3516,6 +3801,7 @@ it: categories: all_results: "Tutti" required: "Obbligatorie" + branding: "Branding" basic: "Impostazioni Base" users: "Utenti" posting: "Pubblicazione" @@ -3541,6 +3827,8 @@ it: search: "Cerca" groups: "Gruppi" dashboard: "Cruscotto" + secret_list: + invalid_input: "I campi di input non possono essere vuoti o contenere il carattere barra verticale." badges: title: Distintivi new_badge: Nuovo Distintivo @@ -3578,6 +3866,7 @@ it: icon: Icona image: Immagine icon_help: "Usa una classe Font Awesome" + image_help: "Inserisci l'URL dell'immagine (sovrascrive il campo icona, se entrambi sono impostati)" query: Query Distintivo (SQL) target_posts: Interroga i messaggi destinazione auto_revoke: Avvia l'istruzione di revoca giornalmente @@ -3609,6 +3898,10 @@ it: with_post: "%{username} per il messaggio in %{link}" with_post_time: "%{username} per messaggio in %{link} in data %{time}" with_time: "%{username} in data %{time}" + badge_intro: + title: "Seleziona un Distintivo esistente o creane uno nuovo per iniziare" + what_are_badges_title: "Cosa sono i Distintivi?" + badge_query_examples_title: "Esempi di Query Distintivo" emoji: title: "Emoji" help: "Aggiungi nuovi emoji da mettere a disposizione per tutti. (Suggerimento: trascina e rilascia più file in una volta sola)" @@ -3646,6 +3939,7 @@ it: save: "Salva Impostazioni Inclusione" permalink: title: "Collegamenti permanenti" + description: "Per favore, nota che questo si applica solo alle fonti esterne, i link inviati sul tuo forum non saranno rediretti." url: "URL" topic_id: "ID dell'argomento" topic_title: "Argomento" @@ -3662,8 +3956,10 @@ it: reseed: action: label: "Sostituisci testo..." + title: "Sostituisci il testo delle Categorie e degli Argomenti con le traduzioni" modal: title: "Sostituisci testo" + subtitle: "Sostituire il testo di Categorie e Argomenti generato automaticamente con le traduzioni più recenti" categories: "Categorie" topics: "Argomenti" replace: "Sostituisci" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 842e415268..3dd093bbd9 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -279,6 +279,7 @@ ja: search: "タイトルでメッセージを検索:" placeholder: "メッセージのタイトルをここに入力してください" review: + in_reply_to: "こちらへの回答" delete: "削除する" settings: saved: "保存しました" @@ -307,6 +308,8 @@ ja: minimum_score: "最小スコア:" refresh: "更新" category: "カテゴリ" + orders: + created_at: "作られた" scores: score: "スコア" date: "日付" @@ -1300,6 +1303,8 @@ ja: posted: '{{username}} が投稿しました "{{topic}}" - {{site_title}}' linked: '"{{topic}}"にあるあなたの投稿に{{username}}がリンクしました - {{site_title}}' confirm_body: "成功!通知を可能にしました!" + titles: + watching_first_post: "新着トピック" upload_selector: title: "画像のアップロード" title_with_attachments: "画像/ファイルをアップロード" @@ -1455,6 +1460,8 @@ ja: edit_message: help: "メッセージの最初の投稿を編集" title: "メッセージを編集" + defer: + title: "取り下げる" list: "トピック" new: "新着トピック" unread: "未読" @@ -2254,6 +2261,10 @@ ja: admin: title: "Discourseの管理者" moderator: "モデレータ" + tags: + remove_muted_tags_from_latest: + always: "常に含める" + never: "追跡しない" dashboard: title: "ダッシュボード" version: "Version" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 30e66c2d9a..bf3fe66820 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -281,6 +281,7 @@ ko: search: "제목으로 메시지 검색:" placeholder: "여기에 메시지 제목을 입력하십시오" review: + in_reply_to: "답글" delete: "삭제" settings: save_changes: "변경사항 저장" @@ -1281,6 +1282,9 @@ ko: linked: '{{username}}님이 "{{topic}}" - {{site_title}}에 내 글을 링크했습니다' confirm_title: "알림 활성 - %{site_title}" confirm_body: "완료! 알림이 활성화되었습니다." + titles: + watching_first_post: "새로운 주제" + post_approved: "게시물 승인됨" upload_selector: title: "이미지 추가하기" title_with_attachments: "이미지 또는 파일 추가하기" @@ -1427,6 +1431,8 @@ ko: edit_message: help: "메시지의 첫 번째 게시물 수정" title: "메시지 수정" + defer: + title: "연기" list: "주제 목록" new: "새로운 주제" unread: "읽지 않은" @@ -2300,6 +2306,10 @@ ko: admin: title: "Discourse 관리자" moderator: "운영자" + tags: + remove_muted_tags_from_latest: + always: "항상 알림 받기" + never: "하지않음" dashboard: title: "대시보드" version: "버전" diff --git a/config/locales/client.lt.yml b/config/locales/client.lt.yml index 49c42eebff..f4bbda9932 100644 --- a/config/locales/client.lt.yml +++ b/config/locales/client.lt.yml @@ -1282,6 +1282,8 @@ lt: linked: '{{username}} panaudojo tavo įrašą "{{topic}}" - {{site_title}}' confirm_title: "Pranešimai įgalinti - %{site_title}" confirm_body: "Pavyko! Pranešimai buvo įgalinti." + titles: + watching_first_post: "nauja tema" upload_selector: title: "Pridėti nuotrauką" title_with_attachments: "Pridėti nautrauką arba dokumentą" @@ -1429,6 +1431,8 @@ lt: help: "Perkelti žinutes atgal į Pranešimų dėžutę" edit_message: title: "Redaguoti žinutę" + defer: + title: "Atidėti" list: "Temos" new: "nauja tema" unread: "Neperskaitytos" @@ -2297,6 +2301,10 @@ lt: admin: title: "Forumo administratorius" moderator: "Moderatorius" + tags: + remove_muted_tags_from_latest: + always: "visada" + never: "niekada" dashboard: title: "Apžvalga" version: "Versija" diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml index d33ad7f313..2c8720c214 100644 --- a/config/locales/client.lv.yml +++ b/config/locales/client.lv.yml @@ -1125,6 +1125,8 @@ lv: replied: '{{username}} atbildēja jums "{{topic}}" - {{site_title}}' posted: '{{username}} ierakstīja "{{topic}}" - {{site_title}}' linked: '{{username}} ievietoja saiti uz jūsu ierakstu no "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "Jauna tēma" upload_selector: title: "Pievienot attēlu" title_with_attachments: "Pievienot attēlu vai failu" @@ -1265,6 +1267,8 @@ lv: move_to_inbox: title: "Pārvietot uz iesūtni" help: "Atgriezt ziņu uz iesūtni " + defer: + title: "Nepiekrist" list: "Tēmas" new: "Jauna tēma" unread: "nelasīti" @@ -2148,6 +2152,10 @@ lv: admin: title: "Discourse administrators" moderator: "Moderators" + tags: + remove_muted_tags_from_latest: + always: "vienmēr" + never: "nekad" dashboard: title: "Administrācijas panelis" version: "Versija" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index 2bc00bce64..6ffe69eecc 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -279,6 +279,7 @@ nb_NO: title: placeholder: "skriv tittelen på emnet her" review: + in_reply_to: "i svar til" delete: "Slett" settings: save_changes: "Lagre endringer" @@ -1328,6 +1329,9 @@ nb_NO: linked: '{{username}} lenket til innlegget ditt i "{{topic}}" - {{site_title}}' confirm_title: "Varslinger aktivert - %{site_title}" confirm_body: "Suksess! Varslinger er nå aktivert." + titles: + watching_first_post: "nytt emne" + post_approved: "innlegg godkjent" upload_selector: title: "Legg til bilde" title_with_attachments: "Legg til et bilde eller en fil" @@ -1491,6 +1495,8 @@ nb_NO: edit_message: help: "Rediger første innlegg i meldingen" title: "Rediger melding" + defer: + title: "Utsett" list: "Emner" new: "nytt emne" unread: "ulest" @@ -2437,6 +2443,10 @@ nb_NO: admin: title: "Discourse-admin" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "alltid" + never: "aldri" reports: title: "Liste over tilgjengelige rapporter" dashboard: diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 35158c4e0e..a85f1191d6 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -153,6 +153,7 @@ nl: bootstrap_mode_disabled: "De bootstrapmodus wordt binnen 24 uur uitgeschakeld." themes: default_description: "Standaard" + broken_theme_alert: "Uw website werkt mogelijk niet, omdat het thema / onderdeel %{theme} fouten bevat. Schakel het uit via %{path}." s3: regions: ap_northeast_1: "Azië Pacifisch (Tokio)" @@ -307,6 +308,8 @@ nl: search: "Zoeken naar een bericht op titel:" placeholder: "typ hier de titel van het bericht" review: + order_by: "Sorteren op" + in_reply_to: "in reactie op" claim_help: optional: "U kunt dit item opeisen om te voorkomen dat anderen het beoordelen." required: "U moet items opeisen voordat u ze kunt beoordelen." @@ -382,7 +385,13 @@ nl: refresh: "Vernieuwen" status: "Status" category: "Categorie" + orders: + priority: "Prioriteit" + priority_asc: "Prioriteit (omgekeerd)" + created_at: "Lid sinds" + created_at_asc: "Gemaakt op (omgekeerd)" priority: + title: "Minimale prioriteit" low: "Laag" medium: "Gemiddeld" high: "Hoog" @@ -415,6 +424,8 @@ nl: reviewable_flagged_post: title: "Gemarkeerd bericht" flagged_by: "Gemarkeerd door" + reviewable_queued_topic: + title: "Topic in wachtrij" reviewable_queued_post: title: "Bericht in wachtrij" reviewable_user: @@ -727,6 +738,7 @@ nl: allow_private_messages: "Andere gebruikers mogen mij persoonlijke berichten sturen" external_links_in_new_tab: "Alle externe koppelingen openen in een nieuw tabblad" enable_quoting: "Antwoord-met-citaat voor gemarkeerde tekst inschakelen" + enable_defer: "Negeren voor markeren van topics als ongelezen inschakelen" change: "wijzigen" moderator: "{{user}} is een moderator" admin: "{{user}} is een beheerder" @@ -766,6 +778,7 @@ nl: watched_first_post_tags_instructions: "U ontvangt een melding bij het eerste bericht in elk nieuw topic met deze tags." muted_categories: "Genegeerd" muted_categories_instructions: "U ontvangt geen enkele melding over nieuwe topics en berichten in deze categorieën, en ze verschijnen niet op de pagina's Categorieën of Nieuwste." + muted_categories_instructions_dont_hide: "U ontvangt geen enkele melding over nieuwe topics in deze categorieën." no_category_access: "Als moderator hebt u beperkte toegang tot categorieën, opslaan is uitgeschakeld." delete_account: "Mijn account verwijderen" delete_account_confirm: "Weet u zeker dat u uw account definitief wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt!" @@ -1161,6 +1174,11 @@ nl: too_few_topics_and_posts_notice: "Laten we deze discussie starten! Er zijn op dit moment %{currentTopics} / %{requiredTopics} topics en %{currentPosts} / %{requiredPosts} berichten. Nieuwe bezoekers hebben wat conversaties nodig om te lezen en op te reageren." too_few_topics_notice: "Laten we deze discussie starten! Er zijn op dit moment %{currentTopics} / %{requiredTopics} topics. Nieuwe bezoekers hebben wat conversaties nodig om te lezen en op te reageren." too_few_posts_notice: "Laten we deze discussie starten! Er zijn op dit moment %{currentPosts} / %{requiredPosts} berichten. Nieuwe bezoekers hebben wat conversaties nodig om te lezen en op te reageren." + logs_error_rate_notice: + reached_hour_MF: "{relativeAge}{rate, plural, one {# fout/uur} other {# fouten/uur}} heeft de limiet voor de website-instelling van {limit, plural, one {# fout/uur} other {# fouten/uur}} bereikt." + reached_minute_MF: "{relativeAge}{rate, plural, one {# fout/minuut} other {# fouten/minuut}} heeft de limiet voor de website-instelling van {limit, plural, one {# fout/minuut} other {# fouten/minuut}} bereikt." + exceeded_hour_MF: "{relativeAge}{rate, plural, one {# fout/uur} other {# fouten/uur}} heeft de limiet voor de website-instelling van {limit, plural, one {# fout/uur} other {# fouten/uur}} bereikt." + exceeded_minute_MF: "{relativeAge}{rate, plural, one {# fout/minuut} other {# fouten/minuut}} heeft de limiet voor de website-instelling van {limit, plural, one {# fout/minuut} other {# fouten/minuut}} bereikt." learn_more: "meer info..." all_time: "totaal" all_time_desc: "aantal aangemaakte topics" @@ -1216,6 +1234,7 @@ nl: trust_level: "Vertrouwensniveau" search_hint: "gebruikersnaam, e-mailadres of IP-adres" create_account: + disclaimer: "Door te registreren gaat u akkoord met het privacybeleid en de servicevoorwaarden." title: "Nieuwe account maken" failed: "Er is iets misgegaan; mogelijk is het e-mailadres al geregistreerd. Probeer de koppeling 'Wachtwoord vergeten'." forgot_password: @@ -1552,6 +1571,26 @@ nl: confirm_title: "Meldingen ingeschakeld - %{site_title}" confirm_body: "Gelukt! Meldingen zijn ingeschakeld." custom: "Melding van {{username}} op %{site_title}" + titles: + mentioned: "genoemd" + replied: "nieuw antwoord" + quoted: "geciteerd" + edited: "bewerkt" + liked: "nieuwe like" + private_message: "nieuw privébericht" + invited_to_private_message: "uitgenodigd voor privébericht" + invitee_accepted: "uitnodiging geaccepteerd" + posted: "nieuw bericht" + moved_post: "bericht verplaatst" + linked: "gekoppeld" + granted_badge: "badge toegekend" + invited_to_topic: "uitgenodigd voor topic" + group_mentioned: "groep genoemd" + group_message_summary: "nieuwe groepsberichten" + watching_first_post: "nieuw topic" + topic_reminder: "topic-herinnering" + liked_consolidated: "nieuwe likes" + post_approved: "bericht goedgekeurd" upload_selector: title: "Een afbeelding toevoegen" title_with_attachments: "Een afbeelding of bestand toevoegen" @@ -1715,6 +1754,9 @@ nl: edit_message: help: "Het eerste bericht van het bericht bewerken" title: "Bericht bewerken" + defer: + help: "Markeren als ongelezen" + title: "Negeren" list: "Topics" new: "nieuw topic" unread: "ongelezen" @@ -2744,6 +2786,11 @@ nl: admin: title: "Discourse-beheer" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "altijd" + only_muted: "bij enkel of gebruik met andere genegeerde tags" + never: "nooit" reports: title: "Lijst van beschikbare rapporten" dashboard: @@ -2818,6 +2865,7 @@ nl: groups: "Alle groepen" disabled: "Dit rapport is uitgeschakeld" totals_for_sample: "Totalen voor steekproef" + average_for_sample: "Gemiddelde voor steekproef" total: "Totaal sinds begin" no_data: "Geen gegevens om weer te geven." trending_search: @@ -3602,6 +3650,7 @@ nl: delete_posts_failed: "Er is een probleem opgetreden bij het verwijderen van de berichten." penalty_post_actions: "Wat wilt u met het gekoppelde bericht doen?" penalty_post_delete: "Het bericht verwijderen" + penalty_post_delete_replies: "Het bericht + alle antwoorden verwijderen" penalty_post_edit: "Het bericht bewerken" penalty_post_none: "Niets doen" penalty_count: "Aantal minpunten" diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index 9e6c346d5e..e38531047d 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -351,6 +351,7 @@ pl_PL: title: placeholder: "wpisz tytuł wiadomości" review: + in_reply_to: "w odpowiedzi na" delete: "Usuń" settings: saved: "Zapisano" @@ -387,6 +388,8 @@ pl_PL: title: "Typ" refresh: "Odśwież" category: "Kategoria" + orders: + created_at: "Utworzony" scores: date: "Data" type: "Typ" @@ -1418,6 +1421,8 @@ pl_PL: replied: '{{username}} odpowiada na twój wpis w "{{topic}}" - {{site_title}}' posted: '{{username}} pisze w "{{topic}}" - {{site_title}}' linked: '{{username}} linkuje do twojego wpisu z "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "nowy temat" upload_selector: title: "Dodaj obraz" title_with_attachments: "Dodaj obraz lub plik" @@ -1576,6 +1581,8 @@ pl_PL: help: "Przenieś wiadomość z powrotem do skrzynki odbiorczej" edit_message: title: "Edytuj wiadomość" + defer: + title: "Zignoruj" list: "Tematy" new: "nowy temat" unread: "nieprzeczytane" @@ -2547,6 +2554,10 @@ pl_PL: admin: title: "Administrator Discourse" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "zawsze" + never: "nigdy" dashboard: title: "Raporty" version: "Wersja" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 0ddc712ed1..d6876b4725 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -214,6 +214,8 @@ pt: every_hour: "a cada hora" daily: "diário" weekly: "semanal" + every_month: "cada mês" + every_six_months: "a cada seis meses" max_of_count: "máximo de {{count}}" alternation: "ou" character_count: @@ -304,6 +306,12 @@ pt: search: "Pesquisar por uma Mensagem por título:" placeholder: "escreva o título da mensagem aqui" review: + in_reply_to: "Em resposta a" + claim_help: + optional: "Pode reivindicar este item para prevenir que outras pessoas o revejam." + required: "Deve reivindicar items antes de as poder rever." + claim: + title: "reivindicar este tópico" delete: "Eliminar" settings: save_changes: "Guardar alterações" @@ -324,6 +332,8 @@ pt: title: "Tipo" refresh: "Atualizar" category: "Categoria" + orders: + created_at: "Criado a" scores: date: "Data" type: "Tipo" @@ -872,6 +882,8 @@ pt: every_hour: "de hora em hora" daily: "diariamente" weekly: "semanalmente" + every_month: "cada mês" + every_six_months: "a cada seis meses" email_level: title: "Enviem-me um email quando alguém me citar, responder a uma publicação minha, mencionar o meu @nome_de_utilizador ou me convidar para um tópico" always: "sempre" @@ -1430,6 +1442,8 @@ pt: watching_first_post: '{{username}} criou um novo tópico "{{topic}}" - {{site_title}}' confirm_title: "Notificações ativas - %{site_title}" confirm_body: "Sucesso! As notificações foram ativadas." + titles: + watching_first_post: "novo tópico" upload_selector: title: "Adicionar uma imagem" title_with_attachments: "Adicionar uma imagem ou um ficheiro" @@ -1593,6 +1607,8 @@ pt: edit_message: help: "Editar primeira publicação da mensagem" title: "Editar Mensagem" + defer: + title: "Diferir" list: "Tópicos" new: "novo tópico" unread: "não lido" @@ -2476,6 +2492,10 @@ pt: admin: title: "Administração Discourse" moderator: "Moderador" + tags: + remove_muted_tags_from_latest: + always: "sempre" + never: "nunca" dashboard: title: "Painel de Administração" version: "Versão" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index 8b66c161f2..17faacffb3 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -306,6 +306,7 @@ pt_BR: search: "Pesquisar por uma Mensagem pelo título:" placeholder: "digite o título da mensagem aqui" review: + in_reply_to: "Em resposta a" awaiting_approval: "Aguardando Aprovação" delete: "Excluir" settings: @@ -345,6 +346,8 @@ pt_BR: minimum_score: "Pontuação Mínima:" refresh: "Atualizar" category: "Categoria" + orders: + created_at: "Criado a" conversation: view_full: "visualizar conversa completa" scores: @@ -1463,6 +1466,9 @@ pt_BR: watching_first_post: '{{username}} criou um novo tópico "{{topic}}" - {{site_title}}' confirm_title: "Notificações habilitadas - %{site_title}" confirm_body: "Sucesso! Notificações foram habilitadas." + titles: + watching_first_post: "novo tópico" + post_approved: "publicação aprovada" upload_selector: title: "Adicionar uma imagem" title_with_attachments: "Adicionar uma imagem ou um arquivo" @@ -1626,6 +1632,8 @@ pt_BR: edit_message: help: "Editar primeira postagem da mensagem" title: "Editar Mensagem" + defer: + title: "Delegar" list: "Tópicos" new: "novo tópico" unread: "não lido" @@ -2622,6 +2630,10 @@ pt_BR: admin: title: "Discourse Admin" moderator: "Moderador" + tags: + remove_muted_tags_from_latest: + always: "sempre" + never: "nunca" reports: title: "Lista de relatórios disponíveis" dashboard: diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index a15a1aefb9..f919135d57 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -1282,6 +1282,8 @@ ro: replied: '{{username}} ți-a răspuns la "{{topic}}" - {{site_title}}' posted: '{{username}} a postat în "{{topic}}" - {{site_title}}' linked: '{{username}} a pus un link către postarea ta din "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "subiect nou" upload_selector: title: "Adaugă o imagine" title_with_attachments: "Adaugă o imagine sau un fișier" @@ -1431,6 +1433,8 @@ ro: move_to_inbox: title: "Mută în Primite" help: "Mută mesajul în Primite" + defer: + title: "Amânare" list: "Subiecte" new: "subiect nou" unread: "necitit" @@ -2364,6 +2368,10 @@ ro: admin: title: "Admin Discourse" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "întotdeauna" + never: "niciodată" dashboard: title: "Panou de control" version: "Versiune" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 9fd7b23e7c..af82da64e3 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -358,6 +358,8 @@ ru: search: "Поиск в личных сообщениях по названию: " placeholder: "введите название сообщения" review: + order_by: "Сортировать по" + in_reply_to: "в ответе" claim_help: optional: "Вы можете запросить этот элемент, чтобы другие не могли его просмотреть." required: "Вы должны подать заявку до того, как сможете их просмотреть." @@ -447,6 +449,11 @@ ru: refresh: "Обновить" status: "Статус" category: "Категория" + orders: + priority: "Приоритет" + priority_asc: "Приоритет (обратный)" + created_at: "Создан в" + created_at_asc: "Созданные в (обратный)" priority: title: "Минимальный Приоритет:" low: "Низкий" @@ -481,6 +488,8 @@ ru: reviewable_flagged_post: title: "Отмеченный Пост" flagged_by: "Отмечено" + reviewable_queued_topic: + title: "Тема в Очереди" reviewable_queued_post: title: "Пост в очереди" reviewable_user: @@ -805,6 +814,7 @@ ru: allow_private_messages: "Разрешить другим пользователям отправлять мне личные сообщения" external_links_in_new_tab: "Открывать все внешние ссылки в новой вкладке" enable_quoting: "Позволить отвечать с цитированием выделенного текста" + enable_defer: "Включить отложить, чтобы пометить темы как непрочитанные" change: "изменить" moderator: "{{user}} — модератор" admin: "{{user}} — админ" @@ -844,6 +854,7 @@ ru: watched_first_post_tags_instructions: "Уведомлять только о первом сообщении в каждой новой теме с этими тегами." muted_categories: "Выключенные разделы" muted_categories_instructions: "Не уведомлять меня о новых темах в этих разделах и не показывать новые темы на странице «Непрочитанные»." + muted_categories_instructions_dont_hide: "Вы не будете уведомлены о новых темах в этих категориях." no_category_access: "Как модератор Вы ограничены в доступе к разделу, сохранения отклонены." delete_account: "Удалить мою учётную запись" delete_account_confirm: "Вы уверены, что хотите удалить свою учётную запись? Отменить удаление будет невозможно!" @@ -1674,6 +1685,26 @@ ru: 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: "связанный" + granted_badge: "награда получена" + invited_to_topic: "приглашен в тему" + group_mentioned: "упомянутая группа" + group_message_summary: "новые групповые сообщения" + watching_first_post: "новая тема" + topic_reminder: "напоминание о теме" + liked_consolidated: "новые симпатии" + post_approved: "сообщение утверждено" upload_selector: title: "Add an image" title_with_attachments: "Добавить изображение или файл" @@ -1843,6 +1874,9 @@ ru: edit_message: help: "Изменить первое сообщение" title: "Редактировать сообщение" + defer: + help: "Отметить как непрочитанное" + title: "Отложить" list: "Темы" new: "новая тема" unread: "непрочитанно" @@ -2972,6 +3006,11 @@ ru: admin: title: "Discourse Admin" moderator: "Модератор" + tags: + remove_muted_tags_from_latest: + always: "всегда" + only_muted: "при использовании самостоятельно или с другими приглушенными тегами" + never: "никогда" reports: title: "Список доступных отчетов" dashboard: @@ -3048,6 +3087,7 @@ ru: groups: "Все группы" disabled: "Этот отчет отключен" totals_for_sample: "Итоги по выборке" + average_for_sample: "Среднее для выборки" total: "Всего за все время" no_data: "Нет данных для отображения." trending_search: diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml index 8088498961..2178614c35 100644 --- a/config/locales/client.sk.yml +++ b/config/locales/client.sk.yml @@ -1288,6 +1288,8 @@ sk: replied: '{{username}} vám odpovedal v "{{topic}}" - {{site_title}}' posted: '{{username}} prispel v "{{topic}}" - {{site_title}}' linked: '{{username}} odkázal na váš príspevok z "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "nová téma" upload_selector: title: "Pridať obrázok" title_with_attachments: "Pridať obrázok alebo súbor" @@ -1423,6 +1425,8 @@ sk: move_to_inbox: title: "Presunúť do schránky" help: "Presunúť správu späť do schránky" + defer: + title: "Odložiť" list: "Témy" new: "nová téma" unread: "neprečítané" @@ -2265,6 +2269,10 @@ sk: admin: title: "Administrátor Discourse" moderator: "Moderátor" + tags: + remove_muted_tags_from_latest: + always: "vždy" + never: "nikdy" dashboard: title: "Ovládací panel" version: "Verzia" diff --git a/config/locales/client.sl.yml b/config/locales/client.sl.yml index 9c9be251f8..3f7a611de7 100644 --- a/config/locales/client.sl.yml +++ b/config/locales/client.sl.yml @@ -1253,7 +1253,7 @@ sl: sign_up: "Registracija" hide_session: "Spomni me jutri" hide_forever: "ne, hvala" - hidden_for_session: "V redu, vas ponovno vprašamo jutri. Vedno lahko uporabite gumb 'Prijava' da ustvarite nov račun." + hidden_for_session: "Hvala, ponovno vas vprašamo jutri. Vedno pa lahko uporabite gumb 'Prijava', da ustvarite nov račun." intro: "Pozdravljeni! Vidimo, da uživate v branju razprave, vendar se še niste registrirali kot uporabnik." value_prop: "Ko registrirate uporabniški račun, si lahko zapomnimo točno kaj ste že prebrali, tako da boste naslednjič lahko nadaljevali od tam, kjer ste končali. Lahko boste tudi prejemali obvestila, tukaj ali preko e-sporočila vsakič, ko vam nekdo odgovori. In lahko boste všečkali prispevke, ki so vam všeč. :heartpulse:" summary: @@ -1634,6 +1634,9 @@ sl: confirm_title: "Obvestila omogočena - %{site_title}" confirm_body: "Uspelo! Obvestila so bila omogočena." custom: "Obvestilo od {{username}} na %{site_title}" + titles: + watching_first_post: "nova tema" + post_approved: "prispevek odobren" upload_selector: title: "Dodaj sliko" title_with_attachments: "Dodaj sliko ali datoteko" @@ -2932,6 +2935,10 @@ sl: admin: title: "Discourse Admin" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "vedno" + never: "nikoli" reports: title: "Seznam poročil" dashboard: diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index 71429563de..d58d65904d 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -245,6 +245,7 @@ sq: title: placeholder: "shkruaj titullin e temës këtu" review: + in_reply_to: "në përgjigje të" delete: "Fshij" settings: save_changes: "Ruaj ndryshimet" @@ -1045,6 +1046,8 @@ sq: replied: '{{username}} ju u përgjigj në "{{topic}}" - {{site_title}}' posted: '{{username}} postoi në "{{topic}}" - {{site_title}}' linked: '{{username}} vendosi një lidhje për postimin tuaj nga "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "temë e re" upload_selector: title: "Shto një imazh" title_with_attachments: "Shto një imazh ose një skedar" @@ -1154,6 +1157,8 @@ sq: move_to_inbox: title: "Lëviz në Inbox" help: "Riktheje mesazhin në inbox" + defer: + title: "Shty" list: "Temat" new: "temë e re" unread: "palexuar" @@ -1927,6 +1932,10 @@ sq: admin: title: "Administrator" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "gjithmonë" + never: "asnjëherë" dashboard: title: "Paneli i kontrollit" version: "Versioni" diff --git a/config/locales/client.sr.yml b/config/locales/client.sr.yml index 4d43e32dfb..6c8c5cb330 100644 --- a/config/locales/client.sr.yml +++ b/config/locales/client.sr.yml @@ -922,6 +922,8 @@ sr: none: "Nemoguće je učitati notifikacije ovog trenutka." empty: "Nisu pronađene notifikacije" more: "pogledaj starije notifikacije" + titles: + watching_first_post: "nova tema" upload_selector: title: "Dodaj sliku" title_with_attachments: "Dodaj sliku ili datoteku" @@ -1000,6 +1002,8 @@ sr: create_long: "Otvori novu Temu" archive_message: title: "Arhiviraj" + defer: + title: "Odloži" list: "Teme" new: "nova tema" unread: "nepročitano" @@ -1577,6 +1581,10 @@ sr: admin: title: "Administrator" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "uvek" + never: "nikada" dashboard: title: "Komandna Tabla" version: "Verzija" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 434a03406e..76f6d77f6c 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -265,6 +265,7 @@ sv: title: placeholder: "skriv ämnets rubrik här" review: + in_reply_to: "som svar till" delete: "Radera" settings: save_changes: "Spara ändringar" @@ -285,6 +286,8 @@ sv: title: "Typ" refresh: "Uppdatera" category: "Kategori" + orders: + created_at: "Skapad den" scores: type: "Typ" statuses: @@ -1098,6 +1101,8 @@ sv: replied: '{{username}} svarade dig i "{{topic}}" - {{site_title}}' posted: '{{username}} skrev i "{{topic}}" - {{site_title}}' linked: '{{username}} länkade till ett inlägg du gjort från "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "nytt ämne" upload_selector: title: "Lägg till en bild" title_with_attachments: "Lägg till en bild eller en fil" @@ -1234,6 +1239,8 @@ sv: move_to_inbox: title: "Flytta till inkorgen" help: "Flytta tillbaka meddelandet till inkorgen" + defer: + title: "Skjut upp" list: "Ämnen" new: "nytt ämne" unread: "oläst" @@ -2065,6 +2072,10 @@ sv: admin: title: "Discourse Admin" moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "alltid" + never: "aldrig" dashboard: title: "Översiktspanel" version: "Version" diff --git a/config/locales/client.sw.yml b/config/locales/client.sw.yml index 7b58d695ff..19d0d577b3 100644 --- a/config/locales/client.sw.yml +++ b/config/locales/client.sw.yml @@ -1312,6 +1312,9 @@ sw: linked: '{{jina la mtumiaji}} ametengeneza kiungo kutoka kwenye "{{mada}}" - {{jina la_tovuti}}' confirm_title: "Taarifa mubashara zimewezeshwa - %{site_title}" confirm_body: "Taarifa mubashara zimewezeshwa kikamilifu" + titles: + watching_first_post: "mada mpya" + post_approved: "Chapisho Limepitishwa" upload_selector: title: "Ongeza picha au faili" title_with_attachments: "Ongeza picha au faili" @@ -2306,6 +2309,10 @@ sw: admin: title: "Kiongozi wa Discourse" moderator: "Msimamizi" + tags: + remove_muted_tags_from_latest: + always: "mara kwa mara" + never: "kamwe" reports: title: "Orodha ya ripoti zilizopo" dashboard: diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index 858eb08c5b..86d157bf77 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -732,6 +732,8 @@ te: notifications: none: "ఈ సమయంలో ప్రకటనలు చూపలేకున్నాము." more: "పాత ప్రకటనలు చూడు" + titles: + watching_first_post: "కొత్త విషయం" upload_selector: title: "ఒక బొమ్మ కలుపు" title_with_attachments: "ఒక బొమ్మ లేదా దస్త్రం కలుపు" @@ -795,6 +797,8 @@ te: topic: create: "కొత్త విషయం" create_long: "కొత్త విషయం సృష్టించు" + defer: + title: "వాయిదావేయి" list: "విషయాలు" new: "కొత్త విషయం" unread: "చదవని" @@ -1314,6 +1318,10 @@ te: admin: title: "డిస్కోర్సు అధికారి" moderator: "నిర్వాహకుడు" + tags: + remove_muted_tags_from_latest: + always: "ఎల్లప్పుడూ" + never: "ఎప్పటికీ వద్దు" dashboard: title: "రంగస్థలం" version: "రూపాంతరం" diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml index 51a0ea5bc6..d1f0468ec4 100644 --- a/config/locales/client.th.yml +++ b/config/locales/client.th.yml @@ -969,6 +969,8 @@ th: replied: '{{username}} ตอบคุณใน "{{topic}}" - {{site_title}}' posted: '{{username}} โพสท์ใน "{{topic}}" - {{site_title}}' linked: '{{username}} ลิงค์โพสของคุณจาก "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "สร้างกระทู้" upload_selector: title: "เพิ่มรูปภาพ" title_with_attachments: "เพิ่มรูปภาพหรือไฟล์" @@ -1514,6 +1516,10 @@ th: admin: title: "ผู้ดูแลระบบดิสคอส" moderator: "ผู้ดูแล" + tags: + remove_muted_tags_from_latest: + always: "เสมอ" + never: "ไม่เลย" dashboard: title: "แดชบอร์ด" version: "เวอร์ชั่น" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 20c61cd39a..8f3b76fa55 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -1335,6 +1335,9 @@ tr_TR: linked: '{{username}}, "{{topic}}" başlıklı konudaki gönderinize bağlantı yaptı - {{site_title}}' confirm_title: "Bildirimler etkin - %{site_title}" confirm_body: "Başarılı! Bildirimler etkinleştirildi." + titles: + watching_first_post: "yeni konu" + post_approved: "gönderi onaylandı" upload_selector: title: "Resim ekle" title_with_attachments: "Resim ya da dosya ekle" @@ -1498,6 +1501,8 @@ tr_TR: edit_message: help: "Mesajın ilk gönderisini düzenle" title: "Gönderiyi düzenle" + defer: + title: "Ertele" list: "Konular" new: "yeni konu" unread: "okunmamış" @@ -2414,6 +2419,10 @@ tr_TR: admin: title: "Yönetici Panel Dili" moderator: "Moderatör" + tags: + remove_muted_tags_from_latest: + always: "her zaman" + never: "asla" reports: title: "Mevcut raporlar listesi" dashboard: diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index fa5d9bae04..346b9467f3 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -177,6 +177,7 @@ uk: title: placeholder: "введіть назву теми" review: + in_reply_to: "у відповідь на" delete: "Видалити" settings: save_changes: "Зберегти зміни" @@ -745,6 +746,8 @@ uk: label: "Нова тема" notifications: more: "переглянути старіші сповіщення" + titles: + watching_first_post: "нова тема" upload_selector: title: "Додати зображення" title_with_attachments: "Додати зображення або файл" @@ -814,6 +817,8 @@ uk: title: "Архів" move_to_inbox: title: "Перемістити у Вхідні" + defer: + title: "Відкласти" list: "Теми" new: "нова тема" unread: "непрочитані" @@ -1238,6 +1243,10 @@ uk: admin: title: "Адміністратор Discourse" moderator: "Модератор" + tags: + remove_muted_tags_from_latest: + always: "завжди" + never: "ніколи" dashboard: title: "Майстерня" version: "Версія" diff --git a/config/locales/client.ur.yml b/config/locales/client.ur.yml index 07a55ac3a6..c5be10c8af 100644 --- a/config/locales/client.ur.yml +++ b/config/locales/client.ur.yml @@ -153,6 +153,7 @@ ur: bootstrap_mode_disabled: "بُوٹسٹرَیپ مَوڈ 24 گھنٹوں کے اندر غیر فعال کر دیا جائے گا۔" themes: default_description: "ڈِیفالٹ" + broken_theme_alert: "تِھیم/کمپنَینٹ %{theme} میں ایسی غلطیاں ہیں جو آپ کی سائٹ کو صحیح طریقے سے کام کرنے سے روک سکتی ہیں! آپ اِسے %{path} پر غیر فعال کریں۔" s3: regions: ap_northeast_1: "ایشیا پیسفک (ٹوکیو)" @@ -307,6 +308,8 @@ ur: search: "بزریعہ عنوان پیغام کو تلاش کریں:" placeholder: "یہاں پیغام کا عنوان لکھیں" review: + order_by: "کے حساب سے آرڈر" + in_reply_to: "کے جواب میں" claim_help: optional: "آپ اس چیز کو کََلیم کرسکتے ہیں کہ دوسروں کو اِسکا جائزہ لینے سے روکا جا سکے۔" required: "اشیاء کا جائزہ لینے سے پہلے آپ کا اُن کو کََلیم کرنا ضروری ہے۔" @@ -382,7 +385,13 @@ ur: refresh: "رِیفریش" status: "سٹیٹس" category: "زمرہ" + orders: + priority: "ترجیح" + priority_asc: "ترجیح (ریوَرس)" + created_at: "کو بنایا گیا" + created_at_asc: "کو بنایا گیا (ریوَرس)" priority: + title: "کم از کم ترجیح" low: "کم" medium: "درمیانی" high: "زیادہ" @@ -415,6 +424,8 @@ ur: reviewable_flagged_post: title: "فلَیگ کردہ پوسٹ" flagged_by: "کی طرف سے فلَیگ کردہ" + reviewable_queued_topic: + title: "قطار شدہ ٹاپک" reviewable_queued_post: title: "قطار شدہ پوسٹ" reviewable_user: @@ -727,6 +738,7 @@ ur: allow_private_messages: "دوسرے صارفین کو مجھے ذاتی پیغامات بھیجنے کی اجازت دیں" external_links_in_new_tab: "تمام بیرونی ویب سائٹ کے لنکس ایک نئے ٹیب میں کھولیں" enable_quoting: "روشنی ڈالے گئے ٹَیکسٹ کے لئے اقتباسی جواب فعال کریں" + enable_defer: "ٹاپکس کو بغیر پڑھا نشان زد کرنے کیلئے ملتوی فعال کریں" change: "بدلیں" moderator: "{{user}} ایک ماڈریٹر ہے" admin: "{{user}} ایک ایڈمِن ہے" @@ -766,6 +778,7 @@ ur: watched_first_post_tags_instructions: "آپ کو اِن ٹیگ والے ہر نئے ٹاپک کی پہلی پوسٹ کے بارے میں مطلع کیا جائے گا۔" muted_categories: "خاموش کِیا ہوا" muted_categories_instructions: "آپ کو اِن زمرہ جات میں موجود نئے ٹاپکس کی کسی بھی چیز کے بارے میں مطلع نہیں کیا جائے گا، اور یہ زمرہ جات یا تازہ ترین صفحات پر نظر نہیں آئیں گے۔" + muted_categories_instructions_dont_hide: "آپ کو اِن زمرہ جات میں نئے ٹاپک کی کسی بھی چیز کے بارے میں مطلع نہیں کیا جائے گا۔" no_category_access: "ایک ماڈریٹر کے طور پر آپ کو زمرہ پر محدود رسائی حاصل ہے، محفوظ کرنا غیر فعال ہے۔" delete_account: "میرا اکاؤنٹ حذف کریں" delete_account_confirm: "کیا آپ واقعی مستقل طور پر اپنا اکاؤنٹ حذف کرنا چاہتے ہیں؟ اس عمل کو کالعدم نہیں کیا جا سکتا!" @@ -1553,6 +1566,26 @@ ur: 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: "لنک کردہ" + granted_badge: "بَیج عطا کیا گیا" + invited_to_topic: "ٹاپک کیلئے دعوت دی گئی" + group_mentioned: "گروپ کا ذکر کیا گیا" + group_message_summary: "نئے گروپ پیغامات" + watching_first_post: "نیا ٹاپک" + topic_reminder: "ٹاپک یاد دہانی" + liked_consolidated: "نئے لائیکس" + post_approved: "منظورشدہ پوسٹ" upload_selector: title: "ایک تصویر شامل کریں" title_with_attachments: "ایک تصویر یا ایک فائل شامل کریں" @@ -1716,6 +1749,9 @@ ur: edit_message: help: "پیغام کی پہلی اشاعت میں ترمیم کریں" title: "پیغام میں ترمیم کریں" + defer: + help: "بغیر پڑھا نشان زد کریں" + title: "ملتوی کریں" list: "ٹاپک" new: "نیا ٹاپک" unread: "بغیر پڑھے" @@ -2745,6 +2781,11 @@ ur: admin: title: "ڈِسکورس ایڈمن" moderator: "ماڈریٹر" + tags: + remove_muted_tags_from_latest: + always: "ہمیشہ" + only_muted: "جب اکیلے یا دوسرے خاموش کردہ ٹیگز کے ساتھ استعمال ہو" + never: "کبھی نہیں " reports: title: "دستیاب رپورٹوں کی فہرست" dashboard: @@ -2819,6 +2860,7 @@ ur: groups: "تمام گروپس" disabled: "یہ رپورٹ غیر فعال ہے" totals_for_sample: "نمونہ کیلئے کل" + average_for_sample: "نمونہ کیلئے اوسط" total: "تمام وقت کے کل" no_data: "ظاہر کرنے کیلئے کوئی ڈیٹا نہیں۔" trending_search: @@ -3602,6 +3644,7 @@ ur: delete_posts_failed: "پوسٹس حذف کرنے میں ایک مسئلہ پیش آیا۔" penalty_post_actions: "آپ اِس مُنسلِک پوسٹ کے ساتھ کیا کرنا چاہیں گے؟" penalty_post_delete: "پوسٹ حذف کریں" + penalty_post_delete_replies: "پوسٹ حذف کریں + جوابات کو بھی" penalty_post_edit: " پوسٹ ترمیم کریں" penalty_post_none: "کچھ نہ کریں" penalty_count: "سزا شمار" diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml index 5463623ee4..e01a020de8 100644 --- a/config/locales/client.vi.yml +++ b/config/locales/client.vi.yml @@ -259,6 +259,8 @@ vi: title: "Loại" refresh: "Tải lại" category: "Danh mục" + orders: + created_at: "Được tạo tại" scores: type: "Loại" statuses: @@ -1069,6 +1071,8 @@ vi: replied: '{{username}} trả lời cho bạn trong "{{topic}}" - {{site_title}}' posted: '{{username}} gửi bài trong "{{topic}}" - {{site_title}}' linked: '{{username}} liên quan đến bài viết của bạn từ "{{topic}}" - {{site_title}}' + titles: + watching_first_post: "chủ đề mới" upload_selector: title: "Thêm một ảnh" title_with_attachments: "Thêm một ảnh hoặc tệp tin" @@ -1192,6 +1196,8 @@ vi: move_to_inbox: title: "Chuyển sang hộp thư" help: "Chuyển tin nhắn trở lại hộp thư" + defer: + title: "Hoãn" list: "Chủ đề" new: "chủ đề mới" unread: "chưa đọc" @@ -1931,6 +1937,10 @@ vi: admin: title: "Quản trị Diễn đàn" moderator: "Điều hành" + tags: + remove_muted_tags_from_latest: + always: "luôn luôn" + never: "không bao giờ" dashboard: title: "Bảng điều khiển" version: "Phiên bản" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index aa20af8b75..b254b4f5f4 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -133,6 +133,7 @@ zh_CN: bootstrap_mode_disabled: "初始化模式将会在24小时后关闭。" themes: default_description: "默认" + broken_theme_alert: "你的网站可能无法正常运行,因为主题/组件%{theme}有错误。 在%{path}禁用它。" s3: regions: ap_northeast_1: "亚太地区(Tokyo)" @@ -152,6 +153,7 @@ zh_CN: us_east_1: "美国东部(N. Virginia)" us_east_2: "美国东部(俄亥俄州)" us_gov_east_1: "AWS政府专用(US-East)" + us_gov_west_1: "AWS GovCloud (US-West)" us_west_1: "美国西部(N. California)" us_west_2: "美国西部(Oregon)" edit: "编辑标题和分类" @@ -281,6 +283,8 @@ zh_CN: search: "按标题搜索消息:" placeholder: "在此输入消息标题" review: + order_by: "排序依据" + in_reply_to: "回复给" claim_help: optional: "你可以认领此条目以防止他人审核。" required: "在你审核之前你必须认领此条目。" @@ -296,6 +300,8 @@ zh_CN: saved: "已保存!" save_changes: "保存更改" title: "设置" + priorities: + title: "需审核优先级" moderation_history: "管理日志" view_all: "查看全部" grouped_by_topic: "依据主题分组" @@ -347,7 +353,13 @@ zh_CN: refresh: "刷新" status: "状态" category: "分类" + orders: + priority: "优先级" + priority_asc: "优先级(倒序)" + created_at: "创建时间" + created_at_asc: "创建时间(倒序)" priority: + title: "最低优先级" low: "低" medium: "中等" high: "高" @@ -380,6 +392,8 @@ zh_CN: reviewable_flagged_post: title: "被标记的帖子" flagged_by: "标记者" + reviewable_queued_topic: + title: "队列中到主题" reviewable_queued_post: title: "队列中的帖子" reviewable_user: @@ -686,6 +700,7 @@ zh_CN: allow_private_messages: "允许其他用户发送私信给我" external_links_in_new_tab: "在新标签页打开外部链接" enable_quoting: "在选择文字时显示引用回复按钮" + enable_defer: "启用延迟以标记未读主题" change: "修改" moderator: "{{user}}是版主" admin: "{{user}}是管理员" @@ -725,6 +740,7 @@ zh_CN: watched_first_post_tags_instructions: "在有了这些标签的每一个新主题,第一帖会通知你。" muted_categories: "静音" muted_categories_instructions: "你不会收到有关这些分类中新主题的任何通知,也不会出现在类别或最新页面上。" + muted_categories_instructions_dont_hide: "你不会收到这些组内关于新主题中的通知。" no_category_access: "无法保存,作为审核人你仅具有受限的 分类 访问权限" delete_account: "删除我的账户" delete_account_confirm: "你真的要永久删除自己的账户吗?删除之后无法恢复!" @@ -810,6 +826,8 @@ zh_CN: 使用我们支持的应用 (AndroidiOS) 扫描此二维码并输入您的授权码。 disable_description: "请输入来自 app 的验证码" show_key_description: "手动输入" + short_description: | + 使用一次性安全码保护你的帐户。 extended_description: | 双重身份验证除了你的密码之外还需要一次性令牌,从而为你的帐户增加了额外的安全性。 可以在AndroidiOS设备。 oauth_enabled_warning: "请注意,一旦你的帐户启用了双重身份验证,系统就会停用社交登录。" @@ -1108,6 +1126,11 @@ zh_CN: too_few_topics_and_posts_notice: "让我们开始讨论!目前有%{currentTopics} / %{requiredTopics}个主题和%{currentPosts} / %{requiredPosts}个帖子。新访客需要能够阅读和回复一些对话。" too_few_topics_notice: "让我们开始讨论!目前有%{currentTopics} / %{requiredTopics}个主题。新访客需要能够阅读和回复一些讨论。" too_few_posts_notice: "让我们开始讨论!目前有%{currentPosts} / %{requiredPosts}个帖子。新访客需要能够阅读和回复一些讨论。" + logs_error_rate_notice: + reached_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} other {# errors/hour}}达到了站点设置中的限制{limit, plural, one {# error/hour} other {# errors/hour}}。" + reached_minute_MF: "{relativeAge}1 – {rate, plural, one {# error/minute} other {# errors/minute}}已经达到站点设置限制 {limit, plural, one {# error/minute} other {# errors/minute}}。" + exceeded_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} other {# errors/hour}}超出了站点设置中的限制{limit, plural, one {# error/hour} other {# errors/hour}}。" + exceeded_minute_MF: "{relativeAge}1 – {rate, plural, one {# error/minute} other {# errors/minute}}已经超出站点设置限制 {limit, plural, one {# error/minute} other {# errors/minute}}。" learn_more: "了解更多…" all_time: "总量" all_time_desc: "创建的主题总量" @@ -1162,6 +1185,7 @@ zh_CN: trust_level: "用户级别" search_hint: "用户名、电子邮件或 IP 地址" create_account: + disclaimer: "注册即表示你同意隐私策略服务条款。" title: "创建新账户" failed: "出问题了,有可能这个邮箱已经被注册了。试试忘记密码链接?" forgot_password: @@ -1200,6 +1224,7 @@ zh_CN: email_placeholder: "电子邮件或者用户名" caps_lock_warning: "大写锁定开启" error: "未知错误" + cookies_error: "你的浏览器似乎禁用了Cookie。如果不先启用它们,你可能无法登录。" rate_limit: "请请稍后再重试" blank_username: "请输入你的邮件地址或用户名。" blank_username_or_password: "请输入你的邮件地址或用户名,以及密码。" @@ -1489,6 +1514,26 @@ zh_CN: 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: "链接" + granted_badge: "勋章授予" + invited_to_topic: "邀请到主题" + group_mentioned: "群组提及" + group_message_summary: "新建群组消息" + watching_first_post: "近期主题" + topic_reminder: "主题提醒" + liked_consolidated: "新的赞" + post_approved: "帖子已审批" upload_selector: title: "插入图片" title_with_attachments: "上传图片或文件" @@ -1649,6 +1694,9 @@ zh_CN: edit_message: help: "编辑消息中的第一帖" title: "编辑消息" + defer: + help: "标记为未读" + title: "推迟处理" list: "主题" new: "近期主题" unread: "未读" @@ -2628,6 +2676,11 @@ zh_CN: admin: title: "Discourse 管理员" moderator: "版主" + tags: + remove_muted_tags_from_latest: + always: "始终" + only_muted: "单独使用或与其他静音标签一起使用时" + never: "从不" reports: title: "可用报告列表" dashboard: @@ -2701,6 +2754,7 @@ zh_CN: groups: "所有群组" disabled: "此报告已禁用" totals_for_sample: "总计样本" + average_for_sample: "平均样本" total: "所有时间总计" no_data: "没有数据显示。" trending_search: @@ -3481,6 +3535,7 @@ zh_CN: delete_posts_failed: "删除帖子时出现问题。" penalty_post_actions: "你想对相关帖子做什么?" penalty_post_delete: "删除帖子" + penalty_post_delete_replies: "删除帖子+所有回复" penalty_post_edit: "编辑帖子" penalty_post_none: "什么都不做" penalty_count: "处罚计数" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 248083b0a1..34c5ff1bcd 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -281,6 +281,7 @@ zh_TW: search: "以標題尋找訊息" placeholder: "輸入訊息標題" review: + in_reply_to: "回覆給" claim_help: optional: "您可以聲明此項目以防止其他人審核。" required: "您必須先審核項目才能查看它們。" @@ -1490,6 +1491,9 @@ zh_TW: confirm_title: "通知已啟用-%{site_title}" confirm_body: "成功! 通知已啟用" custom: "新的通知由{{username}}在%{site_title}" + titles: + watching_first_post: "新話題" + post_approved: "貼文已通過審核" upload_selector: title: "加入圖片" title_with_attachments: "加入圖片或檔案" @@ -1650,6 +1654,8 @@ zh_TW: edit_message: help: "編輯這個訊息的第一篇貼文" title: "編輯訊息" + defer: + title: "延遲" list: "話題" new: "新話題" unread: "未讀" @@ -2623,6 +2629,10 @@ zh_TW: admin: title: "論壇管理員" moderator: "板主" + tags: + remove_muted_tags_from_latest: + always: "總是" + never: "永不" reports: title: "可見報告列表" dashboard: diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index a92c60eadd..71287b6b67 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -1810,6 +1810,8 @@ ar: title: "منظمات" colors: title: "الواجهة" + themes_further_reading: + title: "الواجهات" icons: title: "أيقونة" homepage: diff --git a/config/locales/server.ca.yml b/config/locales/server.ca.yml index b7c92d6b99..4464f4ca4b 100644 --- a/config/locales/server.ca.yml +++ b/config/locales/server.ca.yml @@ -984,9 +984,9 @@ ca: category_style: "Estil visual per a distincions de categoria." max_image_size_kb: "Mida màxima de càrrega d'imatge en kB. Cal configurar-ho a nginx (client_max_body_size) / apache o també al servidor cau." max_attachment_size_kb: "Mida màxima de càrrega de fitxers adjunts en kB. Cal configurar-ho a nginx (client_max_body_size) / apache o també al servidor cau." - authorized_extensions: "Una llista d'extensions de fitxer per a càrrega (fes servir '*' per habilitar tota mena de fitxers)" + authorized_extensions: "Una llista d'extensions de fitxer per a càrrega (fes servir '*' per a habilitar tota mena de fitxers)" max_similar_results: "Quants temes semblants es mostren sobre l'editor en elaborar un nou tema. La comparació es basa en el títol i el cos." - max_image_megapixels: "Quantitat màxima de megapíxels permesos a una imatge." + max_image_megapixels: "Quantitat màxima de megapíxels permesos en una imatge." title_prettify: "Evita errades tipogràfiques freqüents al títol, incloent-hi tot en majúscula, primer caràcter en minúscula, múltiples ! i ?, . extres al final, etcètera." topic_views_heat_low: "Després d'aquestes vistes, el camp de vistes està lleugerament destacat." topic_views_heat_medium: "Després d'aquestes vistes, el camp de vistes està moderadament destacat." diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml index 37976065e7..eb6c83e69a 100644 --- a/config/locales/server.cs.yml +++ b/config/locales/server.cs.yml @@ -1112,6 +1112,8 @@ cs: title: "Organizace" colors: title: "Téma" + themes_further_reading: + title: "Motivy" logos: title: "Loga" fields: diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml index bd3c1a7f6d..524e6eb03c 100644 --- a/config/locales/server.da.yml +++ b/config/locales/server.da.yml @@ -1608,6 +1608,8 @@ da: title: "Organisation" colors: title: "Tema" + themes_further_reading: + title: "Temaer" logos: title: "Logoer" fields: diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index a862c5a3aa..7d894ee971 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -40,6 +40,8 @@ de: themes: bad_color_scheme: "Kann Theme nicht aktualisieren, weil die Farbpalette ungültig ist" other_error: "Etwas ist schief gelaufen beim Aktualisieren des Theme" + compile_error: + unrecognized_extension: "Unbekannte Dateiendung: %{extension}" import_error: generic: Beim Importieren dieses Themes ist ein Fehler aufgetreten about_json: "Importfehler: about.json existiert nicht, oder ist ungültig" @@ -176,6 +178,7 @@ de: confirm_email: "

Du bist fast fertig! Wir haben eine Aktivierungsmail an deine E-Mail-Adresse geschickt. Bitte folge den Anweisungen in der E-Mail, um dein Konto zu aktivieren.

Wenn keine E-Mail ankommt, überprüfe bitte deinen Spam-Ordner.

" bulk_invite: file_should_be_csv: "Die hochzuladende Datei sollte im CSV-Format vorliegen." + max_rows: "Maximal 50.000 Einladungen können auf einmal versendet werden. Versuche die Datei in kleinere aufzuteilen." error: "Es gab einen Fehler beim Hochladen dieser Datei. Bitte versuche es später noch einmal." topic_invite: failed_to_invite: "Der Benutzer kann nicht in dieses Thema eingeladen werden, ohne Mitglied in einer der folgenden Gruppen zu sein: %{group_names}" @@ -642,28 +645,30 @@ de: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" - edge: "Microsoft Edge" discoursehub: "DiscouseHub App" + edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "unbekannter Browser" device: android: "Android-Gerät" + chromebook: "Chromebook" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "Mobilgerät" - mac: "Mac" linux: "GNU/Linux-Computer" + mac: "Mac" + mobile: "Mobilgerät" windows: "Windows-Computer" unknown: "unbekanntes Gerät" os: android: "Android" + chromeos: "ChromeOS" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "unbekanntes Betriebssystem" change_email: @@ -677,6 +682,7 @@ de: description: "Wir senden dir jetzt zur Bestätigung eine E-Mail an deine neue Adresse." associated_accounts: revoke_failed: "Das Widerrufen deines Kontos bei %{provider_name} ist fehlgeschlagen." + connected: "(verbunden)" activation: action: "Klicke hier, um deinen Account zu aktivieren" already_done: "Entschuldige, dieser Link zur Aktivierung des Benutzerkontos ist nicht mehr gültig. Ist dein Konto schon aktiviert?" @@ -885,7 +891,7 @@ de: title: "Nutzerbesuche" xaxis: "Tag" yaxis: "Anzahl der Besuche" - description: "Anzahl aller eindeutigen Seitenbesuche." + description: "Anzahl aller Seitenbesucher" signups: title: "Neue Benutzer" xaxis: "Tag" @@ -1265,6 +1271,9 @@ de: polling_interval: "Polling-Intervall in Millisekunden für angemeldete Clients, wenn Long Polling nicht verwendet wird." anon_polling_interval: "Polling-Intervall in Millisekunden für anonyme Clients." background_polling_interval: "Polling-Intervall in Millisekunden für Clients, wenn sich das Browser-Fenster im Hintergrund befindet." + hide_post_sensitivity: "Die Wahrscheinlichkeit, mit der ein gemeldeter Beitrag verborgen wird" + silence_new_user_sensitivity: "Die Wahrscheinlichkeit, mit der ein neuer Benutzer wegen der Spam-Meldungen stummgeschaltet wird" + auto_close_topic_sensitivity: "Die Wahrscheinlichkeit, mit der ein gemeldetes Thema automatisch geschlossen wird" cooldown_minutes_after_hiding_posts: "Minuten die ein Benutzer warten muss, bevor ein Beitrag bearbeitet werden kann, der wegen Meldungen anderer Benutzer versteckt wurde" max_topics_in_first_day: "Maximale Anzahl an Themen, die ein Benutzer in den ersten 24 Stunden nach dem Schreiben seines ersten Beitrags erstellen kann." max_replies_in_first_day: "Maximale Anzahl an Beiträgen, die ein Benutzer in den ersten 24 Stunden nach dem Schreiben seines ersten Beitrags erstellen kann." @@ -1284,7 +1293,7 @@ de: must_approve_users: "Team-Mitglieder müssen alle neuen Benutzerkonten freischalten, bevor diese Zugriff auf die Website erhalten. ACHTUNG: Das Aktivieren dieser Option für eine Live-Site entfernt den Zugriff auch für alle existierenden Benutzer außer für Team-Mitglieder!" pending_users_reminder_delay: "Benachrichtige die Moderatoren, falls neue Benutzer mehr als so viele Stunden auf ihre Genehmigung gewartet haben. Stelle -1 ein, um diese Benachrichtigungen zu deaktivieren." maximum_session_age: "Benutzer bleiben (n) Stunden nach ihrem letzten Besuch angemeldet" - ga_universal_tracking_code: "Google Universal Analytics (analytics.js) Tracking-Code, z.B. UA-12345678-9; siehe https://google.com/analytics" + ga_universal_tracking_code: "Tracking Code ID von Google Universal Analytics (analytics.js), eg: UA-12345678-9; siehe https://google.com/analytics" ga_universal_domain_name: "Google Universal Analytics (analytics.js) domain name, eg: mysite.com; Siehe https://google.com/analytics" ga_universal_auto_link_domains: "Aktiviere Google Universal Analytics (analytics.js) Cross-Domain-Tracking. Ausgehenden Links zu diesen Domains wird eine Client-ID hinzugefügt. Siehe Googles Cross-Domain-Tracking-Anleitung." gtm_container_id: "Google Tag Manager Container-ID, z.B.: GTM-ABCDEF.
Beachte: Skripte von Drittanbietern, die von GTM geladen weren, müssen erlaubt werden in 'content security policy script src'." @@ -1381,8 +1390,8 @@ de: facebook_app_id: "App-ID für Facebook-Authentifizierung, registriert unter https://developers.facebook.com/apps" facebook_app_secret: "App-Secret für Facebook-Authentifizierung, registriert unter https://developers.facebook.com/apps" enable_github_logins: "GitHub-Authentifizierung aktivieren, erfordert github_client_id und github_client_secret. Siehe Configuring GitHub login for Discourse." - github_client_id: "Client-ID für GitHub-Authentifizierung, registriert auf https://github.com/settings/developers" - github_client_secret: "Client-Secret für GitHub-Authentifizierung, registriert auf https://github.com/settings/developers" + github_client_id: "Client-ID für GitHub-Authentifizierung, registriert auf https://github.com/settings/developers" + github_client_secret: "Client-Secret für GitHub-Authentifizierung, registriert auf https://github.com/settings/developers" readonly_mode_during_backup: "Nur-Lesen-Modus beim Erstellen einer Sicherung aktivieren" enable_backups: "Erlaube Administratoren, Backups des Forums zu erstellen" allow_restore: "Wiederherstellung von Sicherungen zulassen, die ALLE vorhandenen Daten überschreiben! Auf 'false' lassen, sofern Sie nicht planen, eine Sicherung wiederherzustellen." @@ -1522,6 +1531,8 @@ de: max_similar_results: "Anzahl ähnlicher Themen, die beim Erstellen eines neuen Themas über dem Editor angezeigt werden. Ähnlichkeit wird an Hand des Titels und Inhalts bestimmt." max_image_megapixels: "Maximale erlaubte Auflösung für Bilder (in Megapixeln)." title_prettify: "Verhindert gängige Fehler im Titel, wie reine Grossschreibung, Kleinbuchstaben am Anfang, mehrere ! und ?, überflüssiger . am Ende, etc." + title_remove_extraneous_space: "Entferne führende Leerzeichen vor dem Ende-Zeichen." + automatic_topic_heat_values: 'Update die "Themenansicht-Erhitzung" und "Themenbeitrags ''Gefällt mir'' Erhitzung" Einstellungen automatisch basierend auf der Seiten-Aktivität.' topic_views_heat_low: "Aufrufe-Feld leicht hervorheben, sobald das Thema so oft gelesen wurde." topic_views_heat_medium: "Aufrufe-Feld mäßig hervorheben, sobald das Thema so oft gelesen wurde." topic_views_heat_high: "Aufrufe-Feld stark hervorheben, sobald das Thema so oft gelesen wurde." @@ -1722,6 +1733,7 @@ de: default_other_notification_level_when_replying: "Globales Standard-Benachrichtigungslevel, wenn ein Benutzer auf ein Thema antwortet." default_other_external_links_in_new_tab: "Öffne externe Links standardmäßig in einem neuen Tab." default_other_enable_quoting: "Aktiviere standardmäßig die Zitat-Antwort Funktion für hervorgehobenen Text." + default_other_enable_defer: "Standardmäßig Verzögerung der Themen-Funktionalität aktivieren." default_other_dynamic_favicon: "Zeige standardmäßig die Anzahl von neuen und geänderten Beiträgen im Browser-Symbol an." default_other_like_notification_frequency: "Benutzer standardmäßig bei erhaltenen Likes benachrichtigen." default_topics_automatic_unpin: "Standardmäßig Themen automatisch loslösen, wenn ein Benutzer das Ende erreicht." @@ -1753,6 +1765,7 @@ de: allow_staff_to_tag_pms: "Erlaube Team-Mitgliedern, jede Nachricht mit einem Schlagwort zu markieren." min_trust_level_to_tag_topics: "Minimale Vertrauensstufe, um Schlagwörter zu Themen hinzuzufügen." suppress_overlapping_tags_in_list: "Schlagwort nicht zeigen, wenn es genau so im Thementitel vorkommt" + remove_muted_tags_from_latest: "Themen, die nur mit stummgeschalteten Tags versehen sind, nicht in der Liste der aktuellen Themen anzeigen" force_lowercase_tags: "Erzwinge alle neuen tags in Kleinschreibung." company_name: "Firmenname" governing_law: "Anzuwendendes Recht" @@ -2521,6 +2534,13 @@ de: %{rejected_errors} Wenn Sie denken, dass dies ein Fehler ist, [Team Mitarbeiter kontaktieren](%{base_url}/about). + email_reject_reply_not_allowed: + title: "E-Mail Ablehnung Antwort nicht erlaubt" + subject_template: "[%{email_prefix}] E-Mail Problem -- Antwort nicht erlaubt" + text_body_template: | + Entschuldigung, aber deine E-Mail Nachricht an %{destination} (Betreff %{former_title}) hat nicht funktioniert. + + Du hast keine Berechtigung, auf das Thema zu antworten. Falls du glaubst, dass das nicht richtig ist, [kontaktiere ein Team-Mitglied](%{base_url}/about). email_error_notification: title: "Benachrichtigung zu E-Mail-POP-Authentifizierungsfehler" subject_template: "[%{email_prefix}] E-Mail-Problem -- POP-Authentifizierungsfehler" @@ -3863,6 +3883,9 @@ de: placeholder: "Berlin" colors: title: "Design" + themes_further_reading: + title: "Designs" + description: "Möchtest du dein Discourse individuell anpassen? Nutze unser vielseitiges Theming-System: Beliebte Theme Komponenten (für weitere, besuche #theme)" logos: title: "Logos" fields: @@ -3955,7 +3978,7 @@ de: new_topics_unless_trust_level: "Themen von Benutzern in niedrigen Vertrauensstufen müssen von Team-Mitgliedern genehmigt werden. Siehe `approve_new_topics_unless_trust_level`." fast_typer: "Ein neuer Benutzer hat seinen ersten Beitrag verdächtig schnell getippt, Verdacht auf Bot oder Spammer-Verhalten. Siehe `min_first_post_typing_time`." auto_silence_regexp: "Neuer Benutzer, dessen erster Beitrag der `auto_silence_first_post_regex` entspricht." - watched_word: "Beitrag enthält ein Watched Word." + watched_word: "Dieser Beitrag enthielt ein beobachtetes Wort. Siehe deine Liste beobachteter Wörter." staged: "Neue Themen und Beiträge für aufgeführte Benutzer müssen von Team-Mitgliedern genehmigt werden. Siehe `approve_unless_staged`." category: "Beiträge in dieser Kategorie benötigen manuelle Genehmigung von Team-Mitgliedern. Siehe in den Kategorie-Einstellungen." must_approve_users: "Alle neuen Benutzer müssen von Team-Mitgliedern bestätigkt werden. Siehe `must_approve_users`. " diff --git a/config/locales/server.el.yml b/config/locales/server.el.yml index 262f54c20c..938851f30f 100644 --- a/config/locales/server.el.yml +++ b/config/locales/server.el.yml @@ -2729,6 +2729,8 @@ el: title: "Οργάνωση" colors: title: "Θέμα" + themes_further_reading: + title: "Θέματα" logos: title: "Λογότυπα" fields: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 67cd3f701d..9d2025d8ba 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -76,6 +76,8 @@ en: themes: bad_color_scheme: "Can not update theme, invalid color palette" other_error: "Something went wrong updating theme" + compile_error: + unrecognized_extension: "Unrecognized file extension: %{extension}" import_error: generic: An error occured while importing that theme about_json: "Import Error: about.json does not exist, or is invalid" @@ -216,6 +218,7 @@ en: bulk_invite: file_should_be_csv: "The uploaded file should be of csv format." + max_rows: "Maximum of 50,000 invites can be sent at a time. Try splitting the file in smaller parts." error: "There was an error uploading that file. Please try again later." topic_invite: @@ -784,6 +787,7 @@ en: associated_accounts: revoke_failed: "Failed to revoke your account with %{provider_name}." + connected: "(connected)" activation: action: "Click here to activate your account" @@ -1003,7 +1007,7 @@ en: title: "User Visits" xaxis: "Day" yaxis: "Number of visits" - description: "Number of all unique user visits." + description: "Number of all user visits." signups: title: "Signups" xaxis: "Day" @@ -1416,7 +1420,7 @@ en: must_approve_users: "Staff must approve all new user accounts before they are allowed to access the site. WARNING: enabling this for a live site will revoke access for existing non-staff users!" pending_users_reminder_delay: "Notify moderators if new users have been waiting for approval for longer than this many hours. Set to -1 to disable notifications." maximum_session_age: "User will remain logged in for n hours since last visit" - ga_universal_tracking_code: "Google Universal Analytics (analytics.js) tracking code code, eg: UA-12345678-9; see https://google.com/analytics" + ga_universal_tracking_code: "Google Universal Analytics (analytics.js) tracking code ID, eg: UA-12345678-9; see https://google.com/analytics" ga_universal_domain_name: "Google Universal Analytics (analytics.js) domain name, eg: mysite.com; see https://google.com/analytics" ga_universal_auto_link_domains: "Enable Google Universal Analytics (analytics.js) cross-domain tracking. Outgoing links to these domains will have the client id added to them. See Google's Cross-Domain Tracking guide." gtm_container_id: "Google Tag Manager container id. eg: GTM-ABCDEF.
Note: third-party scripts loaded by GTM may need to be whitelisted in 'content security policy script src'." @@ -1712,6 +1716,8 @@ en: title_prettify: "Prevent common title typos and errors, including all caps, lowercase first character, multiple ! and ?, extra . at end, etc." title_remove_extraneous_space: "Remove leading whitespaces in front of the end punctuation." + automatic_topic_heat_values: 'Automatically update the "topic views heat" and "topic post like heat" settings based on site activity.' + topic_views_heat_low: "After this many views, the views field is slightly highlighted." topic_views_heat_medium: "After this many views, the views field is moderately highlighted." topic_views_heat_high: "After this many views, the views field is strongly highlighted." @@ -1978,6 +1984,7 @@ en: default_other_notification_level_when_replying: "Global default notification level when the user replies to a topic." default_other_external_links_in_new_tab: "Open external links in a new tab by default." default_other_enable_quoting: "Enable quote reply for highlighted text by default." + default_other_enable_defer: "Enable defer topic functionality by default." default_other_dynamic_favicon: "Show new/updated topic count on browser icon by default." default_other_like_notification_frequency: "Notify users on likes by default" @@ -2018,7 +2025,6 @@ en: min_trust_level_to_tag_topics: "Minimum trust level required to tag topics" suppress_overlapping_tags_in_list: "If tags match exact words in topic titles, don't show the tag" remove_muted_tags_from_latest: "Don't show topics tagged only with muted tags in the latest topic list." - mute_other_present_tags: "Don't show topics tagged with both muted and unmuted tags in the latest topic list." force_lowercase_tags: "Force all new tags to be entirely lowercase." company_name: "Company Name" @@ -2864,6 +2870,14 @@ en: If you believe this is an error, [contact a staff member](%{base_url}/about). + email_reject_reply_not_allowed: + title: "Email Reject Reply Not Allowed" + subject_template: "[%{email_prefix}] Email issue -- Reply Not Allowed" + text_body_template: | + We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. + + You don't have permissions to reply to the topic. If you believe this is an error, [contact a staff member](%{base_url}/about). + email_error_notification: title: "Email Error Notification" subject_template: "[%{email_prefix}] Email issue -- POP authentication error" @@ -4334,15 +4348,14 @@ en: title: "Theme" themes_further_reading: - title: "Further reading for themes" + title: "Themes" description: - "You can customize many aspects of your community's look-and-feel in just a few clicks (for example by adding a header or footer). Here is a useful list of resources to learn more about themes and theme components in Discourse: + "Looking to customize your Discourse? Take advantage of our powerful theming system: - And remember to visit the #themes category on meta.discourse.org regularly to see the newest themes and components posted by staff and contributors." + Popular theme components (for more, browse #theme)" logos: title: "Logos" @@ -4447,7 +4460,7 @@ en: new_topics_unless_trust_level: "Users at low trust levels must have topics approved by staff. See `approve_new_topics_unless_trust_level`." fast_typer: "New user typed their first post suspiciously fast, suspected bot or spammer behavior. See `min_first_post_typing_time`." auto_silence_regexp: "New user whose first post matches the `auto_silence_first_post_regex` setting." - watched_word: "Post included a Watched Word." + watched_word: "This post included a Watched Word. See your list of watched words." staged: "New topics and posts for staged users must be approved by staff. See `approve_unless_staged`." category: "Posts in this category require manual approval by staff. See the category settings." must_approve_users: "All new users must be approved by staff. See `must_approve_users`." diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index f1f2ec79a0..72ee0dd1c7 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -40,6 +40,8 @@ es: themes: bad_color_scheme: "No se puede actualizar el tema, paleta de colores no válida" other_error: "Algo salió mal actualizando el tema" + compile_error: + unrecognized_extension: "Extensión de archivo no reconocida: %{extension}" import_error: generic: Se produjo un error al importar ese tema about_json: "Error de importación: about.json no existe, o no es válido" @@ -176,6 +178,7 @@ es: confirm_email: "

¡Ya casi has terminado! Te enviamos un correo de activación a tu dirección de correo. Por favor sigue las instrucciones de ese correo para activar tu cuenta.

Si no llega, mira en la carpeta de spam.

" bulk_invite: file_should_be_csv: "El archivo subido debería ser de formato csv." + max_rows: "Se pueden enviar un máximo de 50,000 invitaciones a la vez. Intenta dividir el archivo en partes más pequeñas." error: "Ha ocurrido un error al subir ese archivo. Por favor, inténtalo más tarde de nuevo." topic_invite: failed_to_invite: "No se puede invitar al usuario a este tema sin ser miembro de un grupo en cualquiera de los siguientes grupos: %{group_names}" @@ -200,6 +203,7 @@ es: read_only_mode_enabled: "El sitio está en modo sólo lectura. Las interacciones están deshabilitadas." invalid_grant_badge_reason_link: "El enlace de discourse externo o inválido no está permitido en la razón de la insignia." email_template_cant_be_modified: "Esta plantilla de email no puede ser modificada" + invalid_whisper_access: "O los susurros no están habilitados o no tiene acceso para crear publicaciones de susurros" reading_time: "Tiempo de lectura" likes: "Me gusta" too_many_replies: @@ -362,6 +366,20 @@ es: Puedes editar tu respuesta previa para añadir una cita simplemente seleccionando el texto y pulsando en el botón citar respuesta que aparece. Es más fácil para todos leer temas con menos comentarios y respuestas en profundidad que muchas respuestas pequeñas y comentarios individuales. + dominating_topic: | + ### Deja a los demás unirse a la conversación + + Este tema es muy importante para ti – has publicado más del %{percent}% de las respuestas aquí. + + Podría ser incluso mejor si también tienes espacio para que otras personas compartan sus puntos de vista. ¿Puedes invitarlos? + get_a_room: | + ### Anima a todos a participar en la conversación + + ¡Has respondido %{count} veces a @%{reply_username} en este tema particular! + + Una gran discusión involucra muchas voces y perspectivas. ¿Puedes involucrar a alguien más? + + Y no lo olvides, si desea continuar su conversación con este usuario en particular fuera de la vista pública, [enviarle un mensaje privado](%{base_path}/u/%{reply_username}). too_many_replies: | ### Has alcanzado el límite de respuestas para este tema @@ -408,7 +426,12 @@ es: same_as_username: "es la misma que tu nombre de usuario. Por favor, escoge una contraseña más segura." same_as_email: "es el mismo que tu email. Por favor, utiliza una contraseña más segura." same_as_current: "es la misma que tu contraseña actual." + same_as_name: "es el mismo que tu nombre." unique_characters: "tiene demasiados caracteres repetidos. Por favor usa una contraseña más segura." + username: + same_as_password: "es el mismo que tu contraseña." + name: + same_as_password: "es el mismo que tu contraseña." ip_address: signup_not_allowed: "El registro no está permitido para esta cuenta." user_email: @@ -642,28 +665,30 @@ es: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" - edge: "Microsoft Edge" discoursehub: "DiscourseHub app" + edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "navegador desconocido" device: android: "Dispositivo Android" + chromebook: "Chromebook" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "Dispositivo móvil" - mac: "Mac" linux: "Ordenador GNU/Linux" + mac: "Mac" + mobile: "Dispositivo móvil" windows: "Ordenador Windows" unknown: "dispositivo desconocido" os: android: "Android" + chromeos: "ChromeOS" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "sistema operativo desconocido" change_email: @@ -677,6 +702,7 @@ es: description: "Te enviaremos un email a tu nueva dirección para confirmar." associated_accounts: revoke_failed: "No se ha podido revocar la conexión con %{provider_name}." + connected: "(conectados)" activation: action: "Haz clic aquí para activar tu cuenta" already_done: "Lo sentimos, este link de confirmación de cuenta ya no es válido. ¿Quizás tu cuenta ya está activa?" @@ -885,7 +911,7 @@ es: title: "Visitas de usuario" xaxis: "Día" yaxis: "Número de visitas" - description: "Número de visitantes únicos." + description: "Número de todas las visitas de usuario." signups: title: "Registros" xaxis: "Día" @@ -1265,6 +1291,9 @@ es: polling_interval: "Cuando no este en 'long polling', ¿Qué tan frecuente debe los clientes con sesión iniciada hacer 'poll' en milisegundos?" anon_polling_interval: "¿Cuán a menudo deben hacer poll los usuarios anónimos en milisegundos" background_polling_interval: "Cada cuántos milisegundos debería hacerse cargarse información (mientras que la ventana esté en segundo plano)" + hide_post_sensitivity: "La probabilidad de que una publicación marcada se oculte" + silence_new_user_sensitivity: "La probabilidad de que un nuevo usuario sea silenciado basado en los reportes de spam" + auto_close_topic_sensitivity: "La probabilidad de que un tema reportado sea automáticamente cerrado" cooldown_minutes_after_hiding_posts: "Número de minutos que un usuario debe esperar para poder editar un post oculto por los reportes de la comunidad" max_topics_in_first_day: "El máximo número de temas que un usuario puede crear en las 24 horas posteriores a publicar su primer post" max_replies_in_first_day: "El máximo número de respuestas que un usuario puede crear en las 24 horas posteriores a publicar su primer post" @@ -1295,6 +1324,7 @@ es: blacklist_ip_blocks: "Una lista de IPs bloqueadas privadas que nunca deberían ser rastreadas por Discourse" whitelist_internal_hosts: "Una lista con hosts internos que discourse puede rastrear de forma segura para oneboxing y otros propósitos" allowed_iframes: "Una lista de dominios para iframe src donde discourse pueda permitir de forma segura en mensajes" + whitelisted_crawler_user_agents: "User agents de rastreadores web a los que se debe permitir el acceso al sitio. ¡ADVERTENCIA! ¡CONFIGURAR ESTO INHABILITARÁ TODOS LOS RASTREADORES QUE NO SE ENUMERAN AQUÍ!" blacklisted_crawler_user_agents: "Palabra única insensible a mayúsculas y minúsculas en la cadena del user agent que identifica rastreadores web a los que no se debe permitir el acceso al sitio. No se aplica si se define una lista blanca." slow_down_crawler_user_agents: "User agents de rastreadores web a los que se debe aplicar una tarifa limitada en robots.txt utilizando la directiva de retardo de rastreo" slow_down_crawler_rate: "Si slow_down_crawler_user_agents es especificado, este ratio aplicará para todos los crawlers (la solicitud demora número de segundos)" @@ -1521,6 +1551,8 @@ es: max_similar_results: "Cuántos temas similares se mostrarán encima del editor cuando estés creando un nuevo tema. La comparación se basa en el título y el cuerpo del tema." max_image_megapixels: "Máximo de megapíxeles permitidos por una imagen." title_prettify: "Prevenir errores comunes en el título, incluyendo \"todo mayúsculas\", primera letra minúscula, multiples signos ! o ?, . extra al final, etc." + title_remove_extraneous_space: "Elimine los espacios en blanco iniciales delante de la puntuación final." + automatic_topic_heat_values: 'Actualice automáticamente la configuración de "topic views heat" y "topic post like heat" según la actividad del sitio.' topic_views_heat_low: "Después de este número de vistas, el campo vistas será ligeramente resaltado." topic_views_heat_medium: "Después de este número de visitas, el campo visitas será moderadamente resaltado." topic_views_heat_high: "Después de este número de visitas, el campo visitas será fuertemente resaltado." @@ -1554,6 +1586,9 @@ es: auto_silence_fast_typers_on_first_post: "Silenciar automáticamente a usuarios que no cumplan el umbral establecido en min_first_post_typing_time" auto_silence_fast_typers_max_trust_level: "Máximo nivel de confianza por debajo del cual se podrán silenciar usuarios que escriban demasiado rápido en su primer post" auto_silence_first_post_regex: "Expresión regular que no distingue mayúsculas y minúsculas que si es detectada hará que el primer post de un usuario sea silenciado y enviado a la cola de aprobación. Ejemplo: raging|a[bc]a hará que todos los posts que contengan gaging, aba o aca sean silenciados. Sólo tiene efecto en el primer mensaje de un usuario." + reviewable_claiming: "¿Es necesario reclamar el contenido revisable antes de poder actuar?" + reviewable_default_topics: "Mostrar contenido revisable agrupado por tema por defecto" + reviewable_default_visibility: "No muestre elementos revisables a menos que cumplan con esta prioridad" reply_by_email_enabled: "Habilitar la respuesta a temas por email." reply_by_email_address: "Plantilla para la dirección de email que aparecerá al recibir correos con la función de respuesta por email: %%{reply_key}@respuesta.ejemplo.com o respuestas+%%{reply_key}@ejemplo.com" alternative_reply_by_email_addresses: "Lista de plantillas alternativa para las direcciones de respuesta por email. Ejemplo: %%{reply_key}@reply.example.com|replies+%%{reply_key}@example.com" @@ -1718,6 +1753,7 @@ es: default_other_notification_level_when_replying: "Nivel global de notificación cuando el usuario responde a un tema." default_other_external_links_in_new_tab: "Abrir enlaces externos en una nueva pestaña por defecto." default_other_enable_quoting: "Activar respuesta citando texto seleccionado por defecto." + default_other_enable_defer: "Habilitar la funcionalidad de temas diferidos por defecto." default_other_dynamic_favicon: "Mostrar temas nuevos/actualizados en el icono del navegador por defecto" default_other_like_notification_frequency: "Notificar a los usuarios de los me gusta por defecto" default_topics_automatic_unpin: "Quitar destacado automáticamente cuando el usuario llega al final del tema." @@ -1749,6 +1785,7 @@ es: allow_staff_to_tag_pms: "Permitir a los miembros del staff etiquetar cualquier mensaje personal" min_trust_level_to_tag_topics: "Nivel mínimo requerido para etiquetar temas" suppress_overlapping_tags_in_list: "Si alguna etiqueta coinciden con palabras en el título de los temas, ocultarla." + remove_muted_tags_from_latest: "No muestre temas etiquetados solo con etiquetas silenciadas en la lista de temas más reciente." force_lowercase_tags: "Forzar que todas las etiquetas sean completamente en minúscula" company_name: "Nombre de Compañía" governing_law: "Ley que rige" @@ -2519,6 +2556,13 @@ es: %{rejected_errors} Si crees que se trata de un error, [contacta con un miembro del staff](%{base_url}/about). + email_reject_reply_not_allowed: + title: "Rechazar respuesta de Correo electrónico no permitido" + subject_template: "[%{email_prefix}] Problema de Email -- Respuesta no permitida" + text_body_template: | + Lo sentimos, pero tu mensaje de correo a %{destination} (titulado %{former_title}) no ha funcionado. + + Tu cuenta no tiene los permisos para responder al tema. Si crees que esto es un error, [contacta a un miembro del staff](%{base_url}/about). email_error_notification: title: "Email, error de notificación" subject_template: "[%{email_prefix}] Problema con el correo -- Error de autenticación POP" @@ -2652,6 +2696,10 @@ es: subject_template: one: "%{count} post esperando a ser revisado" other: "%{count} posts esperando a ser revisados" + text_body_template: | + Hola, + + Posts de nuevos usuarios se mantienen moderados y están actualmente esperando a ser revisados. [Aprueba o rechaza desde aquí](%{base_url}/review?type=ReviewableQueuedPost). unsubscribe_link: | Para darte de baja de estos emails, [haz clic aquí](%{unsubscribe_url}). unsubscribe_link_and_mail: | @@ -3674,6 +3722,9 @@ es: title: "Etiquetas" staff_tag_disallowed: 'La etiqueta "%{tag}" solo puede ser insertada por moderadores.' staff_tag_remove_disallowed: 'La etiqueta "%{tag}" solo puede ser eliminada por moderadores.' + minimum_required_tags: + one: "Debes seleccionar al menos %{count} etiqueta." + other: "Debes seleccionar al menos %{count} etiquetas." upload_row_too_long: "El archivo CSV debe tener una etiqueta por línea. Opcionalmente, la etiqueta puede ir seguida de una coma, luego el nombre del grupo de etiquetas." forbidden: in_this_category: '"%{tag_name}" no puede ser usada en esta categoría.' @@ -3781,6 +3832,9 @@ es: placeholder: "San Francisco, California" colors: title: "Tema" + themes_further_reading: + title: "Tema" + description: "¿Buscando personalizar tu Discourse? Aprovecha el poderoso sistema de diseño: Componentes de diseño populares (para más información, explora #theme)" logos: title: "Logos" fields: @@ -3863,6 +3917,8 @@ es: low: "Bajo" medium: "Medio" high: "Alto" + must_claim: "Debes reclamar objetos antes de actuar sobre ellos." + user_claimed: "Este artículo ha sido reclamado por otro usuario." missing_version: "Debes incluir un parámetro de versión" conflict: "Se ha detectado un conflicto de actualización que no te ha permitido realizar esa acción" reasons: @@ -3871,7 +3927,7 @@ es: new_topics_unless_trust_level: "Los usuarios con bajos niveles de confianza deben tener temas aprobados por el staff. Consulta `approve_new_topics_unless_trust_level`." fast_typer: "El nuevo usuario escribió su primer mensaje de forma sospechosamente rápida, presunto bot o comportamiento de spammer. Consulta `min_first_post_typing_time`." auto_silence_regexp: "Nuevo usuario cuya primera publicación coincide con la configuración `auto_silence_first_post_regex`." - watched_word: "La publicación incluye una palabra vigilada." + watched_word: "Esta publicación incluye una Palabra Observada. Ver tu lista de palabras observadas." staged: "Los nuevos temas y publicaciones para usuarios en escena deben ser aprobados por el staff. Consulta `approve_unless_staged`." category: "Los mensajes en esta categoría requieren la aprobación manual del staff. Consulta la configuración de la categoría." must_approve_users: "Todos los nuevos usuarios deben ser aprobados por el staff. Consulta `must_approve_users`." diff --git a/config/locales/server.et.yml b/config/locales/server.et.yml index 388a61fc1c..2e367e0436 100644 --- a/config/locales/server.et.yml +++ b/config/locales/server.et.yml @@ -420,27 +420,27 @@ et: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "tundmatu brauser" device: android: "Androidi seade" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "Mobiilne seade" - mac: "Mac" linux: "GNU/Linuxiga arvuti" + mac: "Mac" + mobile: "Mobiilne seade" windows: "Windowsi arvuti" unknown: "tundmatu seade" os: android: "Android" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "tundmatu operatsioonisüsteem" change_email: @@ -748,6 +748,7 @@ et: min_topic_title_length: "Lühim lubatud teema pealkirja pikkus tähemärkides" max_topic_title_length: "Maksimaalne lubatud teema pealkirja pikkus tähemärkides" min_personal_message_title_length: "Minimaalne lubatud teema pealkirja pikkus tähemärkides" + min_admin_password_length: "Minimaalne administraatori parooli pikkus." enable_instagram_logins: "Luba autentimine Instagrami abil, nõuab instagram_consumer_key ja instagram_consumer_secret" instagram_consumer_key: "Teenusekasutaja avalik võti Instagrami abil autentimiseks" instagram_consumer_secret: "Teenusekasutaja salajane võti Instagrami abil autentimiseks" @@ -806,6 +807,20 @@ et: in_reply_to: "Vastuseks" unsubscribe: title: "Tühista tellimus" + user_replied: + subject_template: "[%{email_prefix}] %{topic_title}" + user_quoted: + subject_template: "[%{email_prefix}] %{topic_title}" + user_linked: + subject_template: "[%{email_prefix}] %{topic_title}" + user_mentioned: + subject_template: "[%{email_prefix}] %{topic_title}" + user_group_mentioned: + subject_template: "[%{email_prefix}] %{topic_title}" + user_posted: + subject_template: "[%{email_prefix}] %{topic_title}" + user_watching_first_post: + subject_template: "[%{email_prefix}] %{topic_title}" user_posted_pm_staged: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 @@ -1048,6 +1063,8 @@ et: title: "Organisatsioon" colors: title: "Kujundusteema" + themes_further_reading: + title: "Kujundused" logos: title: "Logod" fields: @@ -1101,6 +1118,8 @@ et: title: "Ignoreeri" approve: title: "Kinnita" + approve_user: + title: "Kiida kasutaja heaks" reject_user: delete: title: "Kustuta kasutaja" diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml index d80848d9c8..e0eaf6f407 100644 --- a/config/locales/server.fa_IR.yml +++ b/config/locales/server.fa_IR.yml @@ -586,6 +586,8 @@ fa_IR: every_hour: "هر ساعت" daily: "روزانه" weekly: "هفتگی" + every_month: "هر ماه" + every_six_months: "هر شش ماه" user_api_key: title: "اجازه دسترسی به برنامه" authorize: "اجازه دادن" @@ -604,6 +606,7 @@ fa_IR: reports: default: labels: + count: تعداد day: روز post_edits: labels: @@ -613,6 +616,7 @@ fa_IR: user_flagging_ratio: labels: user: کاربر + score: امتیاز moderators_activity: labels: moderator: مدیر @@ -2251,6 +2255,8 @@ fa_IR: title: "سازمان" colors: title: "قالب" + themes_further_reading: + title: "قالب‌ها" logos: title: "لوگو‌ها" fields: @@ -2284,6 +2290,14 @@ fa_IR: title: "Discourse شما آماده‌ست!" joined: "ملحق شده" reviewables: + priorities: + low: "کم" + medium: "متوسط" + high: "بالا" + sensitivity: + low: "کم" + medium: "متوسط" + high: "بالا" actions: agree_and_suspend: title: "کاربر تعلیق شده" diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index 8e8f5d0a82..7735494dfa 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -40,6 +40,8 @@ fi: themes: bad_color_scheme: "Ei voi päivittää teemaa, väripaletti ei kelpaa" other_error: "Jotakin meni vikaan, kun teemaa päivitettiin" + compile_error: + unrecognized_extension: "Tuntematon tiedostomuoto: %{extension}" import_error: generic: Teeman tuomisessa tapahtui virhe about_json: "Virhe tuomisessa: about.json -tiedostoa ei ole olemassa tai se ei kelpaa" @@ -170,10 +172,12 @@ fi:

Jos muistat salasanasi voit kirjautua sisään.

Muussa tapauksessa uusi salasanasi.

+ error_message: "Kutsun hyväksyminen epäonnistui. Ota yhteyttä sivuston ylläpitäjään." user_exists: "Ei ole tarpeen lähettää kutsua osoitteeseen %{email}, sillä kutsuttavalla on jo tunnus!" confirm_email: "

Melkein valmista! Lähetimme aktivointiviestin sähköpostiosoitteeseesi. Aktivoi tilisi noudattamalla sähköpostissa kerrottuja ohjeita.

Jos viesti ei saavu, tarkistathan roskapostikansiosi.

" bulk_invite: file_should_be_csv: "Ladattavan tiedoston tulee olla CSV-muodossa." + max_rows: "Kerrallaan voi lähettää enintään 50 000 kutsua. Jaa tiedosto pienempiin osiin." error: "Tiedoston lataus epäonnistui. Yritä myöhemmin uudelleen." topic_invite: failed_to_invite: "Käyttäjää ei voi kutsua ketjuun, jollei hän ole jonkun näistä ryhmistä jäsen: %{group_names}." @@ -197,6 +201,8 @@ fi: provider_not_found: "Et saa katsella pyydettyä resurssia. Autentikoinnintarjoajaa ei ole olemassa." read_only_mode_enabled: "Sivusto on vain luku-tilassa. Vuorovaikutteiset toiminnot ovat poissa käytöstä." invalid_grant_badge_reason_link: "Ulkoiset linkit ja epäkelvot Discourse-linkit eivät kelpaa ansiomerkin syyksi" + email_template_cant_be_modified: "Tätä sähköpostipohjaa ei voi muokata" + invalid_whisper_access: "Joko kuiskaukset eivät ole käytössä tai sinulla ei ole oikeutta kuiskata." reading_time: "Lukuaika" likes: "Tykkäykset" too_many_replies: @@ -305,6 +311,7 @@ fi: email_already_used_in_group: "'%{email}' on jo käytössä ryhmällä '%{group_name}'." email_already_used_in_category: "'%{email}' on jo käytössä alueella '%{category_name}'." cant_allow_membership_requests: "Et voi sallia ryhmäjäsenyyden hakemista, jos ryhmällä ei ole isäntää." + already_requested_membership: "Olet jo hakenut tämän ryhmän jäsenyyttä." default_names: everyone: "kaikki" admins: "yllapitajat" @@ -317,6 +324,7 @@ fi: trust_level_4: "luottamustaso_4" request_membership_pm: title: "Jäsenhakemus ryhmään @%{group_name}" + handle: "käsittele jäsenyyshakemus" education: until_posts: one: "ensimmäisen viestisi" @@ -357,6 +365,20 @@ fi: Voit muokata edellistä viestiäsi ja lisätä siihen lainauksen maalaamalla lainattavan viestin tekstiä ja klikkaamalla ilmestyvää lainaa-painiketta. On helpompaa lukea ketjua, jossa on vähemmän pidempiä vastauksia kuin sellaista, jossa on paljon lyhyitä yksittäisiä vastauksia. + dominating_topic: | + ### Anna muidenkin osallistua keskusteluun + + Tämä ketju on selvästikin tärkeä sinulle – olet kirjoittanut yli %{percent}% vastauksista. + + Oletko varma, että annat muillekin riittävästi aikaa jakaa omia ajatuksiaan? Voisit kutsua heitä tänne? + get_a_room: | + ### Rohkaise kaikkia osallistumaan keskusteluun + + Olet vastannut %{count} kertaa käyttäjälle @%{reply_username} tässä ketjussa! + + Parhaissa keskusteluissa on monia ääniä ja näkökulmia. Voitko saada jonkun liittymään keskusteluun? + + Pidä mielessä, että jos haluat keskustella syvällisemmin tämän käyttäjän kanssa ei-julkisesti, [lähetä hänelle yksityisviesti](%{base_path}/u/%{reply_username}). too_many_replies: | ### Olet kirjoittanut enimmäismäärän vastauksia tähän ketjuun @@ -403,7 +425,12 @@ fi: same_as_username: "on sama kuin käyttäjätunnuksesi. Valitse turvallisempi salasana." same_as_email: "on sama kuin sähköpostiosoitteesi. Valitse turvallisempi salasana." same_as_current: "on sama kuin nykyinen salasanasi." + same_as_name: "on sama kuin nimesi." unique_characters: "on liikaa toistuvia merkkejä. Valitse turvallisempi salasana." + username: + same_as_password: "on sama kuin salasanasi." + name: + same_as_password: "on sama kuin salasanasi." ip_address: signup_not_allowed: "Liittyminen ei ole sallittu tälle tilille." user_email: @@ -483,6 +510,7 @@ fi: (Lisää tietoa luottamustasoista saat [tästä englanninkielisestä ketjusta][trust]. Huomaa, että pysyäksesi mestarina sinun pitää täyttää vaatimukset jatkossakin.) [trust]: https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/ + admin_quick_start_title: "LUE ENSIN: Ylläpitäjän pika-aloitusopas" category: topic_prefix: "Alueesta %{category}" replace_paragraph: "(Korvaa tämä kappale lyhyellä kuvauksella uudesta alueesta. Tämä kuvaus näytetään alueen valinnan yhteydessä, joten yritä pitää se alle 200 merkin pituisena.)" @@ -496,7 +524,10 @@ fi: email_already_used_in_group: "'%{email}' on jo käytössä ryhmällä '%{group_name}'." email_already_used_in_category: "'%{email}' on jo käytössä alueella '%{category_name}'." description_incomplete: "Alueen kuvauksessa on oltava ainakin yksi kappale." + permission_conflict: "Ryhmällä jolle myönnetään pääsy tytäralueelle, on oltava pääsy emoalueelle. Näillä ryhmillä on pääsy jollekin tytäralueelle, muttei emoalueelle: %{group_names}." + disallowed_topic_tags: "Ketjulla on tunnisteita, jotka eivät ole sallittuja tällä alueella: '%{tags}'" cannot_delete: + uncategorized: "Tämä alue on erityinen. Se on tarkoitettu säilytyspaikaksi ketjuille, joilla ei ole aluetta; sitä ei voi poistaa." has_subcategories: "Aluetta ei voi poistaa, koska sillä on tytäralueita." topic_exists: one: "Aluetta ei voi poistaa, koska siellä on %{count} ketju. Vanhin ketju on %{topic_link}." @@ -518,6 +549,9 @@ fi: post: image_placeholder: broken: "Tämä kuva ei toimi" + has_likes: + one: "%{count} tykkäys" + other: "%{count} tykkäystä" rate_limiter: slow_down: "Olet suorittanut toiminnon liian monta kertaa. Kokeile myöhemmin uudelleen." too_many_requests: "Olet suorittanut toiminnon liian monta kertaa. Odota %{time_left} ennen kuin yrität uudelleen." @@ -630,27 +664,30 @@ fi: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" + discoursehub: "DiscourseHub-sovellus" edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "tuntematon selain" device: android: "Android-laite" + chromebook: "Chromebook" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "mobiililaite" - mac: "Mac" linux: "GNU/Linux -tietokone" + mac: "Mac" + mobile: "mobiililaite" windows: "Windows-tietokone" unknown: "tuntematon laite" os: android: "Android" + chromeos: "ChromeOS" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "tuntematon käyttöjärjestelmä" change_email: @@ -664,6 +701,7 @@ fi: description: "Lähetämme sinulle sähköpostin varmennusta varten." associated_accounts: revoke_failed: "Tunnustasi palveluntarjoajalla %{provider_name} ei onnistuttu perumaan." + connected: "(yhdistetty)" activation: action: "Aktivoi tilisi klikkaamalla tästä" already_done: "Pahoittelut, tämän tilin varmennuslinkki ei ole enää voimassa. Ehkäpä tili on jo varmennettu?" @@ -760,6 +798,8 @@ fi: flagging: you_must_edit: '

Muu yhteisö liputti viestisi. Käy lukemassa saapuneet viestisi.

' user_must_edit: "

Tämä viesti on liputettu ja on siksi piilotettu väliaikaisesti.

" + ignored: + hidden_content: "

Estettyä sisältöä

" archetypes: regular: title: "Tavallinen ketju" @@ -769,6 +809,8 @@ fi: make: "Tämä ketju on nyt banneri. Se näytetään jokaisen sivun ylälaidassa, kunnes käyttäjä kuittaa sen nähdyksi." remove: "Tämä ketju ei ole enää banneri. Sitä ei näytetä enää jokaisen sivun ylälaidassa." unsubscribed: + title: "Sähköpostiasetukset päivitetty!" + description: "sähköpostiasetukset osoitteelle %{email} päivitettiin. Voit muuttaa sähköpostiasetuksia käyttäjäasetuksissasi." topic_description: "Tilaa %{link} uudelleen ketjun alla tai oikealla puolen sijaitsevan ilmoitusvalikon kautta." private_topic_description: "Voit tilata ketjun uudelleen sen alla tai oikealla puolella sijaitsevan ilmoitusvalikon kautta." unsubscribe: @@ -781,7 +823,10 @@ fi: different_user_description: "Olet kirjautunut sisään eri käyttäjänä kuin jolle sähköposti lähetettiin. Kirjaudu ulos tai siirry anonyymitilaan ja yritä sitten uudelleen." not_found_description: "Pahoittelut, tätä tilauksen perumista ei löytynyt. Kenties sinulle lähetetty linkki on vanhentunut?" log_out: "Kirjaudu ulos" + submit: "Tallenna asetukset" digest_frequency: + title: "Saat sähköpostikoosteita %{frequency}" + select_title: "Sähköpostikoosteiden taajuus:" never: "ei koskaan" every_30_minutes: "puolen tunnin välein" every_hour: "tunneittain" @@ -796,6 +841,7 @@ fi: read_write: "luku/kirjoitus" description: '"%{application_name}" pyytää seuraavaa käyttöoikeutta tunnukseesi:' instructions: 'Loimme juuri uuden käyttäjärajapinta-avaimen, jolla voit käyttää palveluamme "%{application_name}". Liitä tämä avainkoodi sovellukseesi:' + otp_description: 'Haluatko sallia sovellukselle "%{application_name}" pääsyn sivustolle?' no_trust_level: "Pahoittelut, luottamustasosi ei ole riittävä käyttäjärajapinnan käyttämiseen" generic_error: "Pahoittelut, API-salausavainta ei muodostettu; sivuston ylläpitäjä on saattanut ottaa ominaisuuden pois käytöstä" scopes: @@ -805,6 +851,8 @@ fi: session_info: "Tiedot käyttäjän istunnosta" read: "Kaikkien luku" write: "Kaikkiin kirjaittaminen" + one_time_password: "Luo kertakäyttöinen kirjautumiskoodi" + invalid_public_key: "Pahoittelut, julkinen avain ei kelpaa." flags: errors: already_handled: "Lippu ehdittiin jo käsitellä" @@ -860,7 +908,7 @@ fi: title: "Vierailut" xaxis: "Päivä" yaxis: "Vierailujen määrä" - description: "Uniikin kävijäkerrat" + description: "Vierailleiden käyttäjien määrä." signups: title: "Liittymiset" xaxis: "Päivä" @@ -1112,6 +1160,13 @@ fi: author: Käyttäjä filesize: Tiedoston koko description: "Luettelo kaikista latauksista järjestettynä tiedostopäätteen, tiedoston koon ja käyttäjän mukaan." + top_ignored_users: + title: "Estetyimmät / Vaimennetuimmat käyttäjät" + labels: + ignored_user: Estetty käyttäjä + ignores_count: Estojen määrä + mutes_count: Vaimennusten määrä + description: "Käyttäjät jotka monet muista on vaimentaneet ja/tai estäneet." dashboard: rails_env_warning: "Palvelintasi ajetaan %{env} moodissa." host_names_warning: "Sivuston config/database.yml tiedosto käyttää oletusisäntänimeä. Päivitä se käyttämään sivuston isäntänimeä." @@ -1141,6 +1196,7 @@ fi: site_settings: censored_words: "Sanat, jotka korvataan automaattisesti merkeillä ■■■■" delete_old_hidden_posts: "Poista automaattisesti kaikki yli 30 päivää piilotettuna olleet viestit." + default_locale: "Tämän Discourse-ympäristön oletuskieli. Voit korvata systeemin luomien alueiden ja ketjujen tekstejä kohteessa Mukauta / Tekstit." allow_user_locale: "Salli käyttäjien vaihtaa käyttöliittymän kieli omista asetuksista" set_locale_from_accept_language_header: "Aseta sivuston kieli kirjautumattomille käyttäjille selaimen kielivalinnan perusteella. (KOKEELLINEN, ei toimi anonyymin välimuistin kanssa)" support_mixed_text_direction: "Salli vasemmalta-oikealle- ja oikealta-vasemmalle-kirjoitusta käytettävän sekaisin." @@ -1162,6 +1218,10 @@ fi: search_query_log_max_size: "Kuinka monta hakukyselyä säilötään enintään" search_query_log_max_retention_days: "Kuinka pitkään hakukyselyä enintään säilötään päivissä." search_ignore_accents: "Älä välitä aksenteista, kun haetaan tekstiä" + category_search_priority_very_low_weight: "Erittäin matalan hakuprioriteetin painoarvo." + category_search_priority_low_weight: "Matalan hakuprioriteetin painoarvo." + category_search_priority_high_weight: "Korkean hakuprioriteetin painoarvo." + category_search_priority_very_high_weight: "Erittäin korkean hakuprioriteetin painoarvo." allow_uncategorized_topics: "Salli ketjujen aloittaminen valitsematta aluetta. VAROITUS: Alueettomat ketjut täytyy siirtää jollekin alueelle ennen kuin poistat asetuksen käytöstä." allow_duplicate_topic_titles: "Salli ketjun aloittaminen identtisellä otsikolla." unique_posts_mins: "Kuinka monen minuutin kuluttua käyttäjä voi lähettää uudestaan samansisältöisen viestin" @@ -1236,7 +1296,6 @@ fi: must_approve_users: "Henkilökunnan täytyy hyväksyä kaikki uudet tilit, ennen uusien käyttäjien päästämistä sivustolle. VAROITUS: tämän asetuksen valitseminen poistaa pääsyn kaikilta jo olemassa olevilta henkilökuntaan kuulumattomilta käyttäjiltä." pending_users_reminder_delay: "Ilmoita valvojille, jos uusi käyttäjä on odottanut hyväksyntää kauemmin kuin näin monta tuntia. Aseta -1, jos haluat kytkeä ilmoitukset pois päältä." maximum_session_age: "Käyttäjä pysyy sisäänkirjautuneena n tuntia vierailunsa jälkeen" - ga_universal_tracking_code: "Google Universal Analytics (analytics.js) tracking code -seurantakoodi, esim.: UA-12345678-9; katso https://google.com/analytics" ga_universal_domain_name: "Google Universal Analytics (analytics.js) verkkotunnus, esim. mysite.com; katso https://google.com/analytics" ga_universal_auto_link_domains: "Ota käyttöön Google Universal Analyticsin (analytics.js) verkkotunnusten välinen seurantapalvelu . Poisvieviin näiden verkkotunnusten linkkeihin lisätään client id -tunniste. Katso lisää Googlen Cross-Domain Tracking -oppaasta." enable_escaped_fragments: "Käytä Googlen Ajax-sivustoille tarkoitettua API:a, jos webcrawleria ei tunnisteta. Katso https://developers.google.com/webmasters/ajax-crawling/docs/learn-more" @@ -3504,6 +3563,8 @@ fi: placeholder: "San Francisco, Kalifornia" colors: title: "Värimaailma" + themes_further_reading: + title: "Teemat" logos: title: "Logot" fields: diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index afff2b1523..188ba124d8 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -646,27 +646,27 @@ fr: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "navigateur inconnu" device: android: "Android Device" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "Appareil portable" - mac: "Mac" linux: "Ordinateur GNU/Linux" + mac: "Mac" + mobile: "Appareil portable" windows: "Ordinateur Windows" unknown: "appareil inconnu" os: android: "Android" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "système d'exploitation inconnu" change_email: @@ -881,7 +881,6 @@ fr: title: "Visites d'utilisateurs" xaxis: "Jour" yaxis: "Nombre de visites" - description: "Nombre de visites d'utilisateurs uniques" signups: title: "Inscriptions" xaxis: "Jour" @@ -1272,7 +1271,6 @@ fr: must_approve_users: "Les responsables doivent approuver les nouveaux utilisateurs afin qu'ils puissent accéder au site. ATTENTION : activer cette option sur un site en production suspendra l'accès des utilisateurs existants qui ne sont pas des responsables !" pending_users_reminder_delay: "Avertir les modérateurs si des nouveaux utilisateurs sont en attente d'approbation depuis x heures. Mettre -1 pour désactiver les notifications." maximum_session_age: "L'utilisateur restera connecté pour n heures après la dernière visite" - ga_universal_tracking_code: "Code de suivi Google Universal Analytics (analytics.js), par exemple : UA-12345678-9 ; voir https://google.com/analytics" ga_universal_domain_name: "Nom de domaine Google Universal Analytics (analytics.js), par exemple : monsite.com ; voir https://google.com/analytics" ga_universal_auto_link_domains: "Activer Google Universal Analytics (analytics.js) pour le suivi interdomaine. Les liens sortants vers ces domaines auront l'identifiant client ajouté. Voir le guide Cross-Domain Tracking de Google." gtm_container_id: "Identifiant de container Google Tag Manager. P.ex : GTM-ABCDEF.
Note: les scripts tiers chargés par GTM devront peut-être être mis sur liste blanche dans 'content security policy script src'." @@ -3954,6 +3952,8 @@ fr: placeholder: "San Francisco, Californie" colors: title: "Thème" + themes_further_reading: + title: "Thèmes" logos: title: "Logos" fields: @@ -4019,10 +4019,12 @@ fr: reviewables: priorities: low: "Faible" + medium: "Moyenne" high: "Fort" sensitivity: disabled: "Désactivé" low: "Faible" + medium: "Moyenne" high: "Fort" missing_version: "Vous devez fournir un paramètre de version" conflict: "Un conflit de mise à jour empêche rend cette action impossible." diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 65118610be..b1bce11ee5 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -40,6 +40,8 @@ he: themes: bad_color_scheme: "לא ניתן לעדכן ערכת עיצוב, מבחר הצבעים שגוי" other_error: "משהו השתבש בעת עדכון ערכת העיצוב" + compile_error: + unrecognized_extension: "סיומת הקובץ אינה מוכרת: %{extension}" import_error: generic: אירעה שגיאה בעת ייבוא ערכת העיצוב הזו about_json: "שגיאת ייבוא: about.json לא קיים או שהוא פגום" @@ -186,6 +188,7 @@ he: confirm_email: "

כמעט סיימת! שלחנו הודעת הפעלה לכתובת הדוא״ל שלך. נא לעקוב אחר ההנחיות המופיעות בהודעה כדי להפעיל את החשבון שלך.

אם ההודעה לא הגיעה אליך כדאי לבדוק בתיקיית הספאם.

" bulk_invite: file_should_be_csv: "הקובץ שנשלח אמור להיות בתסדיר csv." + max_rows: "מותר לשלוח עד 50,000 הודעות כל פעם. כדאי לנסות לפצל את הקובץ לחלקים קטנים יותר." error: "אירעה שגיאה בשליחת הקובץ. נא לנסות שוב מאוחר יותר." topic_invite: failed_to_invite: "לא ניתן להזמין את המשתמש לנושא הזה בהעדר חברות באחת הקבוצות הבאות: %{group_names}" @@ -555,6 +558,11 @@ he: post: image_placeholder: broken: "תמונה זו שבורה" + has_likes: + one: "לייק %{count}" + two: "%{count} לייקים" + many: "%{count} לייקים" + other: "%{count} לייקים" rate_limiter: slow_down: "ביצעת את הפעולה הזאת יותר מדי פעמים, נא לנסות שוב מאוחר יותר." too_many_requests: "ביצעת את הפעולה הזאת יותר מדי פעמים. נא להמתין %{time_left} בטרם ביצוע ניסיון חוזר." @@ -715,28 +723,30 @@ he: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" - edge: "Microsoft Edge" discoursehub: "היישומון DiscourseHub" + edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "דפדפן בלתי ידוע" device: android: "מכשיר Android" + chromebook: "Chromebook" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "מכשיר נייד" - mac: "Mac" linux: "מחשב עם גנו/לינוקס" + mac: "Mac" + mobile: "מכשיר נייד" windows: "מחשב עם Windows" unknown: "מכשיר לא ידוע" os: android: "Android" + chromeos: "ChromeOS" ios: "iOS" - macos: "macOS" linux: "לינוקס" + macos: "macOS" windows: "Windows מבית Microsoft" unknown: "מערכת הפעלה בלתי מוכרת" change_email: @@ -750,6 +760,7 @@ he: description: "אנחנו שולחים כעת הודעה לכתובת הדוא״ל החדשה לאישור." associated_accounts: revoke_failed: "שלילת החשבון שלך מול %{provider_name} נכשלה." + connected: "(מחובר)" activation: action: "יש ללחוץ כאן להפעלת החשבון שלך" already_done: "סליחה, כתובת אישור החשבון הזו אינה זמינה יותר. אולי החשבון שלך כבר פעיל?" @@ -958,7 +969,7 @@ he: title: "ביקורי משתמש" xaxis: "יום" yaxis: "מספר ביקורים" - description: "מספרם של כל הביקורים הייחודיים של משתמשים." + description: "מספר כל המשתמשים המבקרים." signups: title: "הרשמות" xaxis: "יום" @@ -982,6 +993,7 @@ he: edit_reason: סיבה dau_by_mau: xaxis: "יום" + description: "מספר החברים שנכנסו ביממה האחרונה לחלק במספר החברים שנכנסו בחודש האחרון – מחזירה אחוז שמציין את ‚דבקות’ הקהילה. עדיף לכוון לערך שעולה על 30%." daily_engaged_users: title: "משתמשים מעורבים יומית" xaxis: "יום" @@ -1301,19 +1313,25 @@ he: favicon: "סמל האתר שלך, לפרטים https://he.wikipedia.org/wiki/Favicon. כדי שהסמל יפעל כראוי גם דרך CDN עליו להיות מסוג png. גודל הסמל ישתנה לכדי 32×32. בהעדר בחירה, ייעשה שימוש ב־large_icon." notification_email: "מאת: נעשה שימוש בכתובת מייל זו כאשר שולחים את כל הודעות המערכת הנדרשות. כדי שהודעות הדוא\"ל יגיעו, המתחם (domain) המצויין כאן חייב לכלול SPF, DKIM ורשומות reverse PTR מוגדרים כהלכה." email_custom_headers: "רשימה מופרדת pipes (הסימון |) של כותרות מייל מותאמות אישית" + enforce_second_factor: "מאלץ משתמשים להפעיל אימות דו־שלבי. יש לבחור ב‚הכול’ כדי לאלץ את כל המשתמשים. יש לבחור ב‚סגל’ כדי לאכוף על חברי סגל בלבד." force_https: "הכריחו את אתרכם להשתמש אך ורק ב HTTPS. אזהרה: אל תאפשרו זאת עד שתוודאו ש HTTPS מותקן ועובד ממש בכל המקרים! וידאתם את הגדרות ה CDN שלכם, כל שירותי ההתחברות, וכל הלוגואים / תלויות החיצוניים - כדי לוודא שכולם עובדים גם כן עם HTTPS?" same_site_cookies: "שימוש בעוגיות של האתר, הן מבטלות את כל הווקטורים של Cross Site Request Forgery בדפדפנים נתמכים (Lax או Strict). אזהרה: Strict יעבוד רק על אתרים שמכריכים התחברות ומשתמשים ב SSO." summary_score_threshold: "הניקוד המינימלי הנדרש כדי שפוסט ייכלל ב\"סיכום נושא זה\"" summary_posts_required: "מספר הפוסטים המנימלי בנושא לפני שהאפשרות \"סיכום נושא זה\" תתאפשר" summary_likes_required: "מינימום הלייקים לנושא לפני שהאפשרות \"סיכום נושא זה\" תתאפשר" summary_percent_filter: "כאשר משתמש/ת מקליקים על \"סיכום נושא זה\", הציגו את % הפוסטים הראשונים" + summary_max_results: "מספר הפוסטים המרבי שיוחזר על ידי ‚סיכום הנושא הזה’" enable_personal_messages: "הרשו למשתמשי רמת אמון 1 (ניתן להגדרה באמצעות רמת אמון מינימלית לשליחת הודעות) ליצור הודעות ולענות להודעות. שימו לב שהצוות תמיד יכול לשלוח הודעות, לא משנה מה." + enable_system_message_replies: "מאפשר למשתמשים להגיב להודעות מערכת אפילו כשהודעות אישיות מושבתות" enable_long_polling: "באס הודעות שמשמש להתראות יכול להשתמש בתשאול ארוך (long polling)" long_polling_base_url: "בסיס ה-URL שנמצא בשימוש עבור long polling (כאשר CDN מחזיר תוכן דינמי, זכרו להגדיר את ערך זה ל-Origin pull, דוגמת http://origin.site.com)" long_polling_interval: "כמות הזמן שהשרת צריך לחכות לפני שעונה ללקוחות, כאשר אין מידע לשליחה (משתמשים רשומים מחוברים למערכת בלבד)" polling_interval: "כאשר לא מבצעים תשאול ארוך (long polling), כל כמה זמן לקוחות מחוברים למערכת יבצעו poll, במילי-שניות" anon_polling_interval: "באיזו תכיפות לקוחות אנונימיים (anonymous clients) יבצעו תשאול (poll), במילי-שניות" background_polling_interval: "באיזו תכיפות צריכים לקוחות לבצע תשאול (poll) במילישניות (כאשר החלון נמצא ברקע)" + hide_post_sensitivity: "הסבירות שפוסט שסומן בדגל יוסתר" + silence_new_user_sensitivity: "הסבירות שמשתמש חדש יושתק עקב סימוני דגל ספאם" + auto_close_topic_sensitivity: "הסבירות שנושא שסומן בדגל ייסגר אוטומטית" cooldown_minutes_after_hiding_posts: "מספר הדקות שמשתמשים חייבים לחכות לפני שהם יכולים לערוך פוסט שהוסתר בגלל דיגלול קהילתי" max_topics_in_first_day: "הכמות המקסימלית של נושאים שמשתמשים מורשים ליצור ב 24 השעות הראשונות לאחר הפוסט הראשון שלהם" max_replies_in_first_day: "הכמות המקסימלית של תגובות שמשתמשים מורשים ליצור ב 24 השעות הראשונות אחרי יצירת הפוסט הראשון שלהם" @@ -1333,6 +1351,7 @@ he: must_approve_users: "על הצוות לאשר את כל המשתמשים החדשים לפני שהם מקבלים גישה לאתר. אזהרה: בחירה זו עבור אתר קיים תשלול גישה ממשתמשים קיימים שאינם מנהלים." pending_users_reminder_delay: "יש להודיע למפקחים אם משתמשים חדשים ממתינים לעדכון מעבר לכמות כזו של שעות. יש להגדיר ל־‎-1 כדי לנטרל את ההתראות." maximum_session_age: "משתמשים ישארו מחוברים ל n שעות מאז ביקורם האחרון" + ga_universal_domain_name: "שם המתחם של Google Universal Analytics (analytics.js), למשל: mysite.com; יש לעיין ב־https://google.com/analytics" moderators_create_categories: "לאפשר למפקחים ליצור קטגוריות חדשות" cors_origins: "מקורות שאפשר לבצע להם בקשות קרוס-דומיין (Cross origin requests). כל מקור צריך לכלול את התחילית http:// או https:// . משתנה הסביבה DISCOURCE_ENABLE_CORS חייב להיות true כדי לאפשר CORS." use_admin_ip_whitelist: "מנהלים יכולים להתחבר רק אם הכתובת שלהם מופיע ברשימת ה IPs המסוננים (ניהול > לוגים > כתובות IP מסוננות)." @@ -1346,6 +1365,7 @@ he: share_links: "החליטו אילו פריטים יופיעו בתיבת השיתוף, ובאיזה סדר." site_contact_username: "שם משתמש תקין של חבר סגל ממנו יישלחו כל ההודעות האוטומטיות. במקרה של העדר בחירה, ייעשה שימוש בחשבון בררת המחדל של המערכת." send_welcome_message: "שלחו לכל המשתמשים החדשים הודעת \"ברוכים הבאים\" עם הדרכה ראשונית כיצד להתחיל." + send_tl1_welcome_message: "שליחת הודעת קבלת פנים למשתמשים חדשים בדרגת אמון 1." suppress_reply_directly_below: "אל תציגו את סך התגובות המצטבר בפוסט כאשר ישנה תגובה ישירה אחת לפוסט זה." suppress_reply_directly_above: "אל תציגו את את אפשרות ההרחבה \"בתגובה ל..\" לפוסט כאשר יש רק תגובה אחת ישירה מעל לפוסט זה." remove_full_quote: "הסרת כל הציטוטים המלאים בתגובות ישירות." @@ -1524,12 +1544,14 @@ he: enable_mentions: "לאפשר למשתמשים לאזכר משתמשים אחרים." create_thumbnails: "יצירת תמונות מוקטנות והארת תמונות גדולות מידי מלהיכלל בפוסט." email_time_window_mins: "המתינו (n) דקות לפני משלוח כל התראת מייל, כדי לאפשר למשתמשים הזדמנות לערוך ולוודא באופן סופי את הפוסטים שלהם." + personal_email_time_window_seconds: "להמתין (n) שניות בטרם שליחת הודעות בדוא״ל עם התראות על הודעות אישיות, כדי לתת למשתמשים הזדמנות לסיים את עריכת ההודעות שלהם." email_posts_context: "כמה תגובות קודמות יש לכלול כהקשר במיילים עם התראות." flush_timings_secs: "באיזו תדירות אנחנו מזרימים מידע לשרת, בשניות." title_max_word_length: "האורך המקסימלי המותר למילה בכותרת נושא, בתווים. " title_min_entropy: "האנטרופיה (תווים ייחודים שאינם בשפת הכתיבה) המינימלית הנדרשת בכותרת נושא." body_min_entropy: "האנטרופיה (תוים ייחודיים, תווים שאינם באנגלית נחשבים יותר) המינימלית הנדרשת בגוף הפוסט." allow_uppercase_posts: "אפשרו אותיות אנגליות גדולות (Capitalized) בכותרת נושא או בגוף פוסט." + max_consecutive_replies: "מספר הפוסטים שמשתמש עושה ברצף בתוך נושא בטרם החלת איסור על הוספת תגובות." min_title_similar_length: "האורך המינימלי של כותרת לפני שהיא נבדקת עבור איתור נושאים דומים." desktop_category_page_style: "סגנון ויזואלי לדף /categories." category_colors: "רשימה של ערכי צבעים הקסדצימליים מותרים לסימון קטגוריות." @@ -1542,6 +1564,7 @@ he: max_similar_results: "כמה נושאים דומים להציג מעל לעורך כאשר מחברים נושא חדש. ההשוואה מבוססת על הכותרת וגוף הפוסט." max_image_megapixels: "מספר מקסימלי מותר של מגה-פיקסלים לתמונה." title_prettify: "מניעת טעויות נפוצות בכותרת, בכללן טעויות עם אותיות גדולות באנגלית, מספר ! ו ?, נקודה מיותרת בסוף, וכד׳" + title_remove_extraneous_space: "הסרת רווחים לפני ואחרי סימני פיסוק." topic_views_heat_low: "לאחר כמות זו של צפיות, שדה הצפיות יהיה קצת יותר בהיר." topic_views_heat_medium: "לאחר כמות כזו של צפיות, שדה הצפיות יודגש קלות." topic_views_heat_high: "לאחר כמות צפיות זו, שדה הצפיות יודגש באופן בולט." @@ -1610,7 +1633,10 @@ he: pop3_polling_host: "השרת המארח (Host) למשיכת דוא\"ל דרך POP3." pop3_polling_username: "שם המשתמש/ת לחשבון ה-POP3 לתשאול דוא\"ל." pop3_polling_password: "הסיסמה לחשבון ה-POP3 למשיכת הדוא\"ל." + log_mail_processing_failures: "לתעד את כל שגיאות עיבוד הדוא״ל אל ‎/logs" + email_in: 'לאפשר למשתמשים לפרסם נושאים חדשים באמצעות דוא״ל (נדרש תשאול ידני או דרך pop3). יש להגדיר את הכתובות בלשונית ה„הגדרות” שבכל קטגוריה.' email_in_min_trust: "רמת האמון המינימלית הנדרשת למשתמשים כדי שיוכלו להעלות נושאים חדשים באמצעות הדוא\"ל." + email_in_spam_header: "כותרת הודעת הדוא״ל לאיתור ספאם." email_prefix: "ה[תווית] שתשמש כנושא של מיילים. אם לא יוגדר, ברירת המחדל תכוון ל'כותרת' אם לא יוגדר אחרת." email_site_title: "הכותרת של האתר שתשמש כשם השולח של דוא\"ל מהאתר. במידה ולא יוגדר ערך, תכוון ברירת המחדל ל\"כותרת\". אם ה\"כותרת\" שלכם מכילה תוים שאינם מותרים לשימוש במחרוזות \"שם השולח\" בדוא\"ל, השתמשו בהגדרה זו." minimum_topics_similar: "כמה נושאים צריכים להתקיים לפני שנושאים דומים יוצגו בעת חיפוש נושא חדש." @@ -1634,8 +1660,11 @@ he: email_link_color: "צבע הקישורים במיילים מסוג HTML. הכניסו שם צבע ('blue') או ערך הקסדצימלי ('#0000FF')." detect_custom_avatars: "האם לבדוק או לא לבדוק שמשתמשים העלו תמונות פרופיל אישיות." max_daily_gravatar_crawls: "מספר הפעמים המקסימלי ש-Discourse יבדוק אווטרים ב-Gravatar ביום" + public_user_custom_fields: "רשימה של שדות מותאמים לבחירת המשתמש שניתן לאחזר באמצעות ה־API." + staff_user_custom_fields: "רשימה של שדות מותאמים לבחירת המשתמש שחברי הסגל יכולים לאחזר באמצעות ה־API." enable_user_directory: "ספקו ספריייה של משתמשים לגלישה" enable_group_directory: "ספקו ספריה של קבוצות לסיור" + enable_category_group_review: "לאפשר קבוצות כדי לסקור תוכן בקטגוריות מסוימות" allow_anonymous_posting: "אפשרו למשתמשים לעבור למצב אנונימי" anonymous_posting_min_trust_level: "רמת האמון המינמלית הנדרשת כדי לאפשר פרסום אנונימי" anonymous_account_duration_minutes: "בכדי להגן על האנונימות צרו חשבון אנונימי כל N דקות לכל משתמש. לדוגמה: אם מכוון ל-600, כאשר יעברו 600 דקות מהפרסום האחרון והמשתמש/ת יחליפו לזהות אנונימית, חשבון אנונימי חדש יווצר." @@ -1648,7 +1677,9 @@ he: dominating_topic_minimum_percent: "איזה אחוז מהפוסטים משתמש צריך לייצר בנושא לפני שיקבל תזכורת לגבי שתלטנות יתר על הנושא." disable_avatar_education_message: "ניטרול הודעה שמלמדת על שינוי דמות." suppress_uncategorized_badge: "אל תציגו את העיטור לנושאים נטולי קטגוריה ברשימת הנושאים." + header_dropdown_category_count: "כמה קטגוריות ניתן להציג בתפריט הנגלל בכותרת." permalink_normalizations: "החילו את הביטויים הרגולריים האלו לפני שמתאימים קישורים-קבועים, למשל: /(topic.*)\\?.*/\\1 יסיר מחרוזות שאילתה מנתיבי נושאים. הפורמט הוא regex+string משתמש ב \\1 וכד׳ כדי לגשת להתאמות" + global_notice: "הצגת מודעה גלובלית דחופה בגדר חירום לכל המבקרים, יש להחליף בתוכן ריק כדי להסתיר אותה (מותר HTML)." disable_edit_notifications: "ביטול התראות עריכה על ידי משתמש המערכת כאשר 'download_remote_images_to_local' פעיל." automatically_unpin_topics: "הסרת נעיצה אוטומטית של נושאים כאשר המשתמשים מגיעים לתחתית." read_time_word_count: "מספר המילים לדקה כדי להעריך את זמן הקריאה." @@ -3195,6 +3226,8 @@ he: placeholder: "סן פרנסיסקו, קליפורניה" colors: title: "תמה" + themes_further_reading: + title: "תמות" logos: title: "לוגואים" fields: diff --git a/config/locales/server.hu.yml b/config/locales/server.hu.yml index 6e17aaadc5..a0c9472fd1 100644 --- a/config/locales/server.hu.yml +++ b/config/locales/server.hu.yml @@ -1077,6 +1077,8 @@ hu: title: "Szervezet" colors: title: "Téma" + themes_further_reading: + title: "Témák" logos: title: "Logók" fields: diff --git a/config/locales/server.hy.yml b/config/locales/server.hy.yml index f2873e5843..c650cd44cd 100644 --- a/config/locales/server.hy.yml +++ b/config/locales/server.hy.yml @@ -597,27 +597,27 @@ hy: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "անհայտ բրաուզեր" device: android: "Android Սարքավորում" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "Մոբայլ-Սարքավորում" - mac: "Mac" linux: "GNU/Linux Համակարգիչ" + mac: "Mac" + mobile: "Մոբայլ-Սարքավորում" windows: "Windows Համակարգիչ" unknown: "անհայտ սարքավորում" os: android: "Android" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "անհայտ օպերացիոն համակարգ" change_email: @@ -825,7 +825,6 @@ hy: title: "Օգտատիրոջ Այցելություններ" xaxis: "Օր" yaxis: "Այցելությունների քանակ" - description: "Բոլոր եզակի օգտատերերի այցելությունների քանակ" signups: title: "Գրանցումներ" xaxis: "Օր" @@ -1216,7 +1215,6 @@ hy: must_approve_users: "Անձնակազմը պետք է հաստատի բոլոր նոր օգտատերերի հաշիվները՝ մինչ կայքին նրանց հասանելիության թույլտվություն տալը: ՈՒՇԱԴՐՈՒԹՅՈՒՆ՝ այժմեական (live) կայքի համար սա միացնելը հետ կկանչի թույլտվությունը գոյություն ունեցող ոչ անձնակազմի անդամ օգտատերերի համար!" pending_users_reminder_delay: "Ծանուցել մոդերատորներին, եթե նոր օգտատերերը սպասում են հաստատման ավելի երկար, քան այսքան ժամ: Սահմանեք -1՝ ծանուցւոմները անջատելու համար:" maximum_session_age: "Օգտատերը կմնա մուտքագրված վերջին այցելությունից n ժամ անց" - ga_universal_tracking_code: "Google Universal Analytics (analytics.js) դիտման կոդի կոդ, օրինակ՝ UA-12345678-9; այցելեք https://google.com/analytics" ga_universal_domain_name: "Google Universal Analytics (analytics.js) դոմենի անուն, օրինակ՝ mysite.com; այցելեք https://google.com/analytics" ga_universal_auto_link_domains: "Միացնել Google Universal Analytics (analytics.js) խաչաձև դոմենի դիտումը: Այս դոմնեների ելքային հղումները կունենան իրենց ավելացված հաճախորդի id: Այցելեք Google's Cross-Domain Tracking guide." gtm_container_id: "Google Tag Manager container id. eg: GTM-ABCDEF.
Note: third-party scripts loaded by GTM may need to be whitelisted in 'content security policy script src'." @@ -3393,6 +3391,8 @@ hy: placeholder: "Սան-Ֆրանցիսկո, Կալիֆորնիա" colors: title: "Թեմա" + themes_further_reading: + title: "Թեմաներ" logos: title: "Լոգոներ" fields: diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index 587a341b9b..7c0b5355f4 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -583,27 +583,27 @@ it: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "browser sconosciuto" device: android: "Dispositivo Android" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "Dispositivo Mobile" - mac: "Mac" linux: "GNU/Linux Computer" + mac: "Mac" + mobile: "Dispositivo Mobile" windows: "Windows Computer" unknown: "dispositivo sconosciuto" os: android: "Android" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "sistema operativo sconosciuto" change_email: @@ -849,6 +849,7 @@ it: labels: term: Termine searches: Ricerche + click_through: CTR emails: title: "Email Inviate" xaxis: "Giorno" @@ -1476,6 +1477,12 @@ it: different_topics: "I messaggi appartenenti a diversi argomenti non possono essere fusi." different_users: "I messaggi appartenenti a diversi utenti non possono essere fusi." move_posts: + new_topic_moderator_post: + one: "Un messaggio è stato spostato in un nuovo argomento: %{topic_link}" + other: "%{count} messaggi sono stati spostati in un nuovo Argomento: %{topic_link}" + new_message_moderator_post: + one: "Un messaggio è stato spostato in un nuovo messaggio ai moderatori: %{topic_link}" + other: "%{count} messaggi sono stati spostati in un nuovo messaggio ai moderatori: %{topic_link}" existing_topic_moderator_post: one: "Un messaggio è stato unito ad un Argomento esistente: %{topic_link}" other: "%{count} messaggi sono stati uniti ad un Argomento esistente: %{topic_link}" @@ -2876,6 +2883,8 @@ it: title: "Società" colors: title: "Tema" + themes_further_reading: + title: "Temi" logos: title: "Loghi" fields: @@ -2922,10 +2931,12 @@ it: reviewables: priorities: low: "Bassa" + medium: "Media" high: "Alta" sensitivity: disabled: "Disabilitato" low: "Bassa" + medium: "Media" high: "Alta" reasons: trust_level: "Le risposte degli utenti con un basso Livello di Esperienza devono essere approvate dallo Staff. Vedi `approve_unless_trust_level`." diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index fc7c0733f2..365097a62b 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -1565,7 +1565,7 @@ ja: このバッジは初めてメール :e-mail: 経由で返信した人に付与されます new_user_of_the_month: name: "一ヶ月の新規ユーザー" - description: "最初の一ヶ月間の功績\U0001F44C" + description: ! "最初の一ヶ月間の功績\U0001F44C" long_description: "このバッジは毎月二人のユーザーに付与されるバッジで、素晴らしい貢献をコミュニティーにもたらしたことを讃えるものです!おめでとうございます!\U0001F381\n" enthusiast: name: 熱狂者 diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index c2a68daac7..20f4c84f4b 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -483,27 +483,27 @@ ko: user_auth_tokens: browser: chrome: "구글 크롬" - safari: "사파리" - firefox: "파이어폭스" - opera: "오페라" - ie: "인터넷 익스플로러" edge: "마이크로소프트 엣지" + firefox: "파이어폭스" + ie: "인터넷 익스플로러" + opera: "오페라" + safari: "사파리" unknown: "알수없는 브라우저" device: android: "안드로이드 기기" ipad: "아이패드" iphone: "아이폰" ipod: "아이팟" - mobile: "모바일 기기" - mac: "맥" linux: "GNU/리눅스 컴퓨터" + mac: "맥" + mobile: "모바일 기기" windows: "윈도우 컴퓨터" unknown: "알수없는 디바이스" os: android: "안드로이드" ios: "iOS" - macos: "맥OS" linux: "리눅스" + macos: "맥OS" windows: "마이크로소프트 윈도우" unknown: "알수없는 운영체제" change_email: @@ -1896,6 +1896,8 @@ ko: title: "소속" colors: title: "테마" + themes_further_reading: + title: "테마" logos: title: "로고" fields: diff --git a/config/locales/server.lt.yml b/config/locales/server.lt.yml index cb2792dab4..8bfffc9559 100644 --- a/config/locales/server.lt.yml +++ b/config/locales/server.lt.yml @@ -1019,6 +1019,8 @@ lt: title: "Organizacija" colors: title: "Tema" + themes_further_reading: + title: "Temos" logos: title: "Logotipas" fields: diff --git a/config/locales/server.lv.yml b/config/locales/server.lv.yml index 56a6163f22..a9935cbe8c 100644 --- a/config/locales/server.lv.yml +++ b/config/locales/server.lv.yml @@ -329,6 +329,8 @@ lv: title: "Organizācija" colors: title: "Tēma" + themes_further_reading: + title: "Dizaini" homepage: fields: homepage_style: diff --git a/config/locales/server.nb_NO.yml b/config/locales/server.nb_NO.yml index 171fd9d958..6223d4fdef 100644 --- a/config/locales/server.nb_NO.yml +++ b/config/locales/server.nb_NO.yml @@ -1474,6 +1474,8 @@ nb_NO: title: "Organisasjon" colors: title: "Drakt" + themes_further_reading: + title: "Drakter" logos: title: "Logoer" fields: diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index 512ecf0409..2a9a72a804 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -662,28 +662,30 @@ nl: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" - edge: "Microsoft Edge" discoursehub: "DiscourseHub-app" + edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "onbekende browser" device: android: "Android-apparaat" + chromebook: "Chromebook" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "Mobiel apparaat" - mac: "Mac" linux: "GNU/Linux-computer" + mac: "Mac" + mobile: "Mobiel apparaat" windows: "Windows-computer" unknown: "onbekend apparaat" os: android: "Android" + chromeos: "ChromeOS" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "onbekend besturingssysteem" change_email: @@ -697,6 +699,7 @@ nl: description: "We sturen nu ter bevestiging een e-mail naar uw nieuwe adres." associated_accounts: revoke_failed: "Intrekken van uw account bij %{provider_name} is mislukt." + connected: "(verbonden)" activation: action: "Klik hier om uw account te activeren" already_done: "Sorry, deze koppeling voor het bevestigen van uw account is niet meer geldig. Misschien is uw account al actief?" @@ -905,7 +908,6 @@ nl: title: "Gebruikersbezoeken" xaxis: "Dag" yaxis: "Aantal bezoeken" - description: "Aantal alle unieke gebruikersbezoeken." signups: title: "Registraties" xaxis: "Dag" @@ -1285,6 +1287,9 @@ nl: polling_interval: "Als geen 'long polling' wordt toegepast, hoe vaak aangemelde clients moeten pollen in milliseconden" anon_polling_interval: "Hoe vaak anonieme clients moeten pollen in milliseconden" background_polling_interval: "Hoe vaak clients moeten pollen in milliseconden (als het venster in de achtergrond staat)" + hide_post_sensitivity: "De waarschijnlijkheid dat een gemarkeerd bericht wordt verborgen" + silence_new_user_sensitivity: "De waarschijnlijkheid dat een nieuwe gebruiker wordt gedempt op basis van spammarkeringen" + auto_close_topic_sensitivity: "De waarschijnlijkheid dat een gemarkeerd topic automatisch wordt gesloten" cooldown_minutes_after_hiding_posts: "Het aantal minuten dat een gebruiker moet wachten voordat deze een via de gemeenschap gemarkeerd bericht kan bewerken" max_topics_in_first_day: "Het maximale aantal topics dat een gebruiker in de 24 uursperiode na het schrijven van een eerste bericht mag aanmaken" max_replies_in_first_day: "Het maximale aantal antwoorden dat een gebruiker in de 24 uursperiode na het schrijven van een eerste bericht mag aanmaken" @@ -1304,7 +1309,6 @@ nl: must_approve_users: "Stafleden moeten alle nieuwe gebruikersaccounts goedkeuren voordat ze de website mogen bezoeken. WAARSCHUWING: als dit voor een actieve website wordt ingeschakeld, wordt alle toegang voor bestaande niet-stafleden ingetrokken!" pending_users_reminder_delay: "Moderators inlichten als nieuwe gebruikers langer dan dit aantal uren op goedkeuring wachten. Stel dit in op -1 om meldingen uit te schakelen." maximum_session_age: "Gebruikers blijven (n) uur na hun laatste bezoek aangemeld" - ga_universal_tracking_code: "Google Universal Analytics (analytics.js)-trackingcode, bv. UA-12345678-9; zie https://google.com/analytics" ga_universal_domain_name: "Google Universal Analytics (analytics.js)-domeinnaam, bv. mysite.com; zie https://google.com/analytics" ga_universal_auto_link_domains: "Google Universal Analytics (analytics.js)-cross-domain-tracking inschakelen. Aan uitgaande koppelingen naar deze domeinen wordt de client-ID toegevoegd. Zie Google's Cross-Domein Tracking-handleiding." gtm_container_id: "Google Tag Manager-container-ID, bv. GTM-ABCDEF.
Opmerking: scripts van derden die door GTM worden geladen, dienen mogelijk op de whitelist te worden geplaatst in 'content security policy script src'." @@ -1542,6 +1546,7 @@ nl: max_similar_results: "Hoeveel vergelijkbare topics om boven de editor te tonen bij het opstellen van een nieuw topic. Vergelijking is gebaseerd op titel en inhoud." max_image_megapixels: "Maximale aantal toegestane megapixels voor een afbeelding." title_prettify: "Veelvoorkomende fouten in titels voorkomen, waaronder alles in hoofdletters, eerste woord zonder hoofdletter, meerdere ! en ?, een extra . aan het eind, etc." + title_remove_extraneous_space: "Voorloopspaties voor de eindinterpunctie verwijderen." topic_views_heat_low: "Na dit aantal weergaven wordt het veld 'weergaven' licht gemarkeerd." topic_views_heat_medium: "Na dit aantal weergaven wordt het veld 'weergaven' middelmatig gemarkeerd." topic_views_heat_high: "Na dit aantal weergaven wordt het veld 'weergaven' sterk gemarkeerd." @@ -1742,6 +1747,7 @@ nl: default_other_notification_level_when_replying: "Globale standaard meldingsniveau wanneer de gebruiker op een topic antwoordt." default_other_external_links_in_new_tab: "Externe koppelingen standaard in een nieuw tabblad openen." default_other_enable_quoting: "Antwoord-met-citaat voor gemarkeerde tekst standaard inschakelen." + default_other_enable_defer: "Topicnegeerfunctionaliteit standaard inschakelen." default_other_dynamic_favicon: "Aantal nieuwe / bijgewerkte topics standaard tonen op browserpictogram." default_other_like_notification_frequency: "Gebruikers standaard informeren bij likes" default_topics_automatic_unpin: "Topics standaard automatisch losmaken wanneer de gebruiker de onderkant bereikt." @@ -1773,6 +1779,7 @@ nl: allow_staff_to_tag_pms: "Stafleden mogen persoonlijke berichten taggen" min_trust_level_to_tag_topics: "Het minimale vertrouwensniveau dat nodig is om topics te taggen" suppress_overlapping_tags_in_list: "Als tags exacte overeenkomsten met woorden in topictitels hebben, de tag niet tonen" + remove_muted_tags_from_latest: "Topics met alleen gedempt-tags niet in de lijst met nieuwste topics tonen." force_lowercase_tags: "Gebruik van kleine letters voor alle nieuwe tags afdwingen." company_name: "Bedrijfsnaam" governing_law: "Toepasselijk recht" @@ -2404,6 +2411,23 @@ nl: name: Zeer populaire koppeling famous_link: name: Uiterst populaire koppeling + appreciated: + name: Gewaardeerd + respected: + name: Gerespecteerd + thank_you: + name: Bedankt + empathetic: + name: Empathisch + description: Heeft 500 geliked en 1000 likes gegeven + first_emoji: + name: Eerste emoji + description: Een emoji in een bericht gebruikt + first_reply_by_email: + name: Eerste antwoord via e-mail + description: Bericht beantwoord via e-mail + new_user_of_the_month: + name: "Nieuwe gebruiker van de maand" badge_title_metadata: "%{display_name}-badge op %{site_title}" admin_login: success: "E-mail verstuurd" @@ -2420,13 +2444,19 @@ nl: congratulations: "Gefeliciteerd, u hebt Discourse geïnstalleerd!" register: button: "Registreren" + title: "Beheerdersaccount registreren" + help: "Registreer een nieuw account om te beginnen" + confirm_email: + title: "Bevestig uw e-mailadres" resend_email: title: "Activeringsmail opnieuw versturen" message: "

We hebben de activeringsmail opnieuw naar %{email} verstuurd" safe_mode: + title: "Veilige modus starten" no_customizations: "Huidige thema uitschakelen" only_official: "Niet-officiële plug-ins uitschakelen" no_plugins: "Alle plug-ins uitschakelen" + enter: "Veilige modus starten" wizard: step: locale: @@ -2471,6 +2501,8 @@ nl: placeholder: "San Francisco, Californië" colors: title: "Thema" + themes_further_reading: + title: "Thema's" logos: title: "Logo's" fields: @@ -2595,9 +2627,20 @@ nl: title: "Negeren" approve: title: "Goedkeuren" + approve_post: + title: "Bericht goedkeuren" + reject_post: + title: "Bericht weigeren" + approve_user: + title: "Gebruiker goedkeuren" reject_user: + title: "Gebruiker verwijderen..." delete: title: "Gebruiker verwijderen" + description: "De gebruiker zal van het forum verwijderd worden." + block: + title: "Gebruiker verwijderen en blokkeren" + description: "De gebruiker zal verwijderd worden en we zullen het IP-adres en e-mailadres blokkeren." reject: title: "Weigeren" delete_user: diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index 719a05ada0..2728353962 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -2518,6 +2518,8 @@ pl_PL: title: "Organizacja" colors: title: "Motyw" + themes_further_reading: + title: "Motywy" logos: title: "Loga" fields: diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 23bb0f6819..474807ca52 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -583,6 +583,8 @@ pt: every_hour: "de hora em hora" daily: "diariamente" weekly: "semanalmente" + every_month: "cada mês" + every_six_months: "a cada seis meses" user_api_key: title: "Autorizar o acesso da aplicação" authorize: "Autorizar" diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index 34ba206b28..58b685cd49 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -2938,6 +2938,8 @@ pt_BR: title: "Organização" colors: title: "Tema" + themes_further_reading: + title: "Temas" logos: title: "Logotipos" fields: diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml index 39fd845b41..d0795c0b0e 100644 --- a/config/locales/server.ro.yml +++ b/config/locales/server.ro.yml @@ -2019,6 +2019,8 @@ ro: title: "Organizație" colors: title: "Temă" + themes_further_reading: + title: "Teme" logos: title: "Logo-uri" fields: diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index 4ab3749a60..58f9f03c87 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -16,6 +16,7 @@ ru: short_no_year: "%d %B" date_only: "%d.%m.%Y" long: "%d.%m.%Y %H:%M:%S" + no_day: "%B %Y" date: month_names: [~, Январь, Февраль, Март, Апрель, Май, Июнь, Июль, Август, Сентябрь, Октябрь, Ноябрь, Декабрь] <<: *datetime_formats @@ -39,6 +40,8 @@ ru: themes: bad_color_scheme: "Не удается обновить тему, недопустимая цветовая палитра" other_error: "При обновлении темы оформления что-то пошло не так." + compile_error: + unrecognized_extension: "Нераспознанное расширение файла: %{extension}" import_error: generic: Произошла ошибка при импорте этой темы about_json: "Ошибка импорта: about.json не существует или является недопустимым" @@ -172,9 +175,11 @@ ru: <<: *errors invite: not_found: "Неверный код приглашения. Пожалуйста, свяжитесь с администратором сайта." + error_message: "При приеме приглашения произошла ошибка. Пожалуйста, свяжитесь с администратором сайта." user_exists: "Нет необходимости приглашать %{email}, у него уже есть аккаунт!" bulk_invite: file_should_be_csv: "Загружаемый файл должен быть в csv-формате." + max_rows: "Максимум 50 000 приглашений могут быть отправлены одновременно. Попробуйте разделить файл на более мелкие части." error: "Произошла ошибка при загрузке файла. Пожалуйста, повторите попытку позже." topic_invite: user_exists: "К сожалению, этот пользователь уже был приглашён. Вы можете пригласить пользователя в тему только один раз." @@ -185,6 +190,7 @@ ru: invalid_filename: "Резервная копия файла содержит недопустимые символы. Допустимые символы A-Z 0-9. - _." file_exists: "Файл, который вы пытаетесь загрузить, уже существует." location: + local: "Local" s3: "Amazon S3" invalid_params: "Вы задали неверные параметры запроса: %{message}" not_logged_in: "Для этого действия необходимо зарегистрироваться." @@ -195,8 +201,10 @@ ru: provider_not_enabled: "Вам не разрешено просматривать запрошенный ресурс. Поставщик аутентификации не включен." provider_not_found: "Вам не разрешено просматривать запрошенный ресурс. Поставщик аутентификации не существует." read_only_mode_enabled: "Сайт в режиме только для чтения. Какие-либо иные действия запрещены. " + email_template_cant_be_modified: "Этот шаблон e-mail почты не может быть изменен" + invalid_whisper_access: "Либо шепот не включен, либо у вас нет доступа к созданию сообщений шепота" reading_time: "Время на прочтение" - likes: "Лайков" + likes: "Симпатий" too_many_replies: one: "Извините, новые пользователи могут оставлять только один ответ к теме." few: "Извините, новые пользователи могут оставлять только %{count} ответов к одной теме." @@ -261,6 +269,7 @@ ru: few: "Извините, новые пользователи могут размещать только %{count} ссылок в сообщении." many: "Извините, новые пользователи могут размещать только %{count} ссылок в сообщении." other: "Извините, новые пользователи могут размещать только %{count} ссылок в сообщении." + contains_blocked_words: "Ваш пост содержит слово, которое запрещено: %{word}" spamming_host: "Извините, вы не можете разместить ссылку в этом сообщении." user_is_suspended: "Заблокированным пользователям запрещено писать." topic_not_found: "Что-то пошло не так. Возможно, эта тема была закрыта или заархивирована, пока вы ее читали?" @@ -304,8 +313,20 @@ ru: revert_version_same: "Текущая версия такая же, как та, которую вы хотите вернуть." excerpt_image: "изображение" groups: + success: + bulk_add: + one: "%{count} пользователь был добавлен в группу." + few: "%{count} пользователей были добавлены в группу." + many: "%{count} пользователей были добавлены в группу." + other: "%{count} пользователя были добавлены в группу." errors: + grant_trust_level_not_valid: "'%{trust_level}' недопустимый уровень доверия." can_not_modify_automatic: "Вы не можете изменить автоматическую группу." + member_already_exist: + one: "'%{username}' уже является членом этой группы." + few: "Следующие пользователи уже являются членами этой группы:%{username}" + many: "Следующие пользователи уже являются членами этой группы: %{username}" + other: "Следующие пользователи уже являются членами этой группы: %{username}" invalid_domain: "'%{domain}' является некорректным доменом." invalid_incoming_email: "'%{email}' не является корректным адресом электронной почты" email_already_used_in_group: "'%{email}' уже используется группой '%{group_name}'." @@ -433,6 +454,10 @@ ru: attributes: execute_at: in_the_past: "должно быть в будущем." + translation_overrides: + attributes: + value: + invalid_interpolation_keys: 'Следующий ключ(и) интерполяции недопустимы: "%{keys}"' watched_word: attributes: word: @@ -464,6 +489,7 @@ ru: description_incomplete: "Описание категории должна иметь по крайней мере один абзац." disallowed_topic_tags: "В этой теме есть теги, не разрешенные этой категорией: '%{tags}'" cannot_delete: + uncategorized: "Эта категория особенная. Он предназначен для хранения тем, которые не имеют категории; она не может быть удалена." has_subcategories: "Невозможно удалить этот раздел, т.к. в нем есть подразделы." topic_exists_no_oldest: "Невозможно удалить этот раздел, т.к. количество тем в нем равно %{count}." uncategorized_description: "Темы, которым не нужен раздел, или которые не попадают ни в один из существующих разделов." @@ -482,6 +508,11 @@ ru: post: image_placeholder: broken: "Это изображение сломано" + has_likes: + one: "%{count} Симпатия" + few: "%{count} Симпатий" + many: "%{count} Симпатий" + other: "%{count} Симпатий" rate_limiter: slow_down: "Вы выполнили это действие слишком много раз, повторите попытку позже." by_type: @@ -633,30 +664,35 @@ ru: title: "Сбросить пароль" success: "Смена пароля прошла успешно, вы вошли на сайт." success_unapproved: "Смена пароля прошла успешно." + email_login: + title: "Вход по E-mail" user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" + discoursehub: "Приложение DiscourseHub" edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "неизвестный браузер" device: android: "Android Устройство" + chromebook: "Chromebook" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "Мобильное устройство" - mac: "Mac" linux: "GNU/Linux Компьютер" + mac: "Mac" + mobile: "Мобильное устройство" windows: "Windows Компьютер" unknown: "неизвестное устройство" os: android: "Android" + chromeos: "ChromeOS" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "неизвестная операционная система" change_email: @@ -670,6 +706,7 @@ ru: description: "Мы отправили сообщение с ссылкой для подтверждения вашего нового адреса." associated_accounts: revoke_failed: "Не удалось отозвать учетную запись %{provider_name}." + connected: "(связанный)" activation: action: "Нажмите сюда, чтобы активировать вашу учетную запись" already_done: "Извините, ссылка на активацию учетной записи устарела. Возможно, ваша учетная запись уже активирована?" @@ -760,6 +797,8 @@ ru: email_body: "%{link}\n\n%{message}" flagging: user_must_edit: "

На это сообщение поступили жалобы от участников сообщества, поэтому оно временно скрыто.

" + ignored: + hidden_content: "

Проигнорированное содержание

" archetypes: regular: title: "Обычная тема" @@ -768,6 +807,8 @@ ru: message: make: "Эта тема теперь является объявлением и будет отображаться сверху на всех страницах, пока пользователи не скроют его, каждый сам для себя." remove: "Эта тема теперь не является объявлением и больше не будет отображаться вверху всех страниц." + unsubscribed: + title: "Настройки E-mail обновлены!" unsubscribe: title: "Отписаться" stop_watching_topic: "Прекратить наблюдение за этой темой, %{link}" @@ -777,7 +818,10 @@ ru: all: "Не отправлять мне почту с %{sitename}" not_found_description: "Извините, мы не можем отписать вас от рассылки. Возможно, ссылка, полученная вами по электронной почте, уже недействительна." log_out: "Выйти" + submit: "Сохранить настройки" digest_frequency: + title: "Вы получаете сводные сообщения E-mail почты %{frequency}" + select_title: "Установите частоту отправки E-mail писем:" never: "никогда" every_30_minutes: "каждые 30 минут" every_hour: "каждый час" @@ -800,6 +844,10 @@ ru: session_info: "Прочитать информацию о сеансе пользователя" read: "Прочитать всё" write: "Написать всё" + one_time_password: "Создать токен для одноразового входа" + invalid_public_key: "Извините, открытый ключ недействителен." + invalid_auth_redirect: "Извините, этот хост auth_redirect не разрешен." + invalid_token: "Токен отсутствует, недействителен или просрочен." flags: errors: already_handled: "Жалоба уже была обработана" @@ -810,12 +858,15 @@ ru: percent: Процентов day: День post_edits: + title: "Редактировать Пост" labels: post: Пост editor: Редактор author: Автор edit_reason: Причина + description: "Количество новых пост-правок." user_flagging_ratio: + title: "Коэффициент Пометки Пользователя" labels: user: Пользователь agreed_flags: Согласованные жалобы @@ -831,6 +882,9 @@ ru: time_read: Время чтения topic_count: Созданных тем post_count: Созданных постов + pm_count: PMs создан + revision_count: Редакция + description: "Список действий модератора, включая просмотренные флаги, время чтения, созданные темы, созданные сообщения, личные сообщения и изменения." flags_status: title: "Статус Флагов" values: @@ -841,11 +895,12 @@ ru: labels: flag: Тип assigned: Назначенные темы + time_to_resolution: Время разрешения visits: title: "Визиты форумчан" xaxis: "Дата" yaxis: "Количество визитов" - description: "Количество всех уникальных посещений пользователя." + description: "Количество всех посещений пользователей." signups: title: "Регистрация" xaxis: "Дата" @@ -857,6 +912,7 @@ ru: yaxis: "Количество новых участников" description: "Количество пользователей, которые сделали свой первый пост в этот период." consolidated_page_views: + title: "Консолидированные просмотры страниц" xaxis: page_view_crawler: "Сканеры" page_view_anon: "Анонимный пользователь" @@ -869,21 +925,30 @@ ru: author: Автор edit_reason: Причина dau_by_mau: + title: "DAU/MAU" xaxis: "День" + yaxis: "DAU/MAU" + description: "Количество участников, которые вошли в систему в последний день, поделенное на количество участников, которые вошли в систему в прошлом месяце, возвращает %, который указывает на 'липкость' сообщества. Стремитесь к >30%." daily_engaged_users: + title: "Ежедневно Вовлеченные Пользователи" xaxis: "День" + yaxis: "Вовлеченные Пользователи" + description: "Количество пользователей, которым нравились сообщения или они публиковали их в последний день." profile_views: title: "Просмотры профилей" xaxis: "Дата" yaxis: "Количество просмотров профилей пользователей" + description: "Всего новых просмотров профилей пользователей." topics: title: "Темы" xaxis: "Дата" yaxis: "Количество новых тем" + description: "Новые темы, созданные за этот период." posts: title: "Сообщения" xaxis: "Дата" yaxis: "Количество новых сообщений" + description: "Новые посты, созданные за этот период" likes: title: "Симпатии" xaxis: "Дата" @@ -893,15 +958,21 @@ ru: title: "Жалобы" xaxis: "Дата" yaxis: "Количество поданных жалоб" + description: "Количество новых жалоб." bookmarks: title: "Закладки" xaxis: "Дата" yaxis: "Количество новых закладок" + description: "Количество новых тем и постов, добавленных в закладки." users_by_trust_level: title: "Пользователи по уровню доверия" xaxis: "Уровень доверия" yaxis: "Количество пользователей" + labels: + level: Уровень + description: "Количество пользователей, сгруппированных по уровню доверия." users_by_type: + title: "Пользователи по Типу" xaxis: "Тип" yaxis: "Количество пользователей" labels: @@ -911,37 +982,49 @@ ru: moderator: Модератор suspended: Приостановлен silenced: Отключенный + description: "Количество пользователей, сгруппированных по администраторам, модераторам, приостановлено и отключено." trending_search: + title: Тенденции Поиска labels: term: Правило searches: Поиски click_through: CTR + description: "Самые популярные поисковые термины с их рейтингом кликов." emails: title: "Электронные письма" xaxis: "Дата" yaxis: "Количество электронных писем" + description: "Количество новых отправленных писем." user_to_user_private_messages: + title: "Пользователь-Пользователь (за исключением ответов)" xaxis: "Дата" yaxis: "Количество сообщений" + description: "Количество новых личных сообщений." user_to_user_private_messages_with_replies: + title: "Пользователь-Пользователь (с ответами)" xaxis: "День" yaxis: "Количество сообщений" + description: "Количество всех новых личных сообщений и ответов." system_private_messages: title: "Системные сообщения" xaxis: "Дата" yaxis: "Количество сообщений" + description: "Количество личных сообщений, отправляемых системой автоматически." moderator_warning_private_messages: title: "Предупреждения модераторов" xaxis: "Дата" yaxis: "Количество предупреждений" + description: "Количество предупреждений, отправленных личными сообщениями от модераторов." notify_moderators_private_messages: title: "Жалобы модераторам" xaxis: "Дата" yaxis: "Количество жалоб" + description: "Количество раз, когда модераторы были конфиденциально уведомлены флагом." notify_user_private_messages: title: "Жалобы пользователям" xaxis: "День" yaxis: "Количество жалоб" + description: "Количество раз, когда пользователи были конфиденциально уведомлены флагом." top_referrers: title: "Топ распространителей" xaxis: "Пользователь" @@ -951,6 +1034,7 @@ ru: user: "Пользователь" num_clicks: "Заходов" num_topics: "Темы" + description: "Пользователи перечислены по количеству кликов по ссылкам, которыми они поделились." top_traffic_sources: title: "Топ источников трафика" xaxis: "Домен" @@ -961,19 +1045,23 @@ ru: domain: Домен num_clicks: Заходов num_topics: Темы + description: "Внешние источники, которые связаны с этим сайтом больше всего." top_referred_topics: title: "Топ тем, на которые ссылаются" labels: num_clicks: "Заходов" topic: "Тема" + description: "Темы, которые получили наибольшее количество кликов из внешних источников." page_view_anon_reqs: title: "Анонимы" xaxis: "Дата" yaxis: "Гостевых просмотров страниц" + description: "Количество новых просмотров страниц посетителями, не вошедшими в учетную запись." page_view_logged_in_reqs: title: "Форумчане" xaxis: "Дата" yaxis: "Авторизованных просмотров страниц" + description: "Количество новых просмотров страниц от зарегистрированных пользователей." page_view_crawler_reqs: title: "Просмотров страниц поисковыми системами" xaxis: "Дата" @@ -993,6 +1081,7 @@ ru: title: "Гостевых просмотров страниц" xaxis: "Дата" yaxis: "Гостевых просмотров страниц с мобильных устройств" + description: "Количество новых просмотров страниц от посетителей на мобильном устройстве, которые не вошли в систему." http_background_reqs: title: "Фоновые" xaxis: "Дата" @@ -1021,21 +1110,28 @@ ru: title: "Время до первого ответа" xaxis: "Дата" yaxis: "Среднее время, часов" + description: "Среднее время (в часах) первого ответа на новые темы." topics_with_no_response: title: "Темы без ответов" xaxis: "Дата" yaxis: "Всего" + description: "Количество созданных новых тем, которые не получили ответа." mobile_visits: title: "Посещения пользователей (мобильные)" xaxis: "Дата" yaxis: "Количество визитов" + description: "Количество уникальных пользователей, которые посетили с помощью мобильного устройства." web_crawlers: + title: "Агенты Пользователей Web Crawler" labels: user_agent: "User Agent" page_views: "Просмотров" + description: "Список пользовательских агентов, отсортированный по просмотрам страниц." suspicious_logins: + title: "Подозрительные Логины" labels: user: Пользователь + client_ip: IP-адрес клиента location: Местонахождение browser: Броузер device: Устройство @@ -1047,6 +1143,7 @@ ru: labels: user: Пользователь location: Местоположение + login_at: Войти на description: "Список времени входа администратора с местоположениями." top_uploads: title: "Лучшие Загрузки" @@ -1057,28 +1154,49 @@ ru: filesize: Размер файла description: "Список всех загрузок по расширению, размеру файла и автору." top_ignored_users: + title: "Топ Игнорируемых / Приглушенных Пользователей" labels: ignored_user: Игнорируемый Пользователь + description: "Пользователи, которые были отключены и / или проигнорированы другими пользователями." dashboard: rails_env_warning: "Ваш сервер работает в режиме %{env}." host_names_warning: "Ваш файл config/database.yml использует локальное имя хоста по умолчанию. Поменяйте его на имя хоста вашего сайта." + gc_warning: 'Ваш сервер использует параметры сборки мусора ruby по умолчанию, что не даст вам наилучшую производительность. Прочитайте этот раздел о настройке производительности: Tuning Ruby and Rails for Discourse.' sidekiq_warning: 'Sidekiq не запущен. Сейчас многие задачи, такие как отправка электронных писем, выполняются асинхронно. Пожалуйста, убедитесь, что хотя бы один процесс sidekiq запущен. Узнайте больше о Sidekiq здесь.' queue_size_warning: "Количество задач в очереди достигло большого размера %{queue_size}. Это может привести к проблемам с процессом(ами) Sidekiq, или вам придется добавить больше Sidekiq workers." memory_warning: "Общее количество памяти, используемое вашим сервером, составляет менее 1 GB. Рекомендовано использовать минимум 1 GB." + out_of_date_themes: "Доступны обновления для следующих тем:" + unreachable_themes: "Нам не удалось проверить наличие обновлений по следующим темам:" site_settings: censored_words: "Слова, которые будут автоматически заменены на ■■■■" delete_old_hidden_posts: "Автоматически удалять сообщения, скрытые дольше чем 30 дней." + default_locale: "Язык по умолчанию для этого экземпляра Discourse. Вы можете заменить текст системных категорий и тем на Настроить / Текстовое Содержимое." allow_user_locale: "Позволять пользователям выбирать язык интерфейса" + set_locale_from_accept_language_header: "установить язык интерфейса для анонимных пользователей из языковых заголовков их веб-браузера. (ЭКСПЕРИМЕНТАЛЬНО, не работает с анонимным кешем)" + support_mixed_text_direction: "Поддержка смешанных направлений текста слева направо и справа налево." min_post_length: "Минимально допустимое количество символов в одном сообщении." min_first_post_length: "Минимально допустимое количество символов в первом сообщении (или теле темы)" min_personal_message_post_length: "Минимально допустимое количество символов в сообщении в беседе." max_post_length: "Максимально допустимое количество символов в одном сообщении." topic_featured_link_enabled: "Разрешить публиковать ссылку с темами." + show_topic_featured_link_in_digest: "Показать ссылку на тему в e-mail дайджесте." min_topic_title_length: "Минимально допустимое количество символов в названии темы." max_topic_title_length: "Максимально допустимое количество символов в названии темы." min_personal_message_title_length: "Минимально допустимое количество символов в заголовке сообщения в беседе." + max_emojis_in_title: "Максимально допустимые смайлики в заголовке темы" min_search_term_length: "Минимальное количество символов в поисковом запросе." + search_tokenize_chinese_japanese_korean: "Принудительный поиск для токенизации Китайского/Японского /Корейского даже на сайтах, отличных от CJK" + search_prefer_recent_posts: "Если поиск на большом форуме выполняется медленно, этот параметр сначала пытается индексировать более последние сообщения" + search_recent_posts_size: "Сколько последних постов сохранить в индексе" + log_search_queries: "Журнал поисковых запросов, выполненных пользователями" + search_query_log_max_size: "Максимальное количество поисковых запросов для хранения" + search_query_log_max_retention_days: "Максимальное количество времени для хранения поисковых запросов, в днях." search_ignore_accents: "Игнорировать акценты при поиске текста." + category_search_priority_very_low_weight: "Вес применяется к ранжированию для очень низкого приоритета поиска категории." + category_search_priority_low_weight: "Вес применяется к ранжированию для низкого приоритета поиска категории." + category_search_priority_high_weight: "Вес, примененный к ранжированию для высокого приоритета поиска категории." + category_search_priority_very_high_weight: "Вес применяется к ранжированию для очень высокого приоритета поиска категории." + allow_uncategorized_topics: "Разрешить создание тем без категории. ВНИМАНИЕ: Если есть какие-либо темы без категории, вы должны переклассифицировать их, прежде чем отключить." allow_duplicate_topic_titles: "Разрешить создание тем с одинаковыми названиями." unique_posts_mins: "Количество минут до того, как пользователь сможет разместить сообщение с тем же содержанием." educate_until_posts: "Количество первых сообщений новых пользователей, для которых необходимо показывать всплывающую подсказку с советами для новичков." @@ -1090,6 +1208,7 @@ ru: crawl_images: "Скачивать картинки с других сайтов для автоматического определения их размеров." download_remote_images_to_local: "Скачивать картинки, вставленные в сообщения ссылками на другие сайты, и хранить их локально, чтобы предотвратить их изменения или утерю." download_remote_images_threshold: "Минимальное доступное место на диске (в процентах), при котором разрешено автоматическое скачивание картинок для локального хранения." + download_remote_images_max_days_old: "Не загружайте удаленные изображения для сообщений, которые старше n дней." disabled_image_download_domains: "Список доменов, разделенный знаком \"|\", в которых не нужно скачивать картинки." editing_grace_period: "Количество секунд после отправки соощения, в течении которых правка сообщения не будет записываться в историю правок." staff_edit_locks_post: "Посты будут заблокированы от редактирования, если они редактируются персоналом" @@ -1100,7 +1219,11 @@ ru: fixed_category_positions: "Если включено, разделы можно будет отсортировать в определенном порядке. Иначе разделы будут отображаться в порядке активности в них." add_rel_nofollow_to_user_content: 'Добавить "rel nofollow" для всех ссылок за исключением внутренних (включая родительский домен). Изменение данной настройки потребует обновления всех сообщений с помощью команды "rake posts:rebake"' post_excerpt_maxlength: "Максимальная длина краткого изложения сообщения." + show_pinned_excerpt_mobile: "Показать выдержку по закрепленным темам в мобильном представлении." + show_pinned_excerpt_desktop: "Показать выдержку по закрепленным темам в режиме рабочего стола." post_onebox_maxlength: "Максимальная длина сообщения с форума Discourse в режиме умной вставки." + onebox_domains_blacklist: "Список доменов, которые никогда не будут в oneboxed." + max_oneboxes_per_post: "Максимальное количество oneboxes в сообщении." notification_email: "Отправитель: E-mail использующийся при отправке всех системных писем. Домен, указанный здесь должен иметь правильно сконфигурированные SPF, DKIM и reverse PTR записи, для успешной отправки." email_custom_headers: "Разделенный чертой список дополнительных заголовков в почтовых сообщениях" email_subject: "Настраиваемый формат темы для стандартных писем. Смотреть: https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" @@ -1108,6 +1231,7 @@ ru: summary_posts_required: "Минимальное количество сообщение в теме для активации кнопки \"Сводка по теме\"" summary_likes_required: "Минимальное количество симпатий в теме для активации кнопки \"Сводка по теме\"" summary_percent_filter: "При нажатии на кнопку \"Сводка по теме\", показывать лучшие % сообщений" + summary_max_results: "Максимальное количество сообщений, возвращаемых в 'Сводке по Теме'" enable_system_message_replies: "Позволяет пользователям отвечать на системные сообщения, даже если личные сообщения отключены" enable_long_polling: "Использовать механизм long polling для уведомлений о событиях" long_polling_base_url: "Базовый URL, используемый для long polling (при использовании CDN для раздачи динамического контента установите в этом параметре адрес origin pull), например: http://origin.site.com" @@ -1115,18 +1239,31 @@ ru: polling_interval: "Если не используется long polling, как часто следует вошедшим клиентам опрашивать сервер, в миллисекундах" anon_polling_interval: "Как часто следует анонимным клиентам опрашивать сервер, в миллисекундах" background_polling_interval: "Как часто следует клиентам опрашивать сервер, в миллисекундах (когда окно находится в фоновом режиме)" + hide_post_sensitivity: "Вероятность того, что помеченный пост будет скрыт" + silence_new_user_sensitivity: "Вероятность того, что новый пользователь будет отключен на основе флагов спама" + auto_close_topic_sensitivity: "Вероятность того, что помеченная тема будет автоматически закрыта" cooldown_minutes_after_hiding_posts: "Количество минут, которое должен подождать пользователь перед редактированием сообщения скрытого по жалобам" max_topics_in_first_day: "Максимальное количество тем, которое пользователь может создать в течение 24 часов с момента создания своего первого сообщения" max_replies_in_first_day: "Максимальное количество ответов, которое пользователь может сделать в течение 24 часов с момента создания своего первого сообщения" tl2_additional_likes_per_day_multiplier: "Увеличить лимит лайков в день для tl2 (member) до" tl3_additional_likes_per_day_multiplier: "Увеличить лимит лайков в день для tl3 (member) до" tl4_additional_likes_per_day_multiplier: "Увеличить лимит лайков в день для tl4 (leader) до" + notify_mods_when_user_silenced: "Отправить сообщение всем модераторам, если пользователь автоматически отключается." traditional_markdown_linebreaks: "Использовать стандартный способ переноса строки в Markdown: строка должна заканчиваться двумя пробелами, чтобы перенос строки после нее сработал." post_undo_action_window_mins: "Количество минут, в течение которых пользователь может отменить действия, связанные с сообщениями: 'Мне нравится', 'Жалоба' и др." must_approve_users: "Персонал должен подтвердить регистрации новых пользователей перед тем, как им будет разрешен доступ к сайту.\nВНИМАНИЕ: включая данную функцию на существуещем сайте будет закрыт доступ к сайту для всех пользователей, кроме персонала." pending_users_reminder_delay: "Уведомлять модераторов, если новые пользователи ждут одобрения больше чем столько часов. Установите в -1 для отключения уведомлений." + maximum_session_age: "Пользователь останется в системе в течение n часов с момента последнего посещения" + ga_universal_tracking_code: "Google Universal Analytics (analytics.js) tracking код ID, например: UA-12345678-9; больше информации можно узнать на странице: https://google.com/analytics" + ga_universal_domain_name: "Google Universal Analytics (analytics.js) доменное имя, например: mysite.com; больше можно найти на странице  https://google.com/analytics" moderators_create_categories: "Разрешить модераторам создавать новые разделы" cors_origins: "Разрешенные источники для cross-origin запросов (CORS). Каждый запрос должен включать http:// или https://. Чтобы включить CORS, переменная окружения DISCOURSE_ENABLE_CORS должна быть установлена в значение true." + blacklist_ip_blocks: "Список частных блоков IP, которые никогда не должны сканироваться Discourse" + whitelist_internal_hosts: "Список внутренних хостов, которые discourse может безопасно сканировать для oneboxing и других целей" + allowed_iframes: "Список префиксов домена iframe src, которые discourse может безопасно разрешить в сообщениях" + content_security_policy: "Включить Политику Безопасности Содержимого" + content_security_policy_report_only: "Включить только Отчет о Политике Безопасности Содержимого" + content_security_policy_collect_reports: "Включить сбор отчетов о нарушениях CSP в /csp_reports" top_menu: "Определите, какие элементы должны располагаться в навигации на главной странице и в каком порядке. Пример: latest|new|unread|categories|top|read|posted|bookmarks" post_menu: "Определите, какие элементы должны отображаться в меню у сообщения и в какой последовательности. Пример: like|edit|flag|delete|share|bookmark|reply" post_menu_hidden_items: "Пункты меню действий над сообщением, которые должны быть спрятаны и появляться по нажатию на кнопку с троеточием." @@ -1135,29 +1272,37 @@ ru: send_welcome_message: "Отправлять всем новым пользователям приветственное сообщение с короткой инструкцией о возможностях форума." suppress_reply_directly_below: "Не показывать разворачиваемое количество ответов на сообщение, если есть всего лишь один ответ непосредственно ниже." suppress_reply_directly_above: "Не показывать разворачиваемый блок \"в ответ на\" для сообщения, если есть всего лишь одно сообщение непосредственно выше." + remove_full_quote: "Автоматически удалять полные кавычки на прямые ответы." suppress_reply_when_quoting: "Не показывать разворачиваемый блок \"в ответ на\", если сообщение уже содержит цитату." max_reply_history: "Максимальное число разворачивающихся ответов в блоке \"в ответ на\"" topics_per_period_in_top_summary: "Количество рекомендованных тем, отображаемых внизу текущей темы." topics_per_period_in_top_page: "Количество рекомендованных тем, отображаемых при нажатии 'Показать больше' в низу текущей темы." redirect_users_to_top_page: "Автоматически перенаправлять новых и давно отсутствующих пользователей к началу страницы." moderators_view_emails: "Разрешить модераторам просматривать сообщения e-mails пользователей" + enable_rich_text_paste: "Включить автоматическое преобразование HTML в Markdown при вставке текста в композитор. (Экспериментальный)" email_token_valid_hours: "Ссылка на восстановление пароля / активацию аккаунта будет действовать в течении (n) часов." enable_badges: "Включить систему наград" + enable_whispers: "Разрешить персоналу личное общение в рамках темы." allow_index_in_robots_txt: "Разрешить поисковикам индексировать сайт в robots.txt" email_domains_blacklist: "Список почтовых доменов, с которых запрещена регистрация учетных записей. Пример: mailinator.com trashmail.net" email_domains_whitelist: "Список почтовых доменов, с которых ДОЛЖНА производиться регистрация учетных записей.\nВНИМАНИЕ: Пользователям других почтовых доменов регистрация будет недоступна. " + hide_email_address_taken: "Не сообщать пользователям, что учетная запись существует с заданным адресом E-mail почты во время регистрации и из формы восстановления пароля." log_out_strict: "Когда пользователь выходит из системы, закрывать все сессии на всех устройствах пользователя." + version_checks: "Проверить обновление на Discourse Hub для получения сообщений о новых версиях и отображения их в /admin панели" new_version_emails: "Отправлять сообщение на адрес contact_email когда будут доступны новые версии." invite_expiry_days: "Срок валидности ключей, высланных приглашенному пользователю, в днях" login_required: "Требовать авторизации для доступа к сайту, анонимный доступ запретить." min_password_length: "Минимальная длина пароля" min_admin_password_length: "Минимальная длина пароля для Администратора." block_common_passwords: "Не позволять использовать пароли из списка 10 000 самых часто используемых паролей." + enable_sso: "Включите единый вход через внешний сайт (ВНИМАНИЕ: ПОЧТОВЫЕ АДРЕСА ПОЛЬЗОВАТЕЛЕЙ *ДОЛЖНЫ* БЫТЬ ПРОВЕРЕНЫ ВНЕШНИМ САЙТОМ!)" sso_secret: "Секретный набор символов, используемый для проверки подлинности зашифрованного входа с помощью SSO, убедитесь, что это 10 или более символов" sso_not_approved_url: "Перенаправлять неподтвержденные SSO-аккаунты на этот URL" allow_new_registrations: "Разрешить регистрацию новых пользователей. Выключите, чтобы запретить посетителям создавать новые учетные записи." google_oauth2_client_id: "Client ID для вашего Google приложения." google_oauth2_client_secret: "Client secret для Google приложения" + twitter_consumer_key: "Ключ пользователя для аутентификации в Twitter, зарегистрированный по адресу https://developer.twitter.com/apps" + twitter_consumer_secret: "Секретный номер для проверки подлинности Twitter, зарегистрированный в https://developer.twitter.com/apps" readonly_mode_during_backup: "Включить режим \"только для чтения\" во время выполнения резервного копирования" allow_restore: "Позволить импорт, который может заменить ВСЕ данные сайта. Оставьте выключенным, если не планируете восстанавливать резервную копию" maximum_backups: "Максимальное количество резервных копий к сохранению. Более старые резервные копии будут автоматически удалены." @@ -1165,6 +1310,8 @@ ru: active_user_rate_limit_secs: "Как часто мы обновляем поле 'last_seen_at', в секундах" verbose_localization: "Показывать ключи используемых строк в интерфейсе для перевода на другой язык" previous_visit_timeout_hours: "Как долго должно длиться посещение сайта, чтобы мы посчитали его «предыдущим посещением», в часах" + rebake_old_posts_count: "Количество старых постов, которые нужно перепроверять каждые 15 минут." + enable_safe_mode: "Разрешить пользователям входить в безопасный режим для отладки плагинов." rate_limit_create_topic: "Пользователи не могут создавать больше одной новой темы в указанное количество секунд." rate_limit_create_post: "Пользователи не могут писать более одного нового сообщения в указанное количество секунд." rate_limit_new_user_create_topic: "Пользователи не могут создавать больше одной новой темы в указанное количество секунд." @@ -1301,6 +1448,10 @@ ru: default_avatars: "URL для аватара, который будет использован по умолчанию для новых пользователей, пока они не изменят его." automatically_download_gravatars: "Скачивать аватарку Gravatar пользователя во время создания учетной записи или изменения e-mail." digest_posts: "Максимальное количество популярных постов для отображения в резюме по электронной почте." + suppress_digest_email_after_days: "Не отображать сводных сообщений e-mail почты для пользователей, не виденных на сайте более (n) дней." + digest_suppress_categories: "Не отображать эти категории из сводных сообщений e-mail почты." + disable_digest_emails: "Отключить сводные e-mail письма для всех пользователей." + email_accent_bg_color: "Акцентирующий цвет, который будет использоваться в качестве фона некоторых элементов в e-mail письмах в HTML. Введите название цвета ('red') или hex значение ('#FF000'). " email_link_color: "Цвет ссылок в HTML письмах. Введите название цвета ('blue') или hex значение ('#0000FF')." detect_custom_avatars: "Проверять ли, что пользователи загрузили свои собственные картинки профиля (аватарки)." max_daily_gravatar_crawls: "Максимальное количество загрузок аватарок с Gravatar за один день" @@ -1409,6 +1560,7 @@ ru: account_not_approved: "Ваша учетная запись ожидает подтверждения. Вы получите уведомление по электронной почте, когда она будет подтверждена." unknown_error: "Произошла проблема с учетной записью. Пожалуйста, свяжитесь с администратором сайта." timeout_expired: "Время входа в учётную запись истекло, пожалуйста, попробуйте войти снова." + email_error: "Аккаунт не может быть зарегистрирован с адресом e-mail почты %{email}. Пожалуйста, свяжитесь с администратором сайта." original_poster: "Автор" most_posts: "Большинство сообщений" most_recent_poster: "Последний автор" @@ -1425,6 +1577,17 @@ ru: errors: different_topics: "Сообщения, принадлежащие другой теме, не могут быть объединены." different_users: "Сообщения, принадлежащие разным пользователям, не могут быть объединены." + move_posts: + new_topic_moderator_post: + one: "Пост был перенесен в новую тему: %{topic_link}" + few: "%{count} постов были перенесены в новую тему: %{topic_link}" + many: "%{count} постов были перенесены в новую тему: %{topic_link}" + other: "%{count} пост был перенесен в новую тему: %{topic_link}" + new_message_moderator_post: + one: "Пост был перенесен в новое сообщение: %{topic_link}" + few: "%{count} постов были перенесены в новое сообщение: %{topic_link}" + many: "%{count} постов были перенесены в новое сообщение: %{topic_link}" + other: "%{count} пост были перенесены в новое сообщение: %{topic_link}" change_owner: post_revision_text: "Право собственности передано" topic_statuses: @@ -1468,6 +1631,21 @@ ru: few: "Эта тема была автоматически закрыта через %{count} минуты после последнего ответа. В ней больше нельзя отвечать." many: "Эта тема была автоматически закрыта через %{count} минут после последнего ответа. В ней больше нельзя отвечать." other: "Эта тема была автоматически закрыта через %{count} минут после последнего ответа. В ней больше нельзя отвечать." + autoclosed_disabled_days: + one: "Эта тема была автоматически открыта после %{count} дня." + few: "Эта тема была автоматически открыта после %{count} дней." + many: "Эта тема была автоматически открыта после %{count} дней." + other: "Эта тема была автоматически открыта после %{count} дня." + autoclosed_disabled_hours: + one: "Эта тема была автоматически открыта после  %{count} часа" + few: "Эта тема была автоматически открыта после %{count} часов." + many: "Эта тема была автоматически открыта после %{count} часов." + other: "Эта тема была автоматически открыта после %{count} часа." + autoclosed_disabled_minutes: + one: "Эта тема была автоматически открыта после %{count} минуты." + few: "Эта тема была автоматически открыта после %{count} минут." + many: "Эта тема была автоматически открыта после %{count} минут." + other: "Эта тема была автоматически открыта после %{count} минуты." autoclosed_disabled: "Эта тема открыта. В ней можно отвечать." autoclosed_disabled_lastpost: "Эта тема теперь открыта, и в ней можно отвечать." auto_deleted_by_timer: "Автоматически удалить по таймеру." @@ -1539,15 +1717,30 @@ ru: unsubscribe_mailer: title: "Отписаться от рассылки" subject_template: "Подтвердите, что вы больше не желаете получать обновления по электронной почте от %{site_title}" + invite_mailer: + subject_template: "%{inviter_name} пригласил вас присоединиться к '%{topic_title}' на %{site_domain_name}" + custom_invite_mailer: + subject_template: "%{inviter_name} пригласил вас присоединиться к '%{topic_title}' на %{site_domain_name}" + invite_forum_mailer: + subject_template: "%{inviter_name} приглашает вас присоединиться к %{site_domain_name}" + custom_invite_forum_mailer: + subject_template: "%{inviter_name} приглашает вас присоединиться к %{site_domain_name}" invite_password_instructions: subject_template: "Задайте пароль для вашей учетной записи на сайте %{site_name}" download_backup_mailer: + subject_template: "[%{email_prefix}] Загрузка Резервной Копии Сайта" no_token: | Извините, ссылка на скачивание резервной копии уже использовалась или срок ее действия истек. admin_confirmation_mailer: title: "Подтверждение Администратора" + test_mailer: + subject_template: "[%{email_prefix}] Проверка доставки по e-mail почте" new_version_mailer: title: "Новая версия Mailer" + subject_template: "[%{email_prefix}] Новая версия Discourse, доступно обновление" + new_version_mailer_with_notes: + title: "Новая Версия Mailer с Заметками" + subject_template: "[%{email_prefix}] доступно обновление" flag_reasons: off_topic: "Сообщество считает, что ваше сообщение **не относится к теме**, учитывая ее заголовок и первое сообщение." inappropriate: "Сообщество считает ваше сообщение **неприемлемым**, т.е. оскорбительным, непристойным или нарушающим [кодекс чести](%{base_path}/guidelines)." @@ -1560,9 +1753,11 @@ ru: ignored: "Спасибо за информацию. Уже рассматриваем." ignored_and_deleted: "Спасибо за информацию. Сообщение удалено." system_messages: + private_topic_title: "Тема #%{id}" contents_hidden: "Пожалуйста, посетите пост, чтобы увидеть его содержимое." post_hidden: title: "Сообщение Скрыто" + subject_template: "Пост скрыт по причине жалоб от сообщества" text_body_template: | Здравствуйте. @@ -1649,14 +1844,27 @@ ru: %{post_error} Если вы не можете исправить ошибку, попробуйте снова. + email_reject_unrecognized_error: + subject_template: "[%{email_prefix}] Проблема с e-mail почтой -- Нераспознанная Ошибка" + email_reject_reply_not_allowed: + subject_template: "[%{email_prefix}] Проблема с e-mail почтой -- Ответ Не Разрешен" + email_revoked: + title: "E-mail отозван" + subject_template: "Ваш E-mail адрес правильный?" ignored_users_summary: title: "Игнорированный пользователь прошел порог" subject_template: "Пользователь игнорируется многими другими пользователями" + too_many_spam_flags: + title: "Слишком много Спам-Флагов" + subject_template: "Новый аккаунт временно заблокирован" + too_many_tl3_flags: + subject_template: "Новый аккаунт временно заблокирован" silenced_by_staff: subject_template: "Учётная запись временно заблокирована" user_automatically_silenced: title: "Пользователь автоматически отключен" spam_post_blocked: + title: "Спам Пост Заблокирован" subject_template: "Сообщения нового пользователя %{username} заблокированы из-за повторяющихся ссылок" unsilenced: subject_template: "Учётная запись больше не заблокирована" @@ -1675,23 +1883,193 @@ ru: download_remote_images_disabled: subject_template: "Загрузка копий изображений выключена" text_body_template: "Настройка `download_remote_images_to_local` была отключена, т.к. диск заполнился до отметки, указанной в настройке `download_remote_images_threshold`." + dashboard_problems: + subject_template: "Новые советы на панели мониторинга сайта" + new_user_of_the_month: + title: "Вы Новый Пользователь Месяца!" + subject_template: "Вы Новый Пользователь Месяца!" unsubscribe_link: | Чтобы отписаться от таких писем, [нажмите сюда](%{unsubscribe_url}). unsubscribe_link_and_mail: | Чтобы отписаться от таких писем, [нажмите сюда](%{unsubscribe_url}). subject_re: "Re:" subject_pm: "[PM]" + email_from: "%{user_name} через %{site_name}" user_notifications: previous_discussion: "Предыдущие ответы" + in_reply_to: "В Ответ На" unsubscribe: title: "Отписаться" description: "Не заинтересованы в получении данных писем? Нет проблем! Нажмите на ссылку ниже, чтобы мгновенно отписаться от рассылки:" + reply_by_email_pm: "[Посетите сообщения](%{base_url}%{url}) или ответьте на это письмо на %{participants}." visit_link_to_respond: "[Посетите тему](%{base_url}%{url}) чтобы ответить." posted_by: "Отправлено %{username} %{post_date}" + pm_participants: "Участники: %{participants}" + user_invited_to_private_message_pm_group: + subject_template: "[%{email_prefix}] %{username} пригласил @%{group_name} в сообщение '%{topic_title}'" + text_body_template: | + %{header_instructions} + + %{message} + + %{respond_instructions} + user_invited_to_private_message_pm: + title: "Пользователь пригласил в PM" + subject_template: "[%{email_prefix}] %{username} пригласил вас в сообщение '%{topic_title}'" + text_body_template: | + %{header_instructions} + + %{message} + + %{respond_instructions} + user_invited_to_private_message_pm_staged: + subject_template: "[%{email_prefix}] %{username} пригласил вас в сообщение '%{topic_title}'" + text_body_template: | + %{header_instructions} + + %{message} + + %{respond_instructions} + user_invited_to_topic: + subject_template: "[%{email_prefix}] %{username} пригласил вас в '%{topic_title}'" + text_body_template: | + %{header_instructions} + + %{message} + + %{respond_instructions} + user_replied: + subject_template: "[%{email_prefix}] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} + user_replied_pm: + subject_template: "[%{email_prefix}] [PM] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} + user_quoted: + subject_template: "[%{email_prefix}] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} + user_linked: + title: "Связанный Пользователь" + subject_template: "[%{email_prefix}] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} + user_mentioned: + title: "Упомянутый Пользователь" + subject_template: "[%{email_prefix}] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} + user_mentioned_pm: + subject_template: "[%{email_prefix}] [PM] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} + user_group_mentioned: + subject_template: "[%{email_prefix}] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} + user_posted: + subject_template: "[%{email_prefix}] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} + user_watching_first_post: + subject_template: "[%{email_prefix}] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} + user_posted_pm: + title: "Пользователь написал в PM" + subject_template: "[%{email_prefix}] [PM] %{topic_title}" + text_body_template: | + %{header_instructions} + + %{message} + + %{context} + + %{respond_instructions} user_posted_pm_staged: subject_template: "%{optional_re}%{topic_title}" + text_body_template: |2 + + %{message} + account_suspended: + title: "Аккаунт Приостановлен" + subject_template: "[%{email_prefix}] Ваш аккаунт был заморожен" + text_body_template: | + Вы были отстранены от форума до %{suspended_till}. + + %{reason} + + %{message} + account_silenced: + title: "Аккаунт Отключен" + subject_template: "[%{email_prefix}] Ваш аккаунт был отключен" + text_body_template: | + Вы были отключены от форума до %{silenced_till}. + + %{reason} + + %{message} account_exists: title: "Учетная запись уже существует" + subject_template: "[%{email_prefix}] Учетная запись уже существует" + account_second_factor_disabled: + title: "Двухфакторная Аутентификация отключена" + subject_template: "[%{email_prefix}] Двухфакторная Аутентификация отключена" digest: why: "Сводка обсуждений на сайте %{site_link} с момента вашего последнего визита %{last_seen_at}" since_last_visit: "За время вашего отсутствия" @@ -1706,15 +2084,25 @@ ru: popular_posts: "Популярные сообщения" more_new: "Новинки для вас" subject_template: "[%{email_prefix}] — сводка" + your_email_settings: "ваши настройки e-mail почты" click_here: "нажмите здесь" from: "Сводка — %{site_name}" preheader: "Сводка новостей с вашего последнего визита %{last_seen_at}" forgot_password: title: "Забыл Пароль" + subject_template: "[%{email_prefix}] Восстановление пароля" + email_login: + title: "Войти по ссылке" + subject_template: "[%{email_prefix}] Войти через ссылку" set_password: title: "Установить Пароль" + subject_template: "[%{email_prefix}] Установить Пароль" + admin_login: + title: "Логин Администратора" + subject_template: "[%{email_prefix}] Войти" account_created: title: "Учётная Запись Создана" + subject_template: "[%{email_prefix}] Ваш Новый Аккаунт" confirm_new_email: title: "Подтвердить Новый E-mail" signup_after_approval: @@ -1722,6 +2110,12 @@ ru: subject_template: "Ваша учетная запись на сайте %{site_name} одобрена!" signup: title: "Регистрация" + activation_reminder: + title: "Напоминание об Активации" + subject_template: "[%{email_prefix}] Напоминание для подтверждения аккаунта" + suspicious_login: + title: "Оповещение о Новом Входе" + subject_template: "[%{site_name}] Новый Логин от %{location}" page_not_found: title: "Упс! Эта страница не существует или скрыта от публичного просмотра." popular_topics: "Популярные" @@ -1772,6 +2166,8 @@ ru: sender_message_to_blank: "Адрес получателя пусто" sender_text_part_body_blank: "Текстовая часть сообщения письма пуста" sender_body_blank: "Cообщения письма пусто" + sender_post_deleted: "пост был удален" + sender_message_to_invalid: "получатель имеет неверный адрес e-mail почты" color_schemes: base_theme_name: "Базовая" light: "Светлая Схема" @@ -1920,19 +2316,23 @@ ru: description: Опубликовал ссылку для oneboxed first_reply_by_email: name: Первый Ответ По Почте + description: Ответил на пост по e-mail почте new_user_of_the_month: name: "Новый Пользователь Месяца" description: Великолепный вклад в первый месяц enthusiast: name: Энтузиаст + description: Посетили 10 дней подряд long_description: | Эта награда выдается для посещения 10 дней подряд. Спасибо, что остаетесь с нами больше недели! aficionado: name: Поклонник + description: Посетили 100 дней подряд long_description: | Эта награда выдается для посещения 100 дней подряд. Это больше трех месяцев! devotee: name: Преданный + description: "Посетил 365 дней подряд " long_description: | Этот награда выдается для посещения 365 дней подряд. Вау, целый год! badge_title_metadata: "%{display_name} награду на %{site_title}" @@ -1951,6 +2351,8 @@ ru: staff_tag_disallowed: 'Тег "%{tag}" может быть применён только персоналом.' staff_tag_remove_disallowed: 'Тег "%{tag}" может быть удалён только персоналом.' upload_row_too_long: "Файл CSV должен иметь один тег на строку. При необходимости за тегом может следовать запятая, а затем имя группы тегов." + forbidden: + in_this_category: '"%{tag_name}" нельзя использовать в этой категории' rss_by_tag: "Темы, отмеченные тегом %{tag}" finish_installation: congratulations: "Поздравляем, Вы установили Discourse!" @@ -2007,6 +2409,8 @@ ru: label: "Открытое" restricted: label: "Частное" + privacy_options: + description: "Как новые пользователи регистрируют учетную запись?" contact: title: "Контакты" fields: @@ -2034,15 +2438,21 @@ ru: placeholder: "Сан-Франциско, Калифорния" colors: title: "Цветовая схема" + themes_further_reading: + title: "Стили" logos: title: "Логотипы" fields: logo: label: "Основной Логотип" description: "Изображение логотипа в левом верхнем углу вашего сайта. Используйте широкое прямоугольное изображение с высотой 120 и соотношением сторон более 3:1" + logo_small: + label: "Квадратный Логотип" icons: title: "Иконки" fields: + favicon: + label: "Значок браузера" large_icon: label: "Большая иконка" homepage: @@ -2080,8 +2490,17 @@ ru: joined: "Вступил" discourse_push_notifications: popup: + mentioned: '%{username} упомянул вас в "%{topic}" - %{site_title}' + group_mentioned: '%{username} упомянул вас в "%{topic}" - %{site_title}' + quoted: '%{username} процитировал вас в "%{topic}" - %{site_title}' + replied: '%{username} ответил вам в "%{topic}" - %{site_title}' + posted: '%{username} опубликовал в "%{topic}" - %{site_title}' + private_message: '%{username} отправил вам личное сообщение в "%{topic}" - %{site_title}' + linked: '%{username} поставил ссылку с "%{topic}" - %{site_title}' + watching_first_post: '%{username} создал новую тему "%{topic}" - %{site_title}' confirm_title: "Уведомления включены - %{site_title}" confirm_body: "Успех! Уведомления включены." + custom: "Уведомление от %{username} на %{site_title}" staff_action_logs: not_found: "не найдено" unknown: "неизвестно" @@ -2097,7 +2516,9 @@ ru: low: "Низкий" medium: "Средний" high: "Высокий" + user_claimed: "Этот элемент был использован другим пользователем." reasons: + auto_silence_regexp: "Новый пользователь, чье первое сообщение соответствует `auto_silence_first_post_regex` настройки." category: "Посты в этой категории требуют ручного одобрения персоналом. Смотрите настройки категории." actions: agree: @@ -2112,12 +2533,15 @@ ru: title: "Отключенный пользователь" description: "Согласиться с флагом и отключить пользователя." agree_and_restore: + title: "Восстановить Пост" description: "Восстановить сообщение, чтобы все пользователи могли его видеть." agree_and_hide: title: "Скрыть Пост" description: "Скрыть этот пост и автоматически отправить пользователю сообщение с просьбой отредактировать его." delete_spammer: title: "Удалить Спамера" + description: "Удалить пользователя и все его сообщения и темы." + confirm: "Вы уверены, что хотите удалить все сообщения, темы и заблокировать его IP-адреса и адреса E-mail почты?" delete_single: title: "Удалить" delete: @@ -2128,12 +2552,14 @@ ru: delete_and_ignore_replies: title: "Удалить Пост + Ответы и игнорировать" description: "Удалить пост и все его ответы; если первый пост, удалить также тему" + confirm: "Вы уверены, что хотите удалить ответы на сообщение?" delete_and_agree: title: "Удалить Пост и Согласиться" description: "Удалить пост; если первый пост, удалится также тема" delete_and_agree_replies: title: "Удалить Пост + Ответы и Согласиться" description: "Удалить пост и все его ответы; если первый пост, удалить также тему" + confirm: "Вы уверены, что хотите удалить ответы на сообщение?" disagree_and_restore: title: "Не согласен и восстановить сообщение" description: "Восстановить сообщение, чтобы все пользователи могли его видеть." @@ -2161,4 +2587,5 @@ ru: title: "Отклонить" delete_user: title: "Удалить Пользователя" + confirm: "Вы уверены, что хотите удалить этого пользователя? Это удалит все их сообщения и заблокирует их E-mail почту и IP-адрес." reason: "Удалено через очередь просмотра" diff --git a/config/locales/server.sl.yml b/config/locales/server.sl.yml index d2e1ca1efe..3b376e0f1a 100644 --- a/config/locales/server.sl.yml +++ b/config/locales/server.sl.yml @@ -473,27 +473,27 @@ sl: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "neznan brskalnik" device: android: "Android naprava" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "Mobilna naprava" - mac: "Mac" linux: "GNU/Linux računalnik" + mac: "Mac" + mobile: "Mobilna naprava" windows: "Windows računalnik" unknown: "neznana naprava" os: android: "Android" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "neznan operacijski sistem" change_email: @@ -2136,6 +2136,8 @@ sl: title: "Organizacija" colors: title: "Videz" + themes_further_reading: + title: "Videzi" homepage: fields: homepage_style: @@ -2189,7 +2191,6 @@ sl: new_topics_unless_trust_level: "Uporabniki na nizkih nivojih zaupanja potrebujejo odobritev teme s strani osebja. Glej `approve_new_topics_unless_trust_level`." fast_typer: "Nov uporabnik je svoj prvi prispevek natipkal sumljivo hitro zato sumimo bota ali spammerja. Glej `min_first_post_typing_time`." auto_silence_regexp: "Nov uporabnik katerega prvi prispevek ustreza `auto_silence_first_post_regex` nastavitvi." - watched_word: "Prispevek vsebuje nadzorovano besedo." staged: "Nove teme in prispevki prirejenih uporabnikov morajo biti odobreni s strani osebja. Glej `approve_unless_staged`." category: "Prispevki v tej kategoriji zahtevajo odobritev s strani osebja. Glej nastavitve kategorije." must_approve_users: "Vsi novi uporabniki morajo biti odobreni s strani osebja. Glej `must_approve_users`." diff --git a/config/locales/server.sw.yml b/config/locales/server.sw.yml index cb3051929b..ea897330d5 100644 --- a/config/locales/server.sw.yml +++ b/config/locales/server.sw.yml @@ -2256,6 +2256,8 @@ sw: title: "Shirika" colors: title: "Mandhari" + themes_further_reading: + title: "Mandhari" logos: title: "Nembo" fields: diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index 2f10800a19..9c25b65337 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -1666,6 +1666,8 @@ tr_TR: title: "Organizasyon" colors: title: "Tema" + themes_further_reading: + title: "Temalar" logos: title: "Görseller" fields: diff --git a/config/locales/server.ur.yml b/config/locales/server.ur.yml index bf16497767..81880b8a3a 100644 --- a/config/locales/server.ur.yml +++ b/config/locales/server.ur.yml @@ -40,6 +40,8 @@ ur: themes: bad_color_scheme: "تِھیم اَپ ڈَیٹ نہیں ہو سکتی، غلط رنگ پیلیٹ" other_error: "تِھیم اَپ ڈَیٹ کرتے ہوے کچھ غلط ہو گیا" + compile_error: + unrecognized_extension: "نامعلوم فائل ایکسٹینشن: %{extension}" import_error: generic: تِھیم درآمد کرتے ہوئے ایک خرابی کا سامنا کرنا پڑا about_json: "درآمد کی خرابی: about.json موجود نہیں ہے، یا غلط ہے" @@ -176,6 +178,7 @@ ur: confirm_email: "

آپ تقریباً کام مکمل کر چکے ہیں! ہم نے آپ کے ایمیل پتہ پر ایک ایکٹیویشن میل بھیج دی ہے۔ براہ مہربانی اپنے اکاؤنٹ کو چالو کرنے کیلئے میل میں دی گئی ہدایات پر عمل کریں۔

اگر یہ آپ کو موصول نہ ہو، تو اپنا سپَیم فولڈر چیک کریں۔

" bulk_invite: file_should_be_csv: "اَپ لوڈ شدہ فائل CSV فارمیٹ میں ہونی چاہئے۔" + max_rows: "ایک وقت میں زیادہ سے زیادہ 50،000 دعوت نامے بھیجے جا سکتے ہیں۔ فائل کو چھوٹے حصوں میں تقسیم کرنے کی کوشش کریں۔" error: "یہ فائل اَپ لوڈ کرنے میں ایک خرابی کا سامنا کرنا پڑا۔ براہ مہربانی کچھ دیر بعد دوبارہ کوشش کریں۔" topic_invite: failed_to_invite: "صارف کو مندرجہ ذیل گروپوں میں سے کسی ایک کی رکنیت کے بغیر اِس ٹاپک میں مدعو نہیں کیا جاسکتا: %{group_names}۔" @@ -662,28 +665,30 @@ ur: user_auth_tokens: browser: chrome: "گُوگَل کرَوم" - safari: "سفاری" - firefox: "فائر فاکس" - opera: "اَوپرا" - ie: "انٹرنیٹ ایکسپلورر" - edge: "مائیکروسافٹ اَیج" discoursehub: "ڈِسکورس ہب ایپ" + edge: "مائیکروسافٹ اَیج" + firefox: "فائر فاکس" + ie: "انٹرنیٹ ایکسپلورر" + opera: "اَوپرا" + safari: "سفاری" unknown: "نامعلوم براؤزر" device: android: "اینڈرائیڈ ڈیوائس" + chromebook: "کرَوم بُک" ipad: "آئی پَیڈ" iphone: "آئی فون" ipod: "آئی پَوڈ" - mobile: "موبائل ڈیوائس" - mac: "مَیک" linux: "جی این یو / لینکس کمپیوٹر" + mac: "مَیک" + mobile: "موبائل ڈیوائس" windows: "وِنڈَوز کمپیوٹر" unknown: "نامعلوم ڈیوائس" os: android: "اینڈرائیڈ" + chromeos: "کرَوم OS" ios: "آئی اَو اَیس" - macos: "مَیک اَو اَیس" linux: "لینکس" + macos: "مَیک اَو اَیس" windows: "مائیکروسافٹ وِنڈَوز" unknown: "نامعلوم آپریٹنگ سسٹم" change_email: @@ -697,6 +702,7 @@ ur: description: "اب ہم تصدیق کیلئے آپ کے نئے ایڈریس پر اِی میل کر رہے ہیں۔" associated_accounts: revoke_failed: "%{provider_name} کے ساتھ آپ کے اکاؤنٹ کو منسوخ کرنے میں ناکامی۔" + connected: "(کنَیکٹ شدہ)" activation: action: "اپنا اکاؤنٹ فعال کرنے کیلئے یہاں کلک کریں" already_done: "معذرت، یہ اکاؤنٹ تصدیق لنک اب درست نہیں ہے۔ شاید آپ کا اکاؤنٹ پہلے ہی سے فعال ہے؟" @@ -785,6 +791,7 @@ ur: short_description: 'ہماری کمیونٹی کے قواعد و ضوابط کی خلاف ورزی' notify_moderators: title: "کچھ اور" + description: 'اِس ٹاپک کو، قواعد و ضوابط، سروس کی شرائط، یا مندرجہ بالا درج وجوہات کے علاوہ، کے مطابق اسٹاف کی عام توجہ کی ضرورت ہے۔' long_form: "اِس کو ماڈریٹر کی توجہ کیلئے فلَیگ کیا گیا" short_description: "اسٹاف کی توجہ کسی اور وجہ کی بناہ پر درکار ہے" email_title: 'ٹاپک "%{title}" پر اسٹاف کی توجہ درکار ہے' @@ -904,7 +911,7 @@ ur: title: "صارف وِزِٹس" xaxis: "دن" yaxis: "وِزِٹس کی تعداد" - description: "تمام منفرد صارف کے دوروں کی تعداد۔" + description: "تمام صارف کے دوروں کی تعداد۔" signups: title: "سائن اَپ" xaxis: "دن" @@ -929,38 +936,54 @@ ur: author: مصنف edit_reason: وجہ dau_by_mau: + title: "DAU/MAU" xaxis: "دن" + yaxis: "DAU/MAU" + description: "گزشتہ دن لاگ اِن ہونے والے ممبران کی تعداد گزشتہ مہینے میں لاگ اِن ہونے والے ممبران کی تعداد سے تقسیم کرنا – ایک ٪ کا حساب لگاتا ہے جس سے کمیونٹی سے 'چپکنے' کا اندازہ لگتا ہے۔ کوشش کریں >30٪ کیلئے۔" daily_engaged_users: + title: "روزانہ کے مشغول صارفین" xaxis: "دن" + yaxis: "مشغول صارفین" + description: "صارفین کی تعداد جنہوں نے گزشتہ ایک دن میں لائیک یا پوسٹ کیا ہے۔" profile_views: title: "صارف پروفائل وِیوز" xaxis: "دن" yaxis: "دیکھے گئے صارفین پروفائلز کی تعداد" + description: "صارف پروفائلز کے کُل نئے وِیوز۔" topics: title: "ٹاپک" xaxis: "دن" yaxis: "نئے ٹاپکس کی تعداد" + description: "اِس مدت کے دوران بنائے گئے نئے ٹاپک۔" posts: title: "پوسٹس" xaxis: "دن" yaxis: "نئی پوسٹس کی تعداد" + description: "اِس مدت کے دوران بنائی گئیں نئی پوسٹس۔" likes: title: "لائیکس" xaxis: "دن" yaxis: "نئے لائیکس کی تعداد" + description: "نئے لائیکس کی تعداد۔" flags: title: "فلَیگز" xaxis: "دن" yaxis: "فلَیگز کی تعداد" + description: "نئے فلَیگز کی تعداد۔" bookmarks: title: "بُک مارکس" xaxis: "دن" yaxis: "نئے بُک مارکس کی تعداد" + description: "بُک مارک کردہ نئے ٹاپکس اور پوسٹس کی تعداد۔" users_by_trust_level: title: "صارفین فی ٹرسٹ لَیول" xaxis: "ٹرسٹ لَیول" yaxis: "صارفین کی تعداد" + labels: + level: لَیول + description: "ٹرسٹ لَیول کے حساب سے گروپ کردہ صارفین کی تعداد۔" users_by_type: + title: "صارفین فی قسم" xaxis: "قِسم" yaxis: "صارفین کی تعداد" labels: @@ -970,38 +993,49 @@ ur: moderator: ماڈریٹر suspended: معطل کردہ silenced: خاموش کیو ہوئے + description: "ایڈمن، ماڈریٹر، معطل، اور خاموش کردہ کے حساب سے گروپ کردہ صارفین کی تعداد۔" trending_search: + title: رجحان سرچ اصطلاحات labels: term: اصطلاح searches: سرچز click_through: CTR + description: "سب سے زیادہ مقبول سرچ اصطلاحات اُن کے کلک-تھرو کی شرح کے ساتھ۔" emails: title: "بھیجی گئیں اِی میل" xaxis: "دن" yaxis: "اِی میل کی تعداد" + description: "بھیجی جانے والی نئی ایمیلز کی تعداد۔" user_to_user_private_messages: + title: "صارف-سے-صارف (جوابات کو چھوڑ کر)" xaxis: "دن" yaxis: "پیغامات کی تعداد" + description: "نئے شروع کردہ ذاتی پیغامات کی تعداد۔" user_to_user_private_messages_with_replies: title: "صارف-سے-صارف (جوابات کے ساتھ)" xaxis: "دن" yaxis: "پیغامات کی تعداد" + description: "تمام نئے ذاتی پیغامات اور جوابات کی تعداد۔" system_private_messages: title: "سِسٹَم" xaxis: "دن" yaxis: "پیغامات کی تعداد" + description: "سِسٹم کے ذریعہ خود کار طریقہ سے بھیجے جانے والے ذاتی پیغامات کی تعداد۔" moderator_warning_private_messages: title: "ماڈریٹر انتباہ" xaxis: "دن" yaxis: "پیغامات کی تعداد" + description: "ماڈریٹرز کی طرف سے ذاتی پیغامات کے ذریعہ بھیجی گئی انتباہات کی تعداد۔" notify_moderators_private_messages: title: "ماڈریٹرز کو مطلع کریں" xaxis: "دن" yaxis: "پیغامات کی تعداد" + description: "ایک فلَیگ کے ذریعہ ماڈریٹرز کو نجی طور پر مطلع کیے جانے کی تعداد۔" notify_user_private_messages: title: "صارف کو مطلع کریں" xaxis: "دن" yaxis: "پیغامات کی تعداد" + description: "ایک فلَیگ کے ذریعہ صارفین کو نجی طور پر مطلع کیے جانے کی تعداد۔" top_referrers: title: "ٹاپ تجویز کنندگان" xaxis: "صارف" @@ -1011,6 +1045,7 @@ ur: user: "صارف" num_clicks: "کلِکس" num_topics: "ٹاپک" + description: "اُن کے شیئر کردہ لنکس پر کلکس کی تعداد کے حساب سے صارفین کی فہرست۔" top_traffic_sources: title: "ٹریفک کے ٹاپ ذرائع" xaxis: "ڈَومَین" @@ -1021,35 +1056,43 @@ ur: domain: ڈَومَین num_clicks: کلِکس num_topics: ٹاپک + description: "بیرونی ذرائع جنہوں سے اِس سائٹ کو سب سے زیادہ لنک کیا ہے۔" top_referred_topics: title: "ٹاپ تجویز کردہ ٹاپک" labels: num_clicks: "کلِکس" topic: "ٹاپک" + description: "ٹاپک جن کو بیرونی ذرائع سے سب سے زیادہ کلکس موصول ہوئے ہیں۔" page_view_anon_reqs: title: "گمنام" xaxis: "دن" yaxis: "صفحہ کے گمنام ملاحظات" + description: "اکاؤنٹ میں لاگ اِن نہ ہونے والے زائرین کی طرف سے نئے صفحہ ملاحظات کی تعداد۔" page_view_logged_in_reqs: title: "لاگڈ اِن" xaxis: "دن" yaxis: "صفحہ کے لاگڈ اِن ملاحظات" + description: "لاگ اِن صارفین کی طرف سے نئے صفحہ ملاحظات کی تعداد۔" page_view_crawler_reqs: title: "صفحہ کے وَیب کرالر ملاحظات" xaxis: "دن" yaxis: "صفحہ کے وَیب کرالر ملاحظات" + description: "وقت کے ساتھ وَیب کرالرز کی طرف سے کُل صفحہ ملاحظات۔" page_view_total_reqs: title: "صفحہ ملاحظات" xaxis: "دن" yaxis: "صفحہ کے کُل ملاحظات" + description: "تمام زائرین کی طرف سے نئے صفحہ ملاحظات کی تعداد۔" page_view_logged_in_mobile_reqs: title: "صفحہ کے لاگڈ اِن ملاحظات" xaxis: "دن" yaxis: "صفحہ کے موبائل لاگڈ اِن ملاحظات" + description: "موبائل ڈیوائسوں پر اور اکاؤنٹ میں لاگ اِن کردہ صارفین کی طرف سے نئے صفحہ ملاحظات کی تعداد۔" page_view_anon_mobile_reqs: title: "صفحہ گمنام ملاحظات" xaxis: "دن" yaxis: "صفحہ موبائل گمنام ملاحظات" + description: "موبائل ڈیوائسوں پر زائرین جو لاگ اِن نہ ہوئے ہوں، اُن کی طرف سے نئے صفحہ ملاحظات کی تعداد۔" http_background_reqs: title: "پس منظر" xaxis: "دن" @@ -1078,46 +1121,86 @@ ur: title: "پہلے جواب تک کا وقت" xaxis: "دن" yaxis: "اوسطً وقت (گھنٹے)" + description: "نئے ٹاپکس پر پہلے جواب کا اوسط وقت (گھنٹوں میں)۔" topics_with_no_response: title: "بۃلا جواب والے ٹاپک" xaxis: "دن" yaxis: "کُل" + description: "بنائے گئے نئے ٹاپکس کی تعداد جن پر کوئی جواب نہیں مل سکا۔" mobile_visits: + title: "صارف وِزِٹس (موبائل)" xaxis: "دن" yaxis: "وِزِٹس کی تعداد" + description: "منفرد صارفین کی تعداد جنہوں نے موبائل ڈیوائس کا استعمال کرتے ہوئے سائیٹ کا دورہ کیا۔" web_crawlers: + title: "وَیب کرالر صارف ایجنٹس" labels: user_agent: "صارف ایجنٹ" page_views: "صفحہ ملاحظات" + description: "وَیب کرالر صارف ایجنٹس کی فہرست، صفحہ ملاحظات کے حساب سے ترتیب کردہ۔" suspicious_logins: + title: "مشکوک لاگ اِن" labels: user: صارف + client_ip: کلائنٹ آئی پی location: محل وقوع + browser: براؤزر + device: ڈیوائس + os: آپریٹنگ سسٹم + login_time: لاگ اِن وقت + description: "نئے لاگ اِن کی تفصیلات جو گزشتہ لاگ اِن سے مشکوک حد تک اختلاف کرتے ہیں۔" staff_logins: + title: "ایڈمن لاگ اِن" labels: user: صارف location: محل وقوع + login_at: پر لاگ ان + description: "محل وقوع کے ساتھ ایڈمن لاگ اِن اوقات کی فہرست۔" top_uploads: + title: "ٹاپ اَپ لوڈز" labels: filename: فائل کا نام + extension: ایکسٹینشن author: مصنف + filesize: فائل سائز + description: "ایکسٹینشن، فائل سائز اور مصنف کے حساب سے تمام اپ لوڈ کو فہرست کریں۔" + top_ignored_users: + title: "ٹاپ نظر انداز / خاموش کردہ صارفین" + labels: + ignored_user: نظر انداز کردہ صارف + ignores_count: شمار کو نظر انداز + mutes_count: شمار کو خاموش + description: "جو صارفین بہت سے دوسرے صارفین کی طرف سے خاموش اور/یا نظر انداز کیے گئے ہیں۔" dashboard: rails_env_warning: "آپ کا سرور %{env} مَوڈ میں چل رہا ہے۔" host_names_warning: "آپ کی config/database.yml فائل ڈیفالٹ localhost نام استعمال کر رہا ہے۔ اِسے اپنے سائیٹ ہوسٹ کے نام پر اپ ڈیٹ کریں۔" + gc_warning: 'آپ کا سرور ڈیفالٹ روبی گاربَیج کولیکشن پیرامیٹرز کا استعمال کر رہا ہے، جو آپ کو بہترین کارکردگی نہیں دے گا۔ کارکردگی کی ٹیوننگ پر اس ٹاپک کو پڑھیں: ڈِسکورس کیلئے روبی اور ریلز کی ٹیوننگ۔' sidekiq_warning: 'Sidekiq نہیں چل رہا۔ بہت سے کام، جیسا کہ ای میل بھیجنا، sidekq کی طرف سے اےسِنکرونسلی مکمل کیے جاتے ہیں۔ براہ کرم یقینی بنائیں کہ کم ازکم ایک sidekq پراسیس چل رہا ہے۔ Sidekiq کے بارے میں یہاں سے جانیے۔' queue_size_warning: "قطار میں موجود جابز کی تعداد %{queue_size} ہے، جو کہ زیادہ ہے۔ یہ Sidekiq پراسیس کے ساتھ ایک مسئلہ کی نشاندہی کر سکتا ہے، یا آپ کو مزید Sidekiq کارکنوں کو شامل کرنے کی ضرورت ہوسکتی ہے۔" memory_warning: "آپ کا سرور مجموعی طور پر 1 GB سے کم میموری کے ساتھ چل رہا ہے۔ کم ازکم 1 GB میموری تجویز کی گئی ہے۔" + google_oauth2_config_warning: 'سرور کو ترتیب دیا گیا ہے کہ گُوگل (OAuth2 (enable_google_oauth2_logins کے ساتھ سائن اپ اور لاگ اِن کی اجازت ہو، لیکن کلائنٹ آئی ڈی اور کلائنٹ سیکرٹ وَیلِیوز مقرر نہیں کیے گئے ہیں۔ سائٹ ترتیبات پر جائیں اور ترتیبات کو اَپ ڈیٹ کریں۔ مزید جاننے کے لئے یہ گائیڈ ملاحظہ کریں۔' + facebook_config_warning: 'سرور کو ترتیب دیا گیا ہے کہ فیس بُک (OAuth2 (enable_facebook_logins کے ساتھ سائن اپ اور لاگ اِن کی اجازت ہو، لیکن اَیپ آئی ڈی اور اَیپ سیکرٹ وَیلِیوز مقرر نہیں کیے گئے ہیں۔ سائٹ ترتیبات پر جائیں اور ترتیبات کو اَپ ڈیٹ کریں۔ مزید جاننے کے لئے یہ گائیڈ ملاحظہ کریں۔' + twitter_config_warning: 'سرور کو ترتیب دیا گیا ہے کہ ٹَوِیٹر (enable_twitter_logins) کے ساتھ سائن اپ اور لاگ اِن کی اجازت ہو، لیکن قیی اور سیکرٹ وَیلِیوز مقرر نہیں کیے گئے ہیں۔ سائٹ ترتیبات پر جائیں اور ترتیبات کو اَپ ڈیٹ کریں۔ مزید جاننے کے لئے یہ گائیڈ ملاحظہ کریں۔' + github_config_warning: 'سرور کو ترتیب دیا گیا ہے کہ گِٹ ہَب (enable_github_logins) کے ساتھ سائن اپ اور لاگ اِن کی اجازت ہو، لیکن کلائنٹ آئی ڈی اور سیکرٹ وَیلِیوز مقرر نہیں کیے گئے ہیں۔ سائٹ ترتیبات پر جائیں اور ترتیبات کو اَپ ڈیٹ کریں۔ مزید جاننے کے لئے یہ گائیڈ ملاحظہ کریں۔' + s3_config_warning: 'سرور کو S3 پر فائلوں کو اَپ لوڈ کرنے کیلئے ترتیب دیا گیا ہے، لیکن کم از کم ایک درج ذیل ترتیب سَیٹ نہیں کی گئی ہے: s3_access_key_id, s3_secret_access_key, s3_use_iam_profile, or s3_upload_bucket۔ سائٹ ترتیبات پر جائیں اور ترتیبات کو اَپ ڈیٹ کریں۔ مزید جاننے کے لئے "S3 پر تصویر اپ لوڈز کیسے سَیٹ کریں؟" ملاحظہ کریں۔' + s3_backup_config_warning: 'سرور کو S3 پر بیک اَپس اَپ لوڈ کرنے کیلئے ترتیب دیا گیا ہے، لیکن کم از کم ایک درج ذیل ترتیب سَیٹ نہیں کی گئی ہے: s3_access_key_id, s3_secret_access_key, s3_use_iam_profile, or s3_backup_bucket۔ سائٹ ترتیبات پر جائیں اور ترتیبات کو اَپ ڈیٹ کریں۔ مزید جاننے کے لئے "S3 پر تصویر اپ لوڈز کیسے سَیٹ کریں؟" ملاحظہ کریں۔' + image_magick_warning: 'سرور کو بڑی تصاویر کے تھَمب نَیل تخلیق کرنے کیلئے ترتیب دیا گیا ہے، لیکن ImageMagick اِنسٹال نہیں ہے۔ اپنے پسندیدہ پیکیج مینیجر کا استعمال کرکے ImageMagick اِنسٹال کریں یا تازہ ترین ورژن ڈاؤن لوڈ کریں۔' + failing_emails_warning: 'ناکام ہونے والی %{num_failed_jobs} اِی مَیل جابز موجود ہیں۔ اپنا app.yml چیک کریں اور یہ یقینی بنائیں کہ میل سرور کی ترتیبات درست ہیں۔ Sidekiq میں ناکام جابز ملاحظہ کریں۔' subfolder_ends_in_slash: "آپ کا سب-فولڈر سیٹ اپ غلط ہے؛ DISCOURSE_RELATIVE_URL_ROOT ایک سلَیش میں ختم ہوتا ہے۔" email_polling_errored_recently: one: "گزشتہ 24 گھنٹوں میں ای میل پولِنگ نے ایک خرابی دکھائی ہے۔ مزید تفصیلات کیلئے لاگز کا ملاحضہ کریں۔" other: "گزشتہ 24 گھنٹوں میں ای میل پولِنگ نے %{count} خرابیاں دکھائی ہیں۔ مزید تفصیلات کیلئے لاگز کا ملاحضہ کریں۔" missing_mailgun_api_key: "سرور کو بذریعہ مَیلگَن ای میل بھیجنے کیلئے ترتیب دیا گیا ہے، لیکن آپ نے وَیب ھُوک٘ پیغامات کی توثیق کرنے کیلئے API کلید فراہم نہیں کی ہے۔" + bad_favicon_url: "فَیوِکان لوڈ نہیں ہو رہا ہے۔ سائٹ ترتیبات میں اپنی فَیوِکان ترتیبات چیک کریں۔" poll_pop3_timeout: "POP3 سرور کے ساتھ کنِکشن کا وقت ختم ہوا جا رہا ہے۔ آنے والی ای میل حاصل نہیں کی جاسکی۔ براہ مہربانی اپنی POP3 ترتیبات اور سروس فراہم کرنے والے کو چیک کریں۔" poll_pop3_auth_error: "POP3 سرور کے ساتھ کنِکشن ایک اوَتھَینٹیکیشن خرابی سے ناکام ہو رہا ہے۔ آنے والی ای میل حاصل نہیں کی جاسکی۔ براہ مہربانی اپنی POP3 ترتیبات کو چیک کریں۔" force_https_warning: "آپ کی ویب سائٹ SSL استعمال کر رہی ہے۔ لیکن `force_https` ابھی تک آپ کی سائٹ ترتیبات میں فعال نہیں ہے۔" + out_of_date_themes: "مندرجہ ذیل تِھیمز کیلئے اَپ ڈیٹس دستیاب ہیں:" + unreachable_themes: "ہم مندرجہ ذیل تِھیمز کیلئے اپ ڈیٹس کو چیک نہ کر سکے:" site_settings: censored_words: "الفاظ جو خود بخود ■■■■ سے تبدیل ہوجائیں گے" delete_old_hidden_posts: "کوئی چھپی ہوئی پوسٹ جو 30 دن سے زائد عرصہ سے چھپا رکھی ہو اُس کو خود کار طریقے سے حذف کر دیں۔" + default_locale: "اِس ڈِسکورس سَیٹ اپ کی پہلے سے طہ شدہ زبان۔ آپ سسٹم کی طرف سے بنائے گئے زُمرہ جات اور ٹاپکس کے متن کو مرضی کے مطابق / ٹَیکسٹ متن پر تبدیل کر سکتے ہیں۔" allow_user_locale: "صارفین کو اپنی زبان انٹرفیس ترجیح کو منتخب کرنے کی اجازت دیں" set_locale_from_accept_language_header: "گمنام صارفین کیلئے اِنٹرفیس زبان اُن کے وَیب براؤزر کے لَینگوَیج ہیڈرز سے سَیٹ کریں۔ (تجرباتی، گمنام کَیشے کے ساتھ کام نہیں کرتا)" support_mixed_text_direction: "مخلوط بائیں-سے-دائیں اور دائیں-سے-بائیں ٹیکسٹ سمتوں کی اجازت دیں۔" @@ -1136,12 +1219,21 @@ ur: search_recent_posts_size: "اِنڈَیکس میں کتنی حالیہ پوسٹس رکھی جائیں" log_search_queries: "صارفین کی طرف سے سرچ قُوَیریز کو لاگ کریں" search_query_log_max_size: "رکھی جانے والی سرچ قُوَیریز کی زیادہ سے زیادہ تعداد" + search_query_log_max_retention_days: "سرچ قُوَیریز رکھنے کیلئے زیادہ سے زیادہ وقت، دنوں میں۔" + search_ignore_accents: "ٹیکسٹ تلاش کرتے ہوئے تلفظ کو نظر انداز کریں۔" + category_search_priority_very_low_weight: "بہت کم زمرہ سرچ ترجیحات کیلئے درجہ بندی پر لاگو وزن۔" + category_search_priority_low_weight: "کم زمرہ سرچ ترجیحات کیلئے درجہ بندی پر لاگو وزن۔" + category_search_priority_high_weight: "اعلی زمرہ سرچ ترجیحات کیلئے درجہ بندی پر لاگو وزن۔" + category_search_priority_very_high_weight: "بہت اعلی زمرہ سرچ ترجیحات کیلئے درجہ بندی پر لاگو وزن۔" allow_uncategorized_topics: "ٹاپکس کو بغیر کسی زُمرہ کے تخلیق ہونے کی اجازت دیں۔ انتباہ: اگر کوئی بِلا زُمرہ ٹاپکس موجود ہوں، تو اِس کو بند کرنے سے پہلے آپ کو اُنہیں دوبارہ کسی زُمرہ میں ڈالنا ہوگا۔" allow_duplicate_topic_titles: "ایک جیسے، نقل عنوانات کے ساتھ ٹاپکس کی اجازت دیں۔" unique_posts_mins: "ایک ہی مواد والی دوبارہ پوسٹ صارف کتنے منٹ بعد بنا سکتا ہے" educate_until_posts: "جب صارف اپنی پہلی (ن) نئی پوسٹس کو ٹائپ کرنا شروع کرے، تو کمپوزر میں نیا صارف تعلیمی پینل کا پاپ-اپ دکھائیں۔" title: "اس سائٹ کا نام، جیسا کہ عنوان ٹَیگ میں استعمال ہوتا ہے۔" site_description: "اِس سائٹ کو ایک جملہ میں بیان کریں، جیسا کہ مَیٹا وضاحتی ٹَیگ میں استعمال کیا جاتا ہے۔" + short_site_description: "مختصر وضاحت، جیسا کہ ہوم پیج پر عنوان ٹَیگ میں استعمال ہوتا ہے۔" + contact_email: "اِس سائٹ کیلئے ذمہ دار اہم کانٹیکٹ کا ای میل ایڈریس۔ اہم اطلاعات کے لئے استعمال کیا جاتا ہے، اور اُس کے ساتھ ساتھ فوری طور کے معاملات کیلئے /about رابطہ فارم پر۔" + contact_url: "اِس سائٹ کے لئے رابطہ URL۔ فوری طور کے معاملات کیلئے /about رابطہ فارم پر استعمال کیا جاتا ہے۔" crawl_images: "صحیح چوڑائی اور اونچائی والے طول و عرض داخل کرنے کیلئے ریمَوٹ URL سے تصاویر حاصل کریں۔" download_remote_images_to_local: "ڈاؤن لوڈ کرکے ریمَوٹ تصاویر کو مقامی تصاویر میں تبدیل کریں؛ اِس طرح سے ٹوٹی ہوئی تصاویر کو روکا جا سکتا ہے۔" download_remote_images_threshold: "مقامی طور پر ریمَوٹ تصاویر کو ڈاؤن لَوڈ کرنے کیلئے ڈِسک میں کم از کم جگہ (فیصد میں)" @@ -1151,10 +1243,12 @@ ur: editing_grace_period_max_diff: "ترمیم کی رعایتی مدت کے دوران حروف تبدیلیوں کی زیادہ سے زیادہ تعداد، اگر اِس سے زیادہ تبدیل کیے جائیں تو ایک اور پوسٹ رَوِیژن سٹور کریں (ٹرسٹ لَیول 0 اور 1)" editing_grace_period_max_diff_high_trust: "ترمیم کی رعایتی مدت کے دوران حروف تبدیلیوں کی زیادہ سے زیادہ تعداد، اگر اِس سے زیادہ تبدیل کیے جائیں تو ایک اور پوسٹ رَوِیژن سٹور کریں (ٹرسٹ لَیول 2 اور اوپر)" staff_edit_locks_post: "پوسٹس کو ترمیم کرنے سے روک دیا جائے گا اگر وہ اسٹاف کے اراکین کی طرف سے ترمیم کی گئی ہوں" + post_edit_time_limit: "مصنف اشاعت کے بعد (ن) منٹ تک اپنی پوسٹ میں ترمیم کرسکتے ہیں۔ ہمیشہ کیلئے 0 پر سیٹ کریں۔" edit_history_visible_to_public: "سب کو ایک ترمیم شدہ پوسٹ کے پچھلے ورژن دیکھنے کی اجازت دیں۔ غیر فعال ہونے پر، صرف اسٹاف ممبران دیکھ سکتے ہیں۔" delete_removed_posts_after: "مصنف کی طرف سے ہٹائی گئی پوسٹس خود کار طریقے سے (ن) گھنٹوں کے بعد حذف کردی جائیں گی۔ اگر 0 پر سیٹ ہو، تو پوسٹس کو فوری طور پر حذف کردیا جائے گا۔" max_image_width: "پوسٹ میں تصاویر کی زیادہ سے زیادہ تھَمب نَیل چوڑائی" max_image_height: "پوسٹ میں تصاویر کی زیادہ سے زیادہ تھَمب نَیل اونچائی" + responsive_post_image_sizes: "مندرجہ ذیل پکسل تناسب کے اعلی ڈی پی آئی اسکرینز کیلئے لائیٹ باکس پیش نظارہ تصاویر کا سائز تبدیل کریں۔ رِسپَونسِو تصاویر غیر فعال کرنے کیلئے تمام اقدار ہٹا دیں۔" fixed_category_positions: "اگر باکس چیک شدہ ہو تو، آپ زُمرہ جات کو ایک مقررہ سلسلہ کے حساب سے اُن کی درجہ بندی کرسکیں گے۔ اگر چیک شدہ نہ ہو تو، سرگرمیوں کی تعداد کے مطابق زُمرہ جات درج کیے جاتے ہیں۔" fixed_category_positions_on_create: "اگر باکس چیک شدہ ہو تو، ٹاپک تخلیق ڈائیلاگ پر زُمرہ جات کی درجہ بندی کی ترتیب برقرار رکھی جائے گی (fixed_category_positions ضروری ہے)۔" add_rel_nofollow_to_user_content: 'اندرونی لِنکس (بالائی ڈومینز سمیت) کے علاوہ، تمام شائع کردہ صارف کے مواد پر رَیل nofollow شامل کریں۔ اگر آپ اس کو تبدیل کرتے ہیں تو، آپ کو تمام پوسٹس کو دوبارہ رِیبَیک کرنا ہوگا: "rake posts:rebake"' @@ -1167,14 +1261,27 @@ ur: inline_onebox_domains_whitelist: "ڈومینز کی ایک فہرست جو چھوٹے فارم میں وَن باکسڈ کیے جائیں گے اگر وہ عنوان کے بغیر لنک کیے جائیں" enable_inline_onebox_on_all_domains: "inline_onebox_domain_whitelist سائٹ ترتیب کو نظر انداز کریں اور تمام ڈَومینز پر وَن باکس کی اجازت دیں۔" max_oneboxes_per_post: "ایک پوسٹ میں وَن باکس کی زیادہ سے زیادہ تعداد۔" + logo: "آپ کی سائٹ کے سب سے اوپر بائیں پر لوگو کی تصویر۔ 120 کی اونچائی اور 3:1 سے زائد اَیسپَیکٹ رَیشو والی وسیع مستطیل تصویر کا استعمال کریں۔ اگر خالی چھوڑ دیا گیا ہو تو سائٹ کا عنوان دکھایا جائے گا۔" + logo_small: "آپ کی سائٹ کے سب سے اوپر بائیں پر چھوٹی سی لوگو کی تصویر، نیچے سکرول کرنے پر نظر آتی ہے۔ ایک مربع 120 × 120 تصویر کا استعمال کریں۔ اگر خالی چھوڑ دیا گیا ہو تو ایک ہَوم گلِف دکھایا جائے گا۔" + digest_logo: "آپ کی سائٹ کے ای میل خلاصہ کے اوپر استعبال ہونے والا متبادل کوگو۔ ایک وسیع مستطیل شکل استعمال کریں۔ ایک SVG تصویر استعمال نہ کریں۔ اگر خالی چھوڑ دیا گیا ہو، تو `لَوگَو` ترتیب والی تصویر کا استعمال کیا جائے گا۔" + mobile_logo: "آپکی سائٹ کے موبائل ورژن پر استعمال ہونے والا لَوگَو۔ 120 کی اونچائی اور 3:1 سے زائد اَیسپَیکٹ رَیشو والی وسیع مستطیل تصویر کا استعمال کریں۔ اگر خالی چھوڑ دیا گیا ہو، تو `لَوگَو` ترتیب والی تصویر کا استعمال کیا جائے گا۔" + large_icon: "تصویر جو دوسرے مَیٹا ڈَیٹا آئیکن کیلئے بنیاد کے طور پر استعمال کی جاتی ہے۔ مثالی طور پر 512x512 سے بڑی ہونی چاہئیے۔ اگر خالی چھوڑ دیا گیا ہو، تو logo_small کا استعمال کیا جائے گا۔" + manifest_icon: "تصویر جو اینڈرائڈ پر لوگو/سپلیش تصویر کے طور پر استعمال ہو۔ خود کار طریقے سے 512 × 512 کے سائیز پر دوبارہ تبدیل کر دی جائے گی۔ اگر خالی چھوڑ دیا گیا ہو، تو large_icon کا استعمال کیا جائے گا۔" + favicon: "آپ کی ویب سائٹ کیلئے ایک فَیوِکان، http://en.wikipedia.org/wiki/Favicon دیکھیے۔ ایک CDN پر صحیح کام کرنے کیلئے اِس کا png ہونا ضروری ہے۔ 32x32 کے سائیز پر دوبارہ تبدیل کر دی جائے گی۔ اگر خالی چھوڑ دیا گیا ہو، تو large_icon کا استعمال کیا جائے گا۔" + apple_touch_icon: "ایپل ٹچ ڈیوائسوں کیلئے استعمال کیا جانے والا آئکن۔ خود بخود 180x180 کے سائیز پر دوبارہ تبدیل کر دیا جائے گا۔ اگر خالی چھوڑ دیا گیا ہو، تو large_icon کا استعمال کیا جائے گا۔" + opengraph_image: "ڈِیفالٹ اَوپَن٘گراف تصویر، استعمال کی جاتی ہے جب صفحہ میں کوئی اور مناسب تصویر نہ ہو۔ اگر خالی چھوڑ دیا گیا ہو، تو large_icon کا استعمال کیا جائے گا۔" + twitter_summary_large_image: "ٹویٹر کارڈ 'خلاصہ بڑی تصویر' (کم از کم 280 چوڑائی میں، اور کم از کم 150 اونچائی میں ہونی چاہئیے)۔ اگر خالی چھوڑ دیا گیا ہو، تو opengraph_image کا استعمال کرتے ہوئے عام کارڈ مَیٹا ڈَیٹا بنایا جاتا ہے۔" notification_email: "تمام ضروری سِسٹم ای میلز بھیجنے کیلئے استعمال ہونے والا from: ای میل ایڈریس۔ یہاں درج کردہ ڈومین کا SPF ،DKIM اور ریورس PTR ریکارڈ، ای میل کے پہنچنے کیلئے، صحیح طریقے سے مقرر ہونا لازمی ہے۔" email_custom_headers: "اپنی مرضی کے ای میل ہیڈرز کی پائپ سے علیحدہ کردہ فہرست" + email_subject: "سٹینڈرڈ ای میلز کیلئے اپنی مرضی کا موضوع فارمَیٹ۔ دیکھیے https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" + enforce_second_factor: "صارفین کو دوسری فیکٹر توثیق فعال کرنے کیلئے مجبور کرتی ہے۔ تمام صارفین پر لاگو کرنے کیلئے 'تمام' منتخب کریں۔ صرف سٹاف صارفین پر لاگو کرنے کیلئے 'سٹاف' منتخب کریں۔" force_https: "صرف HTTPS استعمال کرنے کیلئے اپنی سائٹ کو مجبورکریں۔ انتباہ: جب تک آپ تصدیق نہ کر لیں کہ HTTPS مکمل طور پر سیٹ ہے اور ہر جگہ کام کر رہا ہے، اِس کو فعال نہ کریں! کیا آپ نے اپنا CDN، تمام سماجی لاگ ان، اور بیرونی لوگو / انحصارات کو چیک کر کے یہ یقینی بنا لیا ہے کہ وہ تمام HTTPS سے مطابقت رکھتے ہیں؟" same_site_cookies: "سَیم سائٹ کے کُوکِیز کا استعمال کریں، وہ قابل براؤزرز (Lax یا Strict) پر تمام ویکٹر Cross Site Request Forgery کو ختم کر دیتے ہیں۔ انتباہ: Strict صرف اُن سائٹس پر کام کرے گا جو لاگ اِن پر مجبور کرتے ہیں اور SSO کا استعمال کرتے ہیں۔" summary_score_threshold: "'اِس ٹاپک کا خلاصہ کریں' میں شامل ہونے کیلئے ایک پوسٹ کا کم از کم سکور" summary_posts_required: "'اِس ٹاپک کا خلاصہ کریں' فعال ہونے سے پہلے ٹاپک میں پوسٹس کی کم از کم تعداد" summary_likes_required: "'اِس ٹاپک کا خلاصہ کریں' فعال ہونے سے پہلے ٹاپک میں لائیکس کی کم از کم تعداد" summary_percent_filter: "جب صارف 'اِس ٹاپک کا خلاصہ کریں' پر کلک کرتا ہے، تو سب سے اوپر % پوسٹس دکھائیں" + summary_max_results: "'اِس ٹاپک کا خلاصہ کریں' کی طرف سے لوٹائی جانے والی زیادہ سے زیادہ پوسٹس" enable_personal_messages: "ٹرسٹ لَیول 1 (پیغامات بھیجنے کیلئے کم از کم ٹرسٹ لَیول کے ذریعہ ترتیب دے سکتے ہیں) والے صارفین کو پیغامات بنانے اور پیغامات کا جواب دینے کی اجازت دیں۔ نوٹ کریں کہ جوبھی ہو، اسٹاف ہمیشہ پیغامات بھیج سکتا ہے۔" enable_system_message_replies: "صارفین کو سِسٹم پیغامات کا جواب دینے کی اجازت دیں، یہاں تک کہ اگر ذاتی پیغامات بھی غیر فعال ہوں" enable_long_polling: "نوٹیفکیشن کیلئے مَیسج بس استعمال ہو رہا ہے، لانگ پولِنگ کا استعمال کیا جا سکتا ہے" @@ -1183,6 +1290,9 @@ ur: polling_interval: "جب لانگ پولِنگ نہ ہو، تو لاگ ہوئے کلائنٹس کو مِلی سیکنڈوں میں کتنی دفعہ پَول کرنا چاہئے" anon_polling_interval: "گمنام کلائنٹس کو مِلی سیکنڈوں میں کتنی دفعہ پَول کرنا چاہئے" background_polling_interval: "کلائنٹس کو مِلی سیکنڈوں میں کتنی دفعہ پَول کرنا چاہئے (جب وِنڈو پسِ منظر میں ہو)" + hide_post_sensitivity: "اِس کا امکان کہ ایک فلَیگ کردہ پوسٹ چھپا دی جائے گی" + silence_new_user_sensitivity: "اِس کا امکان کہ ایک نیا صارف سپیم فلَیگز کی بنیاد پر خاموش کر دیا جائے گا" + auto_close_topic_sensitivity: "اِس کا امکان کہ ایک فلَیگ کردہ ٹاپک خود بخود بند ہو جائے گا" cooldown_minutes_after_hiding_posts: "منٹوں کی تعداد جن کیلئے ایک صارف کو کمیونٹی فلَیگ بندی کے ذریعہ چھپائی گئی پوسٹ میں ترمیم کرنے سے پہلے انتظار کرنا لازمی ہے" max_topics_in_first_day: "اُن کی پہلی پوسٹ بنانے کے بعد 24 گھنٹوں کی مدت میں ایک صارف کی طرف سے تخلیق کردہ ٹاپکس کی زیادہ سے زیادہ تعداد" max_replies_in_first_day: "اُن کی پہلی پوسٹ بنانے کے بعد 24 گھنٹوں کی مدت میں ایک صارف کی طرف سے تخلیق کردہ جوابات کی زیادہ سے زیادہ تعداد" @@ -1193,33 +1303,53 @@ ur: num_tl3_flags_to_silence_new_user: "اگر کسی نئے صارف کی پوسٹس کو num_tl3_users_to_silence_new_user مختلف ٹرسٹ لَیول 3 والے صارفین سے اتنے فلَیگز ملیں، تو اُن کی تمام پوسٹس کو چھپا اور مستقبل کی اشاعت کو روک دیں۔ غیر فعال کرنے کیلئے 0۔" num_tl3_users_to_silence_new_user: "اگر کسی نئے صارف کی پوسٹس کو اتنے مختلف ٹرسٹ لَیول 3 والے صارفین سے num_tl3_flags_to_silence_new_user فلَیگز ملیں، تو اُن کی تمام پوسٹس کو چھپا اور مستقبل کی اشاعت کو روک دیں۔ غیر فعال کرنے کیلئے 0۔" notify_mods_when_user_silenced: "اگر صارف خود کار طریقے سے خاموش کر دیا جائے، تو تمام ماڈریٹرزکو ایک پیغام بھیجیں۔" + flag_sockpuppets: "اگر ایک نیا صارف اُسی IP ایڈریس سے کسی ٹاپک پر جواب دیتا ہے جس IP ایڈریس سے ایک دوسرے صارف نے وہی ٹاپک شروع کیا تھا، تو اُن دونوں کی پوسٹس کو ممکنہ سپَیم کے طور پر فلَیگ کریں۔" traditional_markdown_linebreaks: "مارکڈائون میں روایتی لائن وقفے کا استعمال کریں، جس میں ایک لائن وقفے کیلئے دو ٹریلنگ خالی جگہوں کی ضرورت ہوتی ہے۔" + enable_markdown_typographer: "ٹیکسٹ کا پڑھنا آسان بنانے کیلئے چپھائی کے قوانین کا استعمال کریں: سیدھے قَوٹس ' کو گول قَوٹس ’ سے تبدیل کریں، (c) (tm) کو علامات کے ساتھ تبدیل کردیں، -- کو اَیم ڈَیش – کے ساتھ، وغیرہ" + enable_markdown_linkify: "ٹیکسٹ جو ایک لنک کی طرح لگے اُسے خود کار طریقے سے ایک لنک کے طور پر دکھائیں: www.example.com اور https://example.com خود بخود لِنک کر دیے جائیں گے" markdown_linkify_tlds: "سب سے اوپر کی سطح کے ڈَومینز کی فہرست جو خود بخود لِنکس کے طور پر دکھائے جاتے ہیں" post_undo_action_window_mins: "منٹوں کی تعداد جب تک صارفین کو ایک پوسٹ پر حالیہ کارروائیوں (لائیک، فلَیگ، وغیرہ) کو واپس کرلینے کی اجازت ہے۔" must_approve_users: "سائٹ تک رسائی حاصل کرنے سے پہلے اسٹاف کو تمام نئے صارف اکاؤنٹس کو منظور کرنا ضروری ہے۔ انتباہ: کسی لائیو سائٹ کیلئے اِس کو فعال کرنے سے غیر اسٹاف صارفین کیلئے رسائی منسوخ ہو جائے گی!" pending_users_reminder_delay: "ماڈریٹرز کو مطلع کریں اگر نئے صارفین اِس سے زیادہ گھنٹوں سے منظوری کے منتظر ہوں۔ اطلاعات کو غیر فعال کرنے کیلئے -1 سَیٹ کریں۔" maximum_session_age: "آخری وزٹ سے ن گھنٹوں بعد تک صارف کو لاگڈ اِن رکھا جائے گا" + ga_universal_tracking_code: "گُوگل یونیوَرسل اَینَیلَیٹِکس (analytics.js) ٹرَیکِنگ کَوڈ آئی ڈی، مثال: UA-12345678-9؛ دیکھیے https://google.com/analytics" + ga_universal_domain_name: "گُوگل یونیوَرسل اَینَیلَیٹِکس (analytics.js) ڈَومَین نام، مثال: mysite.com؛ دیکھیے https://google.com/analytics" + ga_universal_auto_link_domains: "گُوگل یونیوَرسل اَینَیلَیٹِکس (analytics.js) کراس ڈَومَین ٹرَیکِنگ فعال کریں۔ اِن ڈومینز کے باہر جانے والے لِنکس کے ساتھ کلائینٹ id شامل کر دی جائے گی۔ گُوگل کی کراس ڈَومَین ٹریکنگ گائیڈ ملاحظہ کریں۔" + gtm_container_id: "گُوگل ٹیگ مینیجر کی کنٹینر آئی ڈی. مثال: GTM-ABCDEF۔
نوٹ: GTM کی طرف سے لوڈ کردہ تیسری پارٹی کے سکرپٹس کو 'کَنٹینٹ سیکورٹی پالیسی سکرپٹ ایس آر سی' میں وائٹ لسٹ کرنے کی ضرورت ہوسکتی ہے۔" + enable_escaped_fragments: "اگر کسی وَیب کرالر کا پتہ نہ لگے تو گُوگل اَیجَیکس-کرالِنگ API کا استعمال کریں۔ دیکھیے https://developers.google.com/webmasters/ajax-crawling/docs/learn-more" moderators_create_categories: "منتظمین کو نئے زُمرہ جات بنانے کی اجازت دیں" cors_origins: "وہ اَوریجِن جن کیلئے کراس-اَوریجِن درخواستوں (CORS) کی اجازت ہے۔ ہراَوریجِن میں http:// یا https:// شامل ہونا ضروری ہے۔ CORS کو فعال کرنے کیلئے DISCOURSE_ENABLE_CORS کی وَیلِیو ٹرُو پر مقرر ہونا لاذمی ہے۔" use_admin_ip_whitelist: "ایڈمن صرف اُس صورت میں لاگ ان کرسکتے ہیں اگر وہ ایسے IP ایڈریس میں ہیں جو اسکرین کردہ آئی پیز کی فہرست (ایڈمن > لاگز > اسکرین کردہ آئی پیز) میں بیان کیا گیا ہو۔" blacklist_ip_blocks: "نجی IP بلاکس کی فہرست جو ڈِسکَورس کی طرف سے کبھی کرال نہیں ہونی چاہئیں" whitelist_internal_hosts: "اندرونی ہَوسٹس کی فہرست جو ڈِسکَورس محفوظ طریقے سے وَن باکس اور دیگر مقاصد کیلئے کرال کر سکتا ہے" allowed_iframes: "iframe src ڈَومَین کے سابقوں کی ایک فہرست جو ڈِسکَورس محفوظ طریقے سے پوسٹس میں شامل ہونے کی اجازت دے سکتا ہے" + whitelisted_crawler_user_agents: "وَیب کرالرز کے صارف ایجنٹوں جن کو سائٹ تک رسائی کی اجازت دی جانی چاہیئے۔ انتباہ! اِس ترتیب کو فعال کرنے سے وہ تمام کرالرز جو یہاں فہرست کردہ نہیں ہیں رد کر دہے جائیں گے!" + blacklisted_crawler_user_agents: "صارف ایجنٹ سٹرنگ میں منفرد کیس غیر حساس لفظ جس سے اُن وَیب کرالرز کی شناخت ہوتی ہے جن کو اِس سائٹ تک رسائی کی اجازت نہیں دی جانی چاہیئے۔ اگر وائٹ لِسٹ کی وضاحت کی گئی ہو تو لاگو نہیں ہوتا۔" + slow_down_crawler_user_agents: "crawl-delay ہدایت کا استعمال کرتے ہوئے، ویب کرالرز کے صارف ایجنٹ جو robots.txt میں شرح محدود ہونے چاہئیں" slow_down_crawler_rate: "اگر slow_down_crawler_user_agents کی وضاحت کی گئی ہے تو یہ شرح تمام کرالرز پر لاگو ہوگی (درخواستوں کے درمیان سیکنڈوں کی تاخیر)" + content_security_policy: "کَنٹینٹ-سیکورٹی-پالیسی فعال کریں" + content_security_policy_report_only: "کَنٹینٹ-حفاظتی-پالیسی-رپورٹ-اَونلی فعال کریں" + content_security_policy_collect_reports: "سی ایس پی کی خلاف ورزی کی رپورٹ کو /csp_reports پر جمع کرنے کو فعال کریں" + content_security_policy_script_src: "اضافی وائِٹ لِسٹ کردہ سکرپٹ کے ذرائع۔ موجودہ ہَوسٹ اور سی ڈی این ڈیفالٹ سے شامل ہیں۔" + invalidate_inactive_admin_email_after_days: "ایڈمن اکاؤنٹس جنہوں نے دنوں کی اتنی تعداد میں سائٹ کا دورہ نہیں کیا، اُن کو لاگ اِن کرنے سے پہلے اپنے ایمیل ایڈریس کی دوبارہ توثیق کرنے کی ضرورت ہوگی۔ غیر فعال کرنے کیلئے 0 پر سَیٹ کریں۔" top_menu: "اِس بات کا تعین کریں کہ ہَوم پیج نیویگیشن پر کونسی، اور کس ترتیب کے ساتھ اشیاء ظاہر ہوں۔ مثال کے طور پر تازہ ترین|نئی|بغیر پڑھی|زُمرہ جات|ٹاپ|پڑھ لیے گئے|شائع کیے|بُکمارکس" post_menu: "اِس بات کا تعین کریں کہ پوسٹ مَینِیو پر کونسی، اور کس ترتیب کے ساتھ اشیاء ظاہر ہوں۔ مثال کے طور پر لائیک|ترمیم| فلَیگ|حذف|شئیر|بُکمارک|جواب" post_menu_hidden_items: "پوسٹ مینیو میں ڈیفالٹ کے طور پرچھپائی جانے والی اشیاء جب تک کسی توسیعی ellipsis پر کلِک نہ کیا جائے۔" share_links: "اِس بات کا تعین کریں کہ شیئر ڈائیلاگ پر کونسی، اور کس ترتیب کے ساتھ اشیاء ظاہر ہوں۔" site_contact_username: "ایک درست سٹاف صارف نام جس کی طرف سے تمام خود کار طریقے سے پیغامات بھیجے جائیں۔ اگر خالی چھوڑ دیا گیا ہو تو ڈیفالٹ سِسٹم اکاؤنٹ استعمال کیا جائے گا۔" + site_contact_group_name: "تمام خود کار پیغامات پر مدعو ہونے کیلئے ایک درست گروپ کا نام۔" send_welcome_message: "تمام نئے صارفین کو فوری شروعات کے گائیڈ کے ساتھ خوش آمدید کا پیغام بھیجیں۔" + send_tl1_welcome_message: "نئے ٹرسٹ لَیول 1 صارفین کو خوش آمدید کا پیغام بھیجیں۔" suppress_reply_directly_below: "پوسٹ پر وسیع ہو سکنے والا جوابات کی تعداد مت دکھائیں جب اِس پوسٹ کے براہ راست نیچے ایک ہی واحد جواب موجود ہو۔" suppress_reply_directly_above: "پوسٹ پر وسیع ہو سکنے والا جس-کے-جواب-میں مت دکھائیں جب اِس پوسٹ کے براہ راست اوپر ایک ہی واحد جواب موجود ہو۔" + remove_full_quote: "براہ راست جوابوں پر مکمل اقتباس خود بخود ہٹا دیں۔" suppress_reply_when_quoting: "پوسٹ پر وسیع ہو سکنے والا جس-کے-جواب-میں مت دکھائیں جب پوسٹ میں جواب کا اقتباس شامل ہو۔" max_reply_history: "جس-کے-جواب-میں توسیع کرتے وقت، توسیع کیے گئے جوابات کی زیادہ سے زیادہ تعداد" topics_per_period_in_top_summary: "ڈیفالٹ کے طور پر ٹاپ ٹاپکس خلاصہ میں دکھائے گئے ٹاپ ٹاپکس کی تعداد۔" topics_per_period_in_top_page: "توسیع کردہ ٹاپ ٹاپکس 'مزید دکھائیں' میں دکھائے گئے ٹاپ ٹاپکس کی تعداد۔" redirect_users_to_top_page: "نئے اور طویل غیر حاضر صارفین کو خود کار طریقے سے ٹاپ صفحے پر ریڈائرَیکٹ کریں۔" top_page_default_timeframe: "ٹاپ وِیُو صفحے کیلئے ڈِیفالٹ ٹائم فریم۔" + moderators_view_emails: "ماڈریٹرز کو صارف ایمیلز دیکھنے کی اجازت دیں" prioritize_username_in_ux: "صارف صفحے، صارف کارڈ اور پوسٹس پر پہلے صارف نام دکھائیں (جب غیر فعال ہو تو نام پہلے دکھایا جاتا ہے)" enable_rich_text_paste: "کمپَوزر میں ٹیکسٹ پَیسٹ کرتے وقت خود کار طریقہ سے HTML سے مارکڈائون میں تبادلہ فعال کریں۔ (تجرباتی)" email_token_valid_hours: "پاسورڈ بھولنے / اکاؤنٹ چالو کرنے کے ٹوکن (ن) گھنٹوں کیلئے درست ہیں۔" @@ -1230,33 +1360,57 @@ ur: email_domains_whitelist: "ای میل ڈومینز کی پائپ سے علیحدہ کردہ فہرست جن کے ساتھ صارفین کو اکاؤنٹس رجسٹر کرنا لاذمی ہے۔ انتباہ: اِس فہرست کے علاوہ ای میل ڈومینز والے صارفین کو روک دیا جائے گی!" hide_email_address_taken: "سائن اَپ کے دوران اور بھولا پاسورڈ فارم سے صارفین کو مطلع نہ کریں کہ فراہم کیے گئے ای میل ایڈریس کے ساتھ ایک اکاؤنٹ موجود ہے۔" log_out_strict: "لاگ آؤٹ ہونے پر، صارف کیلئے تمام ڈِیوائیسِز پر تمام سیشنوں کو لاگ آؤٹ کریں" + version_checks: "ورژن اپ ڈیٹ کیلئے ڈِسکورس ہَب کو پِنگ کریں اور /admin ڈیش بورڈ پر نئے ورژن کا پیغام دکھائیں" new_version_emails: "جب ڈِسکورس کا نیا ورژن دستیاب ہو تو contact_email ایڈریس پر ایک ای میل بھیجیں۔" invite_expiry_days: "دنوں میں، کتنے عرصے تک صارف دعوت نامہ کلیدیں درست رہیں" + invite_only: "تمام نئے صارفین کو واضح طور پر قابل اعتماد صارفین یا سٹاف کی طرف سے مدعو کیا جانا لاذمی ہے۔ عوامی رجسٹریشن غیر فعال ہے،" login_required: "اِس سائٹ پر مواد پڑھنے کیلئے اکاؤنٹ کی توثیق ہونا ضروری بنائیں، گمنام صارفین تک رسائی کو مسترد کریں۔" min_username_length: "حروف میں صارف نام کی کم از کم لمبائی۔ انتباہ: اگر کوئی موجودہ صارفین یا گروپوں کے نام اِس سے چھوٹے ہیں، تو آپ کی سائٹ ٹوٹ جائے گی!" max_username_length: "حروف میں صارف نام کی زیادہ سے زیادہ لمبائی۔ انتباہ: اگر کوئی موجودہ صارفین یا گروپوں کے نام اِس سے زیادہ لمبے ہیں، تو آپ کی سائٹ ٹوٹ جائے گی!" + unicode_usernames: "صارف اور گروپ ناموں میں یونیکَوڈ حروف اور نمبر شامل کرنے کی اجازت دیں۔" + unicode_username_character_whitelist: "صارف نام میں صرف چند یونیکَوڈ حروف کی اجازت دینے کیلئے رَیگولر اَیکسپرَیشَن۔ ASCII حروف اور نمبر کی ہمیشہ اجازت ہے اور اُنہیں وائِٹ لِسٹ میں شامل کرنے کی ضرورت نہیں ہے۔" reserved_usernames: "صارف نام جن کیلئے سائن اَپ کی اجازت نہیں ہے۔ وائلڈ کارڈ علامت * کسی بھی حرف کو صفر یا اِس سے زیادہ بار میچ کرنے کیلئے استعمال کیا جا سکتا ہے۔" min_password_length: "پاسورڈ کی کم از کم لمبائی۔" min_admin_password_length: "اَیڈمن کیلئے پاسورڈ کی کم از کم لمبائی۔" password_unique_characters: "منفرد حروف کی کم از کم تعداد جو پاسورڈ میں ہونا لاذمی ہے۔" block_common_passwords: "ایسا پاسورڈ جو 10،000 سب سے زیادہ عام پاسورڈز میںشامل ہو، اۃسے رکھنے کی اجازت نہ دیں۔" enable_sso: "بیرونی سائٹ کے ذریعہ واحد سائن اَن کو فعال کریں (انتباہ: صارفین کے ای میل ایڈریس بیرونی سائٹ کی طرف سے توثیق کیے جانا *لازمی* ہے!)" + verbose_sso_logging: "بہت زیادہ تفصیلی SSO سے متعلقہ تشخیص /logs میں ریکارڈ کریں" + enable_sso_provider: "/session/sso_provider کے اینڈپوائنٹ پر ڈِسکورس SSO پرووَائیڈر پروٹوکول کو نافذ کریں، sso_provider_secrets کو مقرر کیا جانا ضروری ہے" sso_url: "URL واحد سائن اَن اینڈپوائنٹ کا (اhttp:// یا https:// کا شامل ہونا لاذمی ہے)" sso_secret: "خفیہ سٹرنگ جو کرِیپٹَوگرافی کے زریعہ SSO معلومات کی توثیق کرنے کیلئے استعمال کیا جاتا ہے، یقینی بنائیں کہ یہ 10 یا اُس سے زیادہ حروف لمبا ہے" + sso_provider_secrets: "ڈَومَین-سیکرٹ جوڑوں کی فہرست جو SSO پرووَائیڈر کے طور پر ڈِسکورس کو استعمال کر رہے ہیں۔ یقینی بنائیں کہ SSO سیکرٹ 10 حروف یا اِس سے زائید ہے۔ وائلڈ کارڈ علامت * کسی بھی ڈومین یا اُس کے صرف ایک حصے کو میچ کرنے کیلئے استعمال کیا جا سکتا ہے (مثلاً *.example.com)۔" sso_overrides_bio: "صارف پروفائل میں صارف کی بائیو کی جگہ لے لیتا ہے اور اِس کو تبدیل کرنے سے صارف کو روک دیتا ہے" + sso_overrides_groups: "تمام دستی گروپ رکنیت کو گروپس ایس ایس او صفت میں متعین کردہ گروپس کے ساتھ مطابقت پذیر کریں (انتباہ: اگر آپ گروپس کی وضاحت نہیں کرتے ہیں تو تمام دستی گروپ رکنیت صارف کیلئے صاف ہوجائے گی)" sso_overrides_email: "ہر لاگ اِن پر SSO پے لوڈ سے بیرونی ویب سائٹ ای میل مقامی ای میل کی جگہ لے لیتا ہے، اور مقامی تبدیلیوں کو روک دیتا ہے۔ (انتباہ: مقامی ای میلز کو معمول پر لانے کی وجہ سے اختلافات ہوسکتے ہیں)" sso_overrides_username: "ہر لاگ اِن پر SSO پے لوڈ سے بیرونی ویب سائٹ صارف نام مقامی صارف نام کی جگہ لے لیتا ہے، اور مقامی تبدیلیوں کو روک دیتا ہے۔ (انتباہ: صارف نام کی لمبائی/ضروریات کے فرق کی وجہ سے اختلافات ہوسکتے ہیں)" sso_overrides_name: "ہر لاگ اِن پر SSO پے لوڈ سے بیرونی ویب سائٹ پورا نام مقامی پورا نام کی جگہ لے لیتا ہے، اور مقامی تبدیلیوں کو روک دیتا ہے۔" + sso_overrides_avatar: "SSO پے لوڈ سے بیرونی ویب سائٹ اوتار صارف اوتار کی جگہ لے لیتا ہے۔ اگر فعال ہو تو، صارفین کو ڈِسکورس پر اوتار اَپ لوڈ کرنے کی اجازت نہیں ہو گی۔" + sso_overrides_profile_background: "SSO پے لوڈ سے بیرونی ویب سائٹ اوتار صارف پروفائل پس منظر کی جگہ لے لیتا ہے۔" + sso_overrides_card_background: "SSO پے لوڈ سے بیرونی ویب سائٹ اوتار صارف کارڈ پس منظر کی جگہ لے لیتا ہے۔" sso_not_approved_url: "غیر منظور شدہ SSO اکاؤنٹس کو اِس URL پر ریڈائرَیکٹ کریں" sso_allows_all_return_paths: "SSO کے ذریعہ فراہم کردہ return_paths کیلئے ڈَومین کو محدود نہ کریں (ڈِیفالٹ کے طور پر ریٹرن پاتھ کا موجودہ سائٹ پر ہونا لاذمی ہے)" + enable_local_logins: "مقامی صارف نام اور پاسورڈ لاگ اِن کی بنیاد پر اکاؤنٹس فعال کریں۔ دعوتیں کام کرنے کیلئے اِسے فعال ہونا لاذمی ہے۔ انتباہ: اگر غیر فعال ہے، تو آپ لاگ ان کرنے کے قابل نہیں رہیں گے اگر آپ نے کم از کم ایک متبادل لاگ اِن کا طریقہ ترتیب نہ دیا ہو۔" enable_local_logins_via_email: "صارفین کو بذریعہ ای میل بھیجے جانے والے ایک کلِک لاگ اِن لِنک کی درخواست کرنے کی اجازت دیں۔" allow_new_registrations: "نئے صارف رجسٹریشنوں کی اجازت دیں۔ کسی کو نیا اکاؤنٹ بنانے سے روکنے کیلئے اس کو غیر چیک شدہ کریں۔" enable_signup_cta: "واپس آنے والے گمنام صارفین کو ایک نوٹس دکھائیں جو اُنہیں اکاؤنٹ بنانے کیلۓ قائل کرے۔" + enable_google_oauth2_logins: "گُوگل Oauth2 توثیق کو فعال کریں۔ گُوگل فی الحال اِس توثیق کے طریقہ کی اجازت دیتا ہے۔ کلید اور سیکرٹ کی ضرورت ہے۔ دیکھیے ڈسکورس کیلئے گُوگَل لاگ اِن ترتیب دیں۔" google_oauth2_client_id: "آپ کی گُوگل ایپلی کیشن کی کلائنٹ آئی ڈی." google_oauth2_client_secret: "آپ کی گُوگل ایپلی کیشن کا کلائنٹ سیکرٹ." + google_oauth2_prompt: "خالی جگہ سے علیحدہ کردہ سٹرنگ وَیلِیوز کی ایک اختیاری فہرست جو اِس بات کی وضاحت کرتی ہے کہ اَوتھرائزیشن سرور صارف کو دوبارہ تصدیق اور رضامندی کیلئے پوچھتا ہے کہ نہیں۔ ممکنہ اقدار کیلئے https://developers.google.com/identity/protocols/OpenIDConnect#prompt دیکھیے۔" + google_oauth2_hd: "ایک اختیاری گُوگل اَیپس ہوسٹِڈ ڈومَین جس تک کہ سائن اِن محدود ہو گا۔ مزید تفصیلات کیلئے https://developers.google.com/identity/protocols/OpenIDConnect#hd-param ملاحظہ کریں۔" + enable_twitter_logins: "ٹویٹر توثیق فعال کریں۔ twitter_consumer_key اور twitter_consumer_secret ضروری ہیں۔ دیکھیے ڈسکورس کیلئے ٹویٹر لاگ اِن ترتیب دیں (اور رِچ اَیمبَیڈ)۔" + twitter_consumer_key: "ٹویٹر توثیق کیلئے کنزِیُومر کِی، https://developer.twitter.com/app پر رجسٹر کردہ" + twitter_consumer_secret: "ٹویٹر توثیق کیلئے کنزِیُومر سیکرٹ، https://developer.twitter.com/app پر رجسٹر کردہ" enable_instagram_logins: "اِنسٹاگرام توثیق کو فعال کریں، instagram_consumer_key اور instagram_consumer_secret کی ضرورت ہے" instagram_consumer_key: "اِنسٹاگرام توثیق کیلئے کنزِیُومر کلید" instagram_consumer_secret: "اِنسٹاگرام توثیق کیلئے کنزِیُومر سیکرٹ" + enable_facebook_logins: "فَیسبُک توثیق فعال کریں۔ facebook_app_id اور facebook_app_secret ضروری ہیں۔ دیکھیے ڈسکورس کیلئے فَیسبُک لاگ اِن ترتیب دیں۔" + facebook_app_id: "فَیسبُک توثیق کیلئے اَیپ آئی ڈی، https://developers.facebook.com/apps پر رجسٹر کردہ" + facebook_app_secret: "فَیسبُک توثیق کیلئے اَیپ سیکرٹ، https://developers.facebook.com/apps پر رجسٹر کردہ" + enable_github_logins: "گِٹ ہَب توثیق فعال کریں۔ github_client_id اور github_client_secret ضروری ہیں۔ دیکھیے ڈسکورس کیلئے گِٹ ہَب لاگ اِن ترتیب دیں۔" + github_client_id: "گِٹ ہَب توثیق کیلئے کلائنٹ آئی ڈی، https://github.com/settings/developers پر رجسٹر کردہ" + github_client_secret: "گِٹ ہَب توثیق کیلئے کلائنٹ سیکرٹ، https://github.com/settings/developers پر رجسٹر کردہ" readonly_mode_during_backup: "بیک اَپ کرتے وقت صرف پڑھنے کا مَوڈ فعال کریں" enable_backups: "ایڈمِنِسٹریٹروں کو فورم کے بیک اَپ بنانے کی اجازت دیں" allow_restore: "رِیسٹور کی اجازت دیں، جو سائٹ کا تمام ڈیٹا تبدیل کر سکتا ہے! فالس چھوڑ دیں جب تک کہ آپ بیک اَپ بحال نہ کرنے لگیں" @@ -1264,9 +1418,15 @@ ur: automatic_backups_enabled: "خودکار بیک اَپس چلائیں جیسا کہ بیک اَپ فریکوئنسی میں بیان کیا گیا ہے" backup_frequency: "بَیک اَپس کے درمیان دنوں کی تعداد۔" s3_backup_bucket: "بیک اَپس رکھنے کیلئے ریمَوٹ بَکِّٹ۔ انتباہ: یقینی بنائیں کہ یہ ایک زاتی بَکِّٹ ہے۔" + s3_endpoint: "اَینڈ پوائنٹ کو S3 سے مطابقت رکھنے والی سروس جیسے کہ ڈیجیٹل اَوشن سپَیسز یا مینیو پر بیک اَپ کرنے کیلئے ترمیم کیا جا سکتا ہے۔ انتباہ: اگر AWS S3 کا استعمال کر رہے ہوں تو خالی چھوڑ دیں۔" + s3_configure_tombstone_policy: "سنگ مزار اپ لوڈوں کیلئے خود کار طریقے سے حذف کر دینے کی پالیسی فعال کریں۔ اہم: اگر غیر فعال ہو، تو اپلوڈ حذف ہونے پر کوئی جگہ دوبارہ حاصل نہیں کی جائے گی۔" s3_disable_cleanup: "جب مقامی طور پر بیک اَپ ہٹا دیا جائے اُس کے ساتھ S3 پر سے بھی ہٹا دینا غیر فعال کریں۔" + enable_s3_inventory: "رپورٹیں بنائیں اور اَیمَیزَون S3 انوینٹری کا استعمال کرتے ہوئے اپلوڈ کی توثیق کریں۔ اہم: درست S3 اسناد ضروری ہیں (ایکسَیس قی آئی ڈی اور سیکرٹ ایکسَیس قی، دونوں)۔" backup_time_of_day: "دن کا وقت UTC جب بیک اَپ ہونا چاہئے۔" backup_with_uploads: "شیڈول کردہ بیک اَپس میں اپلوڈز شامل کریں۔ اِس کو غیر فعال کرنے پر صرف ڈَیٹا بَیس بیک اَپ کیا جائے گا۔" + backup_location: "مقام جہاں بیک اپ سٹور کیے جاتے ہے۔ اہم: فائل ترتیبات میں درست S3 اسناد کا درج ہونا S3 کیلئے ضروری ہے۔" + backup_gzip_compression_level_for_uploads: "اپ لوڈ کمپریس کرنے کیلئے استعمال کیے جانے والا Gzip کمپریشن لیول۔" + include_thumbnails_in_backups: "بیک اپ میں پیدا کردہ تھَمب نَیل شامل کریں۔ اِس کو غیر فعال کرنے سے بیک اپ چھوٹے ہو جائیں گے، لیکن رِیسٹور کے بعد تمام پوسٹس کو دوبارہ رِیبَیک کرنا پرے گا۔" active_user_rate_limit_secs: "سیکنڈوں میں، 'last_seen_at' فیلڈ کو ہم کتنی بار اَپ ڈیٹ کریں" verbose_localization: "UI میں توسیعی لَوکلائزَیشن کی تجاویز دکھائیں" previous_visit_timeout_hours: "ایک وِزٹ کتنا لمبا ہونا چاہئے جس کے بعد ہم اُسے 'پچھلا' وِزٹ سمجھنا شروع کر دیں، گھنٹوں میں" @@ -1274,6 +1434,7 @@ ur: top_topics_formula_first_post_likes_multiplier: "ٹاپ ٹاپکس کے فارمولے میں پہلی پوسٹ لائیکس کے مَلٹیپلائر (ن) کی عدد: `log(views_count) * 2 + op_likes_count * (n) + LEAST(likes_count / posts_count, 3) + 10 + log(posts_count)`" top_topics_formula_least_likes_per_post_multiplier: "ٹاپ ٹاپکس کے فارمولے میں فی پوسٹ کم از کم لائیکس کے مَلٹیپلائر (ن) کی عدد: `log(views_count) * 2 + op_likes_count * 0.5 + LEAST(likes_count / posts_count, (n)) + 10 + log(posts_count)`" rebake_old_posts_count: "ہر 15 منٹ پر رِیبَیک کرنے والی پرانی پوسٹس کی تعداد۔" + enable_safe_mode: "صارفین کو پلَگ اِنز ڈیبَگ کرنے کیلئے سَیف مَوڈ میں داخل ہونے کی اجازت دیں۔" rate_limit_create_topic: "ایک ٹاپک بنانے کے بعد، کوئی دوسرا ٹاپک بنانے سے پہلے صارفین کو (ن) سیکنڈوں کیلئے انتظار کرنا ہوگا۔" rate_limit_create_post: "ایک پوسٹ شائع کرنے کے بعد، دوسری پوسٹ بنانے سے پہلے صارفین کو (ن) سیکنڈوں کیلئے انتظار کرنا ہوگا۔" rate_limit_new_user_create_topic: "ایک ٹاپک بنانے کے بعد، کوئی دوسرا ٹاپک بنانے سے پہلے نئے صارفین کو (ن) سیکنڈوں کیلئے انتظار کرنا ہوگا۔" @@ -1288,12 +1449,15 @@ ur: max_topic_invitations_per_day: "ایک صارف کے فی دن بھیجی گئی ٹاپک دعوتوں کی زیادہ سے زیادہ تعداد۔" max_logins_per_ip_per_hour: "ایک IP ایڈریس سے فی گھنٹہ لاگ اِنوں کی زیادہ سے زیادہ تعداد" max_logins_per_ip_per_minute: "ایک IP ایڈریس سے فی منٹ لاگ اِنوں کی زیادہ سے زیادہ تعداد" + max_post_deletions_per_minute: "ایک صارف فی منٹ زیادہ سے زیادہ کتنی پوسٹس حذف کر سکتا ہے۔" + max_post_deletions_per_day: "ایک صارف فی دن زیادہ سے زیادہ کتنی پوسٹس حذف کر سکتا ہے۔" alert_admins_if_errors_per_minute: "اِیڈمن الرٹ کو متحرک کرنے کیلئے فی منٹ خرابیوں کی تعداد۔ 0 کی وَیلِیو اِس خصوصیت کو غیر فعال کردیتی ہے۔ نوٹ: رِیسٹارٹ ضروری ہے۔" alert_admins_if_errors_per_hour: "اِیڈمن الرٹ کو متحرک کرنے کیلئے فی گھنٹہ خرابیوں کی تعداد۔ 0 کی وَیلِیو اِس خصوصیت کو غیر فعال کردیتی ہے۔ نوٹ: رِیسٹارٹ ضروری ہے۔" categories_topics: "/categories صفحے پر دکھائے جانے والے ٹاپکس کی تعداد۔" suggested_topics: "ٹاپک کے نچلے حصے پر دکھائے گئے تجویز کردہ ٹاپکس کی تعداد۔" limit_suggested_to_category: "تجویز کردہ ٹاپکس میں صرف موجودہ زُمرہ سے ٹاپکس دکھائیں۔" suggested_topics_max_days_old: "تجویز کردہ ٹاپکس ن دنوں سے زیادہ پرانے نہیں ہونے چاہئیں۔" + suggested_topics_unread_max_days_old: "تجویز کردہ بغیر پڑھے ٹاپکس ن دنوں سے زیادہ پرانے نہیں ہونے چاہئیں۔" clean_up_uploads: "غیر قانونی ہوسٹنگ کو روکنے کیلئے یتیم غیر حوالہ دیے گئے اَپ لوڈ ہٹائیں۔ انتباہ: آپ اِس ترتیب کو فعال کرنے سے پہلے آپ شاید /uploads ڈائریکٹری کو بیک اَپ کرنا چاہیں گے۔" clean_orphan_uploads_grace_period_hours: "یتیم اَپ لوڈ ہٹانے سے پہلے رعایتی مدت (گھنٹوں میں)۔" purge_deleted_uploads_grace_period_days: "حذف کردہ اَپ لوڈ مٹا دینے سے پہلے رعایتی مدت (دنوں میں)۔" @@ -1301,14 +1465,22 @@ ur: enable_s3_uploads: "اَیمَیزَون S3 سٹوریج پر اَپ لوڈز رکھیں۔ اہم: درست S3 اسناد ضروری ہیں (دونوں ایکسَیس قی آئی ڈی اور سیکرٹ ایکسَیس قی دونوں کا ہونا ضروری ہو)۔" s3_use_iam_profile: 'S3 بَکِّٹ تک رسائی فراہم کرنے کیلئے AWS EC2 اِنسٹَنس پرَوفائل کا استعمال کریں۔ نوٹ: اِس کو فعال کرنے کیلئے ڈِسکورس کو مناسب طریقے سے ترتیب کردہ ایک EC2 اِنسٹَنس کے اندر چلانے کی ضرورت ہوتی ہے، اور "s3 ایکسَیس کلید آئی ڈی" اور "s3 سیکرٹ ایکسَیس کلید" کی ترتیبات کی جگہ لے لیتا ہے۔' s3_upload_bucket: "اَیمَیزَون S3 بَکِّٹ کا نام جس میں فائلیں اَپ لوڈ کی جائیں گی۔ انتباہ: لوئر کَیس، کسی قسم کے پیریڈ، یا انڈر سکور لازمی طور پر نہیں ہونے چاہئیں۔" + s3_access_key_id: "اَیمَیزَون S3 ایکسَیس کِی آئی ڈی جو تصاویر، اٹیچمنٹس، اور بیک اپ کو اَپ لوڈ کرنے کیلئے استعمال کی جائے گی۔" + s3_secret_access_key: "اَیمَیزَون S3 سیکرٹ ایکسَیس کِی جو تصاویر، اٹیچمنٹس، اور بیک اپ کو اَپ لوڈ کرنے کیلئے استعمال کی جائے گی۔" + s3_region: "اَیمَیزَون S3 خطے کا نام جو تصاویر اور بیک اپ کو اَپ لوڈ کرنے کیلئے استعمال کیا جائے گا۔" s3_cdn_url: "تمام s3 اثاثوں کیلئے استعمال ہونے والا CDN URL (مثال: https://cdn.somewhere.com)۔ انتباہ: اِس ترتیب کو تبدیل کرنے کے بعد آپ کو تمام پرانی پوسٹس کو رِیبَیک کرنا ہوگا۔" avatar_sizes: "خود کار طریقے سے تیار کردہ اوتار کے سائزوں کی فہرست۔" external_system_avatars_enabled: "بیرونی سسٹم کی اوتار سروس کا استعمال کریں۔" external_system_avatars_url: "بیرونی سسٹم کی اوتار سروس کا URL۔ اجازت شدہ متبادلات {username} {first_letter} {color} {size} ہیں" + restrict_letter_avatar_colors: "خط اوتار کے پس منظرمیں استعمال کیلئے 6-عددی ہیکسا ڈَیسیمل رنگوں کے اقدار کی ایک فہرست۔" + selectable_avatars_enabled: "صارفین کو فہرست سے اوتار منتخب کرنے کیلئے مجبور کریں۔" + selectable_avatars: "اوتار کی فہرست جس میں سے صارفین منتخب کر سکتے ہیں۔" allow_all_attachments_for_group_messages: "گروپ کے پیغامات کیلئے تمام ای میل منسلکات کی اجازت دیں۔" png_to_jpg_quality: "تبدیل شدہ JPG فائل کا معیار (1 سب سے کم معیار ہے، 99 بہترین معیار ہے، 100 غیر فعال)۔" allow_staff_to_upload_any_file_in_pm: "سٹاف ارکان کو PM میں کوئی بھی فائل اَپ لوڈ کرنے کی اجازت دیں۔" strip_image_metadata: "تصویر مَیٹا ڈَیٹا کو ہٹائیں۔" + min_ratio_to_crop: "لمبی تصاویر کو کاٹنے کیلئے استعمال کیے جانے والا تناسب۔ چوڑائی / اونچائی کا نتیجہ درج کریں۔" + simultaneous_uploads: "فائلوں کی زیادہ سے زیادہ تعداد جو کمپوزر میں ڈرَیگ & ڈراپ کی جا سکتی ہے" enable_flash_video_onebox: "وَن باکس میں swf اور flv (اَیڈَوبِ فلَیش) لِنکس کو اَیمبَیڈ کرنے کی اجازت دیں۔ انتباہ: یہ سیکورٹی خطروں کو متعارف کرا سکتا ہے۔" default_invitee_trust_level: "مدعو صارفین کیلئے ڈِیفالٹ ٹرسٹ لَیول (0-4)۔" default_trust_level: "تمام نئے صارفین کیلئے ڈِیفالٹ ٹرسٹ لَیول (0-4)۔ انتباہ! اِسے تبدیل کرنے سے آپ کو سپَیم کیلئے سنگین خطرہ لاہک ہو جائے گا۔" @@ -1346,6 +1518,7 @@ ur: min_trust_to_flag_posts: "پوسٹس فلَیگ کرنے کیلئے کم از کم مطلوبہ ٹرسٹ لَیول" min_trust_to_post_links: "پوسٹس میں لِنکس شامل کرنے کیلئے کم از کم مطلوبہ ٹرسٹ لَیول۔" min_trust_to_post_images: "ایک پوسٹ میں تصاویر شامل کرنے کیلئے کم از کم مطلوبہ ٹرسٹ لَیول۔" + whitelisted_link_domains: "ڈَومَین جو صارفین لنک کر سکتے ہیہں اگرچہ اُن کے پاس لنکس پوسٹ کرنے کیلئے مناسب ٹرسٹ لَیول نہ ہو۔" newuser_max_links: "ایک نیا صارف ایک پوسٹ میں کتنے لِنکس شامل کر سکتا ہے۔" newuser_max_images: "ایک نیا صارف ایک پوسٹ میں کتنے کتنی تصاویر شامل کر سکتا ہے۔" newuser_max_attachments: "ایک نیا صارف ایک پوسٹ میں کتنی اٹیچمنٹس شامل کر سکتا ہے۔" @@ -1363,6 +1536,8 @@ ur: title_min_entropy: "ایک ٹاپک کے عنوان میں کم از کم مطلوبہ اَینٹراپی (منفرد حروف، مزید کیلئے غیر انگریزی حروف)۔" body_min_entropy: "ایک پوسٹ کے متن میں کم از کم مطلوبہ اَینٹراپی (منفرد حروف، مزید کیلئے غیر انگریزی حروف)۔" allow_uppercase_posts: "ایک ٹاپک عنوان یا پوسٹ متن میں تمام کَیپس کی اجازت دیں۔" + max_consecutive_replies: "ایک ہی ٹاپک میں لگاتار کتنی پوسٹس ایک صارف شائع کرے اِس سے پہلے کہ اُسے ایک اور جواب شامل کرنے سے روک دیا جائے۔" + title_fancy_entities: "ٹاپک عنوانات میں عام ASCII حروف کو شوخ HTML اشیاء میں تبدیل کریں، بذریعہ سمارٹی پینٹس https://daringfireball.net/projects/smartypants/" min_title_similar_length: "عنوان کی کم از کم لمبائی، اِس سے پہلے کہاُس کو اُسی طرح کے دوسرے ٹاپکس کیلئے چیک کیا جائے گا۔" desktop_category_page_style: " /categories صفحے کیلئے بصری سٹائل۔" category_colors: "زُمرہ جات کیلئے ہیکسا ڈَیسیمل رنگوں کے اقدار کی ایک فہرست۔" @@ -1375,6 +1550,8 @@ ur: max_similar_results: "ایک نیا ٹاپک کمپوز کرتے وقت کتنے اُسی جیسے دوسرے ٹاپک اَیڈیٹر کے اوپر دکھائے جائیں۔ موازنہ عنوان اور متن پر مبنی ہے۔" max_image_megapixels: "ایک تصویر کیلئے مَیگا پِکسل کی زیادہ سے زیادہ عدد۔" title_prettify: "عام عنوان ٹائپینگ کی غلطیوں کو روکیں، بشمول تمام کَیپس، سب سے پہلے حرف کا لوئر کََیس ہونا، کئی! اور؟، آخر میں اضافی ۔، وغیرہ" + title_remove_extraneous_space: "آخری وقفی علامت کے آگے سے خالی جگہیں ہٹا دیں۔" + automatic_topic_heat_values: 'سائٹ کی سرگرمیوں پر مبنی "ٹاپک وِیوز گرمی" اور "ٹاپک پوسٹ لائیک گرمی" کی ترتیبات خود بخود اپ ڈیٹ کریں۔' topic_views_heat_low: "اتنے وِیوز کے بعد، وِیوز فیلڈ ذرا سی اُجاگر کر دی جاتی ہے۔" topic_views_heat_medium: "اتنے وِیوز کے بعد، وِیوز فیلڈ درمیانی سی اُجاگر کر دی جاتی ہے۔" topic_views_heat_high: "اتنے وِیوز کے بعد، وِیوز فیلڈ زور کے ساتھ اُجاگر کر دی جاتی ہے۔" @@ -1408,16 +1585,22 @@ ur: auto_silence_fast_typers_on_first_post: "صارفین کو خود بخود خاموش کر دیں جو min_first_post_typing_time کو پورا نہیں کرتے" auto_silence_fast_typers_max_trust_level: "تیز رفتار ٹائپرز کو خود بخود خاموش کرنے کیلئے زیادہ سے زیادہ ٹرسٹ لَیول" auto_silence_first_post_regex: "کَیس کے حساب سے غیر حساس رَیج اَیکس جو اگر مَیچ کر جائے تو صارف کی طرف سے پہلی پوسٹ خاموش کر دی جائے گی اور منظوری کی قطار میں بھیج دی جائے گی۔ مثال: raging|a[bc]a تمام raging یا aba یا aca پر مشتمل پوسٹس کو پہلے پر خاموش کر دیے جانے کا سبب بن جائے گا۔ صرف پہلی پوسٹ پر لاگو ہوتا ہے۔" + reviewable_claiming: "قابل تجدید مواد پر عمل کیے جانے سے پہلے، کیا اُس کا کََلیم کیا جانا ضروری ہے؟" + reviewable_default_topics: "ڈیفالٹ کے طور پر ٹاپک کے حساب سے گروپ کردہ قابل تجدید مواد دکھائیں" + reviewable_default_visibility: "قابل تجدید اشیاء نہ دکھائیں جب تک کہ وہ اِس ترجیح کو پورا نہ کریں" reply_by_email_enabled: "بذریعہ ای میل، ٹاپکس کا جواب دینا فعال کریں۔" reply_by_email_address: "جواب بذریعہ ای میل کا آنے والا ای میل ایڈریس کیلئے ٹَیمپلیٹ، مثال کے طور پر: %%{reply_key}@reply.example.com یا replies+%%{reply_key}@example.com" alternative_reply_by_email_addresses: "جواب بذریعہ ای میل کے آنے والا ای میل ایڈریس کیلئے متبادل ٹیمپلیٹس، مثال: %%{reply_key}@reply.example.com|replies+%%{reply_key}@example.com" incoming_email_prefer_html: "آنے والے ای میل کیلئے ٹیکسٹ کے بجائے HTML کا استعمال کریں۔" + strip_incoming_email_lines: "آنے والی ای میلز کی ہر سطر سے آگے اور پیچھے آنی والی خالی جگہیں ہٹا دیں۔" + disable_emails: "ڈِسکورس کو کسی بھی قسم کے ای میل بھیجنے سے روکیں۔ تمام صارفین کیلئے ای میلز غیر فعال کرنے کیلئے 'ہاں' منتخب کریں۔ صرف غیر سٹاف صارفین کیلئے ای میلز غیر فعال کرنے کیلئے 'غیر سٹاف' کا انتخاب کریں۔" strip_images_from_short_emails: "2800 بائٹس سے کم سائز والے ای میلز سے تصاویر ہٹا دیں" short_email_length: "بائٹس میں مختصر ای میل کی لمبائی" display_name_on_email_from: "ای میل کی \"سے\" فیلڈ پر مکمل نام دکھائیں" unsubscribe_via_email: "صارفین کو ای میل کے موضوع یا متن میں 'غیر سَبسکرائب' کا لِنک بھیج کر ای میلز سے غیر سَبسکرائب ہو جانے کی اجازت دیں" unsubscribe_via_email_footer: "بھیجی گئی ای میلز کے فُوٹر میں غیر سَبسکرائب بذریعہ ای میل mailto: کا لِنک منسلک کریں" delete_email_logs_after_days: "(ن) دنوں کے بعد ای میل لاگز حذف کریں۔ غیر متعینہ مدت تک رکھنے کیلئے 0" + disallow_reply_by_email_after_days: "(ن) دنوں کے بعد بزریعہ ایمیل جواب کی اجازت نہ دیں۔ غیر متعینہ مدت تک رکھنے کیلئے 0" max_emails_per_day_per_user: "صارفین کو فی دن بھیجی گئی ای میلز کی زیادہ سے زیادہ تعداد۔ حد کو غیر فعال کرنے کیلئے 0" enable_staged_users: "آنے والی ای میلز سے نمٹتے وقت خود کار طریقے سے سٹَیجڈ صارفین بنائیں۔" maximum_staged_users_per_email: "آنے والی ایک ای میل سے نمٹتے وقت تشکیل کردہ سٹَیجڈ صارفین کی زیادہ سے زیادہ تعداد۔" @@ -1434,6 +1617,7 @@ ur: attachment_filename_blacklist: "مطلوبہ الفاظ کی فہرست جو فائل نام کی بنیاد پر منسلک اٹیچمنٹس کو بلَیک لِسٹ کرنے کیلئے استعمال ہوتی ہے۔" enable_forwarded_emails: "[BETA] صارفین کو ایک ای میل فارورڈ کر کہ ٹاپک بنائے کی اجازت دیں۔" always_show_trimmed_content: "ہمیشہ آنے والی ای میلز کا حصے دکھائیں۔ انتباہ: ای میل پتوں کو افشاں کر سکتا ہے۔" + private_email: "مزید رازداری کیلئے ایمیل عنوان یا ایمیل متن میں پوسٹس یا ٹاپکس سے مواد شامل نہ کریں۔ نوٹ: ڈائجسٹ ایمیلز کو بھی غیر فعال کر دیتا ہے۔" manual_polling_enabled: "ای میل جوابات کیلئے API کا استعمال کرتے ہوئے ای میلز پُش کریں۔" pop3_polling_enabled: "ای میل جوابات کیلئے پَول بذریعہ POP3 کریں۔" pop3_polling_ssl: "POP3 سرور سے کنیکٹ کرتے وقت SSL کا استعمال کریں۔ (تجویز کردہ)" @@ -1443,8 +1627,11 @@ ur: pop3_polling_host: "جس ہَوسٹ کیلئے پَول کیا جائے بذریعہ POP3۔" pop3_polling_username: "ای میل کیلئے پَول کرنے کیلئے POP3 اکاؤنٹ کا صارف نام۔" pop3_polling_password: "ای میل کیلئے پَول کرنے کیلئے POP3 اکاؤنٹ کا پاسورڈ۔" + pop3_polling_delete_from_server: "سرور سے ایمیلز حذف کریں۔ نوٹ: اگر آپ اس کو غیر فعال کرتے ہیں تو آپ کو دستی طور پر اپنے میل اِنباکس کو صاف کرنا چاہئے" + log_mail_processing_failures: "تمام ایمیل سے نمٹتے پر پیش آنے والی خرابیوں کو /logs پر لاگ کریں" email_in: 'صارفین کو بذریعہ ای میل نئے ٹاپکس شائع کرنے کی اجازت دیں (دستی یا pop3 پَولنگ کی ضرورت ہے)۔ ہر قِسم کیلئے "ترتیبات" ٹَیب میں پتوں کو ترتیب دیں۔' email_in_min_trust: "نئے ٹاپک بذریعہ ای میل شائع کرنے کیلئے کم از کم ٹرسٹ لَیول جس کی صارف کو ضرورت ہے۔" + email_in_spam_header: "سپیم کا پتہ لگانے کیلئے ایمیل ہیڈر۔" email_prefix: "ای میلز کے موضوع میں استعمال کیا جانے والا [لیبل]۔ یہ سَیٹ کردہ نہ ہو تو 'عنوان' پر ڈِیفالٹ ہو جائے گا۔" email_site_title: "سائٹ سے ای میلز کے \"بھیجنے والا\" کے طور پر استعمال ہونے والا سائٹ کا عنوان۔ یہ سَیٹ نہ ہو تو 'عنوان' پر ڈِیفالٹ ہو جائے گا۔ اگر آپ کے 'عنوان' میں ایسے حروف شامل ہیں جن کی ای میل \"بھیجنے والا\" سٹرِنگ میں اجازت نہیں ہے، تو اِس ترتیب کو استعمال کریں۔" find_related_post_with_key: "جواب دی گئی پوسٹ کو تلاش کرنے کیلئے صرف 'جواب کلید' استعمال کریں۔ انتباہ: اِس کو غیر فعال کرنے سے ای میل ایڈریس پر مبنی صارف نقالی ممکن ہو جاتی ہے۔" @@ -1454,6 +1641,7 @@ ur: delete_all_posts_max: "تمام پوسٹس حذف کریں والے بٹن سے ایک وقت میں حذف کیے جانے والی پوسٹس کی زیادہ سے زیادہ تعداد۔ اگر ایک صارف کی اِس سے زیادہ پوسٹس ہیں ، تو سبھی پوسٹس ایک ہی وقت میں حذف نہیں ہوسکتیں اور صارف کو حذف نہیں کیا جا سکتا۔" username_change_period: "رجسٹریشن کے بعد دنوں کی زیادہ سے زیادہ تعداد جب تک کہ اکاؤنٹس اپنا صارف نام تبدیل کرسکتے ہیں (صارف نام کی تبدیلی کو غیر فعال کرنے کیلئے 0)۔" email_editable: "رجسٹریشن کے بعد صارفین کو اپنا ای میل ایڈریس تبدیل کرنے کی اجازت دیں۔" + logout_redirect: "لاگ آؤٹ کے بعد براؤزر کو ریڈائرَیکٹ کرنے کا مقام (مثال: https://example.com/logout)" allow_uploaded_avatars: "صارفین کو اپنی مرضی کی پروفائل تصاویر اَپ لوڈ کرنے کی اجازت دیں۔" allow_animated_avatars: "صارفین کو اَینیمَیٹِڈ GIF پروفائل تصاویر استعمال کرنے کی اجازت دیں۔ انتباہ: اِس ترتیب کو تبدیل کرنے کے بعد avatars:refresh رَیک ٹاسک چلائیں۔" allow_animated_thumbnails: "اَینیمَیٹِڈ GIFs کے اَینیمَیٹِڈ تھَمب نَیل پیدا کریں۔" @@ -1471,8 +1659,12 @@ ur: email_link_color: "HTML ای میلز میں لِنکس کا رنگ۔ رنگ کا نام درج کریں ('blue') یا ہَیکس قدر ('#0000FF')۔" detect_custom_avatars: "چیک کیا جائے یا نہ کیا جائے کہ صارفین نے اپنی مرضی کی پروفائل تصاویر اَپ لوڈ کی ہیں کہ نہیں۔" max_daily_gravatar_crawls: "ایک دن میں زیادہ سے زیادہ کتنی دفعہ ڈِسکورس گَرِیوَّٹار کو اپنی مرضی کے اوتار کیلئے چیک کرے گا" + public_user_custom_fields: "صارف کے اپنی مرضی کے فیلڈز کی ایک فہرست جو بزریعہ API حاصل کی جا سکتی ہے۔" + staff_user_custom_fields: "صارف کے اپنی مرضی کے فیلڈز کی ایک فہرست جو بزریعہ API سٹاف ممبران کیلئے حاصل کی جا سکتی ہے۔" enable_user_directory: "براؤزِنگ کیلئے صارفین کی ایک ڈائرِکٹری فراہم کریں" enable_group_directory: "براؤزِنگ کیلئے گروپوں کی ایک ڈائرِکٹری فراہم کریں" + enable_category_group_review: "گروپوں کو مخصوص زُمرہ جات میں مواد کا جائزہ لینے کی اجازت دیں" + group_in_subject: "ایمیل کے موضوع میں %%{optional_pm} کو ذاتی پیغام میں پہلے گروپ کے نام پر سَیٹ کریں، دیکھیے: سٹینڈرڈ ایمیلز کیلئے موضوع فارمَیٹ کو حسب ضرورت تبدیل کریں" allow_anonymous_posting: "صارفین کو گمنام مَوڈ میں تبدیل ہونے کی اجازت دیں" anonymous_posting_min_trust_level: "گمنام پوسٹنگ کو فعال کرنے کیلئے کم از کم مطلوبہ ٹرسٹ لَیول" anonymous_account_duration_minutes: "صارف کی گمنامی کی حفاظت کیلئے ہر صارف کیلئے ہر ن منٹ بعد ایک نیا گمنام اکاؤنٹ بنائیں۔ مثال: اگر 600 پر سَیٹ کیا جائے، تو جیسے ہی آخری پوسٹ سے 600 منٹ گزر چکے ہوں گے اور صارف گمنام مَوڈ پر تبدیل ہو گا، تو ایک نیا گمنام اکاؤنٹ پیدا کر دیا جائے گا۔" @@ -1480,6 +1672,9 @@ ur: show_inactive_accounts: "لاگ اِن ہوے صارفین کو غیر فعال اکاؤنٹس کی پروفائلز کو براؤز کرنے کی اجازت دیں۔" hide_suspension_reasons: "صارف پروفائلز پر عوامی طور پر معطلی کی وجوہات ظاہر نہ کریں۔" log_personal_messages_views: "دیگر صارفین/گروپوں کیلئے ایڈمن کی طرف سے ذاتی پیغام وِیوز کو لاگ کریں۔" + ignored_users_count_message_threshold: "اتنے صارفین کی طرف سے کسی خاص صارف کو نظر انداز کیے جانے پر ماڈریٹرز کو مطلع کریں۔" + ignored_users_message_gap_days: "کئی صارفین کی طرف سے نظر انداز کیے جانے والے ایک صارف کے بارے میں ماڈریٹرز کو دوبارہ مطلع کرنے سے پہلے کتنا انتظار کریں۔" + clean_up_inactive_users_after_days: "دنوں کی تعداد جس کے بعد غیر متحرک صارف (ٹرسٹ لَیول 0 کسی بھی اشاعت کے بغیر) ہٹا دیا جائے گا۔ ہٹا دینا غیر فعال کرنے کیلئے 0 پر سَیٹ کریں۔" user_website_domains_whitelist: "اِن ڈومینز کے ساتھ صارف ویب سائٹ کی تصدیق کی جائے گی۔ پائپ کے ساتھ الگ کی گئی فہرست۔" allow_profile_backgrounds: "صارفین کو پروفائل پسِ منظر اَپ لوڈ کرنے کی اجازت دیں۔" sequential_replies_threshold: "ایک ہی ٹاپک میں لگاتار کتنی پوسٹس ایک صارف شائع کرے اِس سے پہلے کہ اُسے بہت زیادہ لگاتار جوابات ہونے کی یاد دہانی کرائی جائے۔" @@ -1488,13 +1683,20 @@ ur: dominating_topic_minimum_percent: "ایک ٹاپک میں ایک ہی صارف کی طرف سے کتنے فیصد پوسٹس ہونی چاہئیں، اِس سے پہلے کہ اُسے ٹاپک پر حد سے زیادہ غالب ہو جانے کی یاد دہانی کرائی جائے۔" disable_avatar_education_message: "اوتار تبدیل کرنے کیلئے تعلیمی پیغام کو غیر فعال کریں۔" suppress_uncategorized_badge: "ٹاپک فہرستوں میں غیر زُمرہ جات والے ٹاپکس کیلئے بَیج نہ دکھائیں۔" + header_dropdown_category_count: "ہیڈر ڈراپ ڈاؤن مینو میں کتنے زُمرہ جات ظاہر کیے جا سکتے ہیں۔" permalink_normalizations: "دائمی لِنکس میچ کرنے سے پہلے درج ذیل رَیج اَیکس کا اطلاق کریں، مثال کے طور پر: /(topic.*)\\?.*/\\1 ٹاپک روٹس میں سے قُوَیری سٹرِنگ کو نکال دے گا۔ فارمیٹ regex+string ہے، میچ کردہ کو ایکسَیس کرنے کیلئے \\1 وغیرہ کا استعمال کریں" + global_notice: "تمام زائرین کو فوری،اَیمرجنسی ناقابلِ برطرف گلوبل بَینر نوٹِس دکھائیں، اِسے چھپانے کیلئے خالی جگہ میں تبدیل کریں (HTML کی اجازت)۔" disable_edit_notifications: "سِسٹم صارف کی طرف سے ترمیم اطلاعات کو غیر فعال کریں جب 'download_remote_images_to_local' فعال ہو۔" + likes_notification_consolidation_threshold: "لائیک اطلاعات کی تعداد جس سے پہلے اطلاعات ایک میں جمع کر دی جائیں۔ غیر فعال کرنے کیلئے 0 پر سَیٹ کریں۔ ونڈو بذریعہ `SiteSetting.likes_notification_consolidation_window_mins` ترتیب دی جا سکتی ہے۔" + likes_notification_consolidation_window_mins: "منٹوں میں مدت جہاں لائیک اطلاعات کو ایک میں جمع کر دیا جاتا ہے، جب حد تک پہنچ جایا جائے۔ حد کو بذریعہ `SiteSetting.likes_notification_consolidation_threshold` ترتیب دیا جا سکتا ہے۔" automatically_unpin_topics: "صارفین جب ٹاپک کے آخر تک پہنچ جائیں تو خود بخود ٹاپکس پر سے پِن ہٹا دیں۔" read_time_word_count: "متوقع پڑھنے کے وقت کا حساب لگانے کیلئے فی منٹ الفاظ کی تعداد۔" topic_page_title_includes_category: "ٹاپک صفحہ کے عنوان میں زُمرہ کا نام شامل ہے۔" + native_app_install_banner_ios: "iOS ڈیوائسوں پر عام صارفین (ٹرسٹ لَیول 1 اور اوپر) کو ڈِسکورس ہب اَیپ بینر دکھاتا ہے۔" + native_app_install_banner_android: "اینڈرائیڈ ڈیوائسوں پر عام صارفین (ٹرسٹ لَیول 1 اور اوپر) کو ڈِسکورس ہب اَیپ بینر دکھاتا ہے۔" share_anonymized_statistics: "گمنام استعمال کے اعداد و شمار کا اشتراک کریں۔" auto_handle_queued_age: "اُن ریکارڈز کو خود کار طریقہ سے ہَینڈل کریں جو اتنے دنوں کے بعد نظر ثانی کے منتظر ہیں۔ فلَیگز کو نظر انداز کیا جائے گا۔ قطار شدہ پوسٹس اور صارفین کو مسترد کردیا جائے گا۔ اِس خصوصیت کو غیر فعال کرنے کیلئے 0 پر سَیٹ کریں۔" + svg_icon_subset: "اضافی فونٹ اَوسم 5 آئیکن شامل کریں جو آپ اپنے اثاثوں میں شامل کرنا چاہتے ہیں۔ ٹھوس آئیکنز کیلئے 'fa-' سابقہ، عام آئیکنز کیلئے 'far-' سابقہ اور برانڈ آئیکنز کیلئے 'fab-' سابقہ استعمال کریں۔" max_prints_per_hour_per_user: "/print صفحہ نقوش کی زیادہ سے زیادہ تعداد (غیر فعال کرنے کیلئے 0 پر سَیٹ کریں)" full_name_required: "مکمل نام ایک صارف کے پروفائل کا لازمی فیلڈ ہے۔" enable_names: "صارف کا مکمل نام اُن کے پروفائل، صارف کارڈ، اور ای میل پر دکھائیں۔ ہر جگہ مکمل نام چھپانے کیلئے غیر فعال کریں۔" @@ -1504,11 +1706,14 @@ ur: default_code_lang: "گِٹ ہَب کَوڈ بلاکس پر لاگو ڈِیفالٹ پروگرامِنگ زبان سِنٹیکس ہائلائیٹِنگ (lang-auto، ruby، python وغیرہ)" warn_reviving_old_topic_age: "جب کوئی ایسے ٹاپک پر جواب دینا شروع کرے جہاں پچھلا آخری جواب اتنے دنوں سے زیادہ پرانا ہو، تو ایک انتباہ ظاہر کی جائے گی۔ 0 پر سَیٹ کر کہ غیر فعال کریں۔" autohighlight_all_code: "تمام پہلے سے فارمَیٹ کردہ کَوڈ بلاکس پر ذبردستی کَوڈ ہائلائیٹِنگ لاگو کریں، یہاں تک کہ جب زبان بھی خاص طور پر واضح نہ کی گئی ہو۔" + highlighted_languages: "سِنٹیکس ہائلائیٹِنگ کے قوانین شامل کریں۔ (انتباہ: بہت زیادہ زبانوں کو شامل کرنے سے کارکردگی پر اثر ہو سکتا ہے) ڈَیمو کیلئے ملاحظہ کریں: https://highlightjs.org/static/demo" embed_truncate: "اَیمبَیڈ کی گئی پوسٹس کو تراشیں۔" embed_support_markdown: "اَیمبَیڈ کردہ پوسٹس کیلئے مارکڈائون کی اجازت دیں۔" + embed_whitelist_selector: "علاماتِ وقف سے علیحدہ کردہ CSS عنصروں کی ایک فہرست جن کی اَیمبَیڈز میں اجازت ہے۔" allowed_href_schemes: "http اور https کے علاوہ لِنکس میں اجازت دی گئی اسکیمز۔" embed_post_limit: "اَیمبَیڈ کیے جانے والی پوسٹس کی زیادہ سے زیادہ تعداد۔" embed_username_required: "ٹاپک کی تخلیق کیلئے صارف نام ضروری ہے۔" + notify_about_flags_after: "اگر ایسے فلَیگ موجود ہیں جن پر اتنے گھنٹوں کے بعد بھی کام نہیں کیا جا سکا، تو ماڈریٹرز کو ذاتی پیغام بھیجیں۔ غیر فعال کرنے کیلئے 0 پر سَیٹ کریں۔" show_create_topics_notice: "اگر سائٹ میں 5 سے کم عوامی ٹاپک موجود ہیں، تو کچھ ٹاپک تخلیق کرنے کیلئے منتظمین کو ایک نوٹس دکھائیں۔" delete_drafts_older_than_n_days: (ن) دنوں سے پرانے ڈرافٹ حذف کریں۔ bootstrap_mode_min_users: "بُوٹسٹرَیپ مَوڈ غیر فعال کرنے کیلئے درکار صارفین کی کم از کم تعداد (غیر فعال کرنے کیلئے 0 پر سَیٹ کریں)" @@ -1517,6 +1722,8 @@ ur: enable_emoji: "اِیمَوجی فعال کریں" enable_emoji_shortcuts: "عام سمائلی ٹیکسٹ جیسا کہ :) :p :( اِیمَوجیوں میں تبدیل کر دیا جائے گا" emoji_set: "آپ اپنا اِیمَوجی کیسا پسند کریں گے؟" + emoji_autocomplete_min_chars: "خود تکمیل اِیمَوجی پاپ اپ متحرک کرنے کیلئے مطلوبہ حروف کی کم از کم تعداد" + enable_inline_emoji_translation: "اِن لائن اِیمَوجیوں کیلئے ترجمہ کو فعال بناتا ہے (پہلے کسی خالی جگہ یا وقفی علامت کے بغیر)" approve_post_count: "ایک نئے یا بَیسِک صارف کی طرف سے پوسٹس کی تعداد جن کا منظور کیے جانا لاذمی ہے " approve_unless_trust_level: "اِس ٹرسٹ لَیول سے نیچے کے صارفین کیلئے پوسٹس کا منظور شدہ ہونا لازمی ہے" approve_new_topics_unless_trust_level: "اِس ٹرسٹ لَیول سے نیچے کے صارفین کیلئے نئے ٹاپکس کا منظور شدہ ہونا لازمی ہے" @@ -1527,8 +1734,14 @@ ur: code_formatting_style: "کمپَوزر میں کَوڈ بٹن اِس کوڈ فارمَیٹِنگ سٹائل پر ڈِیفالٹ کرے گا" max_allowed_message_recipients: "ایک پیغام میں وصول کنندگان کی زیادہ سے زیادہ تعداد۔" watched_words_regular_expressions: "دیکھے گئے الفاظ رَیگولر اَیکسپرَیشَن ہیں۔" + old_post_notice_days: "دن جن سے پہلے پوسٹ نوٹِس پرانا بن جاتا ہے" + new_user_notice_tl: "نئے صارف کے پوسٹ نوٹِسز کو دیکھنے کیلئے کم از کم مطلوبہ ٹرسٹ لَیول۔" + returning_user_notice_tl: "واپس لوٹنے والے صارف کے پوسٹ نوٹِسز کو دیکھنے کیلئے کم از کم مطلوبہ ٹرسٹ لَیول۔" + returning_users_days: "صارف کو واپس لوٹنے والا جاننے سے پہلے کتنے دن گزرنے چاہئیں۔" default_email_digest_frequency: "صارفین کو ڈِیفالٹ کے طور پر خلاصہ ای میل کتنی بار ملتی ہیں۔" default_include_tl0_in_digests: "ڈِیفالٹ کے طور پر خلاصہ ای میلز میں نئے صارفین کی طرف سے پوسٹس شامل کریں۔ صارفین اسے اپنی ترجیحات میں تبدیل کرسکتے ہیں۔" + default_email_level: "ڈیفالٹ ایمیل نوٹیفیکیشن سطح مقرر کریں جب کوئی کسی صارف، کا اقتباس/ کو جواب / کا ذکر یا کو مدعو کرتا ہے۔" + default_email_messages_level: "ڈیفالٹ ایمیل نوٹیفیکیشن سطح مقرر کریں جب کوئی صارف کو پیغام بھیجتا ہے۔" default_email_mailing_list_mode: "ڈِیفالٹ کے طور ہر نئی پوسٹ پر ایک ای میل بھیجیں" default_email_mailing_list_mode_frequency: "صارفین جو مَیلنگ لِسٹ مَوڈ فعال کرتے ہیں اُن کو ڈِیفالٹ کے طور پر اِس کثرت سے ای میلز ملیں گی۔" disable_mailing_list_mode: "صارفین کو مَیلنگ لِسٹ مَوڈ فعال کرنے سے روکیں۔" @@ -1539,6 +1752,7 @@ ur: default_other_notification_level_when_replying: " گلوبل ڈیفالٹ اطلاعات کا لَیول جب صارف کسی ٹاپک پر جواب دے۔" default_other_external_links_in_new_tab: "ڈِیفالٹ کے طور پر تمام بیرونی ویب سائٹ کے لنکس ایک نئے ٹیب میں کھولیں۔" default_other_enable_quoting: "ڈِیفالٹ کے طور پر روشنی ڈالے گئے ٹَیکسٹ کے لئے اقتباسی جواب فعال کریں۔" + default_other_enable_defer: "ڈیفالٹ سے ٹاپک ملتوی خصوصیت فعال کریں۔" default_other_dynamic_favicon: "ڈِیفالٹ کے طور پر براؤزر آئکن پر نئے / اَپ ڈیٹ کردہ ٹاپکس کی گنتی دکھائیں۔" default_other_like_notification_frequency: "ڈِیفالٹ کے طور پر صارفین کو لائیکس پر اطلاع دیں" default_topics_automatic_unpin: "ڈِیفالٹ کے طور پر صارفین جب ٹاپک کے آخر تک پہنچ جائیں تو خود بخود ٹاپکس پر سے پِن ہٹا دیں۔" @@ -1546,12 +1760,17 @@ ur: default_categories_tracking: "زُمرہ جات کی فہرست جو ڈِیفالٹ کے طور پر ٹرَیک کردہ ہوں۔" default_categories_muted: "زُمرہ جات کی فہرست جو ڈِیفالٹ کے طور پر خاموش کردہ ہوں۔" default_categories_watching_first_post: "زُمرہ جات کی فہرست جس میں ڈِیفالٹ کے طور پر ہر نئے ٹاپک میں پہلی پوسٹ دیکھی جائے گی۔" + default_text_size: "متن کا سائز جو ڈیفالٹ سے منتخب کیا جاتا ہے" + default_title_count_mode: "صفحہ عنوان کاؤنٹر کیلئے ڈیفالٹ مَوڈ" retain_web_hook_events_period_days: "ویب ہک اِیونٹ ریکارڈ برقرار رکھنے کیلئے دنوں کی تعداد۔" + retry_web_hook_events: "خود کار طریقے سے 4 مرتبہ ناکام ویب ہک اِیونٹوں کی دوبارہ کوشش کریں۔ دوبارہ کوششوں کے درمیان ٹائم فرق 1، 5، 25 اور 125 منٹ ہے۔" allow_user_api_keys: "صارف API کلیدیں تخلیق کرنے کی اجازت دیں" allow_user_api_key_scopes: "صارف API کلیدوں کیلئے اجازت دی جانے والی سکَوپس کی فہرست" max_api_keys_per_user: "فی صارف، صارف API کلیدوں کی زیادہ سے زیادہ تعداد" min_trust_level_for_user_api_key: "API کلیدوں کی تخلیق کیلئے مطلوبہ ٹرسٹ لَیول" + allowed_user_api_auth_redirects: "صارف API کیز کیلئے توثیقی ریڈائرَیکٹ کیلئے اجازت یافتہ URL۔ وائلڈ کارڈ علامت * اُس کے کسی بھی حصے کو میچ کرنے کیلئے استعمال کیا جا سکتا ہے (مثلاً www.example.com/*)۔" allowed_user_api_push_urls: "صارف API پر سرور پُش کیلئے اجازت یافتہ URL" + expire_user_api_keys_days: "دنوں کی تعداد جس سے پہلے ایک صارف API کِی کی میعاد ختم ہو جائے (کبھی نہیں کیلئے 0)" tagging_enabled: "ٹاپکس پر ٹیگز فعال کریں؟" min_trust_to_create_tag: "ٹَیگ بنانے کیلئے کم از کم مطلوبہ ٹرسٹ لَیول۔" max_tags_per_topic: "ایک ٹاپک پر لگائے جانے والے ٹَیگز کی زیادہ سے زیادہ تعداد۔" @@ -1560,13 +1779,25 @@ ur: show_filter_by_tag: "ٹیگ کی بنیاد پر ایک ٹاپک فہرست کو فلٹر کرنے کیلئے ڈراپ ڈاؤن دکھائیں۔" max_tags_in_filter_list: "فلٹر ڈراپ ڈاؤن میں دکھانے گئے ٹَیگز کی زیادہ سے زیادہ تعداد۔ سب سے زیادہ استعمال شدہ ٹَیگز دکھائے جائیں گے۔" tags_sort_alphabetically: "ٹَیگز کو حروف تہجی کی ترتیب میں دکھائیں۔ ڈِیفالٹ کے طور پر مقبولیت کے مطابق دکھائے جائیں گے۔" + tags_listed_by_group: "ٹیگز صفحے پر ٹیگ گروپ کے حساب سے ٹیگز کو فہرست کریں۔" tag_style: "ٹَیگ بیَجوں کیلئے بصری سٹائل۔" allow_staff_to_tag_pms: "سٹاف ممبران کو کسی بھی ذاتی پیغام پر ٹَیگ لگانے کی اجازت دیں" min_trust_level_to_tag_topics: "ٹاپکس پر ٹَیگ لگانے کیلئے کم از کم مطلوبہ ٹرسٹ لَیول" suppress_overlapping_tags_in_list: "اگر ٹَیگز ٹاپک عنوانات میں موجود مکمل الفاظ سے مَیچ کرتے ہوں، تو ٹَیگ مت دکھائیں" + remove_muted_tags_from_latest: "تازہ ترین ٹاپک فہرست میں صرف خاموش شدہ ٹَیگز کے ساتھ ٹَیگ کردہ ٹاپکس مت دکھائیں۔" + force_lowercase_tags: "تمام نئے ٹیگز کو مکمل طور پر لَوئر کَیس ہونے پر مجبور کریں۔" + company_name: "کمپنی نام" + governing_law: "گورننگ قانون" + city_for_disputes: "تنازعات کیلئے شہر" + shared_drafts_category: "ٹاپک ڈرافٹس کیلئے ایک زُمرَہ کو مقرر کر کہ مشترکہ ڈرافٹس کی صلاحیت کو فعال کریں۔ اِس زُمرہ کے ٹاپکس کو سٹاف صارفین کیلئے ٹاپک فہرستوں سے دبا دیا جائے گا۔" + push_notifications_prompt: "صارف رضامندی پرامپٹ دکھائیں۔" + push_notifications_icon: "نوٹیفکیشن کونے میں ظاہر ہونے والا بَیج آئکن۔ درکار سائز 96 × 96 ہے۔" + short_title: "مختصر عنوان صارف کے ہوم اسکرین، لانچر، یا دیگر جگہیں جہاں خلا محدود ہوسکتی ہے، پر استعمال کیا جائے گا۔ یہ 12 حروف تک محدود ہونا چاہئیے۔" + dashboard_general_tab_activity_metrics: "جنرل ٹیب پر سرگرمی میٹریکس کے طور پر ظاہر ہونے والی رپورٹیں منتخب کریں۔" errors: invalid_email: "غلط ای میل ایڈریس۔" invalid_username: "اِس صارف نام والا کوئی صارف نہیں ہے۔" + invalid_group: "اِس نام کے ساتھ کوئی گروپ موجود نہیں۔" invalid_integer_min_max: "قدر کا %{min} اور %{max} کے درمیان ہونا ضروری ہے۔" invalid_integer_min: "قدر کا %{min} یا زیادہ ہونا ضروری ہے۔" invalid_integer_max: "قدر %{max} سے زیادہ نہیں ہوسکتی۔" @@ -1592,9 +1823,26 @@ ur: staged_users_disabled: "اِس ترتیب کو فعال کرنے سے پہلے آپ کا 'سٹَیجڈ صارفین' فعال کرنا ضروری ہے۔" reply_by_email_disabled: "اِس ترتیب کو فعال کرنے سے پہلے آپ کا 'جواب بذریعہ ای میل' فعال کرنا ضروری ہے۔" sso_url_is_empty: "اِس ترتیب کو فعال کرنے سے پہلے آپ کا 'sso url' مقرر کرنا ضروری ہے۔" + sso_invite_only: "آپ بیک وقت sso اور صرف بزریعہ دعوت فعال نہیں کرسکتے۔" enable_local_logins_disabled: "اِس ترتیب کو فعال کرنے سے پہلے آپ کا 'مقامی لاگ اِن' فعال کرنا ضروری ہے۔" + min_username_length_exists: "آپ صارف نام کی کم از کم لمبائی، سب سے مختصر صارف نام کی لمبائی سے کم مقرر نہیں کرسکتے (%{username})۔" min_username_length_range: "آپ کم از کم، زیادہ سے زیادہ سے اوپر مقرر نہیں کرسکتے۔" + max_username_length_exists: "آپ صارف نام کی زیادہ سے زیادہ لمبائی، سب سے طویل صارف نام کی لمبائی سے کم مقرر نہیں کرسکتے (%{username})۔" max_username_length_range: "آپ زیادہ سے زیادہ، کم از کم سے نیچے مقرر نہیں کرسکتے۔" + invalid_hex_value: "رنگ اقدار کا 6-عددی ہیکسا ڈَیسیمل کوڈ ہونا ضروری ہے۔" + category_search_priority: + very_low_weight_invalid: "آپ وزن 'category_search_priority_low_weight' سے زیادہ مقرر نہیں کرسکتے۔" + low_weight_invalid: "آپ وزن زیادہ یا 1 کے برابر یا 'category_search_priority_very_low_weight' سے کم مقرر نہیں کرسکتے۔" + high_weight_invalid: "آپ وزن زیادہ یا 1 کے برابر یا 'category_search_priority_very_high_weight' سے زیادہ مقرر نہیں کرسکتے۔" + very_high_weight_invalid: "آپ وزن 'category_search_priority_high_weight' سے کم مقرر نہیں کرسکتے۔" + unicode_username_whitelist: + regex_invalid: "رَیگولر اَیکسپرَیشَ غلط ہے: %{error}" + leading_trailing_slash: "رَیگولر اَیکسپرَیشَ ایک سلیش کے ساتھ شروع اور ختم نہیں ہونا چاہئیے۔" + unicode_usernames_avatars: "اندرونی سسٹم کے اوتار یونیکَوڈ صارف ناموں کی کفالت نہیں کرتے۔" + placeholder: + sso_provider_secrets: + key: "www.example.com" + value: "SSO سیکرٹ" search: within_post: "%{username} کی طرف سے #%{post_number}" types: @@ -1624,6 +1872,21 @@ ur: errors: different_topics: "مختلف ٹاپکس کی پوسٹس کو ضم نہیں کیا جا سکتا۔" different_users: "مختلف صارفین کی پوسٹس کو ضم نہیں کیا جا سکتا۔" + move_posts: + new_topic_moderator_post: + one: "ایک پوسٹ کو ایک نئے ٹاپک پر تقسیم کر دیا گیا تھا: %{topic_link}" + other: "%{count} پوسٹس کو ایک نئے ٹاپک پر تقسیم کر دیا گیا تھا: %{topic_link}" + new_message_moderator_post: + one: "ایک پوسٹ کو ایک نئے پیغام پر تقسیم کر دیا گیا تھا: %{topic_link}" + other: "%{count} پوسٹس کو ایک نئے پیغام پر تقسیم کر دیا گیا تھا: %{topic_link}" + existing_topic_moderator_post: + one: "ایک پوسٹ کو ایک موجودہ ٹاپک میں ضم کر دیا گیا تھا: %{topic_link}" + other: "%{count} پوسٹس کو ایک موجودہ ٹاپک میں ضم کر دیا گیا تھا: %{topic_link}" + existing_message_moderator_post: + one: "ایک پوسٹ کو ایک موجودہ پیغام میں ضم کر دیا گیا تھا: %{topic_link}" + other: "%{count} پوسٹس کو ایک موجودہ پیغام میں ضم کر دیا گیا تھا: %{topic_link}" + change_owner: + post_revision_text: "ملکیت منتقل ہو گئی" topic_statuses: autoclosed_message_max_posts: one: "%{count} جواب کی زیادہ سے زیادہ حد تک پہنچنے کے بعد یہ پیغام خود کار طریقہ سے بند کر دیا گیا تھا۔" @@ -1699,6 +1962,11 @@ ur: second_factor_title: "دو فیکٹر توثیق" second_factor_description: "براہ مہربانی اپنی اَیپ میں سے مطلوبہ توثیقی کَوڈ درج کریں:" second_factor_backup_description: "براہ مہربانی اپنے بیک اپ کوڈز میں سے ایک درج کریں:" + second_factor_backup_title: "دو فیکٹر بیک اپ کوڈ" + invalid_second_factor_code: "غلط توثیقی کَوڈ۔ ہر کَوڈ صرف ایک بار ہی استعمال کیا جاسکتا ہے۔" + second_factor_toggle: + totp: "بجائے ایک اَوتھینٹیکَیٹر اَیپ کا استعمال کریں" + backup_code: "بجائے ایک بیک اپ کوڈ استعمال کریں" admin: email: sent_test: "بھیج دی گئی!" @@ -1711,6 +1979,7 @@ ur: username: short: "کم از کم %{min} حروف کا ہونا لازمی ہے" long: "%{max} حروف سے زیادہ نہ ہونا لازمی ہے" + too_long: "بہت طویل ہے" characters: "صرف نمبر، حروف، ڈَیشِز، اور انڈر سکَور کا شامل ہونا لازمی ہے" unique: "منفرد ہونا لازمی ہے" blank: "موجود ہونا لازمی ہے" @@ -1732,6 +2001,14 @@ ur: unused_staged_user: "غیر استعمال شدہ سٹَیجڈ صارف" fixed_primary_email: "سٹَیجڈ صارف کیلئے بنیادی ای میل مقرر کر دیا گیا" same_ip_address: "دوسرے صارفین کی طرح ایک ہی IP ایڈریس (%{ip_address})" + inactive_user: "غیر متحرک صارف" + reviewables_reminder: + submitted: + one: "اشیاء %{count} گھنٹہ پہلے جمع کیے گئے تھے۔ [براہ مہربانی ان کا جائزہ لیں](%{base_path}/review)۔" + other: "اشیاء %{count} گھنٹوں پہلے جمع کیے گئے تھے۔ [براہ مہربانی ان کا جائزہ لیں](%{base_path}/review)۔" + subject_template: + one: "%{count} شے کا جائزہ لینے کی ضرورت ہے" + other: "%{count} اشیاء کا جائزہ لینے کی ضرورت ہے" unsubscribe_mailer: title: "مَیلر غیر سَبسکرائب کریں" subject_template: "تصدیق کریں کہ اب آپ %{site_title} سے ای میل اَپ ڈیٹس حاصل نہیں کرنا چاہتے ہیں" @@ -1840,12 +2117,79 @@ ur: test_mailer: title: "مَیلر ٹیسٹ کریں" subject_template: "[%{email_prefix}] ای میل قابل ترسیل ہونے کا ٹیسٹ" + text_body_template: | + یہ اِس سے ایک ٹیسٹ ای میل ہے + + [**%{base_url}**][0] + + ای میل کا قابل ترسیل ہونا ترسیل پیچیدہ ہے۔ یہ چند اہم چیزیں ہیں جنہیں آپ کو پہلے چیک کرنا چاہیئے: + + - اپنی سائٹ ترتیبات میں درست طریقے سے `نوٹیفکیشن ای میل` کیلئے from: ایڈریس مقرر کرنے کا *یقین* کر لیں۔ ** آپ کے بھیجے جانے والے ای میلز کے "from" ایڈریس میں درج کردہ ڈَومین وہ ڈَومین ہے جس کی بنیاد پر آپ کی ای میل کی تصدیق کی جائے گی**۔ + + - اپنے میل کلائنٹ میں ای میل کی رَا سورس دیکھنے کا طریقہ معلوم کر لیں، تاکہ آپ اہم سراگوں کیلئے ای میل ہیڈرز کی جانچ پڑتال کر سکیں۔ Gmail میں، ہر میل کے سب سے اوپر دائیں جانب ڈراپ-ڈاؤن مَینِیو میں یہ "show original" کی آپشن ہے۔ + + - **اہم:** کیا آپ کے ISP کے پاس، جن ڈومین ناموں اور IP ایڈریسوں سے آپ ای میل بھیجتے ہیں، اُن کے ساتھ منسلک کرنے کیلئے ریوَرس DNS ریکارڈ درج ہے؟ یہاں [اپنا ریوَرس PTR ریکارڈ ٹیسٹ کریں][2]۔ اگر آپ کا ISP مناسب ریورس DNS پوائنٹر ریکارڈ درج نہیں کرتا، تو اِس کا بہت کم امکان ہے کہ آپ کا کوئی بھی ای میل ارسال کیا جا سکے گا۔ + + - کیا آپ کے ڈومَین کا [SPF ریکارڈ][8] درست ہے؟ یہاں [اپنا SPF ریکارڈ چیک کریں][1]۔ نوٹ کریں کہ SPF ریکارڈ کی درست آفیشل ریکارڈ ٹائپ TXT ہے۔ + + - کیا آپ کے ڈومَین کا [DKIM ریکارڈ][3] درست ہے؟ یہ ای میل کے قابلِ ترسیل ہونے کو نمایاں طور پر بہتر بنائے گا۔ یہاں [اپنا DKIM ریکارڈ چیک کریں][7]۔ + + اگر آپ اپنا میل سرور چلاتے ہیں، تو اِس بات کا یقین کرنے کیلئے چیک کریں کہ آپ کے میل سرور کے IP [کسی بھی ای میل بلَیک لِسٹ پر موکود نہیں ہیں][4]۔ اِس بات کی بھی تصدیق کر لیں کہ یہ یقینی طور پر پوری طرح سے قوالیفائڈ ہَوسٹ نام بھیج رہا ہے جو اپنے HELO پیغام میں DNS پر ریزَولو ہوتا ہے۔ اگر ایسا نہ ہو، تو یہ آپ کا ای میل بہت سی میل سروِسوں کی طرف سے رد کیے جانے کا بائث بنے گا۔ + + - ہم آپ کو ذور کے ساتھ تجویز دیتے ہیں کہ آپ **[mail-tester.com][mt] پر ایک میل بھیجیں** اِس بات کا یقین کرنے کیلئے کہ اوپر سب صحیح طریقے سے کام کر رہا ہے۔ + + (*آسان* راستہ ہے کہ [SendGrid][sg]، [SparkPost][sp]، [Mailgun][mg] یا [Mailjet][mj]، پر ایک مفت اکاؤنٹ بنائیں جن پر سخی مفت میلنگ پلَین ہوتے ہیں اور چھوٹی کمیونٹیوں کیلئے ٹھیک ہیں۔ اگرچہ! آپ کو ابھی بھی اپنے DNS میں SPF اور DKIM ریکارڈ مقرر کرنے کی ضرورت ہوگی) + + ہمیں امید ہے کہ آپ کو ای میل کے قابلِ ترسیل ہونے کا ٹیسٹ ٹھیک سے موصول ہو گیا ہو گا! + + قسمت کامیاب کرے، + + آپ کے [ڈِسکورس](https://www.discourse.org) پر دوست + + [0]: %{base_url} + [1]: https://www.kitterman.com/spf/validate.html + [2]: https://mxtoolbox.com/ReverseLookup.aspx + [3]: http://www.dkim.org/ + [4]: https://whatismyipaddress.com/blacklist-check + [7]: https://www.mail-tester.com/spf-dkim-check + [8]: http://www.openspf.org/SPF_Record_Syntax + [sg]: https://goo.gl/r1WMF6 + [sp]: https://www.sparkpost.com/ + [mg]: https://www.mailgun.com/ + [mj]: https://www.mailjet.com/pricing/ + [mt]: https://www.mail-tester.com/ new_version_mailer: title: "نیا ورژن مَیلر" subject_template: "[%{email_prefix}] نیا ڈِسکورس ورژن، اَپ ڈیٹ دستیاب ہے" + text_body_template: | + ہُرے، [ڈِسکورس](https://www.discourse.org) کا ایک نیا ورژن دستیاب ہے! + + آپ کا ورژن: %{installed_version} + نیا ورژن: **%{new_version}** + + - ہمارے آسان ** [ایک-کلِک براؤزر اَپ گریڈ](%{base_url}/admin/upgrade)** کا استعمال کرتے ہوئے اَپ گریڈ کریں + + - [ریلیز نوٹس](https://meta.discourse.org/tags/release-notes) میں ملاحظہ کریں کہ نیا کیا ہے یا [رَا گِٹ ہَب تبدیلیوں کا لاگ](https://github.com/discourse/discourse/commits/master) دیکھیں + + ڈِسکورس کی خبر، بحث، اور مدد کیلئے [meta.discourse.org](https://meta.discourse.org) وِزِٹ کریں new_version_mailer_with_notes: title: "نوٹس کے ساتھ نیا ورژن مَیلر" subject_template: "[%{email_prefix}] اَپ ڈیٹ دستیاب ہے" + text_body_template: | + ہُرے، [ڈِسکورس](https://www.discourse.org) کا ایک نیا ورژن دستیاب ہے! + + آپ کا ورژن: %{installed_version} + نیا ورژن: **%{new_version}** + + - ہمارے آسان ** [ایک-کلِک براؤزر اَپ گریڈ](%{base_url}/admin/upgrade)** کا استعمال کرتے ہوئے اَپ گریڈ کریں + + - [ریلیز نوٹس](https://meta.discourse.org/tags/release-notes) میں ملاحظہ کریں کہ نیا کیا ہے یا [رَا گِٹ ہَب تبدیلیوں کا لاگ](https://github.com/discourse/discourse/commits/master) دیکھیں + + ڈِسکورس کی خبر، بحث، اور مدد کیلئے [meta.discourse.org](https://meta.discourse.org) وِزِٹ کریں + + ### ریلیز نوٹس + + %{notes} flag_reasons: off_topic: "آپ کی پوسٹ کو **موضوع سے ہٹ کر** کے طور پر فلَیگ کیا گیا تھا: کمیونٹی سمجھتی ہے کہ یہ ٹاپک کیلئے اچھی فِٹ نہیں ہے، جیسا کہ عنوان اور پہلی پوسٹ کی طرف سے فی الحال بیان کیا گیا ہے۔" inappropriate: "آپ کی پوسٹ کو **نامناسب** کے طور پر فلَیگ کیا گیا تھا: کمیونٹی سمجھتی ہے کہ یہ توہین آمیز، بدسلوکی، یا [ہماری کمیونٹی کے قواعد و ضوابط]() کی خلاف ورزی ہے۔" @@ -1857,6 +2201,9 @@ ur: disagreed: "ہمیں بتانے کیلئے شکریہ۔ ہم اِس سے نمٹ رہے ہیں۔" ignored: "ہمیں بتانے کیلئے شکریہ۔ ہم اِس سے نمٹ رہے ہیں۔" ignored_and_deleted: "ہمیں بتانے کیلئے شکریہ۔ ہم نے پوسٹ ہٹا دی ہے۔" + temporarily_closed_due_to_flags: + one: "یہ ٹاپک کمیونٹی فلَیگز کی بڑی تعداد کی وجہ سے کم از کم %{count} گھنٹہ کیلئے عارضی طور پر بند کردیا گیا ہے۔" + other: "یہ ٹاپک کمیونٹی فلَیگز کی بڑی تعداد کی وجہ سے کم از کم %{count} گھنٹوں کیلئے عارضی طور پر بند کردیا گیا ہے۔" system_messages: private_topic_title: "ٹاپک #%{id}" contents_hidden: "اِس کے مواد کو دیکھنے کیلئے براہ کرم پوسٹ پر جائیں۔" @@ -1879,6 +2226,45 @@ ur: اضافی رہنمائی کیلئے، براہ کرم ہماری [کمیونٹی کی رہنما ہدایات](%{base_url}/guidelines) دیکھیے۔ post_hidden_again: title: "پوسٹ دوبارہ چھاپی دی گئی" + subject_template: "کمیونٹی فلَیگز کی وجہ سے پوسٹ چھپا دی گئی، سٹاف کو مطلاع کر دیا گیا" + text_body_template: | + ہیلو، + + یہ %{site_name} کی طرف سے ایک خود کار پیغام ہے تاکہ آپ کو بتایا جاسکے کہ آپ کی پوسٹ چھپا دی گئی تھی۔ + + <%{base_url}%{url}> + + %{flag_reason} + + اِس پوسٹ کو چھپانے سے پہلے ایک سے زیادہ کمیونٹی ممبران نے اِسے فلَیگ کیا تھا۔ **کیونکہ یہ پوسٹ ایک سے زائد مرتبہ چھپائی گئی ہے، اب آپ کی پوسٹ سٹاف کی طرف سے نمٹائے جانے تک چھپی رہے گی۔** + + اضافی رہنمائی کیلئے، براہ کرم ہماری [کمیونٹی کی رہنما ہدایات](%{base_url}/guidelines) دیکھیے۔ + flags_agreed_and_post_deleted: + title: "فلَیگ کردہ پوسٹ سٹاف کی طرف سے ہٹا دی گئی" + subject_template: "فلَیگ کردہ پوسٹ سٹاف کی طرف سے ہٹا دی گئی" + text_body_template: | + ہیلو، + + یہ %{site_name} کی طرف سے ایک خود کار پیغام ہے تاکہ آپ کو بتایا جاسکے کہ [آپ کی پوسٹ](%{base_url}%{url}) حذف کر دی گئی تھی۔ + + <> + + %{flag_reason} + + یہ پوسٹ کمیونٹی کی طرف سے فلَیگ کی گئی تھی اور سٹاف کے ایک رکن نے اِسے ہٹانے کا انتخاب کیا۔ + + [details="ہٹائی گئی پوسٹ کو کھولنے کیلئے کلک کریں"] + ``` مارکڈائون + %{flagged_post_raw_content} + ``` + [/details] + + اضافی رہنمائی کیلئے، براہ کرم ہماری [کمیونٹی کی رہنما ہدایات](%{base_url}/guidelines) دیکھیے۔ + usage_tips: + text_body_template: | + نئے صارف کے طور پر شروعات کرنے پر چند فوری تجاویز کیلئے، [اِس بلاگ پوسٹ کو دیکھیں](https://blog.discourse.org/2016/12/discourse-new-user-tips-and-tricks/)۔ + + جیسے جیسے کہ آپ یہاں شرکت کریں گے، ہم آپ کو جان پائیں گے، اور نئے صارف پر عارضی پابندیاں اٹھا دی جائیں گی۔ وقت کے ساتھ ساتھ آپ [ٹرسٹ لَیول](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) حاصل کر سکیں گے، جن میں اِس کمیونٹی کو مل کر مینیج کرنے کیلئے خصوصی صلاحیتیں شامل ہوں گی۔ welcome_user: title: "خوش آمدید صارف" subject_template: "%{site_name} پر خوش آمدید!" @@ -1890,6 +2276,13 @@ ur: ہم ہر وقت [مہذب کمیونٹی رویہ](%{base_url}/guidelines) پر یقین رکھتے ہیں۔ اپنے قیام کا لطف اٹھائیں! + welcome_tl1_user: + title: "خوش آمدید TL1 صارف" + subject_template: "ہمارے ساتھ وقت گزارنے کیلئے شکریہ" + text_body_template: | + ارے۔ ہم نے دیکھا کہ آپ پڑھنے میں مصروف ہیں، جو بہت خوب ہے، لہٰذا ہم نے آپ کو ایک [ٹرسٹ لَیول!](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) اوپر ترقی دے دی ہے + + ہم واقعی بہت خوش ہیں کہ آپ ہمارے ساتھ وقت گزار رہے ہیں اور ہم آپ کے بارے میں مزید جاننا پسند کریں گے۔ ایک لمحہ کیلئے رُک کر [اپنی پروفائل کو بھریں](%{base_url}/my/preferences/profile)، یا آسانی سے [ایک نیا ٹپک شروع کریں](%{base_url}/categories)۔ welcome_invite: title: "خوش آمدید دعوت" subject_template: "%{site_name} پر خوش آمدید!" @@ -1910,15 +2303,49 @@ ur: backup_succeeded: title: "بیک اَپ کامیاب" subject_template: "بیک اَپ کامیابی سے مکمل کر لیا گیا" + text_body_template: | + بیک اَپ کامیاب ہو گیا تھا۔ + + اپنا نیا بیک اَپ ڈاؤن لوڈ کرنے کیلئے [ایڈمن > بیک اَپ سیکشن](%{base_url}/admin/backups) ملاحظہ کریں۔ + + یہ لاگ ہے: + + ``` text + %{logs} + ``` backup_failed: title: "بیک اَپ ناکام" subject_template: "بیک اَپ ناکام ہو گیا" + text_body_template: | + بیک اَپ ناکام ہو گیا ہے۔ + + یہ لاگ ہے: + + ``` text + %{logs} + ``` restore_succeeded: title: "رِیسٹور کامیاب" subject_template: "رِیسٹور کامیابی سے مکمل ہو گیا" + text_body_template: | + رِیسٹور کامیاب ہو گیا تھا۔ + + یہ لاگ ہے: + + ``` text + %{logs} + ``` restore_failed: title: "رِیسٹور ناکام" subject_template: "رِیسٹور ناکام ہو گیا" + text_body_template: | + رِیسٹور ناکام ہو گیا ہے۔ + + یہ لاگ ہے: + + ``` text + %{logs} + ``` bulk_invite_succeeded: title: "بَلک مدعو کامیاب" subject_template: "بَلک صارف دعوت ناموں پر کامیابی سے عملدرآمد ہو گیا" @@ -1926,8 +2353,25 @@ ur: bulk_invite_failed: title: "بَلک مدعو ناکام" subject_template: "بَلک صارف دعوت ناموں پر خرابں کے ساتھ عملدرآمد ہو گیا" + text_body_template: | + آپ کے بَلک صارف دعوت نامے فائل پر عملدرآمد کر دیا گیا، %{sent} دعوتیں میل کر دی گئیں اور %{failed} خرابی(وں) کا سامنا کرنا پڑا۔ + + یہ لاگ ہے: + + ``` text + %{logs} + ``` csv_export_succeeded: title: "CSV ایکسپورٹ کامیاب" + subject_template: "[%{export_title}] ڈَیٹا ایکسپورٹ مکمل" + text_body_template: | + آپ کا ڈَیٹا ایکسپورٹ کامیاب ہو گیا! :dvd: + + %{file_name} (%{file_size}) + + درجہ بالا ڈاؤن لوڈ لِنک 48 گھنٹے تک درست رہے گا۔ + + ڈَیٹا gzip آرکائیو کے طور پر کمپرَیسڈ کیا گیا ہے۔ اگر آپ کے کھولنے پر آرکائیو خود کو ایکسٹریکٹ نہیں کی لیتی، تو یہاں تجویز کردہ ٹولز استعمال کریں: https://www.gzip.org/#faq4 csv_export_failed: title: "CSV ایکسپورٹ ناکام" subject_template: "ڈَیٹا ایکسپورٹ ناکام ہو گئی" @@ -2036,6 +2480,14 @@ ur: %{post_error} اگر آپ مسئلہ کو درست کر سکتے ہیں، تو براہ کرم دوبارہ کوشش کریں۔ + date_invalid: "پوسٹ بنانے کی کوئی تاریخ نہیں ملی۔ کیا ای-میل کا تاریخ: ہیڈر غائب ہے؟" + email_reject_post_too_short: + title: "ایمیل مسترد پوسٹ بہت مختصر" + subject_template: "[%{email_prefix}] ایمیل مسئلہ -- پوسٹ بہت مختصر" + text_body_template: | + ہمیں افسوس ہے، لیکن آپ کا %{destination} پر ای میل پیغام (عنوان %{former_title}) کام نہیں کیا۔ + + مزید گہرائی والی بحثوں کو فروغ دینے کیلئے، بہت مختصر جوابات کی اجازت نہیں ہے۔ کیا آپ براہ مہربانی کم از کم %{count} حروف کے ساتھ جواب دے سکتے ہیں؟ متبادل طور پر، آپ بزریعہ ایمیل "+1" کا جواب دے کر ایک پوسٹ لائیک کرسکتے ہیں۔ email_reject_invalid_post_action: title: "ای میل مسترد غلط پوسٹ اَیکشن" subject_template: "[%{email_prefix}] ای میل مسئلہ -- غلط پوسٹ اَیکشن" @@ -2057,6 +2509,13 @@ ur: ہمیں افسوس ہے، لیکن آپ کا %{destination} پر ای میل پیغام (عنوان %{former_title}) کام نہیں کیا۔ کوئی بھی منزل ای میل ایڈریس معلوم نہیں، یا ای میل میں Message-ID کے ہیڈر میں ترمیم کی گئی ہے۔ براہ کرم یقینی بنائیں کہ سٹاف کی طرف سے فراہم کردہ صحیح ای میل ایڈریس پر آپ بھیج رہے ہیں۔ + email_reject_old_destination: + title: "ایمیل مسترد پرانی منزل" + subject_template: "[%{email_prefix}] ایمیل مسئلہ -- آپ ایک پرانی اطلاع پر جواب دینے کی کوشش کر رہے ہیں" + text_body_template: | + ہمیں افسوس ہے، لیکن آپ کا %{destination} پر ایمیل پیغام (عنوان %{former_title}) کام نہیں کیا۔ + + ہم صرف %{number_of_days} دنوں تک اصل اطلاعات کے جوابات قبول کرتے ہیں۔ براہ کرم، گفتگو جاری رکھنے کیلئے [ٹاپک پر جائیں](%{short_url})۔ email_reject_topic_not_found: title: "ای میل مسترد ٹاپک نہیں ملا" subject_template: "[%{email_prefix}] ای میل مسئلہ -- ٹاپک نہیں ملا" @@ -2085,6 +2544,23 @@ ur: ہمیں افسوس ہے، لیکن آپ کا %{destination} پر ای میل پیغام (عنوان %{former_title}) کام نہیں کیا۔ آپ کی ای میل پر عمل درامد کے دوران ایک نامعلوم خرابی کا سامنا کرنا پڑا اور وہ شائع نہیں ہوئی۔ آپ کو دوبارہ کوشش کرنی چاہئے، یا [ایک سٹاف ممبر سے رابطہ کریں](%{base_url}/about)۔ + email_reject_attachment: + title: "ایمیل اٹیچمنٹ مسترد" + subject_template: "[%{email_prefix}] ایمیل مسئلہ -- اٹیچمنٹ مسترد" + text_body_template: | + بدقسمتی سے آپ کا %{destination} پر ایمیل پیغام (عنوان %{former_title}) میں کچھ اٹیچمنٹس مسترد ہوگئیں۔ + + تفصیلات: + %{rejected_errors} + + اگر آپ سمجھتے ہیں کہ یہ ایک غلطی ہے، تو [ایک سٹاف ممبر سے رابطہ کریں](%{base_url}/about)۔ + email_reject_reply_not_allowed: + title: "ایمیل مسترد جواب کی اجازت نہیں" + subject_template: "[%{email_prefix}] ای میل مسئلہ -- جواب کی اجازت نہیں" + text_body_template: | + ہمیں افسوس ہے، لیکن آپ کا %{destination} پر ایمیل پیغام (عنوان %{former_title}) کام نہیں کیا۔ + + آپ کو ٹاپک کا جواب دینے کی اجازت نہیں ہے۔ اگر آپ سمجھتے ہیں کہ یہ ایک غلطی ہے، تو [ایک سٹاف ممبر سے رابطہ کریں](%{base_url}/about)۔ email_error_notification: title: "ای میل مسترد اطلاع" subject_template: "[%{email_prefix}] ای میل مسئلہ -- POP توثیق خرابی" @@ -2094,9 +2570,42 @@ ur: براہ مہربانی یقینی بنائیں کہ آپ نے [سائٹ ترتیبات](%{base_url}/admin/site_settings/category/email) میں POP اسناد کو ٹھیک طریقے سے ترتیب دیا ہے۔ اگر POP ای میل اکاؤنٹ کیلئے ویب UI موجود ہے، تو آپ کو ویب پر لاگ اِن کرنے اور اپنی ترتیبات کو وہاں چیک کرنے کی ضرورت پڑ سکتی ہے۔ + email_revoked: + title: "ایمیل منسوخ کر دی گئی" + subject_template: "کیا آپ کا ایمیل پتہ درست ہے؟" + text_body_template: | + ہمیں افسوس ہے، لیکن ہمیں بزریعہ ایمیل آپ تک پہنچنے میں دشواری ہو رہی ہے۔ آپ کیلئے ہماری آخری چند ایمیلز تمام ناقابل ترسیل کے طور پر واپس بھیج دی گئی تھیں۔ + + کیا آپ کو یقینی بنا سکتے ہیں کہ [آپ کا ایمیل ایڈریس](%{base_url}/میری/ترجیحات/ایمیل) درست اور کام کر رہا ہے؟ ترسیل بہتر بنانے کیلئے، آپ ہمارے ایمیل ایڈریس کو اپنی ایڈریس بک / رابطے کی فہرست میں بھی شامل کرنا چاہ سکتے ہیں۔ + email_bounced: | + %{email} پر پیغام واپس بھیج دیا گیا۔ + + ### تفصیلات + + ``` text + %{raw} + ``` + ignored_users_summary: + title: "نظر انداز صارف حد پار کر گیا" + subject_template: "بہت سے دوسرے صارفین کی طرف سے ایک صارف کو نظر انداز کیا جا رہا ہے" + text_body_template: | + ہیلو، + + یہ %{site_name} کی طرف سے ایک خود کار پیغام ہے تاکہ آپ کو بتایا جاسکے کہ %{ignores_threshold} صارفین کی طرف سے [صارف پروفائل](%{base_url}/u/%{username}/summary) نظر انداز کر دی گئی ہے۔ یہ آپ کی کمیونٹی میں ایک بڑھتے ہوئے مسئلہ کی نشاندہی کر سکتا ہے۔ + + شاید آپ کو اِس صارف کے حالیہ اشاعتوں کا جائزہ لینا چاہئیے، اور ممکنہ طور پر [نظر انداز کردہ صارف رپورٹ](%{base_url}/admin/reports/top_ignored_users) کے دیگر صارفین کا بھی۔ + اضافی رہنمائی کیلئے، براہ کرم ہماری [کمیونٹی کی رہنما ہدایات](%{base_url}/guidelines) دیکھیے۔ too_many_spam_flags: title: "بہت زیادہ سپَیم فلَیگز" subject_template: "نیا اکاؤنٹ ہَولڈ پر" + text_body_template: | + ہیلو، + + یہ %{site_name} کی طرف سے ایک خود کار پیغام ہے تاکہ آپ کو بتایا جاسکے کہ آپ کی پوسٹس عارضی طور پر چھپا دیا گیا ہے کیونکہ وہ کمیونٹی کی طرف سے فلَیگ کی گئی تھیں۔ + + احتیاطی تدابیر کے طور پر، آپ کا نیا اکاؤنٹ خاموش کر دیا گیا ہے اور جوابات یا ٹاپک نہیں بنا سکے گا، جب تک کہ ایک سٹاف ممبر آپ کے اکاؤنٹ کا جائزہ نہ لیلے۔ ہم تکلیف کے لیے معذرت خواہ ہیں۔ + + اضافی رہنمائی کیلئے، براہ کرم ہماری [کمیونٹی کی رہنما ہدایات](%{base_url}/guidelines) دیکھیے۔ too_many_tl3_flags: title: "بہت زیادہ ٹ.ل.3 فلَیگز" subject_template: "نیا اکاؤنٹ ہَولڈ پر" @@ -2133,6 +2642,14 @@ ur: spam_post_blocked: title: "سپَیم پوسٹ بلاک کر دی گئی" subject_template: "بار بار لِنکس کی وجہ سے نئے صارف %{username} کی پوسٹس کو بلاک کردیا گیا" + text_body_template: | + یہ ایک خودکار پیغام ہے۔ + + نئے صارف [%{username}](%{user_url}) نے %{domains} کے ساتھ لِنکس والی کئی پوسٹس بنانے کی کوشش کی، لیکن سپَیم سے بچنے کے لئے اُن پوسٹس کو بلاک کر دیا گیا تھا۔ صارف اب بھی نئی پوسٹس بنا سکتا ہے جو %{domains} پر لِنک نہ کریں۔ + + براہ مہربانی [صارف کا جائزہ لیں](%{user_url})۔ + + یہ `newuser_spam_host_threshold` اور `white_listed_spam_host_domains` سائٹ ترتیب کے ذریعے تبدیل کیا جا سکتا ہے۔ %{domains} کو وائِٹ لِسٹ میں شامل کرنے پر غور کریں اگر اُنہیں مستثنیٰ حاصل ہونا چاہئیے۔ unsilenced: title: "خاموشی ختم" subject_template: "اکاؤنٹ اَب ہولڈ پر نہیں ہے" @@ -2147,12 +2664,21 @@ ur: subject_template: one: "%{count} صارف منظوری کا منتظر ہے" other: "%{count} صارفین منظوری کے منتظر ہیں" + text_body_template: | + اِس فورم تک رسائی حاصل کرنے سے پہلے نئے صارف سائن اَپ منظور (یا مسترد) ہونے کے منتظر ہیں۔ + + [براہ مہربانی اُن کا جائزہ لیں](%{base_url}/review)۔ download_remote_images_disabled: title: "ریمَوٹ تصاویر کے ڈاؤن لوڈ غیر فعال" subject_template: "ریمَوٹ تصاویر کے ڈاؤن لوڈ کو غیر فعال کر دیا گیا ہے" text_body_template: "`download_remote_images_to_local` ترتیب غیر فعال کر دی گئی تھی کیونکہ` download_remote_images_threshold` پر ڈِسک میں جگہ کی حد تک پہنچ گئے تھے۔" dashboard_problems: title: "ڈَیش بورڈ مسائل" + subject_template: "آپ کی سائٹ ڈَیش بورڈ پر نیا مشورہ" + text_body_template: | + ہمارے پاس آپ کی موجودہ سائٹ ترتیبات پر مبنی آپ کیلئے کچھ نئے مشورے اور سفارشات ہیں۔ + + اسے دیکھنے کیلئے [اپنے سائٹ ڈَیش بورڈ پر جائیں](%{base_url}/admin)۔ new_user_of_the_month: title: "مہینہ کے سب سے زیادہ قابل قدر نئے صارف!" subject_template: "آپ مہینہ کے سب سے زیادہ قابل قدر نئے صارف ہیں!" @@ -2167,6 +2693,10 @@ ur: subject_template: one: "جائزہ لیے جانے کی منتظر %{count} پوسٹ" other: "جائزہ لیے جانے کی منتظر %{count} پوسٹس" + text_body_template: | + ہیلو، + + نئے صارفین کی پوسٹس کو ماڈرَیشَن کیلئے ہَولڈ پر رکھا گیا اور فی الحال جائزہ لیے جانے کے انتظار میں ہیں۔ [اُن کو منظور یا مسترد یہاں کریں](%{base_url}/review?type=ReviewableQueuedPost)۔ unsubscribe_link: | اِن ای میلز سے غیر سَبسکرائب کرنے کیلئے، [یہاں کلِک کریں](%{unsubscribe_url})۔ unsubscribe_link_and_mail: | @@ -2177,6 +2707,7 @@ ur: اِن ای میلز سے غیر سَبسکرائب کرنے کیلئے، [یہاں کلِک کریں](%{unsubscribe_url})۔ subject_re: "جواب:" subject_pm: "[PM]" + email_from: "%{user_name} بذریعہ %{site_name}" user_notifications: previous_discussion: "پچھلے جوابات" reached_limit: @@ -2187,9 +2718,13 @@ ur: title: "غیر سَبسکرائب" description: "اِن ای میلز کو موصول کرنے میں دلچسپی نہیں ہے؟ کوئی مسئلہ نہیں! فوری طور پر غیر سَبسکرائب کرنے کیلئے ذیل میں کلِک کریں:" reply_by_email: "[ٹاپک وِزِٹ کریں](%{base_url}%{url}) یا جواب دینے کیلئے اس ای میل کا جواب دیں۔" + reply_by_email_pm: "[پیغام وِزِٹ کریں](%{base_url}%{url}) یا %{participants} کا جواب دینے کیلئے اِس ایمیل کا جواب دیں۔" only_reply_by_email: "جواب دینے کیلئے اس ای میل کا جواب دیں۔" + only_reply_by_email_pm: "%{participants} کا جواب دینے کیلئے اِس ایمیل کا جواب دیں۔" visit_link_to_respond: "جواب دینے کیلئے [ٹاپک وِزِٹ کریں](%{base_url}%{url})۔" + visit_link_to_respond_pm: "[پیغام وِزِٹ کریں](%{base_url}%{url}) %{participants} کا جواب دینے کیلئے۔" posted_by: "%{post_date} کو %{username} نے پوسٹ کیا" + pm_participants: "شرکاء: %{participants}" invited_group_to_private_message_body: | %{inviter_name} نے %{group_name} کو ایک پیغام پر مدعو کیا @@ -2312,6 +2847,7 @@ ur: %{respond_instructions} user_mentioned_pm: + title: "صارف ذکر کردہ ذاتی پیغام" subject_template: "[%{email_prefix}] [ذاتی پیغام] %{topic_title}" text_body_template: | %{header_instructions} @@ -2533,6 +3069,33 @@ ur: %{base_url}/u/activate-account/%{email_token} اگر اوپر والا لِنک کلک نہیں ہوتا ہو، تو اپنے ویب براؤزر کے ایڈریس بار میں کاپی اور پَیسٹ کرنے کی کوشش کریں۔ + activation_reminder: + title: "فعال کرنے کی یاد دہانی" + subject_template: "[%{email_prefix}] اپنے اکاؤنٹ کی تصدیق کرنے کیلئے یاد دہانی" + text_body_template: | + %{site_name} میں خوش آمدید! + + یہ آپ کے اکاؤنٹ کی تصدیق کرنے کیلئے دوستانہ یاد دہانی ہے۔ + + اپنے نئے اکاؤنٹ کی تصدیق اور اُسے چالو کرنے کیلئے مندرجہ ذیل لِنک پر کلِک کریں: + %{base_url}/u/activate-account/%{email_token} + + اگر اوپر والا لِنک کلک نہیں ہوتا ہو، تو اپنے ویب براؤزر کے ایڈریس بار میں کاپی اور پَیسٹ کرنے کی کوشش کریں۔ + suspicious_login: + title: "نیا لاگ اِن انتباہ" + subject_template: " [%{site_name}] نیا لاگ اِن %{location} سے" + text_body_template: | + ہیلو، + + ہم نے ایک ایسی ڈیوائس یا مقام سے لاگ اِن دیکھا جسے آپ عام طور پر استعمال نہیں کرتے ہیں۔ کیا یہ آپ تھے؟ + + - مقام: %{location} (%{client_ip}) + - براؤزر: %{browser} + - ڈیوائس: %{device} – %{os} + + اگر یہ آپ ہی تھے، بہت خوب! آپ کو کچھ مزید کرنے کی ضرورت نہیں ہے۔ + + اگر یہ آپ نہیں تھے، تو براہ کرم [اپنے موجودہ سیشنوں کا جائزہ لیں](%{base_url}/my/preferences/account) اور اپنا پاسورڈ تبدیل کرنے پر غور کیجیے۔ page_not_found: title: "افوہ! وہ صفحہ موجود نہیں یا ذاتی ہے۔" popular_topics: "مقبول" @@ -2547,6 +3110,9 @@ ur: welcome_message: | ## [%{title} پر خوش آمدید](#welcome) ایک اکاؤنٹ کی ضرورت ہے۔ برائے مہربانی جاری رکھنے کیلئے ایک اکاؤنٹ بنائیں یا لاگ اِن کریں۔ + welcome_message_invite_only: | + ## [%{title} پر خوش آمدید](#welcome) + ایک اکاؤنٹ کی ضرورت ہے۔ برائے مہربانی جاری رکھنے کیلئے موجودہ رکن سے ایک دعوت نامہ کیلئے درخواست کریں یا لاگ اِن کریں۔ deleted: "حذف کردہ" image: "تصویر" upload: @@ -2589,13 +3155,27 @@ ur: sender_message_to_blank: "پیغام.کیلئے خالی ہے" sender_text_part_body_blank: "text_part.body خالی ہے" sender_body_blank: "متن خالی ہے" + sender_post_deleted: "پوسٹ حذف کردی گئی ہے" + sender_message_to_invalid: "وصول کنندہ کا ایمیل ایڈریس غلط ہے" color_schemes: base_theme_name: "بَیس" light: "لائٹ" dark: "ڈارک" + neutral: "نیوٹرل" + grey_amber: "گرے امبر" + shades_of_blue: "نیلے رنگ کے شَیڈ" + latte: "لا ٹَیے" + summer: "موسمِ گرما" + dark_rose: "گہرا گلابی" default_theme_name: "لائٹ" light_theme_name: "لائٹ" dark_theme_name: "ڈارک" + neutral_theme_name: "نیوٹرل" + grey_amber_theme_name: "گرے امبر" + shades_of_blue_theme_name: "نیلے رنگ کے شَیڈ" + latte_theme_name: "لا ٹیے" + summer_theme_name: "موسمِ گرما" + dark_rose_theme_name: "گہرا گلابی" edit_this_page: " اِس صفحہ میں ترمیم کریں" csv_export: boolean_yes: "جی ہاں" @@ -2792,41 +3372,78 @@ ur: editor: name: ترمیم کنندہ description: پہلی پوسٹ ترمیم + long_description: | + یہ بَیج پہلی دفعہ اپنی اشاعتوں میں سے ایک میں ترمیم کرنے پر دیا جاتا ہے۔ تاہم آپ ہمیشہ کیلئے اپنی اشاعتوں میں ترمیم نہیں کر سکیں گے، ترمیم کرنے کی حوصلہ افزائی کی جاتی ہے — آپ فارمَیٹِنگ بہتر بنا سکتے ہیں، چھوٹی غلطیوں کو درست کر سکتے ہیں، یا ایسا کچھ بھی شامل کریں جو آپ پہلی دفعہ میں شامل کرنا بھول گئے تھے۔ اپنی اشاعتیں کو مذید بہتر بنانے کیلئے اُن کو ترمیم کیجیے! + wiki_editor: + name: وِیکی ایڈیٹر + description: پہلی وِیکی ترمیم + long_description: | + یہ بَیج آپ کے پہلی مرتبہ ایک وِیکی پوسٹ میں ترمیم کرنے پر عطا کیا جاتا ہے۔ basic_user: name: بَیسِک + description: "تمام لازمی کمیونٹی کے افعال عطا کر دیے گئے" + long_description: | + یہ بَیج آپ کے ٹرسٹ لَیول 1 تک پہنچنے پر دیا جاتا ہے۔ یہاں رکنے اور ہماری کمیونٹی کے بارے میں جاننے کیلئے چند ٹاپک پڑھنے کیلئے شکریہ۔ نئے صارف کی پابندیاں ہٹا دی گئیں ہیں؛ آپ کو تمام لازمی کمیونٹی صلاحیات، جیسے کہ ذاتی پیغام رسانی، فلَیگ سازی، وِیکی ترمیم، اور ایک سے زیادہ تصاویر اور لِنکس شائع کرنا، عطا کر دی گئیں ہیں۔ member: name: ممبر + description: "دعوت نامے، گروپ پیغام رسانی، مزید لائیکس عطا کر دیے گئے" + long_description: | + یہ بَیج کے آپ ٹرسٹ لَیول 2 تک پہنچنے پر دیا جاتا ہے۔ ہماری کمیونٹی میں واقعی شامل ہونے کیلئے چند ہفتوں کیلئے حصہ لینے کیلئے شکریہ۔ اَب آپ اپنے صارف صفحے یا انفرادی ٹاپکس سے دعوت نامہ بھیج سکتے ہیں، گروپ ذاتی پیغامات تشکیل دے سکتے ہیں، اور آپ کو فی دن مذید لائیکس حاصل ہو گئے ہیں۔ regular: name: رَیگولر + description: "زُمرہ دوبارہ درج کرنا، نام تبدیل کرنا، فَولَو کردہ لِنکس، وِیکی، مذید لائیکس عطا کر دیے گئے" + long_description: | + یہ بَیج آپ کے ٹرسٹ لَیول 3 تک پہنچنے پر دیا جاتا ہے۔ مہینوں کی مدت کیلئے ہماری کمیونٹی باقاعدگی سے حصہ بننے کیلئے شکریہ۔ اَب آپ سب سے زیادہ سرگرم پڑھنے والوں میں سے ایک ہیں، اور ایک قابل اعتماد شراکت دار ہیں جو ہماری کمیونٹی کو زبردست بنا دیتے ہیں۔ اَب آپ ٹاپکس کے زُمرہ جات اور نام تبدیل کر سکتے ہیں، مزید طاقتور سپَیم فلَیگز کا فائدہ اٹھا سکتے ہیں، ایک ذاتی لاؤنج تک رسائی حاصل کرسکتے ہیں اور آپ کو فی دن کئی زیادہ لائیکس بھی حاصل ہو گئے ہیں۔ leader: name: لِیڈر + description: "گلَوبل ترمیم، پِن، بند، آرکائیو، تقسیم اور ضم کرنا، مزید لائیکس عطا کر دیے گئے" + long_description: | + یہ بَیج آپ کے ٹرسٹ لَیول 4 تک پہنچنے پر دیا جاتا ہے۔ آپ اِس کمیونٹی میں سٹاف کی طرف سے منتخب کردہ ایک رہنما کی حیثیت رکھتے ہیں، اور آپ نے اپنے اعمال اور الفاظ سے باقی کمیونٹی کیلئے ایک مثبت مثال قائم کی ہے۔ آپ کے پاس تمام پوسٹس میں ترمیم کرنے کی صلاحیت ہے، ٹاپک پر ماڈریٹر کے عام افعال جیسے کہ پِن، بند، غیر فہرست شدہ، آرکائیو، تقسیم، اور ضم کرنا۔ welcome: name: خوش آمدید description: لائیک موصول ہوا + long_description: | + یہ بَیج آپ کے اپنی پوسٹ پر پہلا لائیک موصول ہونے پر دیا جاتا ہے۔ مبارک ہو، آپ نے کچھ شائع کیا ہے جو آپ کے ساتھی کمیونٹی ممبران کو دلچسپ، زبردست، یا مفید معلوم ہوا ہے! autobiographer: name: آپ بيتی نويس description: "پروفائل معلومات کو پُر کر دیا" + long_description: | + یہ بَیج اپنی صارف پروفائل بھرنے اور پروفائل تصویر منتخب کرنے پر عطا کیا جاتا ہے۔ کمیونٹی کو بتانے سے کہ آپ کون ہیں اور کن چیزوں میں دلچسپی رکھتے ہیں، ایک بہتر، مذید مشترکہ کمیونٹی بنانے میں مدد دیتا ہے۔ ہمارے ساتھ شامل ہوں! anniversary: name: سالگرٰہ description: ایک سال کیلئے سرگرم رکن، کم از کم ایک دفعہ پوسٹ کیا + long_description: | + یہ بَیج آپ کے ایک سال کیلئے رکن ہونے کے ساتھ ساتھ اُسی سال کم از کم ایک پوسٹ شائع کرنے پر عطا کیا جاتا ہے۔ یہاں رکنے اور ہماری کمیونٹی میں حصہ لینے کیلئے آپ کا شکریہ۔ ہم آپ کے بغیر یہ نہیں کر سکتے تھے۔ nice_post: name: اچھا جواب description: ایک جواب پر 10 لائیکس موصول ہوئے + long_description: | + یہ بَیج آپ کے جواب پر 10 لائیکس موصول ہونے پر عطا کیا جاتا ہے۔ آپ کا جواب کمیونٹی پر اثر انداز ہوا اور گفتگو کو آگے بڑھانے میں مدد کا بائث بنا۔ good_post: name: عمدہ جواب description: ایک جواب پر 25 لائیکس موصول ہوئے + long_description: | + یہ بَیج آپ کے جواب پر 25 لائیکس موصول ہونے پر عطا کیا جاتا ہے۔ آپ کا جواب غیر معمولی تھا اور گفتگو کو کہیں زیادہ دلچسپ بنایا۔ great_post: name: زبردست جواب description: ایک جواب پر 50 لائیکس موصول ہوئے + long_description: | + یہ بَیج آپ کے جواب پر 50 لائیکس موصول ہونے پر عطا کیا جاتا ہے۔ زبردست! آپ کا جواب حوصلہ افزانے والا، انتہائی دلچسپ، مزاحیہ یا بصیرت شعار تھا اور کمیونٹی نے اُسے بہت پسند کیا! nice_topic: name: اچھا ٹاپک description: ایک ٹاپک پر 10 لائیکس موصول ہوئے + long_description: | + یہ بَیج آپ کے ٹاپک پر 10 لائیکس موصول ہونے پر عطا کیا جاتا ہے۔ آپ نے ایک دلچسپ گفتگو شروع کی جس سے کمیونٹی لطف اندوز ہوئی۔ good_topic: name: عمدہ ٹاپک description: ایک ٹاپک پر 25 لائیکس موصول ہوئے + long_description: | + یہ بَیج آپ کے ٹاپک پر 25 لائیکس موصول ہونے پر عطا کیا جاتا ہے۔ آپ نے ایک متحرک گفتگو شروع کی جس میں کمیونٹی نے بھرپور حصہ لیا۔ great_topic: name: زبردست ٹاپک description: ایک ٹاپک پر 50 لائیکس موصول ہوئے + long_description: | + یہ بَیج آپ کے ٹاپک پر 50 لائیکس موصول ہونے پر عطا کیا جاتا ہے۔ آپ نے ایک انتہائی دلچسپ گفتگو کا آغاز کیا اور کمیونٹی نے اُس کے نتیجے میں چلنے والی زندہ دل بحث کو پسند کیا! nice_share: name: اچھا شیئر description: 25 منفرد زائرین کے ساتھ ایک اشاعت شیئر کی @@ -2835,9 +3452,13 @@ ur: good_share: name: عمدہ شیئر description: 300 منفرد زائرین کے ساتھ ایک اشاعت شیئر کی + long_description: | + یہ بَیج ایک ایسا لِنک جس پر 300 بیرونی زائرین نے کلِک کیا ہو شیئر کرنے پر عطا کیا جاتا ہے۔ عمدہ کام! آپ نے نئے لوگوں کے ایک گروپ کو ایک شاندار بحث دکھائی اور اِس کمیونٹی کو بڑھنے میں مدد دی ہے۔ great_share: name: زبردست شیئر description: 1000 منفرد زائرین کے ساتھ ایک اشاعت شیئر کی + long_description: | + یہ بَیج ایک ایسا لِنک جس پر 1000 بیرونی زائرین نے کلِک کیا ہو شیئر کرنے پر عطا کیا جاتا ہے۔ زبردست! آپ نے نیا ناظرین کی ایک بہت بڑی تعداد میں ایک دلچسپ بحث کو فروغ دیا ہے، اور ہماری کمیونٹی کو بڑے پیمانے پر بڑھانے میں مدد دی ہے! first_like: name: پہلا لائیک description: ایک پوسٹ کو لائیک کیا @@ -2846,6 +3467,8 @@ ur: first_flag: name: پہلا فلَیگ description: ایک پوسٹ کو فلَیگ کیا + long_description: | + یہ بَیج آپ کو پہلی مرتبہ پوسٹ فلَیگ کرنے پر عطا کیا جاتا ہے۔ بذریعہ فلَیگز ہم مل کر ہر ایک کیلئے اِس کو اچھی جگہ رکھنے میں مدد دیتے ہیں۔ اگر آپ کسی بھی ایسی پوسٹ کو نوٹس کرتے ہیں جس پر کسی بھی وجہ سے ماڈریٹر کی توجہ کی ضرورت ہوتی ہے تو براہ کرم فلَیگ کرنے سے نہ ہچکچائیں۔ اگر آپ کو کوئی مسئلہ نظر آتا ہے، تو اُسے :flag_black: فلَیگ کریں! promoter: name: پروموٹر description: ایک صارف کو دعوت دی @@ -2854,9 +3477,13 @@ ur: campaigner: name: مہم چلانے والا description: 3 بَیسِک صارفین کو مدعو کیا + long_description: | + جب آپ نے 3 ایسے لوگوں کو مدعہ کیا ہو جنہوں نے بعد میں بَیسِک صارفین بننے کیلئے سائٹ پر کافی وقت گزارا ہو، تو یہ بَیج عطا کیا جاتا ہے۔ ایک متحرک کمیونٹی کو باقاعدگی سے نئے آنے والوں کی، جو باقاعدگی سے شرکت اور گفتگو میں نئی آوازیں شامل کرتے ہیں، کی ضرورت ہوتی ہے۔ champion: name: چَیمپیئن description: 5 ممبران کو دعوت دی + long_description: | + جب آپ نے 5 ایسے لوگوں کو مدعہ کیا ہو جنہوں نے بعد میں مکمل ممبر بننے کیلئے سائٹ پر کافی وقت گزارا ہو، تو یہ بَیج عطا کیا جاتا ہے۔ زبردست! نئے اراکین کے ساتھ ہماری کمیونٹی کی جداگانیت کو بڑھانے کیلئے شکریہ! first_share: name: پہلا شیئر description: ایک پوسٹ شیئر کی @@ -2870,9 +3497,13 @@ ur: first_quote: name: پہلا اقتباس description: ایک پوسٹ کا اقتباس کیا + long_description: | + یہ بَیج آپ کو اپنے جواب میں پہلی دفعہ کسی پوسٹ کا اقتباس کرنے پر عطا کیا جاتا ہے۔ اپنے جواب میں پچھلی پوسٹس کے متعلقہ حصوں کا حوالہ دینے سے مباحثوں کو ایک دوسرے سے منسلک اور موضوع پر رہنے میں مدد ملتی ہے۔ اقتباس کرنے کا سب سے آسان طریقہ ایک پوسٹ کے سیکشن کو اجاگر کرنا، اور پھر کسی بھی جواب کے بٹن کو دبانا ہے۔ ل کھول کر اقتباس کریں! read_guidelines: name: ہدایات پڑھِیں description: "کمیونٹی کے قواعد و ضوابط پڑھیں" + long_description: | + یہ بَیج کمیونٹی کے قواعد و ضوابط پڑھنے پر عطا کیا جاتا ہے۔ اِن سادہ ہدایات پر عمل اور اُنہیں شیئر کرنے سے ہر ایک کیلئے ایک محفوظ، پُر لطف اور پائیدار کمیونٹی بنانے میں مدد ملتی ہے۔ ہمیشہ یاد رکھیے کہ اِس سکرین کی دوسری طرف بھی ایک، آپ سے کافی ملتا جلتا، انسان ہے۔ لہاز رکھیے! reader: name: پڑهنے والا description: 100 سے زائد جوابات کے ساتھ ٹاپک میں ہر جواب پڑھا @@ -2921,6 +3552,8 @@ ur: crazy_in_love: name: محبت میں دیوانہ description: ایک دن میں 50 لائیکس 20 مرتبہ استعمال کر لیے + long_description: | + یہ بَیج آپ کے اپنے فی دن کے تمام 50 لائیکس 20 دنوں کیلئے استعمال کرلینے پر عطا کیا جاتا ہے۔ زبردست! آپ آپنے ساتھی کمیونٹی ممبران کی حوصلہ افزائی کرنے کی ایک مثال ہیں! thank_you: name: آپ کا شکریہ description: 20 لائیک کردہ پوسٹس ہیں اور 10 لائیکس دیے @@ -2934,6 +3567,8 @@ ur: empathetic: name: خیال رکھنے والا description: 500 لائیک کردہ پوسٹس ہیں اور 1000 لائیکس دیے + long_description: | + یہ بَیج آپ کے پاس 500 لائیک کردہ پوسٹس ہونے اور جواب میں 1000 یا اُس سے زائد لائیکس دینے پر عطا کیا جاتا ہے۔ زبردست! آپ سخاوت اور باہمی تعریف کی ایک مثال ہیں :two_hearts:۔ first_emoji: name: پہلی اِیمَوجی description: ایک پوسٹ میں اِیمَوجی کا استعمال کیا @@ -2942,9 +3577,13 @@ ur: first_mention: name: پہلا ذکر description: پوسٹ میں ایک صارف کا ذکر کیا + long_description: | + اس بَیج آپ کے اپنی پوسٹ میں پہلی مرتبہ کسی کا @صارفنام ذکر کرنے پر عطا کیا جاتا ہے۔ ہر ذکر اُس شخص کیلئے ایک اطلاع پیدا کرتا ہے، تاکہ اُنہیں آپ کی پوسٹ کے بارے میں پتہ چل سکے۔ کسی بھی صارف، یا اگر اجازت ہو تو، گروپ کا ذکر کرنے کیلئے صرف @ (at کا نشان) ٹائپ کرنا شروع کریں – یہ اُن کی توجہ میں کچھ لانے کا آسان طریقہ ہے۔ first_onebox: name: پہلا وَن باکس description: ایک وَن باکس کردہ لِنک شائع کیا + long_description: | + یہ بَیج عطا کیا جاتا ہے جب آپ پہلی مرتبہ ایک لِنک کو لائین میں اکیلا شائع کرتے ہیں، جو خود بخود ایک خلاصہ، عنوان، اور (جب دستیاب ہو) ایک تصویر کے ساتھ ایک وَن باکس میں کھل گیا ہو۔ first_reply_by_email: name: بذریعہ ای میل پہلا جواب description: ایک پوسٹ پر بذریعہ ای میل جواب دیا @@ -2957,10 +3596,19 @@ ur: یہ بَیج ہر ماہ دو نئے صارفین کو اپنی شاندار مجموعی شراکتوں کیلئے مبارکباد دینے کیلئے عطا کیا جاتا ہے، جیسا کہ اُن کی پوسٹس کو کتنی کثرت سے اور کن کی طرف سے لائیک کیا گیا تھا، سے ناپا جاتا ہے۔ enthusiast: name: پرجوش + description: مسلسل 10 دن وِزِٹ کیا + long_description: | + یہ بَیج مسلسل 10 دنوں تک وِزِٹ کرنے پر عطا کیا جاتا ہے۔ ہمارے ساتھ ایک ہفتے سے زائد رکنے کیلئے شکریہ! aficionado: name: اَفیشیاناڈو + description: مسلسل 100 دن وِزِٹ کیا + long_description: | + یہ بَیج مسلسل 100 دنوں تک وِزِٹ کرنے پر عطا کیا جاتا ہے۔ یہ تین ماہ سے زائد ہے! devotee: name: جاں نثار + description: مسلسل 365 دن وِزِٹ کیا + long_description: | + یہ بَیج مسلسل 365 دنوں تک وِزِٹ کرنے پر عطا کیا جاتا ہے۔ واہ، ایک پورا سال! badge_title_metadata: "%{site_title}پر %{display_name} بَیج " admin_login: success: "ای میل بھیج دی گئ" @@ -2976,6 +3624,15 @@ ur: title: "ٹیگز" staff_tag_disallowed: '"%{tag}" ٹَیگ صرف سٹاف کی طرف سے لاگو کیا جا سکتا ہے۔' staff_tag_remove_disallowed: '"%{tag}" ٹَیگ صرف سٹاف کی طرف سے ہٹایا جا سکتا ہے۔' + minimum_required_tags: + one: "آپ کا کم از کم %{count} ٹیگ منتخب کرنا ضروری ہے۔" + other: "آپ کا کم از کم %{count} ٹیگز کا انتخاب کرنا ضروری ہے۔" + upload_row_too_long: "CSV فائل میں ایک ٹیگ فی لائن ہونا چاہئیے۔ اختیاری طور پر ٹیگ کے بعد ایک کما اور پھر ٹیگ گروپ کا نام درج کیا جا سکتا ہے۔" + forbidden: + in_this_category: '"%{tag_name}" اِس زمرہ میں استعمال نہیں کیا جا سکتا' + restricted_to: + one: '"%{tag_name}" زُمرہ %{category_names} تک کیلئے محدود ہے' + other: '"%{tag_name}" مندرجہ ذیل زُمرہ جات تک کیلئے محدود ہے: %{category_names}' rss_by_tag: "ٹاپکس %{tag} کے ساتھ ٹَیگ کردہ" finish_installation: congratulations: "مبارک باد، آپ نے ڈِسکورس اِنسٹال کر لیا!" @@ -2983,8 +3640,10 @@ ur: button: "رجسٹر" title: "رجسٹر ایڈمن اکاؤنٹ" help: "شروع کرنے کے لئے ایک نیا اکاؤنٹ رجسٹر کریں" + no_emails: "بدقسمتی سے، سَیٹ اَپ کے دوران کوئی ایڈمِنِسٹریٹر ای میل واضح نہیں کی گئیں، لہٰذا ترتیب کو حتمی شکل دینا مشکل ہوسکتا ہے۔ براہ کرم کسی ڈویلپر ایمیل کو ترتیب فائل میں شامل کریں یا کَونسول سے ایک ایڈمِنِسٹریٹر اکاؤنٹ بنائیں۔" confirm_email: title: "اپنے ای میل کی تصدیق کریں" + message: "

ہم نے %{email} پر ایک ایکٹیویشن میل بھیج دی ہے۔ براہ مہربانی اپنے اکاؤنٹ کو چالو کرنے کیلئے میل میں دی گئی ہدایات پر عمل کریں۔

اگر یہ آپ کو موصول نہ ہو، تو اپنا سپَیم فولڈر چیک کریں، اور یقینی بنائیں کہ آپ نے صحیح طریقہ سے ای میل سَیٹ کر لی ہے۔

" resend_email: title: "ایکٹیویشن اِیمیل دوبارہ بھیجییں" message: "

ہم نے %{email} پر ایکٹیویشن اِیمیل دوبارہ بھیج دی ہے" @@ -3014,8 +3673,12 @@ ur: site_description: label: "اپنی کمیونٹی کو ایک مختصر جملہ میں بیان کریں" placeholder: "جمیلہ اور اس کی دوستوں کیلئے دلچسپ چیزوں پر بحث کرنے کی ایک جگہ" + short_site_description: + label: "اپنی کمیونٹی کو چند الفاظ میں بیان کریں" + placeholder: "ہمیشہ کی سب سے بہترین کمیونٹی" introduction: title: "تعارف" + disabled: "

ہم \"%{topic_title}\" عنوان کے ساتھ کوئی ٹاپک تلاش نہیں کر سکے۔

  • اگر آپ نے عنوان کو تبدیل کیا ہے، تو اُس ٹاپک میں ترمیم کریں کہ آپ کی سائٹ کے تعارفی متن کو تبدیل کیا جا سکے۔
  • اگر آپ نے اِس ٹاپک کو حذف کر دیا ہے، تو \"%{topic_title}\"عنوان کے ساتھ ایک اور ٹاپک بنائیں۔ پہلی پوسٹ کا مواد آپ کی سائٹ کا تعارفی متن ہے۔
" fields: welcome: label: "خیرمقدم ٹاپک" @@ -3023,13 +3686,25 @@ ur: one_paragraph: "براہ مہربانی اپنے خیر مقدم پیغام کو ایک پیراگراف تک محدود رکھیں۔" privacy: title: "رسائی" + description: "

کیا آپ کا کمیونٹی سب کے لئے کھلی ہے، یا یہ رکنیت، دعوت ناموں، یا منظوری کے ذریعہ محدود ہے؟ اگر آپ پسند کریں تو، آپ ذاتی طور پر چیزوں کو مقرر کر سکتے ہیں، اور پھر بعد میں کمیونٹی کو عوامی طور پر کھول سکتے ہیں۔

" fields: privacy: choices: open: label: "عوامی" + description: "کوئی بھی اِس کمیونٹی تک رسائی حاصل کر سکتا ہے" restricted: label: "ذاتی" + description: "صرف لاگ اِن کردہ صارفین اِس کمیونٹی تک رسائی حاصل کر سکتا ہے" + privacy_options: + description: "نئے صارفین ایک اکاؤنٹ کیلئے کیسے سائن اَپ کر سکتے ہیں؟" + choices: + open: + label: "صارفین خود سے سائن اَپ کرسکتے ہیں۔" + invite_only: + label: "صارفین خود سے سائن اَپ کرسکتے ہیں، لیکن سٹاف کی طرف سے منظور کیا جانا ضروری ہے۔" + must_approve: + label: "صارفین کو سائن اَپ کرنے سے پہلے قابل اعتماد صارفین یا سٹاف کی طرف سے مدعو کیا جانا لاذمی ہے۔" contact: title: "رابطہ" fields: @@ -3039,24 +3714,47 @@ ur: description: "اِس کمیونٹی کیلئے ذمہ دار شخص یا گروپ کا ای میل ایڈریس۔ اہم اطلاعات جیسے کہ غیر نمٹائے گئے فلَیگز، سیکورٹی اپ ڈیٹس، اور فوری کمیونٹی رابطے کیلئے آپ کے \"بارے میں\" والے صفحے پر اہم اطلاعات کیلئے استعمال کیا جاتا ہے۔" contact_url: label: "وِیب صفحہ" + placeholder: "https://www.example.com/contact-us" description: "آپ یا آپ کے ادارہ کیلئے عام رابطہ کیلئے وَیب صفحہ۔ آپ کے \"بارے میں\" والے صفحے پر دکھایا جائے گا۔" site_contact: label: "خودکار پیغامات" description: "تمام خودکار ڈِسکورس ذاتی پیغامات اِس صارف کی طرف سے بھیجے جائیں گے جیسے کہ فلَیگ انتباہات اور بیک اَپ تکمیل نوٹس۔" corporate: title: "ادارہ" + description: "یہ معلومات آپ کی سروس کی شرائط میں درج کی جائے گی، جو ایک ٹاپک ہے جسے آپ سٹاف زُمرہ میں ترمیم کرسکتے ہیں۔ اگر آپ کی کمپنی نہیں ہے، تو ابھی کیلئے آپ آسانی سے اِس مرحلے کو چھوڑ سکتے ہیں۔" + fields: + company_name: + label: "کمپنی نام" + placeholder: "مثال ادارہ" + governing_law: + label: "گورننگ قانون" + placeholder: "کَیلیفَورنیا کے قانون" + city_for_disputes: + label: "تنازعات کیلئے شہر" + placeholder: "سان فرانسِسکو، کَیلیفَورنیا" colors: title: "تھیم" + themes_further_reading: + title: "تِھیمز" + description: "اپنے ڈِسکورس کو اپنی مرضی کے مطابق بنانا چاہتے ہیں؟ ہمارے طاقتور تِھیمز کے سسٹم کا فائدہ اٹھائیں: مقبول تِھیم کمپنَینٹ (مزید کیلئے، #theme براؤز کریں)" logos: title: "لَوگَو" fields: logo: label: "بنیادی لَوگَو" + description: "آپ کی سائٹ کے سب سے اوپر بائیں پر لوگو کی تصویر۔ 120 کی اونچائی اور 3:1 سے زائد اَیسپَیکٹ رَیشو والی وسیع مستطیل تصویر کا استعمال کریں۔" + logo_small: + label: "چکور لَوگَو" + description: "آپ کے لَوگَو کا ایک چکور ورژن۔ جو، براؤزر میں، نیچے سکرول کرنے پر آپ کی سائٹ کے سب سے اوپر بائیں جانب دکھایا جائے گا، اور سماجی پلیٹ فارموں پر شئیر کرتے وقت۔ مثالی طور پر 512x512 سے زائد۔" icons: title: "آئیکن" fields: + favicon: + label: "براؤزر آئیکن" + description: "آئیکن کی تصویر جو وَیب براؤزروں میں آپ کی ویب سائٹ کی نمائندگی کرنے کیلئے استعمال کی جاتی ہے اور جو چھوٹے سائزوں پر اچھی لگتی ہے۔ تجویز کردہ تصویر کی ایکسٹینشنز PNG یا JPG ہیں۔ ہم ڈیفالٹ کے طور پر چکور لَوگَو استعمال کریں گے۔" large_icon: label: "بڑا آئیکن" + description: "آئیکن کی تصویر جو جدید ڈِیوائیسِز پر آپ کی ویب سائٹ کی نمائندگی کرنے کیلئے استعمال کی جاتی ہے اور جو بڑے سائزوں پر اچھی لگتی ہے۔ مثالی طور پر 512 × 512 سے زائد۔ ہم ڈیفالٹ کے طور پر چکور لَوگَو استعمال کریں گے۔" homepage: description: "ہم آپ کے ہَوم پَیج پر تازہ ترین ٹاپک پیش کرنے کو تجویز کرتے ہیں، لیکن اگر آپ چاہیں تو ہَوم پَیج پر زُمرہ جات (ٹاپکس کے گروپوں) کو بھی دکھا سکتے ہیں۔" title: "ہَوم پَیج" @@ -3073,21 +3771,44 @@ ur: label: "زُمرہ جات اور تازہ ترین ٹاپکس" categories_and_top_topics: label: "زمرہ جات اور ٹاپ ٹاپکس" + categories_boxes: + label: "زُمرَہ جات بکسے" + categories_boxes_with_topics: + label: "زُمرَہ جات بکسے ٹاپکس کے ساتھ" emoji: title: "اِیمَوجی" description: "آپ اپنی کمیونٹی کیلئے کونسا اِیمَوجی سڑائل پسند کریں گے؟ آپ ایڈمن، مرضی کے مطابق بنائیں، اِیمَوجی کے ذریعہ بعد میں مزید اپنی مرضی کے اِیمَوجی شامل کرسکتے ہیں۔" invites: title: "سٹاف مدعو کریں" description: "آپ تقریباً کام مکمل کر چکے ہیں! چلیے لوگوں کو دعوت دیتے ہیں کہ آپ کے مباحثوں میں دلچسپ ٹاپک اور جوابات شامل کرنے میں آپ کی مدد کریں تاکہ آپ کی کمیونٹی کا آغاز ہو سکے۔" + disabled: "چونکہ مقامی لاگ اِن غیر فعال ہیں، کسی کو بھی دعوت نامہ بھیجنا ممکن نہیں ہے۔ براہ مہربانی اگلے مرحلے پر آگے بڑھیے۔" finished: title: "آپ کا ڈِسکورس تیار ہے!" + description: | +

اگر آپ کبھی بھی اِن ترتیبات کو تبدیل کرنا پسند کریں، تو کسی بھی وقت اِس وزرڈ کو دوبارہ چلائیں، یا اپنے ایڈمن سیکشن پر جائیں؛ اِسے سائٹ مینیو میں رِنچ آئیکن کے ساتھ تلاش کریں۔

+

لطف اندوز ہوں اور اپنی نئی کمیونٹی بنانے میں خدا آپ کو کامیاب کرے!

search_logs: graph_title: "سرچ شمار" joined: "شمولیت اختیار کی" discourse_push_notifications: popup: + mentioned: '%{username} نے آپ کا تذکرہ "%{topic}" میں کیا - %{site_title}' + group_mentioned: '%{username} نے آپ کا تذکرہ "%{topic}" میں کیا - %{site_title}' + quoted: '%{username} نے "%{topic}" میں آپ کا حوالا دیا - %{site_title}' + replied: '%{username} نے "%{topic}" میں آپ کو جواب دیا - %{site_title}' + posted: '%{username} نے "%{topic}" میں پوسٹ کیا - %{site_title}' + private_message: '%{username} نے "%{topic}" میں آپ کو زاتی پیغام بھیجا - %{site_title}' + linked: '%{username} نے "%{topic}" سے آپ کی پوسٹ کو لنک کیا - %{site_title}' + watching_first_post: '%{username} نے ایک نیا ٹاپک "%{topic}" بنایا - %{site_title}' confirm_title: "نوٹیفکیشن فعال - %{site_title}" confirm_body: "کامیابی! اطلاعات فعال ہوگئی ہیں۔" + custom: "%{username} کی طرف سے %{site_title} پر اطلاع" + staff_action_logs: + not_found: "نہیں ملا" + unknown: "نامعلوم" + user_merged: "%{username} اِس اکاؤنٹ میں ضم کر دیا گیا تھا" + user_delete_self: "%{url} سے خود حذف کر دیا گیا" + webhook_deactivation_reason: "آپ کا وَیب ھُوک٘ خود بخود غیر فعال کر دیا گیا ہے۔ ہمیں ایک سے زیادہ '%{status}' HTTP سٹیٹس ناکام جوابات موصول ہوئے۔" reviewables: priorities: low: "کم" @@ -3098,7 +3819,24 @@ ur: low: "کم" medium: "درمیانی" high: "زیادہ" + must_claim: "اشیاء پر عمل کرنے سے پہلے آپ کا اُن کو کََلیم کرنا ضروری ہے۔" + user_claimed: "یہ شے کسی اور صارف نے کََلیم کرلی ہے۔" + missing_version: "آپ کا ایک ورژن پیرامیٹر فراہم کرنا ضروری ہے" + conflict: "ایک اَپ ڈیٹ کا تنازع آپ کو ایسا کرنے سے روک رہا تھا۔" + reasons: + post_count: "ہر صارف کی پہلے چند پوسٹس کو سٹاف کی طرف سے منظور کیا جانا ضروری ہے۔ دیکھیے `approve_post_count`۔" + trust_level: "کم ٹرسٹ لَیول پر صارفین کے جوابات کو سٹاف کی طرف سے منظور کیا جانا ضروری ہے۔ دیکھیے `approve_unless_trust_level`۔" + new_topics_unless_trust_level: "کم ٹرسٹ لَیول پر صارفین کے ٹاپکس کو سٹاف کی طرف سے منظور کیا جانا ضروری ہے۔ دیکھیے `approve_new_topics_unless_trust_level`۔" + fast_typer: "نِے صارف نے اپنی پہلی اشاعت مشکوک حد تک جلدی لکھی، مشتبہ بَوٹ یا سپَیمر رویہ۔ دیکھیے `min_first_post_typing_time`۔" + auto_silence_regexp: "نیا صارف جس کی پہلی اشاعت `auto_silence_first_post_regex` ترتیب سے ملتی ہے۔" + watched_word: "اِس پوسٹ میں ایک نظر رکھا ہوا لفظ شامل ہے۔ اپنی نظر رکھے ہوئے الفاظ کی فہرست دیکھیے۔" + staged: "سٹَیجڈ صارفین کیلئے نئے ٹاپکس اور پوسٹس کو سٹاف کی طرف سے منظور کیا جانا ضروری ہے۔ دیکھیے `approve_unless_staged`۔" + category: "اِس زُمرہ میں پوسٹس کو سٹاف کی طرف سے دستی منظوری کی ضرورت ہوتی ہے۔ زُمرہ کی ترتیبات دیکھیے۔" + must_approve_users: "تمام نئے صارفین کو سٹاف کی طرف سے منظور کیا جانا ضروری ہے۔ دیکھیے `must_approve_users`۔" + invite_only: "تمام نئے صارفین کو مدعو کیا جانا چاہئیے۔ `invite_only` دیکھیے۔" actions: + agree: + title: "اتفاق کریں..." agree_and_keep: title: "پوسٹ برقرار رکھیں" description: "فلَیگ کے ساتھ اتفاق کریں اور پوسٹ کو ویسا ہی رہنے دیں۔" @@ -3109,15 +3847,35 @@ ur: title: "صارف خاموش کریں" description: "فلَیگ کے ساتھ اتفاق کریں اور صارف خاموش کریں۔" agree_and_restore: + title: "پوسٹ بحال کریں" description: "پوسٹ کو بحال کریں تاکہ تمام صارفین اِسے دیکھ سکیں۔" agree_and_hide: title: "پوسٹ چھپائیں" description: "اِس پوسٹ کو چھپائیں اور خود کار طریقے سے صارف کو اِس میں ترمیم کرنے کیلئے زور ڈالنے کیلئے پیغام بھیجیں۔" delete_spammer: title: "سپیم کرنے والے کو حذف کریں" + description: "صارف اور اُس کی تمام پوسٹس اور ٹاپکس کو حذف کریں۔" + confirm: "کیا آپ واقعی اِن صارف کی تمام پوسٹس اور ٹاپکس کو حذف اور اُن کے IP اور ایمَیل ایڈریسوں کو بلاک کردینا چاہتے ہیں۔" delete_single: title: "حذف کریں" + delete: + title: "حذف کریں..." + delete_and_ignore: + title: "پوسٹ حذف اور نظر انداز کریں" + description: "پوسٹ حذف کریں؛ اگر پہلی پوسٹ ہو، تو ٹاپک بھی ساتھ حذف کریں" + delete_and_ignore_replies: + title: "پوسٹ + جوابات حذف اور نظر انداز کریں" + description: "پوسٹ اور اِس کے تمام جوابات کو حذف کریں؛ اگر پہلی پوسٹ ہو، تو ٹاپک بھی ساتھ حذف کریں" + confirm: "کیا آپ واقعی اِس پوسٹ پر جوابات بھی حذف کرنا چاہتے ہیں؟" + delete_and_agree: + title: "پوسٹ حذف اور اتفاق کریں" + description: "پوسٹ حذف کریں؛ اگر پہلی پوسٹ ہو، تو ٹاپک بھی ساتھ حذف کریں" + delete_and_agree_replies: + title: "پوسٹ + جوابات حذف اور اتفاق کریں" + description: "پوسٹ اور اِس کے تمام جوابات کو حذف کریں؛ اگر پہلی پوسٹ ہو، تو ٹاپک بھی ساتھ حذف کریں" + confirm: "کیا آپ واقعی اِس پوسٹ پر جوابات بھی حذف کرنا چاہتے ہیں؟" disagree_and_restore: + title: "اختلاف کریں اور پوسٹ بحال کریں" description: "پوسٹ کو بحال کریں تاکہ تمام صارفین اِسے دیکھ سکیں۔" disagree: title: "اختلاف کریں" @@ -3125,10 +3883,23 @@ ur: title: "نظر انداز کریں" approve: title: "منظور" + approve_post: + title: "پوسٹ منظور کریں" + reject_post: + title: "پوسٹ مسترد کریں" + approve_user: + title: "صارف منظور کریں" reject_user: + title: "صارف حذف کریں..." delete: title: "صارف حذف کریں" + description: "اِس صارف کو فورم سے حذف کردیا جائے گا۔" + block: + title: "صارف کو حذف اور بلاک کریں" + description: "اِس صارف کو حذف کر دیا جائے گا، اور ہم اِس کے IP اور ایمَیل ایڈریسوں کو بلاک کردیں گے۔" reject: title: "مسترد کریں" delete_user: title: "صارف حذف کریں" + confirm: "کیا آپ واقعی اُس صارف کو حذف کرنا چاہتے ہیں؟ یہ اُن کی تمام پوسٹ حذف اور ایمَیل اورIP ایڈریس کو بلاک کردے گا۔" + reason: "بذریعہ نظر ثانی قطار کے حذف کر دیا گیا" diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index c1f30bc9d4..704d0b8747 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -7,7 +7,7 @@ zh_CN: dates: - short_date_no_year: "MMMDokeys: " + short_date_no_year: "|" short_date: "ll" long_date: "lll" datetime_formats: &datetime_formats @@ -40,6 +40,8 @@ zh_CN: themes: bad_color_scheme: "无法更新主题,无效的调色板" other_error: "更新主题时出错" + compile_error: + unrecognized_extension: "无法识别的文件扩展名:%{extension}" import_error: generic: 导入该主题时发生错误 about_json: "导入错误:about.json不存在或格式错误" @@ -171,6 +173,7 @@ zh_CN: confirm_email: "

快完成了!我们发送了一封激活邮件到你的邮件地址。请按照邮件中的步骤来激活你的账户。

如果你没有收到邮件,请检查你的垃圾邮件收件箱。

" bulk_invite: file_should_be_csv: "上传的文件应为 csv 格式。" + max_rows: "同一时间内最多能发送50000个邀请。尝试将文件分割成更小的部分。" error: "上传文件的时候出错了。请重试。" topic_invite: failed_to_invite: "如果没有以下任一组中的组成员身份,则无法邀请用户加入此主题:%{group_names}。" @@ -619,28 +622,30 @@ zh_CN: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" - edge: "Microsoft Edge" discoursehub: "DiscourseHub 应用" + edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "未知浏览器" device: android: "安卓设备" + chromebook: "Chromebook" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "移动设备" - mac: "Mac" linux: "GNU/Linux电脑" + mac: "Mac" + mobile: "移动设备" windows: "Windows电脑" unknown: "未知设备" os: android: "安卓" + chromeos: "ChromeOS" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "未知操作系统" change_email: @@ -654,6 +659,7 @@ zh_CN: description: "我们正向你的新地址发送确认邮件。" associated_accounts: revoke_failed: "无法使用%{provider_name}撤消你的帐户。" + connected: "(已连接)" activation: action: "点击这儿激活你的账户" already_done: "抱歉,此账户激活链接已经失效。可能你的账户已经被激活了?" @@ -862,7 +868,7 @@ zh_CN: title: "用户访问量" xaxis: "天" yaxis: "访问量" - description: "所有唯一身份用户访问次数。" + description: "用户浏览数" signups: title: "注册" xaxis: "天" @@ -1225,12 +1231,14 @@ zh_CN: notification_email: "这个表格:被用于发送所有重要系统邮件的邮箱地址。指定的域名必须正确设置 SPF、DKIM 和反向 PTR 记录以发送邮件。" email_custom_headers: "一个逗号分离的自定义邮件头部" email_subject: "标准邮件的自定义主题格式。参见https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" + enforce_second_factor: "强制用户启用双重因素验证。选择“所有”以强制所有用户。算账“管理员”仅强制管理员用户。" force_https: "强制使用 https。警告:开启前必须确认 HTTPS 已经配置并能够正常使用!你检查了 CDN、第三方登录和站外的 logo / 依赖是否支持 HTTPS 了吗?" same_site_cookies: "使用 Same-site cookies以消除所有跨站请求伪造(CSRF)的攻击面(Lax 或 Strict)。警告:Strict 仅在强制登录并使用 SSO 的站点有效。" summary_score_threshold: "将一个帖子包含在'摘要模式'中所需的最小分值" summary_posts_required: "在一个主题启用'摘要模式'的最小帖子数量" summary_likes_required: "在一个主题启用'摘要模式'的最小赞的数量" summary_percent_filter: "当用户点击摘要,显示前 % 几的帖子" + summary_max_results: "“概括主题”返回的最大帖子数量" enable_personal_messages: "允许信任等级1(可以另外选择发送私信的信任等级)的用户创建私信和回复私信。注意:管理人员不受限制。" enable_system_message_replies: "即使禁用了个人消息,也允许用户回复系统消息" enable_long_polling: "启用 Message bus 使通知功能可以使用长轮询(long polling)" @@ -1239,6 +1247,9 @@ zh_CN: polling_interval: "当不再长轮询时,已登录的客户端多久应该轮询一次,以毫秒为单位" anon_polling_interval: "匿名客户端轮询时间间隔(单位:毫秒)" background_polling_interval: "客户端轮询的间隔,以毫秒计(当窗口在后台时)" + hide_post_sensitivity: "一个被标记的帖子会被隐藏的可能性" + silence_new_user_sensitivity: "一个新用户基于垃圾标记被禁言的可能性" + auto_close_topic_sensitivity: "一个被标记的主题被自动关闭的可能性" cooldown_minutes_after_hiding_posts: "当一个帖子因为标记而隐藏之后,用户需要等待多少分钟才能编辑帖子" max_topics_in_first_day: "新用户在24小时内在发表第一帖后允许创建的主题数" max_replies_in_first_day: "新用户在24小时内在发表第一帖后允许创建的回复数" @@ -1249,6 +1260,7 @@ zh_CN: num_tl3_flags_to_silence_new_user: "如果新用户被 num_tl3_users_to_silence_new_user 个信任等级3的用户标记垃圾信息,隐藏他们的所以帖子并禁止发音。设置为 0 禁用。" num_tl3_users_to_silence_new_user: "如果新用户被不同信任等级3的用户标记 num_tl3_flags_to_silence_new_use 为标记垃圾信息,隐藏他们的所以帖子并禁止发音。设置为 0 禁用。" notify_mods_when_user_silenced: "如果用户被自动禁言了,发私信给所有版主。" + flag_sockpuppets: "如果一个新用户回复了其它用户以同一个IP创建的主题,他们所有的帖子都将被自动标记为有潜在的垃圾。" traditional_markdown_linebreaks: "在 Markdown 中使用传统换行符,即用两个尾随空格来换行" enable_markdown_typographer: "使用排版规则来提高文本的可读性:用引号替换直引号',用符号代替(c)(tm),用emdash等等" enable_markdown_linkify: "自动将看起来像链接的文本视为链接:www.example.com和https://example.com将自动链接" @@ -1257,9 +1269,10 @@ zh_CN: must_approve_users: "新用户在被允许访问站点前需要由管理人员批准。警告:在运行的站点中启用将解除所有非管理人员用户的访问权限!" pending_users_reminder_delay: "如果新用户等待批准时间超过此小时设置则通知版主。设置 -1 关闭通知。" maximum_session_age: "用户自访问后可维持登录 n 小时" - ga_universal_tracking_code: "Google Universal Analytics(analytics.js)跟踪代码,例如:UA-12345678-9; 请参阅https://google.com/analytics" + ga_universal_tracking_code: "Google Universal Analytics(analytics.js)跟踪代码ID,例如:UA-12345678-9; 请参阅https://google.com/analytics" ga_universal_domain_name: "Google Universal Analytics(analytics.js)域名,例如:mysite.com; 请参阅https://google.com/analytics" ga_universal_auto_link_domains: "启用Google Universal Analytics(analytics.js)跨域跟踪。 这些域的传出链接将添加客户端ID。请参阅Google的跨域跟踪指南。" + gtm_container_id: "Google Tag Manager容器id。例如:GTM-ABCDEF。
注意:由GTM加载的第三方脚本需要在“content security policy script src”内。" enable_escaped_fragments: "如果未检测到webcrawler,则回退到Google的Ajax-Crawling API。请参阅https://developers.google.com/webmasters/ajax-crawling/docs/learn-more" moderators_create_categories: "允许版主创建新的分类" cors_origins: "允许跨源请求(CORS)。每个源必须包括 http:// 或 https://。DISCOURSE_ENABLE_CORS 环境变量必须设置为 true 才能启用 CORS 政策。" @@ -1267,6 +1280,7 @@ zh_CN: blacklist_ip_blocks: "Discourse 不会访问抓取的保密 IP 封禁列表" whitelist_internal_hosts: "discourse 可以安全抓取以用于 Onebox 和其他目的的内部主机列表" allowed_iframes: "可以令 Discourse 在主题中安全地发布的 iframe src 域名前缀列表" + whitelisted_crawler_user_agents: "允许访问站点的Web爬虫用户代理。警告!这项设置会禁止所有不在这个列表中的爬虫。" blacklisted_crawler_user_agents: "用户代理字符串中唯一不区分大小写的单词,用于标识不应允许访问该站点的Web爬网程序。如果定义了白名单,则不适用。" slow_down_crawler_user_agents: "Web爬虫的用户代理,应使用Crawl-delay指令在robots.txt中进行速率限制" slow_down_crawler_rate: "如果指定了slow_down_crawler_user_agents,则此速率将适用于所有抓取工具(请求之间的延迟秒数)" @@ -1309,6 +1323,8 @@ zh_CN: login_required: "需要验证才能继续在该站阅读,不允许匿名访问。" min_username_length: "最小用户名长度。警告:如果任何现存的用户或群组名字长度比这短,站点将无法正常工作!" max_username_length: "最大用户名长度。警告:如果任何现存的用户或群组名字长度比这长,站点将无法正常工作!" + unicode_usernames: "允许用户名和群组名含有Unicode字符和数字。" + unicode_username_character_whitelist: "用户名中允许Unicode字符的正则表达式。ASCII字符和数字总是被允许的,不需要包含在表达式内。" reserved_usernames: "不可注册的用户名。通配符 * 能用来匹配字符 0 到多次。" min_password_length: "最小密码长度。" min_admin_password_length: "管理员最短密码长度" @@ -1340,6 +1356,8 @@ zh_CN: google_oauth2_prompt: "可选的以空格分隔的字符串值列表,指定授权服务器是否提示用户进行重新认证和同意。请参阅 https://developers.google.com/identity/protocols/OpenIDConnect#prompt为可能的值。" google_oauth2_hd: "登录将受限的可选Google Apps Hosted域。请参阅https://developers.google.com/identity/protocols/OpenIDConnect#hd- param了解更多详情。" enable_twitter_logins: "启用Twitter身份验证,需要twitter_consumer_key和twitter_consumer_secret。请参阅为话语配置Twitter登录(和丰富的嵌入)。" + twitter_consumer_key: "Twitter身份验证的消费者Key,在https://developer.twitter.com/apps上注册" + twitter_consumer_secret: "Twitter身份验证的客户secret,在https://developer.twitter.com/apps上注册" enable_instagram_logins: "启用 Instagram 验证,需要 instagram_consumer_key 和 instagram_consumer_secret" instagram_consumer_key: "Instagram 验证的 Consumer key" instagram_consumer_secret: "Instagram 验证的 Consumer secret" @@ -1356,11 +1374,15 @@ zh_CN: automatic_backups_enabled: "按照定义的备份频率运行自动备份计划" backup_frequency: "备份之间的天数。" s3_backup_bucket: "远端备份 bucket。警告:确认它使私有的 bucket。" + s3_endpoint: "可以修改端点以备份到S3兼容服务,如DigitalOcean Spaces或Minio。警告:留空以使用AWS S3。" s3_configure_tombstone_policy: "为逻辑删除上载启用自动删除策略。注意:如果禁用,在删除上载后不会回收空间。" s3_disable_cleanup: "当在本地删除备份时不删除 S3 上的备份。" + enable_s3_inventory: "使用Amazon S3存储验证上传并生成报告。重要:需要有效的S3证书(包括 access key id & secret access key)。" backup_time_of_day: "备份的 UTC 时间" backup_with_uploads: "在备份日程中包括上传。关闭此项仅备份数据库。" backup_location: "存储备份的位置。重要信息:S3需要在文件设置中输入有效的S3凭据。" + backup_gzip_compression_level_for_uploads: "用于压缩上传的Gzip压缩级别" + include_thumbnails_in_backups: "备份中包含略缩图。禁用会减小备份的提及,但是恢复备份后需要rabake所有帖子。" active_user_rate_limit_secs: "更新“上次访问”数据的间隔,单位为秒" verbose_localization: "在界面上显示详细的本地化提示" previous_visit_timeout_hours: "系统判断一次访问之后多少小时后为“上一次”访问" @@ -1399,10 +1421,14 @@ zh_CN: enable_s3_uploads: "上传至 Amazon S3 存储的地址。重要:需要有效的 S3 验证资料(包括 access key id & secret access key)。" s3_use_iam_profile: '使用AWS EC2实例配置文件授予对S3的访问权限。注意:启用此功能需要Discourse在配置得当的EC2实例中运行,并覆盖“s3访问密钥ID”和“s3 secret访问密钥”设置。' s3_upload_bucket: "上传文件保存于 Amazon S3 的 bucket 名字。警告:必须为小写,无句点,无下划线。" + s3_access_key_id: "将用于上传图片,附件和备份的Amazon S3 access key id。" + s3_secret_access_key: "将用于上传图片,附件和备份的Amazon S3 secret access key。" + s3_region: "将用于上传图片和备份的 Amazon S3 区域名。" s3_cdn_url: "用户 S3 资料的 CDN URL(例如:https://cdn.somewhere.com)。警告:在改变该设置后你必须重制所有老帖子。" avatar_sizes: "自动生成的头像大小列表。" external_system_avatars_enabled: "使用外部系统头像服务。" external_system_avatars_url: "外部系统头像服务的 URL 地址。可选参数是 {username} {first_letter} {color} {size}" + restrict_letter_avatar_colors: "用于字符型头像背景的十六进制色彩值列表" selectable_avatars_enabled: "强制用户从列表中选择头像。" selectable_avatars: "用户可以选择的头像列表。" allow_all_attachments_for_group_messages: "允许群组私信中包含任何邮件附件。" @@ -1480,6 +1506,8 @@ zh_CN: max_similar_results: "当用户撰写新主题时,显示多少类似主题给用户。比较是根据标题和内容进行的。" max_image_megapixels: "图片允许的最大像素。" title_prettify: "防止常见标题里的错别字和错误,包括全部大写,第一个字符小写,多个'!'和'?',结尾多余的'.'等等。" + title_remove_extraneous_space: "删除结尾标点前的空格。" + automatic_topic_heat_values: '基于站点活动自动更新“主题观点热度”和“主题帖子赞的主题热度”设置。' topic_views_heat_low: "多少次浏览后,该视图将稍稍高亮。" topic_views_heat_medium: "多少次浏览后,该视图将明显高亮。" topic_views_heat_high: "多少次浏览后,该视图将强烈高亮。" @@ -1513,6 +1541,9 @@ zh_CN: auto_silence_fast_typers_on_first_post: "自动禁言未达到 min_first_post_typing_time 的用户" auto_silence_fast_typers_max_trust_level: "自动禁言输入快速的用户的适用信任等级" auto_silence_first_post_regex: "检查用户首贴的大小写不相关的正则表达式。如匹配,用户将被禁言并送至审核队列。例子:raging|a[bc]a 将匹配 raging、aba 或者 aca,因此禁言该用户。只对首贴有效。" + reviewable_claiming: "是否需要审核可审核的内容才能对其进行操作?" + reviewable_default_topics: "默认以主题为分组显示可审核内容" + reviewable_default_visibility: "满足此优先级前不要显示可审核条目" reply_by_email_enabled: "启用通过邮件回复。" reply_by_email_address: "通过邮件回复的回复地址模板,例如:%%{reply_key}@reply.example.com 或 replies+%%{reply_key}@example.com" alternative_reply_by_email_addresses: "通过邮件回复的回复地址模板,例如:%%{reply_key}@reply.example.com|replies+%%{reply_key}@example.com" @@ -1542,6 +1573,7 @@ zh_CN: attachment_filename_blacklist: "基于文件名禁止附件的黑名单列表。" enable_forwarded_emails: "[测试] 允许用户通过抄送邮件发表主题。" always_show_trimmed_content: "总是显示进站邮件中被截断的部分。警告:可能显示某些邮件地址。" + private_email: "不要在邮件标题和主体中包含帖子或主题的内容。注意:会一并关闭摘要邮件。" manual_polling_enabled: "用 API 推送邮件回复。" pop3_polling_enabled: "轮询 POP3 收取邮件回复。" pop3_polling_ssl: "连接至 POP3 服务器时使用 SSL。(推荐)" @@ -1583,8 +1615,11 @@ zh_CN: email_link_color: "HTML 邮件中的链接颜色。输入色彩名字(“blue”)或十六进制值(“#0000FF”)。" detect_custom_avatars: "检测用户是否上传了自定义个人头像。" max_daily_gravatar_crawls: "一天内 Discourse 将自动检查 gravatar 自定义头像的次数" + public_user_custom_fields: "能被API获取的自定义字段列表。" + staff_user_custom_fields: "能被管理员使用API获取的自定义字段列表。" enable_user_directory: "提供可供浏览的用户目录" enable_group_directory: "提供可供浏览的群组目录" + enable_category_group_review: "允许群组审核指定分类的内容" group_in_subject: "在邮件标题中设置%%{optional_pm},取决于PM中第一个组的名称,请参阅:自定义标准邮件的标题格式" allow_anonymous_posting: "允许用户切换至匿名模式" anonymous_posting_min_trust_level: "启用匿名发帖所需的最小信任等级" @@ -1593,6 +1628,9 @@ zh_CN: show_inactive_accounts: "允许已登录用户查看不活跃账户。" hide_suspension_reasons: "不在用户页面公开显示封禁原因。" log_personal_messages_views: "管理员为其他用户/组记录个人消息查看数。" + ignored_users_count_message_threshold: "当一个用户被很多用户忽视时通知版主。" + ignored_users_message_gap_days: "当一个用户被很多用户忽视时再次通知版主要等待多久。" + clean_up_inactive_users_after_days: "非活跃用户(信任等级0且没有任何帖子)被删除前等待的天数。设为0则关闭清理。" user_website_domains_whitelist: "用户网站要属于这些域名中。用 | 分割。" allow_profile_backgrounds: "允许用户上传个人资料背景图片。" sequential_replies_threshold: "在被提醒回复了太多连续的回复前,用户在主题中可以连续回复的帖子的数量。" @@ -1603,10 +1641,15 @@ zh_CN: suppress_uncategorized_badge: "不要为主题列表中的未分类主题显示徽章。" header_dropdown_category_count: "标题下拉菜单可显示多少分类。" permalink_normalizations: "在匹配永久链接之前应用如下正则表达式,例如:/(topic.*)\\?.*/\\1 将去掉所有主题路径的参数字符串。格式为使用正则表达式+使用 \\1 等字符串来访问捕获内容" + global_notice: "为所有访客显示紧急的,不可取消的全局横幅通知,修改为空以隐藏(可使用HTML)。" disable_edit_notifications: "当 'download_remote_images_to_local' 启用时禁用系统编辑提醒。" + likes_notification_consolidation_threshold: "合并为单个提醒前接受的被赞提醒数量。设置为0以关闭。窗口可以通过`SiteSetting.likes_notification_consolidation_window_mins`配置。" + likes_notification_consolidation_window_mins: "当达到阈值时多个赞的提醒合并到单个的持续时间。阈值可通过`SiteSetting.likes_notification_consolidation_threshold`配置。" automatically_unpin_topics: "当用户到达底部时自动解除主题置顶。" read_time_word_count: "一分钟阅读的词的数量,用于估计阅读时间。" topic_page_title_includes_category: "主题页面标题包含分类名。" + native_app_install_banner_ios: "在iOS设备上为普通用户(信任等级1及以上)显示DiscourseHub app横幅。" + native_app_install_banner_android: "在Android设备上为普通用户(信任等级1及以上)显示DiscourseHub app横幅。" share_anonymized_statistics: "分享匿名化的使用数据。" auto_handle_queued_age: "此设定天数后自动处理待审核的记录。标记将被忽略。队列中的帖子及用户将被拒绝。设为 0 将禁用此功能。" svg_icon_subset: "添加你希望包含在资产中的其他FontAwesome 5图标。使用前缀'fa-'表示实心图标,'far-'表示常规图标,'fab-'表示品牌图标。" @@ -1622,6 +1665,7 @@ zh_CN: highlighted_languages: "包含语法突出显示规则。(警告:包含太多语言可能会影响性能)请参阅:https://highlightjs.org/static/demo用于演示" embed_truncate: "截断嵌入的帖子。" embed_support_markdown: "为嵌入式帖子支持Markdown格式。" + embed_whitelist_selector: "以逗号分隔允许嵌入的CSS元素列表" allowed_href_schemes: "除了 http 和 https 外允许的链接协议。" embed_post_limit: "嵌入帖子的最大数量。" embed_username_required: "创建主题需要用户名。" @@ -1646,8 +1690,13 @@ zh_CN: code_formatting_style: "编辑器中的代码格式化按钮设置的默认格式" max_allowed_message_recipients: "私信允许的最大收件人数。" watched_words_regular_expressions: "监视词是正则表达式。" + new_user_notice_tl: "查看新用户发帖通知所需的最低信任级别。" + returning_user_notice_tl: "查看回归用户发帖通知所需的最低信任级别。" + returning_users_days: "在用户被认为回归之前应该经过多少天。" default_email_digest_frequency: "用户收到摘要邮件的默认频率。" default_include_tl0_in_digests: "在摘要邮件中默认包含新用户帖子。用户可以自行在参数设置中更改这个设置。" + default_email_level: "设置当有人引用、回复、提及或邀请用户时的默认邮件提醒等级。" + default_email_messages_level: "设置当有人发消息给用户时的默认邮件提醒等级" default_email_mailing_list_mode: "默认为每一个新帖子发送一封邮件通知。" default_email_mailing_list_mode_frequency: "邮件列表模式下,用户收到邮件的默认频率。" disable_mailing_list_mode: "禁止用户使用邮件列表模式。" @@ -1658,6 +1707,7 @@ zh_CN: default_other_notification_level_when_replying: "用户回复主题时的全局默认通知等级" default_other_external_links_in_new_tab: "默认在新的标签页打开外部链接" default_other_enable_quoting: "默认在高亮选择文字时启用引用回复" + default_other_enable_defer: "默认启用延迟主题功能" default_other_dynamic_favicon: "默认在浏览器图标上显示新/更新的主题数量" default_other_like_notification_frequency: "默认通知用户赞的私信" default_topics_automatic_unpin: "默认时当用户到达底部时自动解除主题置顶。" @@ -1666,12 +1716,14 @@ zh_CN: default_categories_muted: "分类列表默认不显示。" default_categories_watching_first_post: "默认关注该分类中每个新主题的第一条贴子" default_text_size: "默认选择的文本大小" + default_title_count_mode: "页面标题计算器的默认模式" retain_web_hook_events_period_days: "保存 web hook 事件记录的天数。" retry_web_hook_events: "自动重试失败的Web挂钩事件4次。重试之间的时间间隔为1,5,25和125分钟。" allow_user_api_keys: "允许生成用户API密钥" allow_user_api_key_scopes: "用户API授权范围列表" max_api_keys_per_user: "每用户最多持有的用户 API 数" min_trust_level_for_user_api_key: "生成用户 API 密钥所需的信任等级" + allowed_user_api_auth_redirects: "允许用户API密钥认证跳转URL。可以使用通配符*匹配任何部分(例如:www.example.com/*)。" allowed_user_api_push_urls: "允许使用服务器推送至用户 API 的 URL" expire_user_api_keys_days: "用户API密钥自动过期前的天数(0表示从不)" tagging_enabled: "启用标签功能,允许为主题设置标签?" @@ -1687,6 +1739,7 @@ zh_CN: allow_staff_to_tag_pms: "允许管理人员对个人信息打标签" min_trust_level_to_tag_topics: "给主题加标签的最小信任等级" suppress_overlapping_tags_in_list: "如果标签匹配了主题标题中的词,不显示该标签" + remove_muted_tags_from_latest: "在最新主题列表中,不显示仅有静音标签的主题。" force_lowercase_tags: "强制所有新标签完全小写。" company_name: "公司名" governing_law: "适用法律" @@ -1694,6 +1747,8 @@ zh_CN: shared_drafts_category: "为主题草稿指定类别以启用共享草稿功能。此类别中的主题将从管理人员的主题列表中删除。" push_notifications_prompt: "显示用户同意提示。" push_notifications_icon: "通知角中显示的徽章图标。尺寸必须为96px × 96px。" + short_title: "短标题将用于用户主页、启动器或其他可能空间有限的地方。应该控制在12个字符内。" + dashboard_general_tab_activity_metrics: "选择在常规选项卡中显示为活动指标的报告。" errors: invalid_email: "电子邮箱地址无效。" invalid_username: "没有这个用户名的用户。" @@ -1778,6 +1833,8 @@ zh_CN: other: "%{count}个帖子被分拆到新私信:%{topic_link}" existing_topic_moderator_post: other: "%{count} 个帖子已被合并到了现有主题:%{topic_link}" + existing_message_moderator_post: + other: "%{count}个帖子已被合并到一个现有的消息:%{topic_link}" change_owner: post_revision_text: "所有权转移" topic_statuses: @@ -2178,6 +2235,16 @@ zh_CN: backup_succeeded: title: "备份成功" subject_template: "备份成功完成" + text_body_template: | + 备份成功。 + 访问[管理 > 备份](%{base_url}/admin/backups)下载你的新备份。 + + 以下为日志: + + ```text + %{logs} + + ``` backup_failed: title: "备份失败" subject_template: "备份失败" @@ -2218,6 +2285,14 @@ zh_CN: bulk_invite_failed: title: "批量邀请失败" subject_template: "批量邀请完成,并有一些错误" + text_body_template: | + 正在处理批量邀请用户文件中,%{sent}个邀请已经发送,其中有%{failed}个失败。 + + 以下为日志: + + ```text + %{logs} + ``` csv_export_succeeded: title: "成功导致CSV格式" subject_template: "[%{export_title}]数据导出完成" @@ -2411,6 +2486,13 @@ zh_CN: %{rejected_errors} 如果你认为这是一个错误,[联系管理人员](%{base_url}/about)。 + email_reject_reply_not_allowed: + title: "邮件被拒绝:不允许回复" + subject_template: "[%{email_prefix}]邮件问题 -- 不允许回复" + text_body_template: | + 我们很抱歉,但是你发送至%{destination}(标题 %{former_title})的邮件出问题了。 + + 你的账户没有权限回复这个主题。如果你认为这是一个错误,[联系管理人员](%{base_url}/about)。 email_error_notification: title: "邮件错误提醒" subject_template: "[%{email_prefix}] 电子邮件错误 -- POP 验证错误" @@ -2427,9 +2509,25 @@ zh_CN: 很抱歉,我们无法通过邮件与你联系。我们最后几封给你的邮件都因无法投递而全部退回。 你能否确保[你的邮件地址](%{base_url}/我的/设置/邮件)有效且有效?你可能还希望将我们的邮件地址添加到您的地址簿/联系人列表中,以提高可投递性。 + email_bounced: | + %{email}的邮件已退回。 + + ### 细节 + + ```text + %{raw} + ``` ignored_users_summary: title: "忽视的用户超过了阈值" subject_template: "一个用户被许多用户忽视" + text_body_template: | + 你好, + + 这是一个来自%{site_name}自动发出的消息以提醒你[用户资料](%{base_url}/u/%{username}/summary)已被%{ignores_threshold}个用户忽视。这表明你的社区有遇到了问题。 + + 你可能需要审核这名用户最近的帖子,也可能是[被忽视用户报告](%{base_url}/admin/reports/top_ignore_users)中的其他用户。 + + 如需其它指导,请参考[社区守则](%{base_url}/guidelines)。 too_many_spam_flags: title: "太多垃圾标记" subject_template: "新账户被暂时封禁" @@ -2498,6 +2596,10 @@ zh_CN: title: "待审核用户提醒" subject_template: other: "%{count} 个用户等待批准" + text_body_template: | + 有新注册的用户在访问论坛前需要被批准(或者拒绝)。 + + [请审核他们](%{base_url}/admin/users/list/pending)。 download_remote_images_disabled: title: "已被禁止下载远程图片" subject_template: "远程图片下载已禁用。" @@ -2505,6 +2607,10 @@ zh_CN: dashboard_problems: title: "管理面板问题" subject_template: "站点仪表盘的新建议" + text_body_template: | + 基于你当前的站点设置我们有一些建议。 + + [查看你的站点管理仪表盘](%{base_url}/admin)中的建议。 new_user_of_the_month: title: "你是这个月的最佳新用户!" subject_template: "你是这个月的最佳新用户!" @@ -2518,6 +2624,10 @@ zh_CN: title: "待处理帖子提醒" subject_template: other: "%{count} 个帖子等待审核" + text_body_template: | + 你好, + + 为了保持适度新用户的帖子,正在等待审核。[批准还是拒绝](%{base_url}/review?type=ReviewableQueuedPost)。 unsubscribe_link: | 要退订这些邮件,[点击这里](%{unsubscribe_url})。 unsubscribe_link_and_mail: | @@ -2528,6 +2638,7 @@ zh_CN: 要退订这些邮件,[点击这里](%{unsubscribe_url})。 subject_re: "回复:" subject_pm: "[私信]" + email_from: "%{user_name} 自 %{site_name}" user_notifications: previous_discussion: "之前的回复" reached_limit: @@ -3610,9 +3721,13 @@ zh_CN: title: "标签" staff_tag_disallowed: '“%{tag}”只可由管理人员使用。' staff_tag_remove_disallowed: '“%{tag}”只可由管理人员删除。' + minimum_required_tags: + other: "你必须选择至少%{count}个标签" upload_row_too_long: "CSV文件每行应该有一个标签。可选地,标签后面可以是逗号,然后是标签组名称。" forbidden: in_this_category: '"%{tag_name}不能用于此类别"' + restricted_to: + other: '“%{tag_name}”被限制在以下分类:%{category_names}' rss_by_tag: "主题标签 %{tag}" finish_installation: congratulations: "祝贺,你已经安装好了 Discourse!" @@ -3623,6 +3738,7 @@ zh_CN: no_emails: "遗憾的是,在设置过程中未定义管理员电子邮件,因此最终确定配置可能很困难。请在配置文件中添加开发人员电子邮件或从控制台创建管理员帐户。" confirm_email: title: "配置你的邮件" + message: "

我们发送了一封激活邮件到%{email}。请安装电子邮件中的说明激活你的帐号。

如果没有收到,检查你的收件箱的垃圾箱或者确认你的电子邮件地址是正确的

" resend_email: title: "重发激活邮件" message: "

我们重发了激活邮件至%{email}" @@ -3665,6 +3781,7 @@ zh_CN: one_paragraph: "请限制你的欢迎私信至一段话。" privacy: title: "访问" + description: "

你的社区对所有人开发,还在是限制会员、邀请或审批?你可以随意设置为私有的然后切换到公开的。

" fields: privacy: choices: @@ -3712,18 +3829,27 @@ zh_CN: placeholder: "旧金山,加利福尼亚" colors: title: "主题" + themes_further_reading: + title: "主题" + description: "想要定制你的Discourse?利用我们强大的主题系统:热门的主题组件(更多请浏览#主题)" logos: title: "标志" fields: logo: label: "主要Logo" + description: "你的站点左上角标志图像。使用高120且宽高比大于3:1的长方形图像。" + logo_small: + label: "正方形标志" + description: "你的标志的正方形版本。在浏览器下滑时显示在左上角,还显示在分享到社交网络时。推荐大于512x512。" icons: title: "图标" fields: favicon: label: "浏览器图标" + description: "用于在浏览器上代表您的网站的图标图像,在小尺寸的情况下表现出色。推荐图像格式为PNG或JPG。我们默认会使用正方形的标志。" large_icon: label: "大图标" + description: "用于在现代设备上代表您的网站的图标图像,在较大尺寸的情况下表现出色。推荐尺寸大于512 × 512。我们默认会使用正方形的标志。" homepage: description: "我们建议你在主页显示最后发点的主题,但你也可以选择在主页显示主题分类列表。" title: "主页" @@ -3768,6 +3894,7 @@ zh_CN: posted: '%{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} on %{site_title}的通知" @@ -3776,6 +3903,7 @@ zh_CN: unknown: "未知" user_merged: "%{username}已合并到此账户" user_delete_self: "已从%{url}自行删除" + webhook_deactivation_reason: "你的webhook已被自动停用。我们收到多个“%{status}”HTTP失败状态响应。" reviewables: priorities: low: "低" @@ -3786,7 +3914,21 @@ zh_CN: low: "低" medium: "中等" high: "高" + must_claim: "在你操作之前你必须认领此条目。" + user_claimed: "此条目已被其他用户认领。" missing_version: "您必须提供一个版本参数" + conflict: "有一个更新冲突阻止您这样做。" + reasons: + post_count: "所有用户的头几个帖子必须由管理员批准。参阅`approve_post_count`。" + trust_level: "低信任等级用户必须有回复被管理员批准。参阅`approve_unless_trust_level`。" + new_topics_unless_trust_level: "低信任等级用户必须有主题被管理员批准。参阅`approve_new_topics_unless_trust_level`。" + fast_typer: "新用户输入第一个帖子的速度快得可疑,怀疑是机器人或垃圾制造者所为。参阅`min_firse_post_typing_time`。" + auto_silence_regexp: "新用户的首个帖子符合`auto_silence_first_post_regex`设置。" + watched_word: "此帖子包含一个敏感词。参阅敏感词列表" + staged: "暂存用户的新主题和帖子必须由管理员批准。参阅`approve_unless_staged`。" + category: "此分类的帖子需要管理员批准。查看分类设置。" + must_approve_users: "所有新用户需由管理员批准。参阅`must_approve_users`。" + invite_only: "所有新用户应该是被邀请的。参阅`invite_only`。" actions: agree: title: "同意......" @@ -3808,6 +3950,7 @@ zh_CN: delete_spammer: title: "删除垃圾发布者" description: "清除这个用户与其所有主题帖子。" + confirm: "你确定要删除这个用户的所有帖子和主题并且屏蔽其IP和电子邮件地址吗?" delete_single: title: "删除" delete: @@ -3820,11 +3963,14 @@ zh_CN: description: "删除帖子和其所有的回复;如果是第一个帖子则删除整个主题。" confirm: "你确定要删除帖子的这个回复吗" delete_and_agree: + title: "删除帖子并同意" description: "删除此帖;如果这是这个主题内的第一篇帖子则删除主题" delete_and_agree_replies: + title: "删除帖子+回复并同意" description: "删除帖子和其所有的回复;如果是第一个帖子则删除整个主题。" confirm: "你确定要删除帖子的这个回复吗" disagree_and_restore: + title: "不同意并恢复帖子" description: "恢复帖子为所有用户可见。" disagree: title: "否决" @@ -3845,8 +3991,10 @@ zh_CN: description: "该用户将会被删除。" block: title: "删除并屏蔽用户" + description: "这名用户将会被删除,我们会屏蔽其IP和电子邮件地址。" reject: title: "否决" delete_user: title: "删除用户" confirm: "你确定要删除这个用户吗?这将删除用户的所有帖子并封禁这个邮箱和 IP 地址。" + reason: "通过审核队列删除的" diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index 131de4c53d..659d940e02 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -620,28 +620,28 @@ zh_TW: user_auth_tokens: browser: chrome: "Google Chrome" - safari: "Safari" - firefox: "Firefox" - opera: "Opera" - ie: "Internet Explorer" - edge: "Microsoft Edge" discoursehub: "DiscourseHub app" + edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" unknown: "\b不知名的瀏覽器" device: android: "安卓裝置" ipad: "iPad" iphone: "iPhone" ipod: "iPod" - mobile: "行動裝置" - mac: "Mac" linux: "GNU/Linux Computer" + mac: "Mac" + mobile: "行動裝置" windows: "Windows Computer" unknown: "不知名的裝置" os: android: "安卓" ios: "iOS" - macos: "macOS" linux: "Linux" + macos: "macOS" windows: "Microsoft Windows" unknown: "未知的運作系統" change_email: @@ -863,7 +863,6 @@ zh_TW: title: "使用者瀏覽量" xaxis: "天" yaxis: "瀏覽量" - description: "所有唯一使用者的瀏覽量" signups: title: "註冊" xaxis: "天" @@ -1261,7 +1260,6 @@ zh_TW: must_approve_users: "新使用者在被允許訪問站點前需要由管理人員批准。警告:在運行的站點中啟用將解除所有非管理人員使用者的訪問權限!" pending_users_reminder_delay: "如果新使用者等待批准時間超過此小時設置則通知版主。設置 -1 關閉通知。" maximum_session_age: "使用者自訪問後可維持登錄 n 小時" - ga_universal_tracking_code: "Google 通用 Analytics (分析) 追蹤代碼(analytics.js)追蹤代碼,例如:UA-12345678-9;參考 http://google.com/analytics" ga_universal_domain_name: "Google 通用 Analytics (分析) 域名(analytics.js)追蹤代碼,例如:mysite.com;参考 http://google.com/analytics" ga_universal_auto_link_domains: "啟用Google通用Analytics(analytics.js)的跨域追蹤。本網站導向外其他網站時將客戶端 ID添加並追蹤,參閱 Google 跨網域追蹤指南。" gtm_container_id: "Google Tag Manager Container(GTM) ID,例如:GFM-ABCDEF。注意:由GTM加載 Script 時可能需要在內容安全性政策(Content Security Policy Script Src)加入白名單。" @@ -3583,6 +3581,8 @@ zh_TW: placeholder: "舊金山,加洲(California)" colors: title: "主題" + themes_further_reading: + title: "佈景主題" logos: title: "標誌" fields: @@ -3675,7 +3675,6 @@ zh_TW: new_topics_unless_trust_level: "低信任等級的使用者必須擁有管理員批准,才可發佈討論話題,更多資訊請參閱`approve_new_topics_unless_trust_level`。" fast_typer: "新使用者輸入他們的第一個貼文,由於發送速度過快,疑似機器人或垃圾郵件發送者的行為,更多資訊請參閱`min_first_post_typing_time`。" auto_silence_regexp: "第一個貼文與`auto_silence_first_post_regex`設定匹配相符的新使用者" - watched_word: "貼文已包含一個觀看的單詞。" staged: "分階段使用者的新討論話題及貼文,必須得到管理員的批准,更多資訊請參閱`approve_unless_staged`。" category: "在這個分類的貼文需要管理員手動批准,更多資訊請參閱分類設定(category setting)。" must_approve_users: "所有新使用者必須得到管理員的批准,更多資訊請參閱`must_approve_users`。" diff --git a/config/site_settings.yml b/config/site_settings.yml index 4616e7ce6a..acca5be878 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -463,7 +463,13 @@ users: validator: "UnicodeUsernameWhitelistValidator" default: "" locale_default: + cs: "[ěščřžýáíéóůúďťňĚŠČŘŽÝÁÍÉÓŮÚĎŤŇ]" de: "[äöüßÄÖÜẞ]" + fi: "[åäöÅÄÖ]" + ja: '[\p{Han}\p{Katakana}\p{Hiragana}]' + ko: '\p{Hangul}' + zh_CN: '\p{Han}' + zh_TW: '\p{Han}' reserved_usernames: type: list list_type: compact @@ -1490,7 +1496,7 @@ developer: max: 99000 anon_polling_interval: client: true - default: 15000 + default: 30000 max: 99000 flush_timings_secs: client: true @@ -1726,6 +1732,8 @@ uncategorized: summary_percent_filter: 20 summary_max_results: 100 + automatic_topic_heat_values: true + # View heat thresholds topic_views_heat_low: client: true @@ -1735,7 +1743,7 @@ uncategorized: default: 2000 topic_views_heat_high: client: true - default: 5000 + default: 3500 # Post/Like heat thresholds topic_post_like_heat_low: @@ -1939,6 +1947,7 @@ user_preferences: default: 2 default_other_external_links_in_new_tab: false default_other_enable_quoting: true + default_other_enable_defer: false default_other_dynamic_favicon: false default_other_like_notification_frequency: enum: "LikeNotificationFrequencySiteSetting" @@ -2058,9 +2067,10 @@ tags: default: false client: true remove_muted_tags_from_latest: - default: false - mute_other_present_tags: - default: true + client: true + type: enum + default: always + enum: RemoveMutedTagsFromLatestSiteSetting force_lowercase_tags: default: true client: true diff --git a/db/migrate/20190513143015_add_theme_id_to_javascript_cache.rb b/db/migrate/20190513143015_add_theme_id_to_javascript_cache.rb new file mode 100644 index 0000000000..16710de3ee --- /dev/null +++ b/db/migrate/20190513143015_add_theme_id_to_javascript_cache.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +class AddThemeIdToJavascriptCache < ActiveRecord::Migration[5.2] + def up + # Delete any javascript caches with broken foreign keys + execute <<~SQL + DELETE FROM javascript_caches jc + WHERE NOT EXISTS ( + SELECT 1 + FROM theme_fields tf + WHERE tf.id = jc.theme_field_id + ); + SQL + make_changes + execute "ALTER TABLE javascript_caches ADD CONSTRAINT enforce_theme_or_theme_field CHECK ((theme_id IS NOT NULL AND theme_field_id IS NULL) OR (theme_id IS NULL AND theme_field_id IS NOT NULL))" + end + def down + execute "ALTER TABLE javascript_caches DROP CONSTRAINT enforce_theme_or_theme_field" + revert { make_changes } + end + + private + + def make_changes + add_reference :javascript_caches, :theme, foreign_key: { on_delete: :cascade } + add_foreign_key :javascript_caches, :theme_fields, on_delete: :cascade + + begin + Migration::SafeMigrate.disable! + change_column_null :javascript_caches, :theme_field_id, true + ensure + Migration::SafeMigrate.enable! + end + end +end diff --git a/db/migrate/20190531044744_add_enable_defer_to_user_options.rb b/db/migrate/20190531044744_add_enable_defer_to_user_options.rb new file mode 100644 index 0000000000..ecc4ac6720 --- /dev/null +++ b/db/migrate/20190531044744_add_enable_defer_to_user_options.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddEnableDeferToUserOptions < ActiveRecord::Migration[5.2] + def change + add_column :user_options, :enable_defer, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20190531101648_merge_remove_muted_tags_from_latest_site_settings.rb b/db/migrate/20190531101648_merge_remove_muted_tags_from_latest_site_settings.rb new file mode 100644 index 0000000000..0a7629a0d5 --- /dev/null +++ b/db/migrate/20190531101648_merge_remove_muted_tags_from_latest_site_settings.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class MergeRemoveMutedTagsFromLatestSiteSettings < ActiveRecord::Migration[5.2] + def up + execute "UPDATE site_settings SET value = 'always', data_type = 7 WHERE name = 'remove_muted_tags_from_latest' AND value = 't'" + execute "UPDATE site_settings SET value = 'never', data_type = 7 WHERE name = 'remove_muted_tags_from_latest' AND value = 'f'" + execute "DELETE FROM site_settings WHERE name = 'mute_other_present_tags'" + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20190603134013_change_theme_field_compiler_version.rb b/db/migrate/20190603134013_change_theme_field_compiler_version.rb new file mode 100644 index 0000000000..d82a567e39 --- /dev/null +++ b/db/migrate/20190603134013_change_theme_field_compiler_version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class ChangeThemeFieldCompilerVersion < ActiveRecord::Migration[5.2] + def change + change_column(:theme_fields, :compiler_version, :string, limit: 50) + end +end diff --git a/lib/auth/managed_authenticator.rb b/lib/auth/managed_authenticator.rb index b47e7e17d6..bdc7f0951c 100644 --- a/lib/auth/managed_authenticator.rb +++ b/lib/auth/managed_authenticator.rb @@ -4,7 +4,7 @@ class Auth::ManagedAuthenticator < Auth::Authenticator def description_for_user(user) info = UserAssociatedAccount.find_by(provider_name: name, user_id: user.id)&.info return "" if info.nil? - info["email"] || info["nickname"] || info["name"] || "" + info["email"] || info["nickname"] || info["name"] || I18n.t("associated_accounts.connected") end # These three methods are designed to be overriden by child classes diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb index b7f7cdd1e3..a5ea6a0316 100644 --- a/lib/backup_restore/restorer.rb +++ b/lib/backup_restore/restorer.rb @@ -448,19 +448,38 @@ module BackupRestore DbHelper.remap("uploads/#{previous_db_name}", "uploads/#{current_db_name}") end + if SiteSetting.Upload.enable_s3_uploads + migrate_to_s3 + remove_local_uploads(File.join(public_uploads_path, "uploads/#{current_db_name}")) + end + generate_optimized_images unless optimized_images_exist end end end + def migrate_to_s3 + log "Migrating uploads to S3..." + ENV["SKIP_FAILED"] = "1" + ENV["MIGRATE_TO_MULTISITE"] = "1" if Rails.configuration.multisite + Rake::Task["uploads:migrate_to_s3"].invoke + end + + def remove_local_uploads(directory) + log "Removing local uploads directory..." + FileUtils.rm_rf(directory) if Dir[directory].present? + rescue => ex + log "Something went wrong while removing the following uploads directory: #{directory}", ex + end + def generate_optimized_images log 'Optimizing site icons...' + DB.exec("TRUNCATE TABLE optimized_images") SiteIconManager.ensure_optimized! log 'Posts will be rebaked by a background job in sidekiq. You will see missing images until that has completed.' log 'You can expedite the process by manually running "rake posts:rebake_uncooked_posts"' - DB.exec("TRUNCATE TABLE optimized_images") DB.exec(<<~SQL) UPDATE posts SET baked_version = NULL diff --git a/lib/backup_restore/s3_backup_store.rb b/lib/backup_restore/s3_backup_store.rb index 4504314fa6..6425478c11 100644 --- a/lib/backup_restore/s3_backup_store.rb +++ b/lib/backup_restore/s3_backup_store.rb @@ -5,7 +5,6 @@ require_dependency "s3_helper" module BackupRestore class S3BackupStore < BackupStore - DOWNLOAD_URL_EXPIRES_AFTER_SECONDS ||= 15 UPLOAD_URL_EXPIRES_AFTER_SECONDS ||= 21_600 # 6 hours MULTISITE_PREFIX = "backups" @@ -74,11 +73,12 @@ module BackupRestore end def create_file_from_object(obj, include_download_source = false) + expires = S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS BackupFile.new( filename: File.basename(obj.key), size: obj.size, last_modified: obj.last_modified, - source: include_download_source ? presigned_url(obj, :get, DOWNLOAD_URL_EXPIRES_AFTER_SECONDS) : nil + source: include_download_source ? presigned_url(obj, :get, expires) : nil ) end diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 11828186bb..d4ec97ac64 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -97,12 +97,7 @@ class CookedPostProcessor return if previous.blank? - # remove click counters - previous_doc = Nokogiri::HTML::fragment(previous) - previous_doc.css("span.clicks").remove - - previous_text = previous_doc.text.strip - + previous_text = Nokogiri::HTML::fragment(previous).text.strip quoted_text = @doc.css("aside.quote:first-child blockquote").first&.text&.strip || "" return if previous_text.gsub(/(\s){2,}/, '\1') != quoted_text.gsub(/(\s){2,}/, '\1') @@ -631,10 +626,8 @@ class CookedPostProcessor end def pull_hotlinked_images(bypass_bump = false) - # is the job enabled? - return unless SiteSetting.download_remote_images_to_local? # have we enough disk space? - return if disable_if_low_on_disk_space + disable_if_low_on_disk_space # But still enqueue the job # don't download remote images for posts that are more than n days old return unless @post.created_at > (Date.today - SiteSetting.download_remote_images_max_days_old) # we only want to run the job whenever it's changed by a user @@ -647,6 +640,7 @@ class CookedPostProcessor end def disable_if_low_on_disk_space + return false if !SiteSetting.download_remote_images_to_local return false if available_disk_space >= SiteSetting.download_remote_images_threshold SiteSetting.download_remote_images_to_local = false diff --git a/lib/crawler_detection.rb b/lib/crawler_detection.rb index 040c339ddb..6cd1eba542 100644 --- a/lib/crawler_detection.rb +++ b/lib/crawler_detection.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module CrawlerDetection + WAYBACK_MACHINE_URL = "web.archive.org" def self.to_matcher(string, type: nil) escaped = string.split('|').map { |agent| Regexp.escape(agent) }.join('|') @@ -13,8 +14,8 @@ module CrawlerDetection Regexp.new(escaped, Regexp::IGNORECASE) end - def self.crawler?(user_agent) - return true if user_agent.nil? + def self.crawler?(user_agent, via_header = nil) + return true if user_agent.nil? || via_header&.include?(WAYBACK_MACHINE_URL) # this is done to avoid regenerating regexes @non_crawler_matchers ||= {} diff --git a/lib/discourse_tagging.rb b/lib/discourse_tagging.rb index ab276c5158..9f824a259c 100644 --- a/lib/discourse_tagging.rb +++ b/lib/discourse_tagging.rb @@ -56,7 +56,7 @@ module DiscourseTagging selected_tags: tag_names ).to_a - if tags.size < tag_names.size && (category.nil? || (category.tags.count == 0 && category.tag_groups.count == 0)) + if tags.size < tag_names.size && (category.nil? || category.allow_global_tags || (category.tags.count == 0 && category.tag_groups.count == 0)) tag_names.each do |name| unless Tag.where_name(name).exists? tags << Tag.create(name: name) diff --git a/lib/email/processor.rb b/lib/email/processor.rb index 6cb5d88272..5b7cfaa930 100644 --- a/lib/email/processor.rb +++ b/lib/email/processor.rb @@ -65,6 +65,7 @@ module Email when Email::Receiver::InvalidPostAction then :email_reject_invalid_post_action when Discourse::InvalidAccess then :email_reject_invalid_access when Email::Receiver::OldDestinationError then :email_reject_old_destination + when Email::Receiver::ReplyNotAllowedError then :email_reject_reply_not_allowed else :email_reject_unrecognized_error end diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index 6013afd3fb..24883d5b84 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -27,6 +27,7 @@ module Email class SilencedUserError < ProcessingError; end class BadDestinationAddress < ProcessingError; end class StrangersNotAllowedError < ProcessingError; end + class ReplyNotAllowedError < ProcessingError; end class InsufficientTrustLevelError < ProcessingError; end class ReplyUserNotMatchingError < ProcessingError; end class TopicNotFoundError < ProcessingError; end @@ -694,13 +695,13 @@ module Email raise BadDestinationAddress if user.blank? post_reply_key = destination[:obj] + post = Post.with_deleted.find(post_reply_key.post_id) + raise ReplyNotAllowedError if !Guardian.new(user).can_create_post?(post&.topic) if post_reply_key.user_id != user.id && !forwarded_reply_key?(post_reply_key, user) raise ReplyUserNotMatchingError, "post_reply_key.user_id => #{post_reply_key.user_id.inspect}, user.id => #{user.id.inspect}" end - post = Post.with_deleted.find(post_reply_key.post_id) - create_reply(user: user, raw: body, elided: elided, diff --git a/lib/emoji/db.json b/lib/emoji/db.json index 1080922d27..0de251b45d 100644 --- a/lib/emoji/db.json +++ b/lib/emoji/db.json @@ -6657,8 +6657,13 @@ "jp": [ "flag_jp" ], + "north_korea": [ + "flag_kp", + "kp" + ], "kr": [ - "flag_kr" + "flag_kr", + "south_korea" ], "ru": [ "flag_ru" diff --git a/lib/file_store/base_store.rb b/lib/file_store/base_store.rb index 15b85cb994..6e8c53442d 100644 --- a/lib/file_store/base_store.rb +++ b/lib/file_store/base_store.rb @@ -77,7 +77,8 @@ module FileStore if !file max_file_size_kb = [SiteSetting.max_image_size_kb, SiteSetting.max_attachment_size_kb].max.kilobytes - url = SiteSetting.scheme + ":" + upload.url + url = Discourse.store.cdn_url(upload.url) + url = SiteSetting.scheme + ":" + url if url =~ /^\/\// file = FileHelper.download( url, max_file_size: max_file_size_kb, diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb index a23ccb2af5..4389ba3d05 100644 --- a/lib/file_store/s3_store.rb +++ b/lib/file_store/s3_store.rb @@ -21,7 +21,7 @@ module FileStore def store_upload(file, upload, content_type = nil) path = get_path_for_upload(upload) - url, upload.etag = store_file(file, path, filename: upload.original_filename, content_type: content_type, cache_locally: true) + url, upload.etag = store_file(file, path, filename: upload.original_filename, content_type: content_type, cache_locally: true, private: upload.private?) url end @@ -41,9 +41,8 @@ module FileStore filename = opts[:filename].presence || File.basename(path) # cache file locally when needed cache_file(file, File.basename(path)) if opts[:cache_locally] - # stored uploaded are public by default options = { - acl: "public-read", + acl: opts[:private] ? "private" : "public-read", content_type: opts[:content_type].presence || MiniMime.lookup_by_filename(filename)&.content_type } # add a "content disposition" header for "attachments" @@ -105,6 +104,17 @@ module FileStore FileStore::LocalStore.new.path_for(upload) if url && url[/^\/[^\/]/] end + def url_for(upload) + if upload.private? + obj = @s3_helper.object(get_upload_key(upload)) + url = obj.presigned_url(:get, expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS) + else + url = upload.url + end + + url + end + def cdn_url(url) return url if SiteSetting.Upload.s3_cdn_url.blank? schema = url[/^(https?:)?\/\//, 1] @@ -138,8 +148,27 @@ module FileStore end end + def update_upload_ACL(upload) + private_uploads = SiteSetting.prevent_anons_from_downloading_files + key = get_upload_key(upload) + + begin + @s3_helper.object(key).acl.put(acl: private_uploads ? "private" : "public-read") + rescue Aws::S3::Errors::NoSuchKey + Rails.logger.warn("Could not update ACL on upload with key: '#{key}'. Upload is missing.") + end + end + private + def get_upload_key(upload) + if Rails.configuration.multisite + File.join(upload_path, "/", get_path_for_upload(upload)) + else + get_path_for_upload(upload) + end + end + def list_missing(model, prefix) connection = ActiveRecord::Base.connection.raw_connection connection.exec('CREATE TEMP TABLE verified_ids(val integer PRIMARY KEY)') diff --git a/lib/freedom_patches/translate_accelerator.rb b/lib/freedom_patches/translate_accelerator.rb index ea43dac187..7c08da4238 100644 --- a/lib/freedom_patches/translate_accelerator.rb +++ b/lib/freedom_patches/translate_accelerator.rb @@ -19,7 +19,7 @@ module I18n alias_method :reload_no_cache!, :reload! alias_method :locale_no_cache=, :locale= - LRU_CACHE_SIZE = 300 + LRU_CACHE_SIZE = 400 def init_accelerator! @overrides_enabled = true @@ -43,7 +43,8 @@ module I18n # load plural rules from plugins DiscoursePluginRegistry.locales.each do |plugin_locale, options| if options[:plural] - I18n.backend.store_translations(plugin_locale, + I18n.backend.store_translations( + plugin_locale, i18n: { plural: options[:plural] } ) end @@ -94,18 +95,43 @@ module I18n @overrides_enabled = true end - def translate_no_override(*args) - return translate_no_cache(*args) if args.length > 1 && args[1].present? + class MissingTranslation; end - options = args.last.is_a?(Hash) ? args.pop.dup : {} - key = args.shift - locale = options[:locale] || config.locale + def translate_no_override(key, options) + # note we skip cache for :format and :count + should_raise = false + locale = nil + + dup_options = nil + if options + dup_options = options.dup + should_raise = dup_options.delete(:raise) + locale = dup_options.delete(:locale) + end + + if dup_options.present? + return translate_no_cache(key, options) + end + + locale ||= config.locale @cache ||= LruRedux::ThreadSafeCache.new(LRU_CACHE_SIZE) k = "#{key}#{locale}#{config.backend.object_id}" - @cache.getset(k) do - translate_no_cache(key, options).freeze + val = @cache.getset(k) do + begin + translate_no_cache(key, locale: locale, raise: true).freeze + rescue I18n::MissingTranslationData + MissingTranslation + end + end + + if val != MissingTranslation + val + elsif should_raise + raise I18n::MissingTranslationData.new(locale, key) + else + -"translation missing: #{locale}.#{key}" end end @@ -153,11 +179,16 @@ module I18n if @overrides_enabled overrides = {} + # for now lets do all the expensive work for keys with count + # no choice really + has_override = !!options[:count] + I18n.fallbacks[locale].each do |l| - overrides[l] = overrides_by_locale(l) + override = overrides[l] = overrides_by_locale(l) + has_override ||= override.key?(key) end - if overrides.present? + if has_override && overrides.present? if options.present? options[:overrides] = overrides diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb index baf2b77848..a8344e9fa8 100644 --- a/lib/guardian/post_guardian.rb +++ b/lib/guardian/post_guardian.rb @@ -164,6 +164,10 @@ module PostGuardian false end + def can_delete_post_or_topic?(post) + post.is_first_post? ? post.topic && can_delete_topic?(post.topic) : can_delete_post?(post) + end + # Deleting Methods def can_delete_post?(post) return false if !can_see_post?(post) diff --git a/lib/i18n/backend/fallback_locale_list.rb b/lib/i18n/backend/fallback_locale_list.rb index d0abe59c06..e1b3d25ebb 100644 --- a/lib/i18n/backend/fallback_locale_list.rb +++ b/lib/i18n/backend/fallback_locale_list.rb @@ -12,7 +12,7 @@ module I18n site_locale = SiteSetting.default_locale.to_sym locale_list = - if locale == site_locale || site_locale == :en + if locale == site_locale || site_locale == :en || fallback_locale == :en [locale, fallback_locale, :en] else site_fallback_locale = LocaleSiteSetting.fallback_locale(site_locale) diff --git a/lib/middleware/anonymous_cache.rb b/lib/middleware/anonymous_cache.rb index 48a7648a70..51d0e19dec 100644 --- a/lib/middleware/anonymous_cache.rb +++ b/lib/middleware/anonymous_cache.rb @@ -62,7 +62,7 @@ module Middleware @is_crawler ||= begin user_agent = @env[USER_AGENT] - if CrawlerDetection.crawler?(user_agent) + if CrawlerDetection.crawler?(user_agent, @env["HTTP_VIA"]) :true else user_agent.downcase.include?("discourse") ? :true : :false diff --git a/lib/middleware/request_tracker.rb b/lib/middleware/request_tracker.rb index e8af3b5b15..470ab5f167 100644 --- a/lib/middleware/request_tracker.rb +++ b/lib/middleware/request_tracker.rb @@ -165,6 +165,20 @@ class Middleware::RequestTracker # possibly transferred? if info && (headers = result[1]) headers["X-Runtime"] = "%0.6f" % info[:total_duration] + + if GlobalSetting.enable_performance_http_headers + if redis = info[:redis] + headers["X-Redis-Calls"] = redis[:calls].to_s + headers["X-Redis-Time"] = "%0.6f" % redis[:duration] + end + if sql = info[:sql] + headers["X-Sql-Calls"] = sql[:calls].to_s + headers["X-Sql-Time"] = "%0.6f" % sql[:duration] + end + if queue = env['REQUEST_QUEUE_SECONDS'] + headers["X-Queue-Time"] = "%0.6f" % queue + end + end end if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN] && (headers = result[1]) diff --git a/lib/new_post_manager.rb b/lib/new_post_manager.rb index b03bec874b..ba5e468401 100644 --- a/lib/new_post_manager.rb +++ b/lib/new_post_manager.rb @@ -194,10 +194,17 @@ class NewPostManager # Enqueue this post def enqueue(reason = nil) result = NewPostResult.new(:enqueued) + payload = { + raw: @args[:raw], + tags: @args[:tags] + } + %w(typing_duration_msecs composer_open_duration_msecs reply_to_post_number).each do |a| + payload[a] = @args[a].to_i if @args[a] + end reviewable = ReviewableQueuedPost.new( created_by: @user, - payload: { raw: @args[:raw], tags: @args[:tags] }, + payload: payload, topic_id: @args[:topic_id], reviewable_by_moderator: true ) diff --git a/lib/plain_text_to_markdown.rb b/lib/plain_text_to_markdown.rb index ae32557c13..f365731130 100644 --- a/lib/plain_text_to_markdown.rb +++ b/lib/plain_text_to_markdown.rb @@ -3,8 +3,6 @@ class PlainTextToMarkdown SIGNATURE_SEPARATOR ||= "-- ".freeze - URL_REGEX ||= /((?:https?:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.])(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\([^\s()<>]+\)|[^`!()\[\]{};:'".,<>?«»“”‘’\s]))/i - def initialize(plaintext, opts = {}) @plaintext = plaintext @lines = [] @@ -150,14 +148,17 @@ class PlainTextToMarkdown converted_text end - def replace_duplicate_links(text) - text.to_enum(:scan, URL_REGEX) - .map { $& } - .group_by { |url| url } - .keep_if { |_, urls | urls.length > 1 } - .keys.each do |url| + URL_REGEX ||= URI.regexp(%w{http https ftp mailto}) + BEFORE ||= Regexp.escape(%Q|([<«"“'‘|) + AFTER ||= Regexp.escape(%Q|)]>»"”'’|) - text.gsub!(Regexp.new(%Q|#{url}(\s*[()\\[\\]<>«»'"“”‘’]?#{url}[()\\[\\]<>«»'"“”‘’]?)|, Regexp::IGNORECASE), url) + def replace_duplicate_links(text) + urls = Set.new + text.scan(URL_REGEX) { urls << $& } + + urls.each do |url| + escaped = Regexp.escape(url) + text.gsub!(Regexp.new(%Q|#{escaped}\s*[#{BEFORE}]?#{escaped}[#{AFTER}]?|, Regexp::IGNORECASE), url) end text @@ -174,19 +175,20 @@ class PlainTextToMarkdown end def escape_special_characters(text) - escaped_text = +"" + urls = Set.new + text.scan(URL_REGEX) { urls << $& } - text.split(URL_REGEX).each do |text_part| - if text_part =~ URL_REGEX - # no escaping withing URLs - escaped_text << text_part - else - # escape Markdown and HTML - text_part.gsub!(/[\\`*_{}\[\]()#+\-.!~]/) { |c| "\\#{c}" } - escaped_text << CGI.escapeHTML(text_part) - end - end + hoisted = urls + .map { |url| [SecureRandom.hex, url] } + .to_h - escaped_text + hoisted.each { |h, url| text.gsub!(url, h) } + + text.gsub!(/[\\`*_{}\[\]()#+\-.!~]/) { |c| "\\#{c}" } + text = CGI.escapeHTML(text) + + hoisted.each { |h, url| text.gsub!(h, url) } + + text end end diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index 7059844987..dea2adbb87 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -146,6 +146,12 @@ class Plugin::Instance end end + def register_editable_group_custom_field(field) + reloadable_patch do |plugin| + ::Group.register_plugin_editable_group_custom_field(field, plugin) # plugin.enabled? is checked at runtime + end + end + def custom_avatar_column(column) reloadable_patch do |plugin| AvatarLookup.lookup_columns << column diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 70f60c9039..9614a518e6 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -272,8 +272,18 @@ class PostCreator def self.set_reply_info(post) return unless post.reply_to_post_number.present? + # Before the locking here was added, replying to a post and liking a post + # at roughly the same time could cause a deadlock. + # + # Liking a post grabs an update lock on the post and then on the topic (to + # update like counts). + # + # Here, we lock the replied to post before getting the topic lock so that + # we can update the replied to post later without causing a deadlock. + reply_info = Post.where(topic_id: post.topic_id, post_number: post.reply_to_post_number) .select(:user_id, :post_type) + .lock .first if reply_info.present? diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb index 6189a81ca9..7264fe2428 100644 --- a/lib/post_revisor.rb +++ b/lib/post_revisor.rb @@ -444,7 +444,6 @@ class PostRevisor return if @skip_revision # don't create an empty revision if something failed return unless successfully_saved_post_and_topic - return if only_hidden_tags_changed? @version_changed ? create_revision : update_revision end @@ -463,7 +462,8 @@ class PostRevisor user_id: @post.last_editor_id, post_id: @post.id, number: @post.version, - modifications: modifications + modifications: modifications, + hidden: only_hidden_tags_changed? ) end @@ -527,7 +527,7 @@ class PostRevisor modifications = post_changes.merge(@topic_changes.diff) if modifications.keys.size == 1 && tags_diff = modifications["tags"] a, b = tags_diff[0] || [], tags_diff[1] || [] - changed_tags = (a + b) - (a & b) + changed_tags = ((a + b) - (a & b)).map(&:presence).compact if (changed_tags - DiscourseTagging.hidden_tag_names(nil)).empty? return true end diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index 123bb91647..44f535bb8b 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -149,6 +149,7 @@ module PrettyText buffer = +<<~JS __optInput = {}; __optInput.siteSettings = #{SiteSetting.client_settings_json}; + #{"__optInput.disableEmojis = true" if opts[:disable_emojis]} __paths = #{paths_json}; __optInput.getURL = __getURL; __optInput.getCurrentUser = __getCurrentUser; diff --git a/lib/reviewable/conversation.rb b/lib/reviewable/conversation.rb index 116c79cc89..54e6b54cff 100644 --- a/lib/reviewable/conversation.rb +++ b/lib/reviewable/conversation.rb @@ -20,7 +20,7 @@ class Reviewable < ActiveRecord::Base def initialize(meta_topic) @id = meta_topic.id @has_more = false - @permalink = meta_topic.relative_url + @permalink = "#{Discourse.base_url}#{meta_topic.relative_url}" @posts = [] meta_posts = meta_topic.ordered_posts.where(post_type: ::Post.types[:regular]).limit(2) diff --git a/lib/s3_helper.rb b/lib/s3_helper.rb index 6349fb9ba3..5c091f88b0 100644 --- a/lib/s3_helper.rb +++ b/lib/s3_helper.rb @@ -8,6 +8,8 @@ class S3Helper attr_reader :s3_bucket_name, :s3_bucket_folder_path + DOWNLOAD_URL_EXPIRES_AFTER_SECONDS ||= 15 + def initialize(s3_bucket_name, tombstone_prefix = '', options = {}) @s3_client = options.delete(:client) @s3_options = default_s3_options.merge(options) diff --git a/lib/s3_inventory.rb b/lib/s3_inventory.rb index 83b64b25a8..19901765a2 100644 --- a/lib/s3_inventory.rb +++ b/lib/s3_inventory.rb @@ -34,13 +34,16 @@ class S3Inventory download_inventory_files_to_tmp_directory decompress_inventory_files + multisite_prefix = "uploads/#{RailsMultisite::ConnectionManagement.current_db}/" ActiveRecord::Base.transaction do begin connection.exec("CREATE TEMP TABLE #{table_name}(key text UNIQUE, etag text, PRIMARY KEY(etag, key))") connection.copy_data("COPY #{table_name} FROM STDIN CSV") do files.each do |file| CSV.foreach(file[:filename][0...-3], headers: false) do |row| - connection.put_copy_data("#{row[CSV_KEY_INDEX]},#{row[CSV_ETAG_INDEX]}\n") + key = row[CSV_KEY_INDEX] + next if Rails.configuration.multisite && key.exclude?(multisite_prefix) + connection.put_copy_data("#{key},#{row[CSV_ETAG_INDEX]}\n") end end end diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index 59dc8b739c..8b9e49f1b1 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -293,15 +293,9 @@ module SiteSettingExtension changes, deletions = diff_hash(new_hash, current) - changes.each do |name, val| - current[name] = val - clear_uploads_cache(name) - end - - deletions.each do |name, _| - current[name] = defaults_view[name] - clear_uploads_cache(name) - end + changes.each { |name, val| current[name] = val } + deletions.each { |name, _| current[name] = defaults_view[name] } + uploads.clear clear_cache! end diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 33b2fd0498..b5e4d77d3b 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -42,11 +42,6 @@ task 'db:migrate' => ['environment', 'set_locale'] do |_, args| print "Optimizing site icons... " SiteIconManager.ensure_optimized! puts "Done" - puts - print "Recompiling theme fields... " - ThemeField.force_recompilation! - Theme.expire_site_cache! - puts "Done" end if MultisiteTestHelpers.load_multisite? diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index e54ed4260d..92c4b90a55 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -428,7 +428,7 @@ end def create_category_definitions log "Creating category definitions" - Category.where(topic_id: nil).each(&:create_category_definition) + Category.ensure_consistency! end def log(message) diff --git a/lib/tasks/posts.rake b/lib/tasks/posts.rake index 7f79fed3d4..7cba85ec8f 100644 --- a/lib/tasks/posts.rake +++ b/lib/tasks/posts.rake @@ -8,22 +8,30 @@ task 'posts:rebake' => :environment do end task 'posts:rebake_uncooked_posts' => :environment do - RailsMultisite::ConnectionManagement.each_connection do - puts "Rebaking uncooked posts on #{RailsMultisite::ConnectionManagement.current_db}" - uncooked = Post.where('baked_version <> ? or baked_version IS NULL', Post::BAKED_VERSION) + ENV['RAILS_DB'] ? rebake_uncooked_posts : rebake_uncooked_posts_all_sites +end - rebaked = 0 - total = uncooked.count - - uncooked.find_each do |post| - rebake_post(post) - print_status(rebaked += 1, total) - end - - puts "", "#{rebaked} posts done!", "" +def rebake_uncooked_posts_all_sites + RailsMultisite::ConnectionManagement.each_connection do |db| + rebake_uncooked_posts end end +def rebake_uncooked_posts + puts "Rebaking uncooked posts on #{RailsMultisite::ConnectionManagement.current_db}" + uncooked = Post.where('baked_version <> ? or baked_version IS NULL', Post::BAKED_VERSION) + + rebaked = 0 + total = uncooked.count + + uncooked.find_each do |post| + rebake_post(post) + print_status(rebaked += 1, total) + end + + puts "", "#{rebaked} posts done!", "" +end + desc 'Update each post with latest markdown and refresh oneboxes' task 'posts:refresh_oneboxes' => :environment do ENV['RAILS_DB'] ? rebake_posts(invalidate_oneboxes: true) : rebake_posts_all_sites(invalidate_oneboxes: true) @@ -506,38 +514,6 @@ task 'posts:missing_uploads', [:single_site] => :environment do |_, args| end end -def destroy_old_data_exports - topics = Topic.with_deleted.where(<<~SQL, 2.days.ago) - slug LIKE '%-export-complete' AND - archetype = 'private_message' AND - posts_count = 1 AND - created_at < ? AND - user_id = -1 - SQL - - puts "Found #{topics.count} old CSV data exports on #{RailsMultisite::ConnectionManagement.current_db}, destroying" - puts - topics.each do |t| - Topic.transaction do - t.posts.first.destroy! - t.destroy! - print "." - end - end - puts "done" -end - -desc 'destroys all user archive PMs (they may contain broken images)' -task 'posts:destroy_old_data_exports' => :environment do - if RailsMultisite::ConnectionManagement.current_db != "default" - destroy_old_data_exports - else - RailsMultisite::ConnectionManagement.each_connection do - destroy_old_data_exports - end - end -end - def recover_uploads_from_index(path) lookup = [] @@ -663,3 +639,93 @@ task 'posts:recover_uploads_from_index' => :environment do |_, args| end end end + +desc 'invalidate broken images' +task 'posts:invalidate_broken_images' => :environment do + puts "Invalidating broken images.." + + posts = Post.where("raw like '% :environment do |_, args| + dry_run = (ENV["DRY_RUN"].nil? ? true : ENV["DRY_RUN"] != "false") + verbose = ENV["VERBOSE"] + + scope = Post.joins(:post_uploads) + .distinct("posts.id") + .where("raw LIKE '%class=\"attachment%' OR raw LIKE '% e + putc "X" + failed_to_correct_post_ids << post.id + end + end + + puts + puts "#{fixed_count} out of #{affected_posts_count} affected posts corrected" + + if not_corrected_post_ids.present? + puts "Ids of posts that were not corrected: #{not_corrected_post_ids}" + end + + if failed_to_correct_post_ids.present? + puts "Ids of posts that encountered failures: #{failed_to_correct_post_ids}" + end + + if dry_run + puts "Task was ran in dry run mode. Set `DRY_RUN=false` to revise affected posts" + end +end diff --git a/lib/tasks/smoke_test.rake b/lib/tasks/smoke_test.rake index 57630f6c4b..70abebcc98 100644 --- a/lib/tasks/smoke_test.rake +++ b/lib/tasks/smoke_test.rake @@ -2,12 +2,18 @@ desc "run chrome headless smoke tests on current build" task "smoke:test" do - unless system("command -v google-chrome >/dev/null;") + if RbConfig::CONFIG['host_os'][/darwin|mac os/] + google_chrome_cli = "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" + else + google_chrome_cli = "google-chrome" + end + + unless system("command -v \"#{google_chrome_cli}\" >/dev/null") abort "Chrome is not installed. Download from https://www.google.com/chrome/browser/desktop/index.html" end - if Gem::Version.new(`$(command -v google-chrome) --version`.match(/[\d\.]+/)[0]) < Gem::Version.new("59") - abort "Chrome 59 or higher is required to run smoke tests in headless mode." + if Gem::Version.new(`\"#{google_chrome_cli}\" --version`.match(/[\d\.]+/)[0]) < Gem::Version.new("59") + abort "Chrome 59 or higher is required to run tests in headless mode." end system("yarn install --dev") diff --git a/lib/tasks/topics.rake b/lib/tasks/topics.rake index 43b6a21b3d..f9c9264d03 100644 --- a/lib/tasks/topics.rake +++ b/lib/tasks/topics.rake @@ -66,3 +66,23 @@ task "topics:apply_autoclose" => :environment do puts "", "Done" end + +task "topics:watch_all_replied_topics" => :environment do + puts "Setting all topics to Watching on which a user has posted at least once..." + topics = Topic.where("archetype != ?", Archetype.private_message) + total = topics.count + count = 0 + + topics.find_each do |t| + t.topic_users.where(posted: true).find_each do |tp| + tp.update!(notification_level: TopicUser.notification_levels[:watching], notifications_reason_id: TopicUser.notification_reasons[:created_post]) + end + print_status(count += 1, total) + end + + puts "", "Done" +end + +def print_status(current, max) + print "\r%9d / %d (%5.1f%%)" % [current, max, ((current.to_f / max.to_f) * 100).round(1)] +end diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index 59998e93c6..3c4dc29ef5 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -316,17 +316,25 @@ def migrate_to_s3 exit 1 end - unless ENV["DISCOURSE_S3_SECRET_ACCESS_KEY"].present? && + unless ENV["DISCOURSE_S3_BUCKET"].present? && ENV["DISCOURSE_S3_REGION"].present? && - ENV["DISCOURSE_S3_ACCESS_KEY_ID"].present? && - ENV["DISCOURSE_S3_SECRET_ACCESS_KEY"].present? + ( + ( + ENV["DISCOURSE_S3_ACCESS_KEY_ID"].present? && + ENV["DISCOURSE_S3_SECRET_ACCESS_KEY"].present? + ) || + ENV["DISCOURSE_S3_USE_IAM_PROFILE"].present? + ) puts <<~TEXT Please provide the following environment variables - DISCOURSE_S3_BUCKET - DISCOURSE_S3_REGION + and either - DISCOURSE_S3_ACCESS_KEY_ID - DISCOURSE_S3_SECRET_ACCESS_KEY + or + - DISCOURSE_S3_USE_IAM_PROFILE TEXT exit 2 end @@ -337,6 +345,7 @@ def migrate_to_s3 end bucket_has_folder_path = true if ENV["DISCOURSE_S3_BUCKET"].include? "/" + public_directory = Rails.root.join("public").to_s opts = { region: ENV["DISCOURSE_S3_REGION"], @@ -361,7 +370,7 @@ def migrate_to_s3 print " - Listing local files" local_files = [] - IO.popen("cd public && find uploads/#{db}/original -type f").each do |file| + IO.popen("cd #{public_directory} && find uploads/#{db}/original -type f").each do |file| local_files << file.chomp putc "." if local_files.size % 1000 == 0 end @@ -390,7 +399,7 @@ def migrate_to_s3 skip_etag_verify = ENV["SKIP_ETAG_VERIFY"].present? local_files.each do |file| - path = File.join("public", file) + path = File.join(public_directory, file) name = File.basename(path) etag = Digest::MD5.file(path).hexdigest unless skip_etag_verify key = file[file.index(prefix)..-1] @@ -416,6 +425,10 @@ def migrate_to_s3 options[:content_disposition] = %Q{attachment; filename="#{upload.original_filename}"} end + + if upload&.private? + options[:acl] = "private" + end end etag ||= Digest::MD5.file(path).hexdigest @@ -526,7 +539,7 @@ def migrate_to_s3 .where("u.id IS NOT NULL AND u.url LIKE '//%' AND optimized_images.url NOT LIKE '//%'") .delete_all - puts "Flagging all posts containing oneboxes for rebake..." + puts "Flagging all posts containing lightboxes for rebake..." count = Post.where("cooked LIKE '%class=\"lightbox\"%'").update_all(baked_version: nil) puts "#{count} posts were flagged for a rebake" diff --git a/lib/theme_javascript_compiler.rb b/lib/theme_javascript_compiler.rb index dc1dbabd7d..22a47c351d 100644 --- a/lib/theme_javascript_compiler.rb +++ b/lib/theme_javascript_compiler.rb @@ -202,22 +202,36 @@ class ThemeJavascriptCompiler @content << script + "\n" end + def append_module(script, name) + script.prepend theme_variables + template = Tilt::ES6ModuleTranspilerTemplate.new {} + @content << template.module_transpile(script, "", name) + rescue MiniRacer::RuntimeError => ex + raise CompileError.new ex.message + end + def append_js_error(message) @content << "console.error('Theme Transpilation Error:', #{message.inspect});" end private + def theme_variables + <<~JS + const __theme_name__ = "#{@theme_name.gsub('"', "\\\"")}"; + const settings = Discourse.__container__ + .lookup("service:theme-settings") + .getObjectForTheme(#{@theme_id}); + const themePrefix = (key) => `theme_translations.#{@theme_id}.${key}`; + JS + end + def transpile(es6_source, version) template = Tilt::ES6ModuleTranspilerTemplate.new {} wrapped = <<~PLUGIN_API_JS (function() { if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') { - const __theme_name__ = "#{@theme_name.gsub('"', "\\\"")}"; - const settings = Discourse.__container__ - .lookup("service:theme-settings") - .getObjectForTheme(#{@theme_id}); - const themePrefix = (key) => `theme_translations.#{@theme_id}.${key}`; + #{theme_variables} Discourse._registerPluginCode('#{version}', api => { try { #{es6_source} diff --git a/lib/theme_settings_manager.rb b/lib/theme_settings_manager.rb index ec19e42c9d..cc69dee04e 100644 --- a/lib/theme_settings_manager.rb +++ b/lib/theme_settings_manager.rb @@ -34,7 +34,7 @@ class ThemeSettingsManager end def description - @opts[:description] + @opts[:description] # Old method of specifying description. Is now overridden by locale file end def value=(new_value) diff --git a/lib/theme_store/git_importer.rb b/lib/theme_store/git_importer.rb index 6d8cee3c87..673e9a4594 100644 --- a/lib/theme_store/git_importer.rb +++ b/lib/theme_store/git_importer.rb @@ -11,6 +11,7 @@ class ThemeStore::GitImporter def initialize(url, private_key: nil, branch: nil) @url = url if @url.start_with?("https://github.com") && !@url.end_with?(".git") + @url = @url.gsub(/\/$/, '') @url += ".git" end @temp_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_#{SecureRandom.hex}" diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 34b4c260e4..1b90719454 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -446,10 +446,14 @@ class TopicQuery avatar_lookup = AvatarLookup.new(user_ids) primary_group_lookup = PrimaryGroupLookup.new(user_ids) + # memoize for loop so we don't keep looking these up + translations = TopicPostersSummary.translations + topics.each do |t| t.posters = t.posters_summary( avatar_lookup: avatar_lookup, - primary_group_lookup: primary_group_lookup + primary_group_lookup: primary_group_lookup, + translations: translations ) end end @@ -875,7 +879,7 @@ class TopicQuery list end def remove_muted_tags(list, user, opts = nil) - if user.nil? || !SiteSetting.tagging_enabled || !SiteSetting.remove_muted_tags_from_latest + if user.nil? || !SiteSetting.tagging_enabled || SiteSetting.remove_muted_tags_from_latest == 'never' return list end @@ -896,7 +900,7 @@ class TopicQuery return list end - if SiteSetting.mute_other_present_tags + if SiteSetting.remove_muted_tags_from_latest == 'always' list = list.where(" NOT EXISTS( SELECT 1 diff --git a/lib/topic_view.rb b/lib/topic_view.rb index e9b125050d..6f12cda012 100644 --- a/lib/topic_view.rb +++ b/lib/topic_view.rb @@ -419,19 +419,34 @@ class TopicView end def reviewable_counts - if @reviewable_counts.blank? + if @reviewable_counts.nil? + + post_ids = @posts.map(&:id) + + sql = <<~SQL + SELECT target_id, + MAX(r.id) reviewable_id, + COUNT(*) total, + SUM(CASE WHEN s.status = :pending THEN 1 ELSE 0 END) pending + FROM reviewables r + JOIN reviewable_scores s ON reviewable_id = r.id + WHERE r.target_id IN (:post_ids) AND + r.target_type = 'Post' + GROUP BY target_id + SQL - # Create a hash with counts by post so we can quickly look up whether there is reviewable content. @reviewable_counts = {} - Reviewable. - where(target_type: 'Post', target_id: filtered_post_ids). - includes(:reviewable_scores).each do |r| - for_post = (@reviewable_counts[r.target_id] ||= { total: 0, pending: 0, reviewable_id: r.id }) - r.reviewable_scores.each do |s| - for_post[:total] += 1 - for_post[:pending] += 1 if s.pending? - end + DB.query( + sql, + pending: ReviewableScore.statuses[:pending], + post_ids: post_ids + ).each do |row| + @reviewable_counts[row.target_id] = { + total: row.total, + pending: row.pending, + reviewable_id: row.reviewable_id + } end end diff --git a/lib/version.rb b/lib/version.rb index 474c303337..5f1b76a930 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -9,7 +9,7 @@ module Discourse MAJOR = 2 MINOR = 3 TINY = 0 - PRE = 'beta10' + PRE = 'beta11' STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/lib/wizard/builder.rb b/lib/wizard/builder.rb index aff1be8375..8731f4b233 100644 --- a/lib/wizard/builder.rb +++ b/lib/wizard/builder.rb @@ -198,7 +198,10 @@ class Wizard end end - @wizard.append_step('themes-further-reading') + @wizard.append_step('themes-further-reading') do |step| + step.banner = "further-reading.png" + step.add_field(id: 'popular-themes', type: 'component') + end @wizard.append_step('logos') do |step| step.add_field(id: 'logo', type: 'image', value: SiteSetting.site_logo_url) diff --git a/plugins/discourse-details/config/locales/client.et.yml b/plugins/discourse-details/config/locales/client.et.yml index 2dddb33876..703d095b84 100644 --- a/plugins/discourse-details/config/locales/client.et.yml +++ b/plugins/discourse-details/config/locales/client.et.yml @@ -7,5 +7,7 @@ et: js: + details: + title: Peida üksikasjad composer: details_title: Kokkuvõte diff --git a/plugins/discourse-details/config/locales/server.de.yml b/plugins/discourse-details/config/locales/server.de.yml index ac14f8ca1a..97cbe0077e 100644 --- a/plugins/discourse-details/config/locales/server.de.yml +++ b/plugins/discourse-details/config/locales/server.de.yml @@ -8,3 +8,5 @@ de: site_settings: details_enabled: "Aktiviert die Details-Funktion. Wenn du dies änderst, musst du alle Beiträge neu „backen“ mit: \"rake posts:rebake\"." + details: + excerpt_details: "(für mehr Details klicken)" diff --git a/plugins/discourse-details/config/locales/server.es.yml b/plugins/discourse-details/config/locales/server.es.yml index 1c9cea0537..947f73d995 100644 --- a/plugins/discourse-details/config/locales/server.es.yml +++ b/plugins/discourse-details/config/locales/server.es.yml @@ -8,3 +8,5 @@ es: site_settings: details_enabled: "Habilitar la característica de detalles. Si cambias esto, debes rehacer todos los mensajes con: \"rake posts:rebake\"." + details: + excerpt_details: "(haz clic para más detalles)" diff --git a/plugins/discourse-details/config/locales/server.fr.yml b/plugins/discourse-details/config/locales/server.fr.yml index b334f1483a..d2646b6d12 100644 --- a/plugins/discourse-details/config/locales/server.fr.yml +++ b/plugins/discourse-details/config/locales/server.fr.yml @@ -8,3 +8,5 @@ fr: site_settings: details_enabled: "Activer la fonction details. Si vous modifiez ceci, vous devez exécuter la commande \"rake posts:rebake\"." + details: + excerpt_details: "(cliquer pour plus de détails)" diff --git a/plugins/discourse-details/config/locales/server.it.yml b/plugins/discourse-details/config/locales/server.it.yml index 741bf29751..2367bc7c6f 100644 --- a/plugins/discourse-details/config/locales/server.it.yml +++ b/plugins/discourse-details/config/locales/server.it.yml @@ -8,3 +8,5 @@ it: site_settings: details_enabled: "Attiva la funzione dettagli. Se cambi questa impostazione, devi rielaborare tutti i Messaggi con \"rake posts:rebake\"" + details: + excerpt_details: "(clicca per ulteriori dettagli)" diff --git a/plugins/discourse-details/config/locales/server.ru.yml b/plugins/discourse-details/config/locales/server.ru.yml index 11d23be517..64d8acbdc4 100644 --- a/plugins/discourse-details/config/locales/server.ru.yml +++ b/plugins/discourse-details/config/locales/server.ru.yml @@ -8,3 +8,5 @@ ru: site_settings: details_enabled: "Включить дополнительные возможности. Если вы измените данный пункт, то потребуется обновить все посты с помощью команды: \"rake posts:rebake\"" + details: + excerpt_details: "(нажмите для более подробной информации)" diff --git a/plugins/discourse-details/config/locales/server.ur.yml b/plugins/discourse-details/config/locales/server.ur.yml index 16bc7f3cb2..1f894bdc3b 100644 --- a/plugins/discourse-details/config/locales/server.ur.yml +++ b/plugins/discourse-details/config/locales/server.ur.yml @@ -8,3 +8,5 @@ ur: site_settings: details_enabled: "تفصیلات کی خصوصیت فعال کریں۔ اگر آپ اس کو تبدیل کرتے ہیں تو، آپ کو تمام پوسٹس کو دوبارہ رِیبَیک کرنا ہوگا: \"rake posts:rebake\"" + details: + excerpt_details: "(مزید تفصیلات کیلئے کلک کریں)" diff --git a/plugins/discourse-details/config/locales/server.zh_CN.yml b/plugins/discourse-details/config/locales/server.zh_CN.yml index 4b9261fddb..2037c581ef 100644 --- a/plugins/discourse-details/config/locales/server.zh_CN.yml +++ b/plugins/discourse-details/config/locales/server.zh_CN.yml @@ -8,3 +8,5 @@ zh_CN: site_settings: details_enabled: "启用细节功能。如果你改变了这个选项,必须通过此命令重新调制全部帖子:\"rake posts:rebake\"。" + details: + excerpt_details: "(点击显示详情)" diff --git a/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6 b/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6 index 08922bd862..6119ef1a4f 100644 --- a/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6 +++ b/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6 @@ -1,5 +1,6 @@ import { acceptance } from "helpers/qunit-helpers"; import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer"; +import selectKit from "helpers/select-kit-helper"; acceptance("Details Button", { loggedIn: true, diff --git a/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.no-module.es6 b/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.no-module.es6 index 9b5cd8cc55..adac85455c 100644 --- a/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.no-module.es6 +++ b/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.no-module.es6 @@ -201,14 +201,9 @@ } function _createDateTimeRange(dateTime, timezone) { - const startRange = dateTime.tz(timezone).format("LLL"); - const separator = "→"; - const endRange = dateTime - .add(24, "hours") - .tz(timezone) - .format("LLL"); + const dt = moment(dateTime).tz(timezone); - return `${startRange} ${separator} ${endRange}`; + return [dt.format("LLL"), "→", dt.add(24, "hours").format("LLL")].join(" "); } function _generatePreviews(dateTime, displayedTimezone, options) { @@ -222,7 +217,9 @@ timezone: watchingUserTimezone, current: true, dateTime: options.time - ? dateTime.tz(watchingUserTimezone).format("LLL") + ? moment(dateTime) + .tz(watchingUserTimezone) + .format("LLL") : _createDateTimeRange(dateTime, watchingUserTimezone) }); @@ -249,7 +246,9 @@ previewedTimezones.push({ timezone, dateTime: options.time - ? dateTime.tz(timezone).format("LLL") + ? moment(dateTime) + .tz(timezone) + .format("LLL") : _createDateTimeRange(dateTime, timezone) }); }); @@ -258,7 +257,9 @@ previewedTimezones.push({ timezone: "Etc/UTC", dateTime: options.time - ? dateTime.tz("Etc/UTC").format("LLL") + ? moment(dateTime) + .tz("Etc/UTC") + .format("LLL") : _createDateTimeRange(dateTime, "Etc/UTC") }); } diff --git a/plugins/discourse-narrative-bot/config/locales/server.de.yml b/plugins/discourse-narrative-bot/config/locales/server.de.yml index 0dee66e276..c5abe34ae1 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.de.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.de.yml @@ -144,6 +144,14 @@ de: cert_title: "In Anerkennung deines erfolgreichen Abschlusses eines Tutorials für neue Benutzer" hello: title: "Sei gegrüßt!" + message: |- + Vielen Dank für's Mitmachen bei %{title}, und willkommen! + + - Ich bin nur ein Roboter, aber [unser freundliches Team](%{base_uri}/about) sind auch hier und können helfen, wenn du einen Menschen erreichen musst. + + - Aus Sicherheitsgründen limitieren wir, was neue Benutzer machen können. Du bekommst [neue Fähigkeiten](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) und [Abzeichen](%{base_uri}/badges)), wenn wir dich besser kennenlernen. + + - Wir glauben an [zivilisiertes Community-Verhalten](%{base_uri}/guidelines) zu jeder Zeit. onebox: instructions: |- Als Nächstes: Kannst du einen dieser Links mit mir teilen? Antworte mit **einem Link auf einer eigenen Zeile**, und er wird automatisch in eine hübsche, kurze Inhaltsangabe erweitert. diff --git a/plugins/discourse-narrative-bot/config/locales/server.es.yml b/plugins/discourse-narrative-bot/config/locales/server.es.yml index 712e4f3715..793946e7a8 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.es.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.es.yml @@ -141,9 +141,18 @@ es: Mientras tanto, me mantendré fuera de tu camino. new_user_narrative: reset_trigger: "tutorial" + title: "Certificado de finalización del tutorial de nuevo usuario" cert_title: "En reconocimiento a la finalización exitosa del tutorial de nuevo usuario" hello: title: "¡Saludos!" + message: |- + Gracias por unirte %{title}, ¡y bienvenido! + + - Solo soy un robot, pero [nuestro amigable staff](%{base_uri}/about) también está aquí para ayudarte si necesitas comunicarte con una persona. + + - Por razones de seguridad, limitamos temporalmente lo que los nuevos usuarios pueden hacer. Obtendrás [nuevas habilidades](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) (e [insignias](%{base_uri}/badges)) a medida que te conozcamos. + + - Creemos en [el comportamiento de una comunidad civilizada](%{base_uri}/guidelines) en todo momento. onebox: instructions: |- A continuación, ¿podrías compartir uno de estos enlaces conmigo? Responde con **un enlace en una línea por sí mismo**, y se expandirá automáticamente para incluir un resumen ingenioso. diff --git a/plugins/discourse-narrative-bot/config/locales/server.et.yml b/plugins/discourse-narrative-bot/config/locales/server.et.yml index cf76b5d705..90fe6ed5f4 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.et.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.et.yml @@ -7,21 +7,65 @@ et: badges: + certified: + name: Sertifitseeritud licensed: name: Litsenseeritud discourse_narrative_bot: + dice: + results: |- + > :game_die: %{results} quote: trigger: "tsitaat" '1': author: "Albert Einstein" + '2': + author: "Mahatma Gandhi" '3': author: "Dr. Seuss" + '4': + author: "Charles-Guillaume Étienne" + '6': + author: "Forrest Gumpi ema" + '7': + author: "Neil Armstrong" + '8': + author: "Eleanor Roosevelt" + '9': + author: "Bruce Lee" + '10': + author: "Napoleon Hill" magic_8_ball: answers: '1': "See on kindel" '3': "Kahtlemata" '4': "Jah, kindlasti" '5': "Võid sellele loota" + '6': "Ahaa, just" '7': "Tõenäoliselt" + '8': "Väljavaade on hea" '9': "Jah" '12': "Küsi hiljem uuesti" + '14': "Ei suuda praegu ennustada" + '15': "Keskendu ning küsi uuesti" + '16': "Sellele ära looda" + '17': "Minu vastus on ei" + '18': "Minu allikad ütlevad ei" + '20': "Väga kahtlane" + result: |- + > :crystal_ball: %{result} + track_selector: + reset_trigger: 'alusta' + skip_trigger: 'jäta vahele' + help_trigger: 'näita abininfot' + new_user_narrative: + reset_trigger: "õpetus" + hello: + title: "Tere!" + quoting: + reply: |- + Suureprane töö. Valisid minu lemmiktsitaadi! :left_speech_bubble: + certificate: + alt: 'Saavutuse tunnistus' + advanced_user_narrative: + reset_trigger: 'õpetus edasijõutnudele' diff --git a/plugins/discourse-narrative-bot/config/locales/server.fr.yml b/plugins/discourse-narrative-bot/config/locales/server.fr.yml index 82d0ede632..c77d6da17b 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.fr.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.fr.yml @@ -18,7 +18,7 @@ fr: name: Certifié description: "A terminé le tutoriel des nouveaux utilisateurs" long_description: | - Ce badge est décerné quand vous avez terminé avec succès le tutoriel interactif des nouveaux utilisateurs. Vous avez pris l'initiative d'apprendre les outils de base de la discussion et vous êtes maintenant certifié ! + Ce badge est décerné quand vous avez terminé avec succès le tutoriel interactif des nouveaux utilisateurs. Vous avez pris l'initiative d'apprendre les outils de base de la discussion et vous êtes maintenant certifié ! licensed: name: Certifié description: "A terminé le tutoriel des utilisateurs avertis" @@ -113,6 +113,11 @@ fr: random_mention: reply: |- Bonjour ! Pour voir ce que je peux faire, dites `@%{discobot_username} `. + tracks: |- + Pour l'instant j'ai les compétences suivantes : + + `@%{discobot_username} %{reset_trigger} %{default_track}` + > Démarre une des tutoriaux intéractifs suivants : %{tracks}. bot_actions: |- `@%{discobot_username} %{dice_trigger} 2d6` > :game_die: 3, 6 @@ -124,7 +129,7 @@ fr: > :crystal_ball: Vous pouvez compter dessus do_not_understand: first_response: |- - Hey, merci pour la réponse ! + Hey, merci pour la réponse ! Malheureusement, je ne suis qu'un robot mal programmé, et je ne comprends pas celle-ci. :frowning: track_response: Vous pouvez essayer de nouveau, ou si vous préférez passer cette étape, dites `%{skip_trigger}`. Sinon, pour redémarrer, dites `%{reset_trigger}`. @@ -135,9 +140,19 @@ fr: Pendant ce temps là, je serais silencieux. new_user_narrative: + reset_trigger: "tutoriel" + title: "Certificat d'achèvement du tutoriel pour les nouveaux utilisateurs" cert_title: "En reconnaissance de l'accomplissement avec succès du tutoriel nouvel utilisateur" hello: - title: "Bienvenue !" + title: "Bienvenue !" + message: |- + Merci de vous être inscrit à %{title}, et bienvenue ! + + - Je ne suis qu'un robot, mais [notre équipe](%{base_uri}/about) est également disponible si vous avez besoin de contacter quelqu'un. + + - Pour des raisons de sécurité, nous avons temporairement limité ce que les nouveaux utilisateurs peuvent faire. Vous obtiendrez de [nouvelles possibilités](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) (et des [badges](%{base_uri}/badges)) au fur et à mesure que nous vous connaitrons. + + - Nous croyons en [un comportement communautaire civilisé](%{base_uri}/guidelines) de tout temps. onebox: instructions: |- Maintenant, pouvez-vous partager l'un de ces liens avec moi ? Répondez avec **un lien sur une ligne vide**, et il sera automatiquement transformé pour inclure un astucieux résumé. @@ -148,7 +163,7 @@ fr: reply: |- - Super ! Ça fonctionne pour la plupart des liens. N'oubliez pas, ils doivent être sur une ligne _vide_, sans rien devant, ni derrière. + Super ! Ça fonctionne pour la plupart des liens. N'oubliez pas, ils doivent être sur une ligne _vide_, sans rien devant, ni derrière. not_found: |- Désolé, je n'ai pas pu trouvé le lien dans votre réponse ! :cry: @@ -203,14 +218,14 @@ fr: reply: |- Bon travail, vous avez choisi ma citation préférée ! :left_speech_bubble: not_found: |- - Hmm je n'ai pas l'impression que vous m'ayez cité dans votre réponse ? + Hmm je n'ai pas l'impression que vous m'ayez cité dans votre réponse ? - Sectionner n'importe quel texte dans mon message fera apparaitre le bouton **Citer**. Et appuyer sur "Répondre" avec du texte sélectionné fonctionnera également ! Pouvez-vous essayer de nouveau ? + Sectionner n'importe quel texte dans mon message fera apparaitre le bouton **Citer**. Et appuyer sur "Répondre" avec du texte sélectionné fonctionnera également ! Pouvez-vous essayer de nouveau ? bookmark: instructions: |- - Si vous souhaitez en savoir plus, sélectionnez en dessous et **ajouter ce message direct aux signets**. Si vous le faites, il pourrait y avoir un :gift: dans le futur ! + Si vous souhaitez en savoir plus, sélectionnez en dessous et **ajouter ce message direct aux signets**. Si vous le faites, il pourrait y avoir un :gift: dans le futur ! reply: |- - Excellent ! Maintenant, vous pouvez facilement retrouver notre conversation privée à tout moment, à partir de [l'onglet marque page sur votre profil](%{profile_page_url}/activity/bookmarks). Sélectionnez juste votre photo de profil dans le coin en haut à droite ↗ + Excellent ! Maintenant, vous pouvez facilement retrouver notre conversation privée à tout moment, à partir de [l'onglet marque page sur votre profil](%{profile_page_url}/activity/bookmarks). Sélectionnez juste votre photo de profil dans le coin en haut à droite ↗ not_found: |- Uh oh, je ne vois aucun signet sur ce sujet ! Avez-vous trouvé le bouton signet sous chaque message ? Utilisez le bouton Afficher plus pour dévoiler d'autres actions si nécessaire. emoji: @@ -225,7 +240,7 @@ fr: reply: |- C'est :sparkles: _emojitastic !_ :sparkles: not_found: |- - Oops, Je ne vois pas aucun emoji dans votre réponse ? Oh non ! :sob: + Oops, Je ne vois pas aucun emoji dans votre réponse ? Oh non ! :sob: Essayez de taper : pour faire apparaitre la selection d'emoji, puis appuyer sur les premières lettres de ce que vous voulez, comme par exemple `:bird:` @@ -238,18 +253,18 @@ fr: Pouvez vous mentionner **`@%{discobot_username}`** dans votre réponse? reply: |- - _Quelqu'un a dit mon nom ?_ :raised_hand: Je pense que vous l'avez fait ! :wave: Bien, je suis là ! Merci de m'avoir mentionné. :ok_hand: + _Quelqu'un a dit mon nom ?_ :raised_hand: Je pense que vous l'avez fait ! :wave: Bien, je suis là ! Merci de m'avoir mentionné. :ok_hand: not_found: |- - Je ne vois pas mon nom ici ni nulle part. :frowning: Pouvez-vous essayer de me mentionner comme ceci `@%{discobot_username}` une nouvelle fois ? + Je ne vois pas mon nom ici ni nulle part. :frowning: Pouvez-vous essayer de me mentionner comme ceci `@%{discobot_username}` une nouvelle fois ? - (Et oui, mon nom d'utilisateur est écrit «_disco_», comme dans la folie des années 1970. J'adore [la vie nocturne!](https://www.youtube.com/watch?v=B_wGI3_sGf8) :dancer:) + (Et oui, mon nom d'utilisateur est écrit «_disco_», comme dans la folie des années 1970. J'adore [la vie nocturne !](https://www.youtube.com/watch?v=B_wGI3_sGf8) :dancer:) flag: instructions: |- Nous aimons nos discussions amicales, et nous avons besoin de votre aide pour [garder les lieux civilisés](%{guidelines_url}). Si vous voyez un problème, signalez-le s'il vous plait de façon privée à l'auteur, ou à notre [équipe](%{about_url}). > :imp: J'ai écris quelque chose de très méchant ici - Je devine que vous savez ce que vous avez à faire. **Signalez ce message** comme inapproprié ! + Je devine que vous savez ce que vous avez à faire. **Signalez ce message** comme inapproprié ! reply: |- [Notre équipe](%{base_uri}/groups/staff) sera notifié de façon privée de votre signalement. Si assez de membres de la communauté signalent un message, il sera automatiquement masqué par précaution. (Puisque je n'ai pas réellement écrit un article méchant :ange: j'ai anticipé et j'ai enlevé le signalement pour le moment.) @@ -288,6 +303,7 @@ fr: certificate: alt: 'Certificat d''accomplissement' advanced_user_narrative: + reset_trigger: 'tutoriel avancé' cert_title: "En reconnaissance de l'accomplissement avec succès du tutoriel utilisateur avancé " title: ':arrow_up: Fonctionnalités utilisateur avancé' start_message: |- @@ -297,26 +313,26 @@ fr: edit: bot_created_post_raw: "@%{discobot_username} est, de loin, le robot le plus cool que je connaisse :wink:" instructions: |- - Tout le monde fait des erreurs. Mais ne vous inquiétez pas, vous pouvez toujours éditer vos messages pour les réparer. + Tout le monde fait des erreurs. Mais ne vous inquiétez pas, vous pouvez toujours éditer vos messages pour les réparer ! - Pouvez-vous commencer par **éditer** le message que je viens de créer en votre nom? + Pouvez-vous commencer par **éditer** le message que je viens de créer en votre nom ? not_found: |- Il semble que vous n'avez pas encore éditer le [message](%{url}) que j'ai créé pour vous. Pouvez-vous essayer de nouveau ? Utilisez l’icône pour afficher l'éditeur. reply: |- - Bon boulot ! + Bon boulot ! Remarquez que les éditions après 5 minutes seront considérés comme des modifications publiques, et qu'un petit crayon apparaitra dans le coin haut droit avec le nombre de modification. delete: instructions: |- Si vous souhaitez retirer un message que vous avez écrit, vous pouvez le supprimer. - Allons-y et **supprimez** un de vos messages précédents en utilisant la **suppression**. Attention à ne pas supprimer le premier message ! + Allons-y et **supprimez** un de vos messages précédents en utilisant la **suppression**. Attention à ne pas supprimer le premier message ! not_found: |- Je ne vois toujours aucun message supprimé ! Rappelez-vous, afficher plus révélera supprimer. reply: |- - Whoa ! :boom: + Whoa ! :boom: Pour préserver la continuité des discussions, les suppressions ne sont pas immédiates, et le message ne sera supprimé qu'après quelques temps. recover: @@ -327,6 +343,10 @@ fr: Pouvez-vous me faire une faveur et le **restaurer** ? not_found: |- Je ne vois toujours aucun message supprimé ! Rappelez-vous, afficher plus révélera supprimer. + reply: |- + Pfiou, c'était moins une ! Merci d'avoir réparé ça ! :wink: + + Notez que vous n'avez que %{deletion_after} heure(s) pour restaurer un message supprimé. category_hashtag: instructions: |- Saviez-vous que vous pouviez faire référence aux catégories et étiquettes dans vos messages ? Par exemple avez-vous vu la catégorie %{category}? @@ -339,7 +359,7 @@ fr: Je peux créer un lien de catégorie via # ``` reply: |- - Excellent ! Retenez que ceci fonctionne pour les catégories _et_ les étiquettes, si les étiquettes sont activées. + Excellent ! Retenez que ceci fonctionne pour les catégories _et_ les étiquettes, si les étiquettes sont activées. change_topic_notification_level: instructions: |- Chaque sujet a un niveau de notification. Il démarre comme 'normal', c'est à dire que vous ne serez notifié que quand quelqu'un vous parle directement. @@ -348,16 +368,16 @@ fr: Essayons de changer le niveau de notification pour ce sujet. En bas de ce sujet, vous trouverez un bouton qui vous indique que vous **Surveillez** ce sujet. Pouvez-vous changer le niveau de notification pour **Suivi**'? not_found: |- - Il semble que vous êtes encore en train de Surveillez :eyes: ce sujet ! Si vous avez des difficultés à le trouver, le bouton du niveau de notification est situé sous le sujet. + Il semble que vous êtes encore en train de Surveillez :eyes: ce sujet ! Si vous avez des difficultés à le trouver, le bouton du niveau de notification est situé sous le sujet. reply: |- - Super boulot ! J’espère que vous n'avez pas mis ce sujet en silencieux car il m'arrive parfois d'être un peu bavard :grin: + Super boulot ! J’espère que vous n'avez pas mis ce sujet en silencieux car il m'arrive parfois d'être un peu bavard :grin: Remarquez que quand vous répondez à un sujet, ou lisez un sujet pendant plusieurs minutes, le niveau de notification est automatiquement mis sur 'Suivi'. Vous pouvez modifier cela dans [vos préférences utilisateur](%{base_uri}/my/preferences). poll: instructions: |- Savez-vous que vous pouvez ajouter un sondage sur n'importe quel message ? Essayez en utilisant la roue dans l'éditeur pour **créer un sondage**. not_found: |- - Whooops ! Il n'y a pas de sondage dans votre réponse. + Whooops ! Il n'y a pas de sondage dans votre réponse. Utilisez la roue dans l'éditeur, ou copiez et coller ce sondage dans votre prochaine réponse : @@ -368,7 +388,7 @@ fr: [/poll] ``` reply: |- - Hey, bon sondage ! Comment s'est passé votre formation ? + Hey, bon sondage ! Comment s'est passé votre formation ? [poll] :+1: @@ -376,19 +396,19 @@ fr: [/poll] details: instructions: |- - Parfois vous souhaitez **masquer des détails** dans vos réponses : + Parfois vous souhaitez **masquer des détails** dans vos réponses : - Quand vous discutez d'un film ou d'une série TV et que vous pourriez spoiler la communauté. - Quand votre message a besoin de beaucoup de détails optionnels qui peuvent rendre le sujet illisible - [details=Sélectionnez ceci pour voir comment cela fonctionne !] + [details=Sélectionnez ceci pour voir comment cela fonctionne !] 1. Sélectionnez la roue dans l'éditeur. 2. Sélectionnez "Cacher le texte". 3. Éditez le résumé et ajoutez votre contenu. [/details] - Pouvez-vous utiliser la roue dans l'éditeur pour ajouter une section de détails dans votre prochaine réponse ? + Pouvez-vous utiliser la roue dans l'éditeur pour ajouter une section de détails dans votre prochaine réponse ? not_found: |- Des soucis pour créer le widget de détail ? Essayez d'inclure le texte suivant dans votre prochaine réponse : diff --git a/plugins/discourse-narrative-bot/config/locales/server.he.yml b/plugins/discourse-narrative-bot/config/locales/server.he.yml index abfb7d6b61..19160dc56c 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.he.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.he.yml @@ -145,6 +145,14 @@ he: cert_title: "כהוקרה על סיום מוצלח של מדריך המשתמשים החדשים" hello: title: "ברכות!" + message: |- + תודה לך על הצטרפותך אל %{title} וברוך בואך! + + - אני בסך הכול רובוט, אך [הסגל הידידותי שלנו](%{base_uri}/about) כאן כדי לסייע אם נדרשת התערבות מצד גורם אנושי. + + - מטעמי אבטחה, אנו מגבילים את חופש הפעולה למשתמשים חדשים באופן זמני. עם הזמן יתאפשר לך לצבור [מיומנויות חדשות](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) (לרבות [עיטורים](%{base_uri}/badges)) בהתאם לרמת ההיכרות אתך. + + - אנו דוגלים ב[התנהלות קהילתית מתורבתת](%{base_uri}/guidelines) בכל עת. onebox: instructions: |- נתקדם, אפשר לבקש ממך לשתף אתי את אחד הקישורים האלה? יש להגיב **עם קישור בשורה נפרדת** והוא יתרחב אוטומטית ויציג פירוט מגניב. @@ -246,25 +254,99 @@ he: אפשר לבקש ממך לאזכר את **`@%{discobot_username}`** בתגובה שלך? reply: |- _קראתם בשמי!?_ :raised_hand: אני מאמין שכן! :wave: ובכן, אני כאן! תודה שאיזכרתם אותי. :ok_hand: + not_found: |- + איני מצליח למצוא את השם שלי בשום מקום :frowning: אפשר לבקש ממך לאזכר אותי בשם `‎@%{discobot_username}‎` שוב? + + (וכן, שם המשתמש שלי מתחיל ב_disco_ (דיסקו) כמו טירוף הריקודים של שנות השבעים. אני גם [אוהב את חיי הלילה!](https://www.youtube.com/watch?v=B_wGI3_sGf8) :dancer:) + flag: + instructions: |- + אנחנו אוהבים לשמור על נימה חברית בדיונים שלנו ואנו זקוקים לעזרה כדי [לשמור על תרבות שיח](%{guidelines_url}). במקרה של בעיה כלשהי, נא לסמן בדגל כדי ליידע את המחבר או את [הסגל היעיל שלנו](%{about_url}) באופן פרטי. + + > :imp: כתבתי כאן משהו נבזי + + נראה לי שהבנת מה עליך לעשות. קדימה **לסמן את הפוסט הזה בדגל** על היותו בלתי הולם! + reply: |- + [הסגל שלנו](%{base_uri}/groups/staff) יקבל התראה פרטית על הדגל שלך. אם מספיק חברים בקהילה מסמנים פוסט בדגל, הוא יוסתר ליתר ביטחון. (מאחר שלא באמת כתבתי כאן משהו נבזי :angel:, הרשיתי לעצמי להסיר את הדגל בינתיים.) + not_found: |- + אללי, טרם סימנת בדגל פוסט נבזי. :worried: אפשר לבקש ממך לסמן דגל כתוכן בלתי הולם באמצעות ה**דגל** ? לא לשכוח להשתמש בכפתור להציג עוד כדי לחשוף פעולות נוספות עבור כל פוסט. search: + instructions: |- + _פססט_ … החבאתי הפתעה בנושא הזה. אם מעניין אותך להיענות לאתגר, **עליך לבחור בסמל החיפוש** שבפינה השמאלית העליונה ↗ כדי לחפש אותה. + + יש לנסות לחפש את הביטוי „capy​bara” בנושא הזה hidden_message: |- איך הצלחת לפספס את הקפיברה הזאת? :wink: שמת לב שחזרת להתחלה? אולי נאכיל את הקפיברה המסכנה הזאת על ידי **השבה עם האמוג׳י `:herb:`** ומיד הדיון יוקפץ לסופו. + reply: |- + הידד! מצאת אותה :tada: + + - לחיפושים מורכבים יותר, יש לגשת אל [עמוד החיפוש המלא](%{search_url}). + + - כדי לקפוץ למיקום כלשהו בתוך דיון ארוך, יש לנסות להשתמש בפקדי ציר הזמן שמשמאל (ומלמטה במכשירים ניידים). + + - אם יש לך :keyboard: פיזית, יש לנסות ללחוץ על ? כדי לצפות בקיצורי המקלדת השימושיים שלנו. + not_found: |- + המממ… נראה כאילו הסתבכת קצת. מתנצלים. חיפשת אחר הביטוי **capy​bara**? + end: + message: |- + תודה שנשארת אתי עד כאן ‎@%{username}‎! הכנתי את זה עבורך, הרווחת את זה ביושר: + + %{certificate} + + זה הכול לבינתיים! כדאי לבקר ב[**נושאי הדיון העדכניים ביותר שלנו**](%{base_uri}/latest) או ב[**קטגוריות דיון**](%{base_uri}/categories). :sunglasses: + + (אם בא לך לדבר אתי שוב, עליך פשוט לאזכר את `‎@%{discobot_username}‎` בכל עת!) certificate: alt: 'תעודת הישג' advanced_user_narrative: + reset_trigger: 'מדריך מתקדם' cert_title: "כהוקרה על סיום של מדריך המשתמשים המתקדמים בהצלחה" title: ':arrow_up: יכולות משתמשים מתקדמים' + edit: + bot_created_post_raw: "‎@%{discobot_username}‎ הוא, עד כה, הבוט המגניב ביותר שאני מכיר :wink:" + instructions: |- + כולם טועים לפעמים. אך אל חשש, תמיד יתאפשר לך לערוך את הפוסטים שלך כדי לתקן אותם! + + אולי נתחיל ב**עריכת** הפוסט שיצרתי בשמך? + not_found: |- + נראה לי שטרם ערכת את ה[פוסט](%{url}) שיצרתי עבורך. אולי ננסה שוב? + + יש להשתמש בסמל כדי להעלות את העורך. + reply: |- + מרשים ביותר! + + נא לשים לב ששינויים שנערכו לאחר 5 דקות יופיעו כמהדורות עריכה ציבוריות ויופיע סמל קטן של עיפרון בפינה השמאלית העליונה לצד מספר המהדורות. + delete: + reply: |- + ואו! :boom: + + כדי לשמר את המשכיות הדיון, המחיקות אינן מיידיות, לכן הפוסט יוסר לאחר זמן מה. recover: deleted_post_raw: 'מדוע הפוסט שלי נמחק על ידי @%{discobot_username}? :anguished:' instructions: |- שוד ושבר! נראה שמחקתי לך בטעות פוסט חדש שהרגע יצרתי עבורך. יש מצב לבקש ממך **לבטל את המחיקה** שלו? + not_found: |- + הסתבכת? כדאי לזכור כי להציג עוד יחשוף את ביטול המחיקה. + reply: |- + ואו, זה היה קרוב! תודה שתיקנת את זה :wink: + + מוטב לשים לב כי לרשותך %{deletion_after} שעות לבטל מחיקת פוסט. category_hashtag: + instructions: |- + ידעת שיש לך אפשרות להפנות לקטגוריות ולתגיות בפוסט שלך? למשל, ראית את הקטגוריה %{category}? + + עליך להקליד `#` באמצע משפט ולבחור בקטגוריה או בתגית. + not_found: |- + המממ, אני לא רואה שם קטגוריה בשום מקום. נא לשים לב שהתו הראשון לא יכול להיות `#`. אפשר לבקש ממך להעתיק את זה בתגובה הבאה שלך? + + ```text + אוכל ליצור קישור לקטגוריה דרך # + ``` reply: |- מצויין! זיכרו שזה עובד בשביל קטגוריות _וגם_ תגיות, אם תגיות מאופשרות. poll: diff --git a/plugins/discourse-narrative-bot/config/locales/server.ru.yml b/plugins/discourse-narrative-bot/config/locales/server.ru.yml index 75a0ab6a35..28a6c51d5d 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.ru.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.ru.yml @@ -113,6 +113,11 @@ ru: random_mention: reply: |- Привет! Tтобы узнать, что я могу сделать, наберите: `@%{discobot_username} %{help_trigger}`. + tracks: |- + В настоящее время я знаю, как сделать следующие вещи: + + `@%{discobot_username} %{reset_trigger} %{default_track}` + > Запускает одно из следующих интерактивных руководств: %{tracks}. bot_actions: |- `@%{discobot_username} %{dice_trigger} 2d6` > :game_die: 3, 6 @@ -135,9 +140,19 @@ ru: В то же время, я не буду стоять на вашем пути. new_user_narrative: + reset_trigger: "tutorial" + title: "Сертификат о прохождении обучения нового пользователя" cert_title: "В знак признания успешного завершения нового руководство пользователя" hello: title: "Здравствуйте!" + message: |- + Спасибо %{title}, и Добро пожаловать! + + - Я всего лишь робот, но [наш дружный коллектив](%{base_uri}/about) готов помочь вам в любое время. + + - По соображениям безопасности мы временно ограничиваем возможности новых пользователей. Прочитать о уровнях доверия [можно в нашем блоге](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) (а также в разделе [награды](%{base_uri}/badges)) на этом сайте. + + - Мы верим в [цивилизованное поведение](%{base_uri}/guidelines) в нашем сообществе. onebox: instructions: |- Теперь, не могли бы вы отправить одну из этих ссылок мне? Отправьте **только ссылку**, тогда она автоматически покажет превью. @@ -287,6 +302,7 @@ ru: certificate: alt: 'Свидетельство о достижении' advanced_user_narrative: + reset_trigger: 'advanced tutorial' cert_title: "В знак признания успешного завершения расширенного руководства пользователя" title: ':arrow_up: Расширенные функции пользователя' start_message: |- @@ -326,6 +342,10 @@ ru: Можете ли вы сделать мне одолжение и **восстановить** его ? not_found: |- Возникли проблемы? Помните, что нужно нажать на значок "показать еще" чтобы увидеть опцию "восстановить" + reply: |- + Ух, это было отлично! Спасибо, что исправили это :wink: + + Запомните, что у вас есть только %{deletion_after} час(а), чтобы восстановить пост. category_hashtag: instructions: |- Знаете ли вы, что вы можете обратитесь к категории и теги в вашем посте? Например, вы видели %{category} категорию? diff --git a/plugins/discourse-narrative-bot/config/locales/server.ur.yml b/plugins/discourse-narrative-bot/config/locales/server.ur.yml index a3bf44109c..1abef1ce37 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.ur.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.ur.yml @@ -145,6 +145,14 @@ ur: cert_title: "نیا صارف ٹیوٹوریل کی کامیاب تکمیل کے اعتراف میں" hello: title: "خوش آمدید!" + message: |- + %{title}میں شامل ہونے کیلئے شکریہ، اور خوش آمدید! + + - میں صرف ایک رَوبَوٹ ہوں، لیکن اگر آپ کو کسی شخص سے رابطہ کرنے کی ضرورت ہو تو [ہمارا دوستانہ اسٹاف](%{base_uri}/about) بھی یہاں موجود ہے۔ + + - حفاظتی وجوہات کی بنا پر، ہم عارضی طور پر نئے صارفین کی اِس سائیٹ پر کارروائیاں محدود کرتے ہیں۔ آپ کو [نئی صلاحیتیں](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) (اور [بَیج](%{base_uri}/badges)) دیے جائیں گے جیسے جیسے ہم آپ کو بہتر جان پائیں گے۔ + + - ہم، ہر وقت [مہذب کمیونٹی رویے](%{base_uri}/guidelines) پر یقین رکھتے ہیں۔ onebox: instructions: |- پھر، کیا آپ اِن لنکس میں سے ایک میرے ساتھ شئیر کر سکتے ہیں؟ **ایک لائن پر ایک لنک** کے طور پر جواب دیں، اور یہ خود بخود اُس کا ایک اچھا خلاصہ شامل کردے گا۔ diff --git a/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml b/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml index ff028a10cf..e20a2065d6 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml @@ -141,9 +141,18 @@ zh_CN: 同时,我不会再烦你。 new_user_narrative: reset_trigger: "教程" + title: "新用户教程完成证明" cert_title: "你已经成功完成新用户教程了" hello: title: "欢迎!" + message: |- + 感谢加入%{title},欢迎! + + -我只是一个机器人,但是我们[友好的管理员]可以提供真人帮助。 + + -出于安全考虑,我们暂时限制新用户能够使用的功能。当我们更了解你的时候,你就可以获得[新功能](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) (还有 [徽章](%{base_uri}/badges)) + + -我们始终相信[社区文明守则](%{base_uri}/guidelines)。 onebox: instructions: |- 接下来,你能分享下列链接给我么?回复**单独一行的链接**,然后它会自动展开显示一个有用的摘要。 @@ -333,6 +342,10 @@ zh_CN: 你可以帮我**恢复**它么? not_found: |- 碰到麻烦了?记住点击显示更多可以找到恢复按钮。 + reply: |- + 呼,差点啊!感谢你帮忙 :wink: + + 注意你只有%{deletion_after}小时的时间去恢复一个帖子。 category_hashtag: instructions: |- 你知道你可以在你的帖子中提到分类和标签么?例如,你看到过%{category}分类了吗? diff --git a/plugins/poll/config/locales/client.el.yml b/plugins/poll/config/locales/client.el.yml index 7532755fa5..d18141e152 100644 --- a/plugins/poll/config/locales/client.el.yml +++ b/plugins/poll/config/locales/client.el.yml @@ -19,13 +19,13 @@ el: help: at_least_min_options: one: "Επιλέξτε τουλάχιστον %{count} επιλογή" - other: "Επιλέξτε τουλάχιστον %{count} επιλογές" + other: "Επέλεξε τουλάχιστον %{count} επιλογές" up_to_max_options: one: "Επιλέξτε μέχρι %{count} επιλογή" - other: "Επιλέξτε μέχρι %{count} επιλογές" + other: "Επέλεξε μέχρι %{count} επιλογές" x_options: one: "Επιλέξτε %{count} επιλογή" - other: "Επιλέξτε %{count} επιλογές" + other: "Επέλεξε %{count} επιλογές" between_min_and_max_options: "Επιλέξτε ανάμεσα σε %{min} και %{max} επιλογές" cast-votes: title: "Δώσε τις ψήφους σου" diff --git a/plugins/poll/config/locales/client.et.yml b/plugins/poll/config/locales/client.et.yml index dd4ff94bb0..42147796b8 100644 --- a/plugins/poll/config/locales/client.et.yml +++ b/plugins/poll/config/locales/client.et.yml @@ -52,6 +52,7 @@ et: insert: Sisesta hääletus help: options_count: Sisesta vähemalt 2 valikut + min_step_value: Sammu minimaalne väärtus on 1 poll_type: label: Tüüp regular: Üks valik diff --git a/plugins/poll/config/locales/client.fi.yml b/plugins/poll/config/locales/client.fi.yml index e677187751..524a35c111 100644 --- a/plugins/poll/config/locales/client.fi.yml +++ b/plugins/poll/config/locales/client.fi.yml @@ -51,6 +51,9 @@ fi: title: "Sulje äänestys" label: "Sulje" confirm: "Suljetaanko äänestys?" + automatic_close: + closes_in: "Sulkeutuu %{timeLeft} kuluttua" + age: "Sulkeutui %{age}" error_while_toggling_status: "Pahoittelut, äänestyksen tilaa muutettaessa tapahtui virhe." error_while_casting_votes: "Pahoittelut, ääntä annettaessa tapahtui virhe." error_while_fetching_voters: "Pahoittelut, äänestäneiden näyttämisessä tapahtui virhe." diff --git a/plugins/poll/config/locales/client.fr.yml b/plugins/poll/config/locales/client.fr.yml index eeafb28995..75c8daf913 100644 --- a/plugins/poll/config/locales/client.fr.yml +++ b/plugins/poll/config/locales/client.fr.yml @@ -36,7 +36,7 @@ fr: between_min_and_max_options: "Choisissez entre %{min} et %{max} options" cast-votes: title: "Distribuez vos votes" - label: "Votez maintenant !" + label: "Votez maintenant !" show-results: title: "Afficher les résultats du sondage" label: "Afficher les résultats" diff --git a/plugins/poll/config/locales/server.de.yml b/plugins/poll/config/locales/server.de.yml index a4545f4216..58c3b4bc2e 100644 --- a/plugins/poll/config/locales/server.de.yml +++ b/plugins/poll/config/locales/server.de.yml @@ -12,6 +12,7 @@ de: poll_edit_window_mins: "Anzahl Minuten nach der Erstellung eines Beitrags, in denen Umfragen bearbeitet werden können." poll_minimum_trust_level_to_create: "Erforderliche Vertrauensstufe, um Umfragen zu erstellen. " poll: + poll: "Umfrage" invalid_argument: "Ungültiger Wert '%{value}' für Argument '%{argument}'." multiple_polls_without_name: "Es gibt mehrere namenlose Umfragen. Benutze das Attribute 'name', um deine Umfragen eindeutig identifizierbar zu machen." multiple_polls_with_same_name: "Es gibt mehre Umfragen mit dem selben Namen: %{name}. Benutze das Attribute 'name', um deine Umfragen eindeutig identifizierbar zu machen." diff --git a/plugins/poll/config/locales/server.es.yml b/plugins/poll/config/locales/server.es.yml index 3a6234834c..588122d63c 100644 --- a/plugins/poll/config/locales/server.es.yml +++ b/plugins/poll/config/locales/server.es.yml @@ -12,6 +12,7 @@ es: poll_edit_window_mins: "Número de minutos tras la creación del mensaje durante los cuales las encuestas pueden ser editadas." poll_minimum_trust_level_to_create: "Definir el mínimo nivel de confianza requerido para crear encuestas." poll: + poll: "encuesta" invalid_argument: "Valor no válido '%{value}' para el argumento '%{argument}'." multiple_polls_without_name: "Hay múltiples encuestas sin nombre. Utiliza el atributo 'nombre' para identificar de forma única tus encuestas." multiple_polls_with_same_name: "Hay múltiples encuestas con el mismo nombre: %{name}. Utiliza el atributo 'nombre' para identificar de forma única tus encuestas." diff --git a/plugins/poll/config/locales/server.et.yml b/plugins/poll/config/locales/server.et.yml index c97281b971..6b24914011 100644 --- a/plugins/poll/config/locales/server.et.yml +++ b/plugins/poll/config/locales/server.et.yml @@ -7,6 +7,7 @@ et: site_settings: + poll_enabled: "Lubada küsitlused?" poll_maximum_options: "Maksimaalne valikute arv, mis on küsitluses lubatud." poll_edit_window_mins: "Minutite arv peale loomist, mille jooksul saab küsitlust veel muuta." poll: diff --git a/plugins/poll/config/locales/server.fr.yml b/plugins/poll/config/locales/server.fr.yml index 047b65b6a6..eff5c025c3 100644 --- a/plugins/poll/config/locales/server.fr.yml +++ b/plugins/poll/config/locales/server.fr.yml @@ -12,17 +12,18 @@ fr: poll_edit_window_mins: "Nombre de minutes après la création du message durant lesquelles les sondages peuvent être modifiés." poll_minimum_trust_level_to_create: "Définir le niveau de confiance minimum requis pour créer des sondages." poll: + poll: "sondage" invalid_argument: "Valeur '%{value}' invalide pour paramètre '%{argument}'." multiple_polls_without_name: "Plusieurs sondages n'ont pas de nom. Utilisez l'attribut 'name' pour donner un identifiant unique aux sondages." multiple_polls_with_same_name: "Plusieurs sondages ont le même nom : %{name}. Utilisez l'attribut 'name' pour donner un identifiant unique aux sondages." default_poll_must_have_at_least_2_options: "Un sondage doit contenir au moins deux options." named_poll_must_have_at_least_2_options: "Le sondage %{name} doit contenir au moins deux options." default_poll_must_have_less_options: - one: "Un sondage peut contenir une option." - other: "Un sondage peut contenir jusqu'à %{count} options." + one: "Un sondage doit contenir moins que %{count}options." + other: "Un sondage doit contenir moins que %{count} options." named_poll_must_have_less_options: - one: "Le sondage % {name} peut contenir une option." - other: "Le sondage % {name} peut contenir jusqu’à % {count} options." + one: "Le %{name} sondage doit contenir moins que %{count} options." + other: "Le %{name} sondage doit contenir moins que %{count} options." default_poll_must_have_different_options: "Les sondages doivent contenir des options différentes les unes des autres." named_poll_must_have_different_options: "Le sondage %{name} doit contenir des options différentes." default_poll_with_multiple_choices_has_invalid_parameters: "Le sondage à choix multiples possède des paramètres invalides." diff --git a/plugins/poll/config/locales/server.ur.yml b/plugins/poll/config/locales/server.ur.yml index d2e4af5803..8f0d931aa1 100644 --- a/plugins/poll/config/locales/server.ur.yml +++ b/plugins/poll/config/locales/server.ur.yml @@ -12,6 +12,7 @@ ur: poll_edit_window_mins: "پوسٹ بنانے کے بعد کتنے منت تک پولز کی ترمیم کی جاسکتی ہے۔" poll_minimum_trust_level_to_create: "پول بنانے کیلئے کم از کم ٹرسٹ لَیول واضح کریں۔" poll: + poll: "پَول" invalid_argument: "آرگیومنٹ کیلئے '%{argument}' غلط وَیلِیو '%{value}'۔" multiple_polls_without_name: "نام کے بغیر ایک سے زیادہ پولزموجود ہیں۔ اپنے پولز کو منفرد شناخت دینے کیلیے 'نام' صفت کا استعمال کریں۔" multiple_polls_with_same_name: "ایک ہی نام والے ایک سے زیادہ پولزموجود ہیں: %{نام}۔ اپنے پولز کو منفرد شناخت دینے کیلیے 'نام' صفت کا استعمال کریں۔" diff --git a/plugins/poll/config/locales/server.zh_CN.yml b/plugins/poll/config/locales/server.zh_CN.yml index baca15ab9f..5113ea538d 100644 --- a/plugins/poll/config/locales/server.zh_CN.yml +++ b/plugins/poll/config/locales/server.zh_CN.yml @@ -12,6 +12,7 @@ zh_CN: poll_edit_window_mins: "投票创建后多少分钟内可以编辑?" poll_minimum_trust_level_to_create: "创建投票所需的最小信任等级。" poll: + poll: "投票" invalid_argument: "无效值‘%{value}' for argument '%{argument}’" multiple_polls_without_name: "有多个投票没有名字。使用“name”属性区分各投票。" multiple_polls_with_same_name: "有多个投票的名字相同:%{name}。使用“name”属性区分各投票。" diff --git a/plugins/poll/spec/lib/pretty_text_spec.rb b/plugins/poll/spec/lib/pretty_text_spec.rb index a1b834ac7c..db8af253a8 100644 --- a/plugins/poll/spec/lib/pretty_text_spec.rb +++ b/plugins/poll/spec/lib/pretty_text_spec.rb @@ -121,14 +121,14 @@ describe PrettyText do end it 'can onebox posts' do - post = Fabricate(:post, raw: <<~EOF) + post = Fabricate(:post, raw: <<~MD) A post with a poll [poll type=regular] * Hello * World [/poll] - EOF + MD onebox = Oneboxer.onebox_raw(post.full_url, user_id: Fabricate(:user).id) doc = Nokogiri::HTML(onebox[:preview]) @@ -136,4 +136,21 @@ describe PrettyText do expect(onebox[:preview]).to include("A post with a poll") expect(onebox[:preview]).to include("poll") end + + it 'can reduce excerpts' do + post = Fabricate(:post, raw: <<~MD) + A post with a poll + + [poll type=regular] + * Hello + * World + [/poll] + MD + + excerpt = PrettyText.excerpt(post.cooked, SiteSetting.post_onebox_maxlength, post: post) + expect(excerpt).to eq("A post with a poll \npoll") + + excerpt = PrettyText.excerpt(post.cooked, SiteSetting.post_onebox_maxlength) + expect(excerpt).to eq("A post with a poll \npoll") + end end diff --git a/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6 b/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6 index ba6bdccdfb..313f5b6a80 100644 --- a/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6 +++ b/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6 @@ -1,3 +1,5 @@ +import selectKit from "helpers/select-kit-helper"; + export function displayPollBuilderButton() { visit("/"); click("#create-topic"); diff --git a/public/403.fr.html b/public/403.fr.html index 1abfb53f74..3d02199f4e 100644 --- a/public/403.fr.html +++ b/public/403.fr.html @@ -19,7 +19,7 @@

403

-

Il n'est pas possible de voir cette ressource !

+

Il n'est pas possible de voir cette ressource !

Ceci sera remplacé par une page 403 personnalisée de Discourse.

diff --git a/public/503.fr.html b/public/503.fr.html index d57807f37a..a1d8adad09 100644 --- a/public/503.fr.html +++ b/public/503.fr.html @@ -7,6 +7,6 @@

Nous sommes actuellement indisponible pour une maintenance planifiée.

Merci de revenir dans quelques minutes.

-

Désolé pour le dérangement !

+

Désolé pour le dérangement !

diff --git a/public/images/wizard/further-reading.png b/public/images/wizard/further-reading.png new file mode 100644 index 0000000000..c40d907d2b Binary files /dev/null and b/public/images/wizard/further-reading.png differ diff --git a/script/import_scripts/answerbase.rb b/script/import_scripts/answerbase.rb index e011737fae..1f436ac0ba 100644 --- a/script/import_scripts/answerbase.rb +++ b/script/import_scripts/answerbase.rb @@ -255,7 +255,7 @@ class ImportScripts::Answerbase < ImportScripts::Base upload = @uploader.create_upload(user_id, path, filename) if upload.present? && upload.persisted? && !upload_ids.include?(upload.id) - raw << "\n" << @uploader.html_for_upload(upload, filename) + raw = "#{raw}\n#{@uploader.html_for_upload(upload, filename)}" end else STDERR.puts "Could not find file: #{path}" diff --git a/script/import_scripts/answerhub.rb b/script/import_scripts/answerhub.rb new file mode 100644 index 0000000000..a81877755a --- /dev/null +++ b/script/import_scripts/answerhub.rb @@ -0,0 +1,444 @@ +# frozen_string_literal: true + +# AnswerHub Importer +# +# Based on having access to a mysql dump. +# Pass in the ENV variables listed below before runing the script. + +require_relative 'base' +require 'mysql2' +require 'open-uri' + +class ImportScripts::AnswerHub < ImportScripts::Base + + DB_NAME ||= ENV['DB_NAME'] || "answerhub" + DB_PASS ||= ENV['DB_PASS'] || "answerhub" + DB_USER ||= ENV['DB_USER'] || "answerhub" + TABLE_PREFIX ||= ENV['TABLE_PREFIX'] || "network1" + BATCH_SIZE ||= ENV['BATCH_SIZE'].to_i || 1000 + ATTACHMENT_DIR = ENV['ATTACHMENT_DIR'] || '' + PROCESS_UPLOADS = ENV['PROCESS_UPLOADS'].to_i || 0 + ANSWERHUB_DOMAIN = ENV['ANSWERHUB_DOMAIN'] + AVATAR_DIR = ENV['AVATAR_DIR'] || "" + SITE_ID = ENV['SITE_ID'].to_i || 0 + CATEGORY_MAP_FROM = ENV['CATEGORY_MAP_FROM'].to_i || 0 + CATEGORY_MAP_TO = ENV['CATEGORY_MAP_TO'].to_i || 0 + SCRAPE_AVATARS = ENV['SCRAPE_AVATARS'].to_i || 0 + + def initialize + super + @client = Mysql2::Client.new( + host: "localhost", + username: DB_USER, + password: DB_PASS, + database: DB_NAME + ) + @skip_updates = true + SiteSetting.tagging_enabled = true + SiteSetting.max_tags_per_topic = 10 + end + + def execute + puts "Now starting the AnswerHub Import" + puts "DB Name: #{DB_NAME}" + puts "Table Prefix: #{TABLE_PREFIX}" + puts + import_users + import_categories + import_topics + import_posts + import_groups + add_users_to_groups + add_moderators + add_admins + import_avatars + end + + def import_users + puts '', "creating users" + + query = + "SELECT count(*) count + FROM #{TABLE_PREFIX}_authoritables + WHERE c_type = 'user' + AND c_active = 1 + AND c_system <> 1;" + total_count = @client.query(query).first['count'] + puts "Total count: #{total_count}" + @last_user_id = -1 + + batches(BATCH_SIZE) do |offset| + query = "SELECT c_id, c_creation_date, c_name, c_primaryEmail, c_last_seen, c_description + FROM #{TABLE_PREFIX}_authoritables + WHERE c_type='user' + AND c_active = 1 + AND c_system <> 1 + AND c_id > #{@last_user_id} + LIMIT #{BATCH_SIZE};" + + results = @client.query(query) + break if results.size < 1 + @last_user_id = results.to_a.last['c_id'] + + create_users(results, total: total_count, offset: offset) do |user| + puts user['c_id'].to_s + ' ' + user['c_name'] + next if @lookup.user_id_from_imported_user_id(user['c_id']) + { id: user['c_id'], + email: "#{SecureRandom.hex}@invalid.invalid", + username: user['c_name'], + created_at: user['c_creation_date'], + bio_raw: user['c_description'], + last_seen_at: user['c_last_seen'], + } + end + end + end + + def import_categories + puts "", "importing categories..." + + # Import parent categories first + query = "SELECT c_id, c_name, c_plug, c_parent + FROM containers + WHERE c_type = 'space' + AND c_active = 1 + AND c_parent = 7 OR c_parent IS NULL" + results = @client.query(query) + + create_categories(results) do |c| + { + id: c['c_id'], + name: c['c_name'], + parent_category_id: check_parent_id(c['c_parent']), + } + end + + # Import sub-categories + query = "SELECT c_id, c_name, c_plug, c_parent + FROM containers + WHERE c_type = 'space' + AND c_active = 1 + AND c_parent != 7 AND c_parent IS NOT NULL" + results = @client.query(query) + + create_categories(results) do |c| + puts c.inspect + { + id: c['c_id'], + name: c['c_name'], + parent_category_id: category_id_from_imported_category_id(check_parent_id(c['c_parent'])), + } + end + end + + def import_topics + puts "", "importing topics..." + + count_query = + "SELECT count(*) count + FROM #{TABLE_PREFIX}_nodes + WHERE c_visibility <> 'deleted' + AND (c_type = 'question' + OR c_type = 'kbentry');" + total_count = @client.query(count_query).first['count'] + + @last_topic_id = -1 + + batches(BATCH_SIZE) do |offset| + # Let's start with just question types + query = + "SELECT * + FROM #{TABLE_PREFIX}_nodes + WHERE c_id > #{@last_topic_id} + AND c_visibility <> 'deleted' + AND (c_type = 'question' + OR c_type = 'kbentry') + ORDER BY c_id ASC + LIMIT #{BATCH_SIZE};" + topics = @client.query(query) + + break if topics.size < 1 + @last_topic_id = topics.to_a.last['c_id'] + + create_posts(topics, total: total_count, offset: offset) do |t| + user_id = user_id_from_imported_user_id(t['c_author']) || Discourse::SYSTEM_USER_ID + if PROCESS_UPLOADS == 1 + body = process_uploads(t['c_body'], user_id) + else + body = t['c_body'] + end + markdown_body = HtmlToMarkdown.new(body).to_markdown + { + id: t['c_id'], + user_id: user_id, + title: t['c_title'], + category: category_id_from_imported_category_id(t['c_primaryContainer']), + raw: markdown_body, + created_at: t['c_creation_date'], + post_create_action: proc do |post| + tag_names = t['c_topic_names'].split(',') + DiscourseTagging.tag_topic_by_names(post.topic, staff_guardian, tag_names) + end + } + end + end + end + + def import_posts + puts "", "importing posts..." + + count_query = + "SELECT count(*) count + FROM #{TABLE_PREFIX}_nodes + WHERE c_visibility <> 'deleted' + AND (c_type = 'answer' + OR c_type = 'comment' + OR c_type = 'kbentry');" + total_count = @client.query(count_query).first['count'] + + @last_post_id = -1 + + batches(BATCH_SIZE) do |offset| + query = + "SELECT * + FROM #{TABLE_PREFIX}_nodes + WHERE c_id > #{@last_post_id} + AND c_visibility <> 'deleted' + AND (c_type = 'answer' + OR c_type = 'comment' + OR c_type = 'kbentry') + ORDER BY c_id ASC + LIMIT #{BATCH_SIZE};" + posts = @client.query(query) + next if all_records_exist? :posts, posts.map { |p| p['c_id'] } + + break if posts.size < 1 + @last_post_id = posts.to_a.last['c_id'] + + create_posts(posts, total: total_count, offset: offset) do |p| + t = topic_lookup_from_imported_post_id(p['c_parent']) + next unless t + user_id = user_id_from_imported_user_id(p['c_author']) || Discourse::SYSTEM_USER_ID + if PROCESS_UPLOADS == 1 + body = process_uploads(p['c_body'], user_id) + else + body = t['c_body'] + end + markdown_body = HtmlToMarkdown.new(body).to_markdown + { + id: p['c_id'], + user_id: user_id, + topic_id: t[:topic_id], + reply_to_post_number: t[:post_number], + raw: markdown_body, + created_at: p['c_creation_date'], + post_create_action: proc do |post_info| + begin + if p['c_type'] == 'answer' && p['c_marked'] == 1 + post = Post.find(post_info[:id]) + if post + user_id = user_id_from_imported_user_id(p['c_author']) || Discourse::SYSTEM_USER_ID + current_user = User.find(user_id) + solved = DiscourseSolved.accept_answer!(post, current_user) + puts "SOLVED: #{solved}" + end + end + rescue ActiveRecord::RecordInvalid + puts "SOLVED: Skipped post_id: #{post.id} because invalid" + end + end + } + end + end + end + + def import_groups + puts "", "importing groups..." + + query = + "SELECT c_id, c_name + FROM network6_authoritables + WHERE c_type='group' + AND c_id > 6;" # Ignore Anonymous, Users, Moderators, etc. + groups = @client.query(query) + + create_groups(groups) do |group| + { + id: group["c_id"], + name: group["c_name"], + visibility_level: 1 + } + end + end + + def add_users_to_groups + puts "", "adding users to groups..." + + query = + "SELECT c_id, c_name + FROM network6_authoritables + WHERE c_type='group' + AND c_id > 6;" # Ignore Anonymous, Users, Moderators, etc. + groups = @client.query(query) + + members_query = + "SELECT * + FROM network6_authoritable_groups;" + group_members = @client.query(members_query) + + total_count = groups.count + progress_count = 0 + start_time = Time.now + + group_members.map + groups.each do |group| + dgroup = find_group_by_import_id(group['c_id']) + + next if dgroup.custom_fields['import_users_added'] + + group_member_ids = group_members.map { |m| user_id_from_imported_user_id(m["c_members"]) if m["c_groups"] == group['c_id'] }.compact + + # add members + dgroup.bulk_add(group_member_ids) + + # reload group + dgroup.reload + + dgroup.custom_fields['import_users_added'] = true + dgroup.save + + progress_count += 1 + print_status(progress_count, total_count, start_time) + end + end + + def add_moderators + puts "", "adding moderators..." + + query = + "SELECT * + FROM network6_authoritable_groups + WHERE c_groups = 4;" + moderators = @client.query(query) + + moderator_ids = moderators.map { |m| user_id_from_imported_user_id(m["c_members"]) }.compact + + moderator_ids.each do |id| + user = User.find(id) + user.grant_moderation! + end + end + + def add_admins + puts "", "adding admins..." + + query = + "SELECT * + FROM network6_authoritable_groups + WHERE c_groups = 5 OR c_groups = 6;" # Super Users, Network Administrators + admins = @client.query(query) + + admin_ids = admins.map { |a| user_id_from_imported_user_id(a["c_members"]) }.compact + + admin_ids.each do |id| + user = User.find(id) + user.grant_admin! + end + end + + def import_avatars + puts "", "importing user avatars" + query = + "SELECT * + FROM network6_user_preferences + WHERE c_key = 'avatarImage'" + avatars = @client.query(query) + + avatars.each do |a| + begin + user_id = user_id_from_imported_user_id(a['c_user']) + user = User.find(user_id) + if user + filename = "avatar-#{user_id}.png" + path = File.join(AVATAR_DIR, filename) + next if !File.exists?(path) + + # Scrape Avatars - Avatars are saved in the db, but it might be easier to just scrape them + if SCRAPE_AVATARS == 1 + File.open(path, 'wb') { |f| + f << open("https://#{ANSWERHUB_DOMAIN}/forums/users/#{a['c_user']}/photo/view.html?s=240").read + } + end + + upload = @uploader.create_upload(user.id, path, filename) + + if upload.persisted? + user.import_mode = false + user.create_user_avatar + user.import_mode = true + user.user_avatar.update(custom_upload_id: upload.id) + user.update(uploaded_avatar_id: upload.id) + else + Rails.logger.error("Could not persist avatar for user #{user.username}") + end + end + rescue ActiveRecord::RecordNotFound + puts "Could not find User for user_id: #{a['c_user']}" + end + end + end + + def process_uploads(body, user_id) + if body.match(//) + # There could be multiple images in a post + images = body.scan(//) + + images.each do |image| + filepath = File.basename(image).split('"')[0] + filepath = File.join(ATTACHMENT_DIR, filepath) + + if File.exists?(filepath) + filename = File.basename(filepath) + upload = create_upload(user_id, filepath, filename) + image_html = html_for_upload(upload, filename) + original_image_html = '' + body.sub!(original_image_html, image_html) + end + end + end + # Non-images + if body.match(//) + # There could be multiple files in a post + files = body.scan(//) + + files.each do |file| + filepath = File.basename(file).split('"')[0] + filepath = File.join(ATTACHMENT_DIR, filepath) + + if File.exists?(filepath) + filename = File.basename(filepath) + upload = create_upload(user_id, filepath, filename) + file_html = html_for_upload(upload, filename) + original_file_html = '' + body.sub!(original_file_html, file_html) + end + end + end + + body + end + + def staff_guardian + @_staff_guardian ||= Guardian.new(Discourse.system_user) + end + + # Some category parent id's need to be adjusted + def check_parent_id(id) + return nil if SITE_ID > 0 && id == SITE_ID + return CATEGORY_MAP_TO if CATEGORY_MAP_FROM > 0 && id == CATEGORY_MAP_FROM + id + end + +end + +ImportScripts::AnswerHub.new.perform diff --git a/script/import_scripts/askbot.rb b/script/import_scripts/askbot.rb index ac409a6c52..422c8fa311 100644 --- a/script/import_scripts/askbot.rb +++ b/script/import_scripts/askbot.rb @@ -118,7 +118,7 @@ class ImportScripts::MyAskBot < ImportScripts::Base { id: user["id"], username: user["username"], - email: user["email"] || (SecureRandom.hex << "@domain.com"), + email: user["email"] || fake_email, admin: user["is_staff"], created_at: Time.zone.at(@td.decode(user["date_joined"])), last_seen_at: Time.zone.at(@td.decode(user["last_seen"])), @@ -239,7 +239,7 @@ class ImportScripts::MyAskBot < ImportScripts::Base # ask.cvxr.com/question/(\d+)/[^'"}]* # I am sure this is incomplete, but we didn't make heavy use of internal # links on our site. - tmp = Regexp.quote("http://" << OLD_SITE) + tmp = Regexp.quote("http://#{OLD_SITE}") r1 = /"(#{tmp})?\/question\/(\d+)\/[a-zA-Z-]*\/?"/ r2 = /\((#{tmp})?\/question\/(\d+)\/[a-zA-Z-]*\/?\)/ r3 = /?/ diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 77abd752a5..944f26c0b0 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -883,6 +883,6 @@ class ImportScripts::Base end def fake_email - SecureRandom.hex << "@domain.com" + SecureRandom.hex << "@email.invalid" end end diff --git a/script/import_scripts/base/csv_helper.rb b/script/import_scripts/base/csv_helper.rb index 3a175b935c..7f7becbd3d 100644 --- a/script/import_scripts/base/csv_helper.rb +++ b/script/import_scripts/base/csv_helper.rb @@ -24,7 +24,7 @@ module ImportScripts first = true row = nil - current_row = "" + current_row = +"" double_quote_count = 0 File.open(filename).each_line do |line| diff --git a/script/import_scripts/bespoke_1.rb b/script/import_scripts/bespoke_1.rb index 8ddb8bfb01..9ca420f319 100644 --- a/script/import_scripts/bespoke_1.rb +++ b/script/import_scripts/bespoke_1.rb @@ -67,7 +67,7 @@ class ImportScripts::Bespoke < ImportScripts::Base first = true row = nil - current_row = "" + current_row = +"" double_quote_count = 0 File.open(filename).each_line do |line| @@ -139,7 +139,7 @@ class ImportScripts::Bespoke < ImportScripts::Base # fake it if row.email.blank? || row.email !~ /@/ - email = SecureRandom.hex << "@domain.com" + email = fake_email end name = row.display_name diff --git a/script/import_scripts/discuz_x.rb b/script/import_scripts/discuz_x.rb index 750a1ef6bf..dc8f28ebfe 100644 --- a/script/import_scripts/discuz_x.rb +++ b/script/import_scripts/discuz_x.rb @@ -787,7 +787,7 @@ class ImportScripts::DiscuzX < ImportScripts::Base FROM #{table_name 'forum_attachment'} WHERE pid = #{post.custom_fields['import_id']}" if !inline_attachments.empty? - sql << " AND aid NOT IN (#{inline_attachments.join(',')})" + sql = "#{sql} AND aid NOT IN (#{inline_attachments.join(',')})" end results = mysql_query(sql) diff --git a/script/import_scripts/jive.rb b/script/import_scripts/jive.rb index 4b253fe6e9..8d386a52c5 100644 --- a/script/import_scripts/jive.rb +++ b/script/import_scripts/jive.rb @@ -69,7 +69,7 @@ class ImportScripts::Jive < ImportScripts::Base first = true row = nil - current_row = "" + current_row = +"" double_quote_count = 0 File.open(filename).each_line do |line| @@ -154,7 +154,7 @@ class ImportScripts::Jive < ImportScripts::Base # fake it if row.email.blank? || row.email !~ /@/ - email = SecureRandom.hex << "@domain.com" + email = fake_email end name = "#{row.firstname} #{row.lastname}" diff --git a/script/import_scripts/jive_api.rb b/script/import_scripts/jive_api.rb index 62a892adcc..cf6df4d5be 100644 --- a/script/import_scripts/jive_api.rb +++ b/script/import_scripts/jive_api.rb @@ -129,7 +129,7 @@ class ImportScripts::JiveApi < ImportScripts::Base filters = "filter=entityDescriptor(#{entities.join(",")})" else path = "places/#{place["placeID"]}/contents" - filters = "filter=status(published)" + filters = +"filter=status(published)" if to_import[:filters] filters << "&filter=type(#{to_import[:filters][:type]})" if to_import[:filters][:type].present? filters << "&filter=creationDate(null,#{to_import[:filters][:created_after].strftime("%Y-%m-%dT%TZ")})" if to_import[:filters][:created_after].present? diff --git a/script/import_scripts/lithium.rb b/script/import_scripts/lithium.rb index e6f536f4ac..83700a87ca 100644 --- a/script/import_scripts/lithium.rb +++ b/script/import_scripts/lithium.rb @@ -1021,7 +1021,7 @@ SQL end def html_for_attachments(user_id, files) - html = "" + html = +"" files.each do |file| upload, filename = find_upload(user_id, file["attachment_id"], file["file_name"]) diff --git a/script/import_scripts/mbox/importer.rb b/script/import_scripts/mbox/importer.rb index 1be622250d..3d823e197d 100644 --- a/script/import_scripts/mbox/importer.rb +++ b/script/import_scripts/mbox/importer.rb @@ -137,7 +137,7 @@ module ImportScripts::Mbox body = receiver.add_attachments(body, user) end - body << Email::Receiver.elided_html(elided) if elided.present? + body = "#{body}#{Email::Receiver.elided_html(elided)}" if elided.present? body end diff --git a/script/import_scripts/mbox/support/indexer.rb b/script/import_scripts/mbox/support/indexer.rb index 4a24e9f8a4..fa51ef62c4 100644 --- a/script/import_scripts/mbox/support/indexer.rb +++ b/script/import_scripts/mbox/support/indexer.rb @@ -155,7 +155,7 @@ module ImportScripts::Mbox end def each_mail(filename) - raw_message = '' + raw_message = +'' first_line_number = 1 last_line_number = 0 @@ -165,7 +165,7 @@ module ImportScripts::Mbox if line =~ @split_regex if last_line_number > 0 yield raw_message, first_line_number, last_line_number - raw_message = '' + raw_message = +'' first_line_number = last_line_number + 1 end else diff --git a/script/import_scripts/nabble.rb b/script/import_scripts/nabble.rb index 05533da201..e877e9058d 100644 --- a/script/import_scripts/nabble.rb +++ b/script/import_scripts/nabble.rb @@ -80,7 +80,7 @@ class ImportScripts::Nabble < ImportScripts::Base create_users(users, total: total_count, offset: offset) do |row| { id: row["user_id"], - email: row["email"] || (SecureRandom.hex << "@domain.com"), + email: row["email"] || fake_email, created_at: Time.zone.at(@td.decode(row["joined"])), name: row["name"], post_create_action: proc do |user| @@ -283,7 +283,7 @@ class String def indent(count, char = ' ') gsub(/([^\n]*)(\n|$)/) do |match| last_iteration = ($1 == "" && $2 == "") - line = "" + line = +"" line << (char * count) unless last_iteration line << $1 line << $2 diff --git a/script/import_scripts/phpbb3/database/database.rb b/script/import_scripts/phpbb3/database/database.rb index 70cc4e4a6f..6b34febffc 100644 --- a/script/import_scripts/phpbb3/database/database.rb +++ b/script/import_scripts/phpbb3/database/database.rb @@ -26,8 +26,10 @@ module ImportScripts::PhpBB3 require_relative 'database_3_1' Database_3_1.new(@database_client, @database_settings) else - raise UnsupportedVersionError, "Unsupported version (#{version}) of phpBB detected.\n" \ - << 'Currently only 3.0.x and 3.1.x are supported by this importer.' + raise UnsupportedVersionError, <<~MSG + Unsupported version (#{version}) of phpBB detected. + Currently only 3.0.x and 3.1.x are supported by this importer. + MSG end end diff --git a/script/import_scripts/phpbb3/importers/poll_importer.rb b/script/import_scripts/phpbb3/importers/poll_importer.rb index 023de32e70..82066bf3de 100644 --- a/script/import_scripts/phpbb3/importers/poll_importer.rb +++ b/script/import_scripts/phpbb3/importers/poll_importer.rb @@ -56,7 +56,7 @@ module ImportScripts::PhpBB3 # @param poll_data [ImportScripts::PhpBB3::PollData] def get_poll_text(poll_data) title = @text_processor.process_raw_text(poll_data.title) - text = "#{title}\n\n" + text = +"#{title}\n\n" arguments = ["results=always"] arguments << "close=#{poll_data.close_time.iso8601}" if poll_data.close_time diff --git a/script/import_scripts/smf2.rb b/script/import_scripts/smf2.rb index e24c9ca6c2..1471d20d3b 100644 --- a/script/import_scripts/smf2.rb +++ b/script/import_scripts/smf2.rb @@ -319,7 +319,7 @@ class ImportScripts::Smf2 < ImportScripts::Base "\n[#{tag}]#{$~[:inner].strip}[/#{tag}]\n" end body.gsub!(XListPattern) do |s| - r = "\n[ul]" + r = +"\n[ul]" s.lines.each { |l| r << '[li]' << l.strip.sub(/^\[x\]\s*/, '') << '[/li]' } r << "[/ul]\n" end @@ -357,7 +357,7 @@ class ImportScripts::Smf2 < ImportScripts::Base inner = $~[:inner].strip params = parse_tag_params($~[:params]) if params['author'].present? - quote = "[quote=\"#{params['author']}" + quote = +"[quote=\"#{params['author']}" if QuoteParamsPattern =~ params['link'] tl = topic_lookup_from_imported_post_id($~[:msg].to_i) quote << ", post:#{tl[:post_number]}, topic:#{tl[:topic_id]}" if tl diff --git a/script/import_scripts/telligent.rb b/script/import_scripts/telligent.rb index 096a8b92c9..db8d6d696d 100644 --- a/script/import_scripts/telligent.rb +++ b/script/import_scripts/telligent.rb @@ -5,10 +5,7 @@ require 'tiny_tds' # Import script for Telligent communities # -# Users are currently imported from a temp table. This will need some -# work the next time this import script is used, because that table -# won't exist. Also, it's really hard to find all attachments, but -# the script tries to do it anyway. +# It's really hard to find all attachments, but the script tries to do it anyway. class ImportScripts::Telligent < ImportScripts::Base BATCH_SIZE ||= 1000 @@ -26,7 +23,8 @@ class ImportScripts::Telligent < ImportScripts::Base host: ENV["DB_HOST"], username: ENV["DB_USERNAME"], password: ENV["DB_PASSWORD"], - database: ENV["DB_NAME"] + database: ENV["DB_NAME"], + timeout: 60 # the user query is very slow ) end @@ -56,48 +54,64 @@ class ImportScripts::Telligent < ImportScripts::Base last_user_id = -1 total_count = count(<<~SQL) SELECT COUNT(1) AS count - FROM temp_User u + FROM cs_Users u WHERE #{user_conditions} SQL + import_count = 0 - batches do |offset| + loop do rows = query(<<~SQL) - SELECT TOP #{BATCH_SIZE} * - FROM - ( - SELECT - u.UserID, - u.Email, - u.UserName, - u.CommonName, - u.CreateDate, - p.PropertyName, - p.PropertyValue - FROM temp_User u - LEFT OUTER JOIN temp_UserProperties p ON (u.UserID = p.UserID) + SELECT * + FROM ( + SELECT TOP #{BATCH_SIZE} + u.UserID, + u.Email, + u.UserName, + u.CreateDate, + p.PropertyName, + p.PropertyValue + FROM cs_Users u + LEFT OUTER JOIN ( + SELECT NULL AS UserID, ap.UserId AS MembershipID, x.PropertyName, x.PropertyValue + FROM aspnet_Profile ap + CROSS APPLY dbo.GetProperties(ap.PropertyNames, ap.PropertyValuesString) x + WHERE ap.PropertyNames NOT LIKE '%:-1%' AND + x.PropertyName IN ('bio', 'commonName', 'location', 'webAddress') + UNION + SELECT up.UserID, NULL AS MembershipID, x.PropertyName, CAST(x.PropertyValue AS NVARCHAR) AS PropertyValue + FROM cs_UserProfile up + CROSS APPLY dbo.GetProperties(up.PropertyNames, up.PropertyValues) x + WHERE up.PropertyNames NOT LIKE '%:-1%' AND + x.PropertyName IN ('avatarUrl', 'BannedUntil', 'UserBanReason') + ) p ON p.UserID = u.UserID OR p.MembershipID = u.MembershipID WHERE u.UserID > #{last_user_id} AND #{user_conditions} - ) x - PIVOT ( - MAX(PropertyValue) - FOR PropertyName - IN (avatarUrl, bio, Location, webAddress, BannedUntil, UserBanReason) - ) y + ORDER BY u.UserID + ) x + PIVOT ( + MAX(PropertyValue) + FOR PropertyName + IN (bio, commonName, location, webAddress, avatarUrl, BannedUntil, UserBanReason) + ) Y ORDER BY UserID SQL break if rows.blank? last_user_id = rows[-1]["UserID"] - next if all_records_exist?(:users, rows.map { |row| row["UserID"] }) - create_users(rows, total: total_count, offset: offset) do |row| + if all_records_exist?(:users, rows.map { |row| row["UserID"] }) + import_count += rows.size + next + end + + create_users(rows, total: total_count, offset: import_count) do |row| { id: row["UserID"], email: row["Email"], username: row["UserName"], - name: row["CommonName"], + name: row["commonName"], created_at: row["CreateDate"], bio_raw: html_to_markdown(row["bio"]), - location: row["Location"], + location: row["location"], website: row["webAddress"], post_create_action: proc do |user| import_avatar(user, row["avatarUrl"]) @@ -105,12 +119,14 @@ class ImportScripts::Telligent < ImportScripts::Base end } end + + import_count += rows.size end end # TODO move into base importer (create_user) and use consistent error handling def import_avatar(user, avatar_url) - return if avatar_url.blank? || avatar_url.include?("anonymous") + return if ENV["FILE_BASE_DIR"].blank? || avatar_url.blank? || avatar_url.include?("anonymous") if match_data = avatar_url.match(LOCAL_AVATAR_REGEX) avatar_path = File.join(ENV["FILE_BASE_DIR"], @@ -141,11 +157,6 @@ class ImportScripts::Telligent < ImportScripts::Base end def import_categories - @new_parent_categories = {} - @new_parent_categories[:archives] = create_category({ name: "Archives" }, nil) - @new_parent_categories[:spotlight] = create_category({ name: "Spotlight" }, nil) - @new_parent_categories[:optimizer] = create_category({ name: "SQL Optimizer" }, nil) - puts "", "Importing parent categories..." parent_categories = query(<<~SQL) SELECT @@ -181,7 +192,7 @@ class ImportScripts::Telligent < ImportScripts::Base create_categories(child_categories) do |row| parent_category_id = parent_category_id_for(row) - if category_id = replace_with_category_id(row, child_categories, parent_category_id) + if category_id = replace_with_category_id(child_categories, parent_category_id) add_category(row['ForumId'], Category.find_by_id(category_id)) url = "f/#{row['ForumId']}" Permalink.create(url: url, category_id: category_id) unless Permalink.exists?(url: url) @@ -199,29 +210,11 @@ class ImportScripts::Telligent < ImportScripts::Base end def parent_category_id_for(row) - name = row["Name"].downcase - - if name.include?("beta") - @new_parent_categories[:archives].id - elsif name.include?("spotlight") - @new_parent_categories[:spotlight].id - elsif name.include?("optimizer") - @new_parent_categories[:optimizer].id - elsif row.key?("GroupId") - category_id_from_imported_category_id("G#{row['GroupId']}") - else - nil - end + category_id_from_imported_category_id("G#{row['GroupId']}") if row.key?("GroupId") end - def replace_with_category_id(row, child_categories, parent_category_id) - name = row["Name"].downcase - - if name.include?("data modeler") || name.include?("benchmark") - category_id_from_imported_category_id("G#{row['GroupId']}") - elsif only_child?(child_categories, parent_category_id) - parent_category_id - end + def replace_with_category_id(child_categories, parent_category_id) + parent_category_id if only_child?(child_categories, parent_category_id) end def only_child?(child_categories, parent_category_id) @@ -236,7 +229,6 @@ class ImportScripts::Telligent < ImportScripts::Base def clean_category_name(name) CGI.unescapeHTML(name) - .sub(/(?:\- )?Forum/i, "") .strip end @@ -360,7 +352,7 @@ class ImportScripts::Telligent < ImportScripts::Base raw = html_to_markdown(raw) || "" filename = row["FileName"] - return raw if filename.blank? + return raw if ENV["FILE_BASE_DIR"].blank? || filename.blank? path = File.join( ENV["FILE_BASE_DIR"], @@ -377,7 +369,7 @@ class ImportScripts::Telligent < ImportScripts::Base upload = @uploader.create_upload(user_id, path, filename) if upload.present? && upload.persisted? && !upload_ids.include?(upload.id) - raw << "\n" << @uploader.html_for_upload(upload, filename) + raw = "#{raw}\n#{@uploader.html_for_upload(upload, filename)}" end else STDERR.puts "Could not find file: #{path}" diff --git a/spec/components/auth/managed_authenticator_spec.rb b/spec/components/auth/managed_authenticator_spec.rb index ea788144a6..ba463ed44e 100644 --- a/spec/components/auth/managed_authenticator_spec.rb +++ b/spec/components/auth/managed_authenticator_spec.rb @@ -261,6 +261,8 @@ describe Auth::ManagedAuthenticator do expect(authenticator.description_for_user(user)).to eq('somenickname') association.update(info: { nickname: "bestname" }) expect(authenticator.description_for_user(user)).to eq('bestname') + association.update(info: {}) + expect(authenticator.description_for_user(user)).to eq(I18n.t("associated_accounts.connected")) end end diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb index c2962e4f65..b5c83c1f90 100644 --- a/spec/components/cooked_post_processor_spec.rb +++ b/spec/components/cooked_post_processor_spec.rb @@ -1082,10 +1082,11 @@ describe CookedPostProcessor do before { cpp.stubs(:available_disk_space).returns(90) } - it "does not run when download_remote_images_to_local is disabled" do + it "runs even when download_remote_images_to_local is disabled" do + # We want to run it to pull hotlinked optimized images SiteSetting.download_remote_images_to_local = false - Jobs.expects(:cancel_scheduled_job).never - cpp.pull_hotlinked_images + expect { cpp.pull_hotlinked_images }. + to change { Jobs::PullHotlinkedImages.jobs.count }.by 1 end context "when download_remote_images_to_local? is enabled" do @@ -1093,10 +1094,10 @@ describe CookedPostProcessor do SiteSetting.download_remote_images_to_local = true end - it "does not run when there is not enough disk space" do - cpp.expects(:disable_if_low_on_disk_space).returns(true) - Jobs.expects(:cancel_scheduled_job).never + it "disables download_remote_images if there is not enough disk space" do + cpp.expects(:available_disk_space).returns(5) cpp.pull_hotlinked_images + expect(SiteSetting.download_remote_images_to_local).to eq(false) end context "and there is enough disk space" do @@ -1135,11 +1136,14 @@ describe CookedPostProcessor do let(:post) { build(:post, created_at: 20.days.ago) } let(:cpp) { CookedPostProcessor.new(post) } - before { cpp.expects(:available_disk_space).returns(50) } + before do + SiteSetting.download_remote_images_to_local = true + cpp.expects(:available_disk_space).returns(50) + end it "does nothing when there's enough disk space" do SiteSetting.expects(:download_remote_images_threshold).returns(20) - SiteSetting.expects(:download_remote_images_to_local).never + SiteSetting.expects(:download_remote_images_to_local=).never expect(cpp.disable_if_low_on_disk_space).to eq(false) end @@ -1368,23 +1372,6 @@ describe CookedPostProcessor do expect(reply.raw).to eq("and this is the third reply") end - it "works with click counters" do - post = Fabricate(:post, - topic: topic, - raw: "[Discourse](https://www.discourse.org) is amazing!", - cooked: %{

Discourse 1 is amazing!

} - ) - - reply = Fabricate(:post, - topic: topic, - raw: "[quote]\n[Discourse](https://www.discourse.org) is amazing!\n[/quote]\nIt sure is :+1:" - ) - - CookedPostProcessor.new(reply).remove_full_quote_on_direct_reply - - expect(reply.raw).to eq("It sure is :+1:") - end - end end diff --git a/spec/components/crawler_detection_spec.rb b/spec/components/crawler_detection_spec.rb index 907b968eb9..7d5393342d 100644 --- a/spec/components/crawler_detection_spec.rb +++ b/spec/components/crawler_detection_spec.rb @@ -5,9 +5,9 @@ require_dependency 'crawler_detection' describe CrawlerDetection do - def crawler!(s) - if (!CrawlerDetection.crawler?(s)) - raise "#{s} should be a crawler!" + def crawler!(user_agent, via = nil) + if (!CrawlerDetection.crawler?(user_agent, via)) + raise "#{user_agent} should be a crawler!" end end @@ -50,6 +50,10 @@ describe CrawlerDetection do crawler! "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)" end + it "returns true when VIA header contains 'web.archive.org'" do + crawler!("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36", "HTTP/1.0 web.archive.org (Wayback Save Page)") + end + it "returns false for non-crawler user agents" do not_crawler! "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36" not_crawler! "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko" diff --git a/spec/components/discourse_tagging_spec.rb b/spec/components/discourse_tagging_spec.rb index 39ff18920d..8ab8b613ab 100644 --- a/spec/components/discourse_tagging_spec.rb +++ b/spec/components/discourse_tagging_spec.rb @@ -108,6 +108,22 @@ describe DiscourseTagging do end end + it 'respects category allow_global_tags setting' do + tag = Fabricate(:tag) + other_tag = Fabricate(:tag) + tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [tag.name]) + category = Fabricate(:category, allowed_tag_groups: [tag_group.name]) + other_category = Fabricate(:category, allowed_tags: [other_tag.name]) + topic = Fabricate(:topic, category: category) + + DiscourseTagging.tag_topic_by_names(topic, Guardian.new(admin), [tag.name, other_tag.name, 'hello']) + expect(topic.tags.pluck(:name)).to contain_exactly(tag.name) + + category.update!(allow_global_tags: true) + DiscourseTagging.tag_topic_by_names(topic, Guardian.new(admin), [tag.name, other_tag.name, 'hello']) + expect(topic.tags.pluck(:name)).to contain_exactly(tag.name, 'hello') + end + context 'respects category minimum_required_tags setting' do fab!(:category) { Fabricate(:category, minimum_required_tags: 2) } fab!(:topic) { Fabricate(:topic, category: category) } diff --git a/spec/components/email/receiver_spec.rb b/spec/components/email/receiver_spec.rb index c288a0e804..e794579e48 100644 --- a/spec/components/email/receiver_spec.rb +++ b/spec/components/email/receiver_spec.rb @@ -298,6 +298,13 @@ describe Email::Receiver do expect(post.user).to eq(user) end + it "raises a ReplyNotAllowedError when user without permissions is replying" do + Fabricate(:user, email: "bob@bar.com") + category.set_permissions(admins: :full) + category.save + expect { process(:reply_user_not_matching_but_known) }.to raise_error(Email::Receiver::ReplyNotAllowedError) + end + it "raises a TopicNotFoundError when the topic was deleted" do topic.update_columns(deleted_at: 1.day.ago) expect { process(:reply_user_matching) }.to raise_error(Email::Receiver::TopicNotFoundError) @@ -444,7 +451,7 @@ describe Email::Receiver do category.email_in = "category@bar.com" category.email_in_allow_strangers = true category.set_permissions(Group[:trust_level_4] => :full) - category.save + category.save! expect { process(:staged_reply_restricted) }.to change { topic.posts.count } end @@ -853,7 +860,7 @@ describe Email::Receiver do group.save category.set_permissions(group => :create_post) - category.save + category.save! # raises an InvalidAccess when the user doesn't have the privileges to create a topic expect { process(:existing_user) }.to raise_error(Discourse::InvalidAccess) @@ -926,7 +933,7 @@ describe Email::Receiver do Fabricate(:user, email: "tl4@bar.com", trust_level: TrustLevel[4]) category.set_permissions(Group[:trust_level_4] => :full) - category.save + category.save! Group.refresh_automatic_group!(:trust_level_4) @@ -1257,7 +1264,7 @@ describe Email::Receiver do context "read-only category" do before do category.set_permissions(everyone: :readonly) - category.save + category.save! Fabricate(:user, email: "alice@foo.com") Fabricate(:user, email: "bob@bar.com") diff --git a/spec/components/file_store/base_store_spec.rb b/spec/components/file_store/base_store_spec.rb index 5ef2579747..3c2e30eef9 100644 --- a/spec/components/file_store/base_store_spec.rb +++ b/spec/components/file_store/base_store_spec.rb @@ -59,13 +59,12 @@ RSpec.describe FileStore::BaseStore do end let(:upload_s3) { Fabricate(:upload_s3) } + let(:store) { FileStore::BaseStore.new } it "should return consistent encodings for fresh and cached downloads" do # Net::HTTP always returns binary ASCII-8BIT encoding. File.read auto-detects the encoding # Make sure we File.read after downloading a file for consistency - store = FileStore::BaseStore.new - first_encoding = store.download(upload_s3).read.encoding second_encoding = store.download(upload_s3).read.encoding @@ -73,5 +72,20 @@ RSpec.describe FileStore::BaseStore do expect(first_encoding).to eq(Encoding::UTF_8) expect(second_encoding).to eq(Encoding::UTF_8) end + + it "should return the file" do + file = store.download(upload_s3) + + expect(file.class).to eq(File) + end + + it "should return the file when s3 cdn enabled" do + SiteSetting.s3_cdn_url = "https://cdn.s3.amazonaws.com" + stub_request(:get, Discourse.store.cdn_url(upload_s3.url)).to_return(status: 200, body: "Hello world") + + file = store.download(upload_s3) + + expect(file.class).to eq(File) + end end end diff --git a/spec/components/file_store/s3_store_spec.rb b/spec/components/file_store/s3_store_spec.rb index 65d132b3b7..0399efcedb 100644 --- a/spec/components/file_store/s3_store_spec.rb +++ b/spec/components/file_store/s3_store_spec.rb @@ -77,6 +77,37 @@ describe FileStore::S3Store do expect(upload.etag).to eq(etag) end end + + describe "when private uploads are enabled" do + it "returns signed URL for eligible private upload" do + SiteSetting.prevent_anons_from_downloading_files = true + SiteSetting.authorized_extensions = "pdf|png|jpg|gif" + upload.update!(original_filename: "small.pdf", extension: "pdf") + + s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once + s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.pdf").returns(s3_object).at_least_once + s3_object.expects(:presigned_url).with(:get, expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS) + + expect(store.store_upload(uploaded_file, upload)).to eq( + "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/original/1X/#{upload.sha1}.pdf" + ) + + expect(store.url_for(upload)).not_to eq(upload.url) + end + + it "returns regular URL for ineligible private upload" do + SiteSetting.prevent_anons_from_downloading_files = true + + s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once + s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.png").returns(s3_object).at_least_once + + expect(store.store_upload(uploaded_file, upload)).to eq( + "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/original/1X/#{upload.sha1}.png" + ) + + expect(store.url_for(upload)).to eq(upload.url) + end + end end describe "#store_optimized_image" do @@ -320,6 +351,33 @@ describe FileStore::S3Store do end end + context 'update ACL' do + include_context "s3 helpers" + let(:s3_object) { stub } + + describe ".update_upload_ACL" do + it "sets acl to private when private uploads are enabled" do + SiteSetting.prevent_anons_from_downloading_files = true + s3_helper.expects(:s3_bucket).returns(s3_bucket) + s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.png").returns(s3_object) + s3_object.expects(:acl).returns(s3_object) + s3_object.expects(:put).with(acl: "private").returns(s3_object) + + expect(store.update_upload_ACL(upload)).to be_truthy + end + + it "sets acl to public when private uploads are disabled" do + SiteSetting.prevent_anons_from_downloading_files = false + s3_helper.expects(:s3_bucket).returns(s3_bucket) + s3_bucket.expects(:object).with("original/1X/#{upload.sha1}.png").returns(s3_object) + s3_object.expects(:acl).returns(s3_object) + s3_object.expects(:put).with(acl: "public-read").returns(s3_object) + + expect(store.update_upload_ACL(upload)).to be_truthy + end + end + end + describe '.cdn_url' do it 'supports subfolder' do diff --git a/spec/components/freedom_patches/translate_accelerator_spec.rb b/spec/components/freedom_patches/translate_accelerator_spec.rb index 9e3de03585..fc12655881 100644 --- a/spec/components/freedom_patches/translate_accelerator_spec.rb +++ b/spec/components/freedom_patches/translate_accelerator_spec.rb @@ -24,6 +24,28 @@ describe "translate accelerator" do expect(override.persisted?).to eq(true) end + it "supports raising if requested, and cache bypasses" do + expect { I18n.t('i_am_an_unknown_key99', raise: true) }.to raise_error(I18n::MissingTranslationData) + + orig = I18n.t('i_am_an_unknown_key99') + + expect(I18n.t('i_am_an_unknown_key99').object_id).to eq(orig.object_id) + expect(I18n.t('i_am_an_unknown_key99')).to eq("translation missing: en_US.i_am_an_unknown_key99") + end + + it "returns the correct language" do + expect(I18n.t('foo', locale: :en)).to eq('Foo in :en') + expect(I18n.t('foo', locale: :de)).to eq('Foo in :de') + + I18n.with_locale(:en) do + expect(I18n.t('foo')).to eq('Foo in :en') + end + + I18n.with_locale(:de) do + expect(I18n.t('foo')).to eq('Foo in :de') + end + end + it "overrides for both string and symbol keys" do key = 'user.email.not_allowed' text_overriden = 'foobar' @@ -124,6 +146,7 @@ describe "translate accelerator" do I18n.overrides_disabled do expect(I18n.t('title')).to eq(orig_title) end + expect(I18n.t('title')).to eq('overridden title') end diff --git a/spec/components/middleware/request_tracker_spec.rb b/spec/components/middleware/request_tracker_spec.rb index ca8e1b67dd..ae73bbaa03 100644 --- a/spec/components/middleware/request_tracker_spec.rb +++ b/spec/components/middleware/request_tracker_spec.rb @@ -274,6 +274,8 @@ describe Middleware::RequestTracker do it "can correctly log detailed data" do + global_setting :enable_performance_http_headers, true + # ensure pg is warmed up with the select 1 query User.where(id: -100).pluck(:id) @@ -283,7 +285,7 @@ describe Middleware::RequestTracker do freeze_time 1.minute.from_now tracker = Middleware::RequestTracker.new(app([200, {}, []], sql_calls: 2, redis_calls: 2)) - tracker.call(env("HTTP_X_REQUEST_START" => "t=#{start}")) + _, headers, _ = tracker.call(env("HTTP_X_REQUEST_START" => "t=#{start}")) expect(@data[:queue_seconds]).to eq(60) @@ -295,6 +297,16 @@ describe Middleware::RequestTracker do expect(timing[:redis][:duration]).to be > 0 expect(timing[:redis][:calls]).to eq 2 + + expect(headers["X-Queue-Time"]).to eq("60.000000") + + expect(headers["X-Redis-Calls"]).to eq("2") + expect(headers["X-Redis-Time"].to_f).to be > 0 + + expect(headers["X-Sql-Calls"]).to eq("2") + expect(headers["X-Sql-Time"].to_f).to be > 0 + + expect(headers["X-Runtime"].to_f).to be > 0 end end diff --git a/spec/components/plain_text_to_markdown_spec.rb b/spec/components/plain_text_to_markdown_spec.rb index afccb0f879..6cdbff949b 100644 --- a/spec/components/plain_text_to_markdown_spec.rb +++ b/spec/components/plain_text_to_markdown_spec.rb @@ -160,6 +160,9 @@ describe PlainTextToMarkdown do context "links" do it "removes duplicate links" do + expect(to_markdown("foo https://www.example.com/foo.html?a=1 bar")) + .to eq("foo https://www.example.com/foo.html?a=1 bar") + expect(to_markdown("foo https://www.example.com/foo.html bar")) .to eq("foo https://www.example.com/foo.html bar") @@ -174,6 +177,14 @@ describe PlainTextToMarkdown do expect(to_markdown("foo https://www.example.com/foo.html bar https://www.example.com/foo.html baz")) .to eq("foo https://www.example.com/foo.html bar https://www.example.com/foo.html baz") end + + it "does not explode with weird links" do + expect { + Timeout::timeout(0.25) { + to_markdown("https://www.discourse.org/?boom=#{"." * 20}") + } + }.not_to raise_error + end end context "code" do diff --git a/spec/components/post_revisor_spec.rb b/spec/components/post_revisor_spec.rb index 1cd9ddea2f..ed4f2887e7 100644 --- a/spec/components/post_revisor_spec.rb +++ b/spec/components/post_revisor_spec.rb @@ -754,10 +754,33 @@ describe PostRevisor do }.to_not change { topic.reload.bumped_at } end - it "doesn't create revision" do + it "doesn't bump topic if only staff-only tags are removed and there are no tags left" do + topic.tags = Tag.where(name: ['important', 'secret']).to_a + expect { + result = subject.revise!(Fabricate(:admin), raw: post.raw, tags: []) + expect(result).to eq(true) + }.to_not change { topic.reload.bumped_at } + end + + it "doesn't bump topic if empty string is given" do + topic.tags = Tag.where(name: ['important', 'secret']).to_a + expect { + result = subject.revise!(Fabricate(:admin), raw: post.raw, tags: [""]) + expect(result).to eq(true) + }.to_not change { topic.reload.bumped_at } + end + + it "creates a hidden revision" do + subject.revise!(Fabricate(:admin), raw: post.raw, tags: topic.tags.map(&:name) + ['secret']) + expect(post.reload.revisions.first.hidden).to eq(true) + end + + it "doesn't notify topic owner about hidden tags" do + PostActionNotifier.enable + Jobs.run_immediately! expect { subject.revise!(Fabricate(:admin), raw: post.raw, tags: topic.tags.map(&:name) + ['secret']) - }.to_not change { post.reload.revisions.size } + }.not_to change { Notification.where(notification_type: Notification.types[:edited]).count } end end diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index 71da86ead1..0b9d317d89 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -1224,7 +1224,7 @@ HTML ![](http://png.com/my.png) ![|220x100](http://png.com/my.png) ![stuff](http://png.com/my.png) - ![|220x100,50%](http://png.com/my.png) + ![|220x100,50%](http://png.com/my.png "some title") MD html = <<~HTML @@ -1232,7 +1232,7 @@ HTML

stuff
-

+

HTML expect(cooked).to eq(html.strip) diff --git a/spec/components/s3_helper_spec.rb b/spec/components/s3_helper_spec.rb index a903c610e7..789c61a071 100644 --- a/spec/components/s3_helper_spec.rb +++ b/spec/components/s3_helper_spec.rb @@ -4,6 +4,8 @@ require "s3_helper" require "rails_helper" describe "S3Helper" do + let(:client) { Aws::S3::Client.new(stub_responses: true) } + before(:each) do SiteSetting.enable_s3_uploads = true SiteSetting.s3_access_key_id = "abc" @@ -70,12 +72,19 @@ describe "S3Helper" do 'some/bucket' => 'bucket/testing', 'some' => 'testing' }.each do |bucket_name, prefix| - s3_helper = S3Helper.new(bucket_name) - bucket = stub('s3_bucket') - s3_helper.expects(:s3_bucket).returns(bucket) - bucket.expects(:objects).with(prefix: prefix) + s3_helper = S3Helper.new(bucket_name, "", client: client) + Aws::S3::Bucket.any_instance.expects(:objects).with(prefix: prefix) s3_helper.list('testing') end end end + + it "should prefix bucket folder path only if not exists" do + s3_helper = S3Helper.new('bucket/folder_path', "", client: client) + + object1 = s3_helper.object("original/1X/def.xyz") + object2 = s3_helper.object("folder_path/original/1X/def.xyz") + + expect(object1.key).to eq(object2.key) + end end diff --git a/spec/components/site_setting_extension_spec.rb b/spec/components/site_setting_extension_spec.rb index 9b225f0b69..809a1b77c8 100644 --- a/spec/components/site_setting_extension_spec.rb +++ b/spec/components/site_setting_extension_spec.rb @@ -143,6 +143,25 @@ describe SiteSettingExtension do expect(settings.upload_type).to eq(upload2) end + + it "refreshes the client_settings_json cache" do + upload = Fabricate(:upload) + settings.setting(:upload_type, upload.id.to_s, type: :upload, client: true) + settings.setting(:string_type, 'haha', client: true) + settings.refresh! + + expect(settings.client_settings_json).to eq( + %Q|{"default_locale":"#{SiteSetting.default_locale}","upload_type":"#{upload.url}","string_type":"haha"}| + ) + + upload.update!(url: "a_new_url") + settings.string_type = "changed" + settings.refresh! + + expect(settings.client_settings_json).to eq( + %Q|{"default_locale":"#{SiteSetting.default_locale}","upload_type":"a_new_url","string_type":"changed"}| + ) + end end describe "multisite" do diff --git a/spec/components/theme_store/git_importer_spec.rb b/spec/components/theme_store/git_importer_spec.rb index 0df9836f90..46783d5c38 100644 --- a/spec/components/theme_store/git_importer_spec.rb +++ b/spec/components/theme_store/git_importer_spec.rb @@ -10,6 +10,7 @@ describe ThemeStore::GitImporter do context "#import" do let(:url) { "https://github.com/example/example.git" } + let(:trailing_slash_url) { "https://github.com/example/example/" } let(:ssh_url) { "git@github.com:example/example.git" } let(:branch) { "dev" } @@ -27,6 +28,13 @@ describe ThemeStore::GitImporter do importer.import! end + it "should work with trailing slash url" do + Discourse::Utils.expects(:execute_command).with("git", "clone", url, @temp_folder) + + importer = ThemeStore::GitImporter.new(trailing_slash_url) + importer.import! + end + it "should import from ssh url" do Discourse::Utils.expects(:execute_command).with({ 'GIT_SSH_COMMAND' => "ssh -i #{@ssh_folder}/id_rsa -o StrictHostKeyChecking=no" diff --git a/spec/components/topic_query_spec.rb b/spec/components/topic_query_spec.rb index 56b8b6ce12..273c64081d 100644 --- a/spec/components/topic_query_spec.rb +++ b/spec/components/topic_query_spec.rb @@ -244,7 +244,7 @@ describe TopicQuery do context 'muted tags' do it 'is removed from new and latest lists' do SiteSetting.tagging_enabled = true - SiteSetting.remove_muted_tags_from_latest = true + SiteSetting.remove_muted_tags_from_latest = 'always' muted_tag, other_tag = Fabricate(:tag), Fabricate(:tag) @@ -263,13 +263,21 @@ describe TopicQuery do topic_ids = topic_query.list_new.topics.map(&:id) expect(topic_ids).to contain_exactly(tagged_topic.id, untagged_topic.id) - SiteSetting.mute_other_present_tags = false + SiteSetting.remove_muted_tags_from_latest = 'only_muted' topic_ids = topic_query.list_latest.topics.map(&:id) - expect(topic_ids).to contain_exactly(muted_tagged_topic.id, tagged_topic.id, untagged_topic.id) + expect(topic_ids).to contain_exactly(tagged_topic.id, muted_tagged_topic.id, untagged_topic.id) topic_ids = topic_query.list_new.topics.map(&:id) - expect(topic_ids).to contain_exactly(muted_tagged_topic.id, tagged_topic.id, untagged_topic.id) + expect(topic_ids).to contain_exactly(tagged_topic.id, muted_tagged_topic.id, untagged_topic.id) + + SiteSetting.remove_muted_tags_from_latest = 'never' + + topic_ids = topic_query.list_latest.topics.map(&:id) + expect(topic_ids).to contain_exactly(muted_topic.id, tagged_topic.id, muted_tagged_topic.id, untagged_topic.id) + + topic_ids = topic_query.list_new.topics.map(&:id) + expect(topic_ids).to contain_exactly(muted_topic.id, tagged_topic.id, muted_tagged_topic.id, untagged_topic.id) end end diff --git a/spec/fabricators/upload_fabricator.rb b/spec/fabricators/upload_fabricator.rb index 4c9f775fd4..8c54009caf 100644 --- a/spec/fabricators/upload_fabricator.rb +++ b/spec/fabricators/upload_fabricator.rb @@ -24,12 +24,15 @@ end Fabricator(:upload_s3, from: :upload) do url do |attrs| sequence(:url) do |n| - File.join( - Discourse.store.absolute_base_url, - Discourse.store.get_path_for( - "original", n + 1, attrs[:sha1], ".#{attrs[:extension]}" - ) + path = +Discourse.store.get_path_for( + "original", n + 1, attrs[:sha1], ".#{attrs[:extension]}" ) + + if Rails.configuration.multisite + path.prepend(File.join(Discourse.store.upload_path, "/")) + end + + File.join(Discourse.store.absolute_base_url, path) end end end diff --git a/spec/fixtures/csv/bulk_invite.csv b/spec/fixtures/csv/bulk_invite.csv deleted file mode 100644 index 9d0d3b0a61..0000000000 --- a/spec/fixtures/csv/bulk_invite.csv +++ /dev/null @@ -1,2 +0,0 @@ -test2@discourse.org -test@discourse.org,GROUP1;group2,999 diff --git a/spec/fixtures/i18n/translate_accelerator.de.yml b/spec/fixtures/i18n/translate_accelerator.de.yml index c583d4b340..8b1e01530f 100644 --- a/spec/fixtures/i18n/translate_accelerator.de.yml +++ b/spec/fixtures/i18n/translate_accelerator.de.yml @@ -1,4 +1,4 @@ -en: - foo: 'Foo in :de' - bar: 'Bar in :de' +de: + foo: "Foo in :de" + bar: "Bar in :de" wat: "Hello %{count}" diff --git a/spec/fixtures/i18n/translate_accelerator.en.yml b/spec/fixtures/i18n/translate_accelerator.en.yml index dd2bee491f..f80233dc69 100644 --- a/spec/fixtures/i18n/translate_accelerator.en.yml +++ b/spec/fixtures/i18n/translate_accelerator.en.yml @@ -1,7 +1,7 @@ en: got: "winter" - foo: 'Foo in :en' - bar: 'Bar in :en' + foo: "Foo in :en" + bar: "Bar in :en" wat: "Hello %{count}" world: "Hello %{world}" items: diff --git a/spec/integration/watched_words_spec.rb b/spec/integration/watched_words_spec.rb index 2e3853be08..2569247e12 100644 --- a/spec/integration/watched_words_spec.rb +++ b/spec/integration/watched_words_spec.rb @@ -14,8 +14,12 @@ describe WatchedWord do let(:flag_word) { Fabricate(:watched_word, action: WatchedWord.actions[:flag]) } let(:block_word) { Fabricate(:watched_word, action: WatchedWord.actions[:block]) } + before_all do + WordWatcher.clear_cache! + end + after do - $redis.flushall + WordWatcher.clear_cache! end context "block" do diff --git a/spec/jobs/bulk_invite_spec.rb b/spec/jobs/bulk_invite_spec.rb index fb4e49de34..d271df7ba8 100644 --- a/spec/jobs/bulk_invite_spec.rb +++ b/spec/jobs/bulk_invite_spec.rb @@ -10,30 +10,22 @@ describe Jobs::BulkInvite do fab!(:group2) { Fabricate(:group, name: 'group2') } fab!(:topic) { Fabricate(:topic, id: 999) } let(:email) { "test@discourse.org" } - let(:basename) { "bulk_invite.csv" } - let(:filename) { "#{Invite.base_directory}/#{basename}" } + let(:invites) { [{ email: 'test2@discourse.org' }, { email: 'test@discourse.org', groups: 'GROUP1;group2', topic_id: 999 }] } - before do - Invite.create_csv( - fixture_file_upload("#{Rails.root}/spec/fixtures/csv/#{basename}"), - "bulk_invite" - ) - end - - it 'raises an error when the filename is missing' do + it 'raises an error when the invites array is missing' do expect { Jobs::BulkInvite.new.execute(current_user_id: user.id) } - .to raise_error(Discourse::InvalidParameters, /filename/) + .to raise_error(Discourse::InvalidParameters, /invites/) end it 'raises an error when current_user_id is not valid' do - expect { Jobs::BulkInvite.new.execute(filename: filename) } + expect { Jobs::BulkInvite.new.execute(invites: invites) } .to raise_error(Discourse::InvalidParameters, /current_user_id/) end it 'creates the right invites' do described_class.new.execute( current_user_id: admin.id, - filename: basename, + invites: invites ) invite = Invite.last @@ -53,7 +45,7 @@ describe Jobs::BulkInvite do described_class.new.execute( current_user_id: admin.id, - filename: basename, + invites: invites ) invite = Invite.last @@ -70,7 +62,7 @@ describe Jobs::BulkInvite do described_class.new.execute( current_user_id: user.id, - filename: basename + invites: invites ) invite = Invite.last @@ -90,7 +82,7 @@ describe Jobs::BulkInvite do expect do described_class.new.execute( current_user_id: admin.id, - filename: basename + invites: invites ) end.to change { Invite.count }.by(1) diff --git a/spec/jobs/pull_hotlinked_images_spec.rb b/spec/jobs/pull_hotlinked_images_spec.rb index f87bc9ba93..446adea973 100644 --- a/spec/jobs/pull_hotlinked_images_spec.rb +++ b/spec/jobs/pull_hotlinked_images_spec.rb @@ -15,14 +15,29 @@ describe Jobs::PullHotlinkedImages do stub_request(:get, image_url).to_return(body: png, headers: { "Content-Type" => "image/png" }) stub_request(:get, broken_image_url).to_return(status: 404) stub_request(:get, large_image_url).to_return(body: large_png, headers: { "Content-Type" => "image/png" }) + + stub_request( + :get, + "#{Discourse.base_url}/uploads/default/original/1X/f59ea56fe8ebe42048491d43a19d9f34c5d0f8dc.gif" + ) + + stub_request( + :get, + "#{Discourse.base_url}/uploads/default/original/1X/c530c06cf89c410c0355d7852644a73fc3ec8c04.png" + ) + SiteSetting.crawl_images = true SiteSetting.download_remote_images_to_local = true SiteSetting.max_image_size_kb = 2 SiteSetting.download_remote_images_threshold = 0 end - describe "#nochange" do - it 'does saves nothing if there are no large images to pull' do + describe '#execute' do + before do + Jobs.run_immediately! + end + + it 'does nothing if there are no large images to pull' do post = Fabricate(:post, raw: 'bob bob') orig = post.updated_at @@ -30,13 +45,6 @@ describe Jobs::PullHotlinkedImages do Jobs::PullHotlinkedImages.new.execute(post_id: post.id) expect(orig).to be_within(1.second).of(post.reload.updated_at) end - end - - describe '#execute' do - before do - Jobs.run_immediately! - FastImage.expects(:size).returns([100, 100]).at_least_once - end it 'replaces images' do post = Fabricate(:post, raw: "") @@ -183,6 +191,11 @@ describe Jobs::PullHotlinkedImages do expect(subject.should_download_image?(src)).to eq(true) end + it "returns false for emoji" do + src = Emoji.url_for("testemoji.png") + expect(subject.should_download_image?(src)).to eq(false) + end + it 'returns false for valid remote URLs' do expect(subject.should_download_image?("http://meta.discourse.org")).to eq(false) end diff --git a/spec/jobs/reindex_search_spec.rb b/spec/jobs/reindex_search_spec.rb index b7f5d8333c..c5ce22c2b4 100644 --- a/spec/jobs/reindex_search_spec.rb +++ b/spec/jobs/reindex_search_spec.rb @@ -115,21 +115,25 @@ describe Jobs::ReindexSearch do post2.save!(validate: false) post3 = Fabricate(:post) post3.topic.trash! - post4 = nil + post4, post5, post6 = nil freeze_time(described_class::CLEANUP_GRACE_PERIOD) do post4 = Fabricate(:post) post4.topic.trash! + + post5 = Fabricate(:post) + post6 = Fabricate(:post, topic_id: post5.topic_id) + post6.trash! end - expect { subject.execute({}) }.to change { PostSearchData.count }.by(-2) + expect { subject.execute({}) }.to change { PostSearchData.count }.by(-3) expect(Post.pluck(:id)).to contain_exactly( - post.id, post2.id, post3.id, post4.id + post.id, post2.id, post3.id, post4.id, post5.id ) expect(PostSearchData.pluck(:post_id)).to contain_exactly( - post.post_search_data.post_id, post3.post_search_data.post_id + post.id, post3.id, post5.id ) end end diff --git a/spec/lib/backup_restore/s3_backup_store_spec.rb b/spec/lib/backup_restore/s3_backup_store_spec.rb index 52251284cc..312c396fa3 100644 --- a/spec/lib/backup_restore/s3_backup_store_spec.rb +++ b/spec/lib/backup_restore/s3_backup_store_spec.rb @@ -131,7 +131,7 @@ describe BackupRestore::S3BackupStore do bucket = Regexp.escape(SiteSetting.s3_backup_bucket) prefix = file_prefix(db_name, multisite) filename = Regexp.escape(filename) - expires = BackupRestore::S3BackupStore::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS + expires = S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS /\Ahttps:\/\/#{bucket}.*#{prefix}\/#{filename}\?.*X-Amz-Expires=#{expires}.*X-Amz-Signature=.*\z/ end diff --git a/spec/lib/i18n/fallback_locale_list_spec.rb b/spec/lib/i18n/fallback_locale_list_spec.rb index 004ebfc0e5..793ac8b2a0 100644 --- a/spec/lib/i18n/fallback_locale_list_spec.rb +++ b/spec/lib/i18n/fallback_locale_list_spec.rb @@ -27,6 +27,7 @@ describe I18n::Backend::FallbackLocaleList do expect(list[:ru]).to eq([:ru, :de, :en]) expect(list[:de]).to eq([:de, :en]) expect(list[:en]).to eq([:en]) + expect(list[:en_US]).to eq([:en_US, :en]) end context "when plugin registered fallback locale" do diff --git a/spec/lib/upload_creator_spec.rb b/spec/lib/upload_creator_spec.rb index 1225873778..5754a5c6cb 100644 --- a/spec/lib/upload_creator_spec.rb +++ b/spec/lib/upload_creator_spec.rb @@ -170,9 +170,44 @@ RSpec.describe UploadCreator do end end + describe 'private uploads' do + let(:filename) { "small.pdf" } + let(:file) { file_from_fixtures(filename, "pdf") } + + before do + SiteSetting.prevent_anons_from_downloading_files = true + SiteSetting.authorized_extensions = 'pdf|svg|jpg' + end + + it 'should mark uploads as private' do + upload = UploadCreator.new(file, filename).create_for(user.id) + stored_upload = Upload.last + + expect(stored_upload.private?).to eq(true) + end + + it 'should not mark theme uploads as private' do + fname = "custom-theme-icon-sprite.svg" + upload = UploadCreator.new(file_from_fixtures(fname), fname, for_theme: true).create_for(-1) + + expect(upload.private?).to eq(false) + end + + it 'should not mark image uploads as private' do + fname = "logo.jpg" + upload = UploadCreator.new(file_from_fixtures(fname), fname).create_for(user.id) + stored_upload = Upload.last + + expect(stored_upload.original_filename).to eq(fname) + expect(stored_upload.private?).to eq(false) + end + end + describe 'uploading to s3' do let(:filename) { "should_be_jpeg.png" } let(:file) { file_from_fixtures(filename) } + let(:pdf_filename) { "small.pdf" } + let(:pdf_file) { file_from_fixtures(pdf_filename, "pdf") } before do SiteSetting.s3_upload_bucket = "s3-upload-bucket" @@ -197,6 +232,19 @@ RSpec.describe UploadCreator do expect(upload.etag).to eq('ETag') end + + it 'should return signed URL for private uploads in S3' do + SiteSetting.prevent_anons_from_downloading_files = true + SiteSetting.authorized_extensions = 'pdf' + + upload = UploadCreator.new(pdf_file, pdf_filename).create_for(user.id) + stored_upload = Upload.last + signed_url = Discourse.store.url_for(stored_upload) + + expect(stored_upload.private?).to eq(true) + expect(stored_upload.url).not_to eq(signed_url) + expect(signed_url).to match(/Amz-Credential/) + end end end end diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb index 39bce54d18..18cf7ce280 100644 --- a/spec/models/category_spec.rb +++ b/spec/models/category_spec.rb @@ -890,4 +890,37 @@ describe Category do end end + describe "#ensure_consistency!" do + it "creates category topic" do + + # corrupt a category topic + uncategorized = Category.find(SiteSetting.uncategorized_category_id) + uncategorized.create_category_definition + uncategorized.topic.posts.first.destroy! + + # make stuff extra broken + uncategorized.topic.trash! + + category = Fabricate(:category) + category_destroyed = Fabricate(:category) + category_trashed = Fabricate(:category) + + category_topic_id = category.topic.id + category_destroyed.topic.destroy! + category_trashed.topic.trash! + + Category.ensure_consistency! + # step one fix corruption + expect(uncategorized.reload.topic_id).to eq(nil) + + Category.ensure_consistency! + # step two don't create a category definition for uncategorized + expect(uncategorized.reload.topic_id).to eq(nil) + + expect(category.reload.topic_id).to eq(category_topic_id) + expect(category_destroyed.reload.topic).to_not eq(nil) + expect(category_trashed.reload.topic).to_not eq(nil) + end + end + end diff --git a/spec/models/locale_site_setting_spec.rb b/spec/models/locale_site_setting_spec.rb index 1addea5c94..75fedf7568 100644 --- a/spec/models/locale_site_setting_spec.rb +++ b/spec/models/locale_site_setting_spec.rb @@ -50,6 +50,7 @@ describe LocaleSiteSetting do after do DiscoursePluginRegistry.reset! + LocaleSiteSetting.reset! end describe '.valid_value?' do diff --git a/spec/models/remote_theme_spec.rb b/spec/models/remote_theme_spec.rb index efdf798a57..83d5a3f2b0 100644 --- a/spec/models/remote_theme_spec.rb +++ b/spec/models/remote_theme_spec.rb @@ -11,7 +11,7 @@ describe RemoteTheme do `cd #{repo_dir} && git init . ` `cd #{repo_dir} && git config user.email 'someone@cool.com'` `cd #{repo_dir} && git config user.name 'The Cool One'` - `cd #{repo_dir} && mkdir desktop mobile common assets locales scss` + `cd #{repo_dir} && mkdir desktop mobile common assets locales scss stylesheets` files.each do |name, data| File.write("#{repo_dir}/#{name}", data) `cd #{repo_dir} && git add #{name}` @@ -48,8 +48,9 @@ describe RemoteTheme do setup_git_repo( "about.json" => about_json, "desktop/desktop.scss" => scss_data, - "scss/file.scss" => ".class1{color:red}", - "scss/empty.scss" => "", + "scss/oldpath.scss" => ".class2{color:blue}", + "stylesheets/file.scss" => ".class1{color:red}", + "stylesheets/empty.scss" => "", "common/header.html" => "I AM HEADER", "common/random.html" => "I AM SILLY", "common/embedded.scss" => "EMBED", @@ -81,7 +82,7 @@ describe RemoteTheme do expect(remote.theme_version).to eq("1.0") expect(remote.minimum_discourse_version).to eq("1.0.0") - expect(@theme.theme_fields.length).to eq(7) + expect(@theme.theme_fields.length).to eq(8) mapped = Hash[*@theme.theme_fields.map { |f| ["#{f.target_id}-#{f.name}", f.value] }.flatten] expect(mapped["0-header"]).to eq("I AM HEADER") @@ -94,7 +95,7 @@ describe RemoteTheme do expect(mapped["4-en"]).to eq("sometranslations") - expect(mapped.length).to eq(7) + expect(mapped.length).to eq(8) expect(@theme.settings.length).to eq(1) expect(@theme.settings.first.value).to eq(true) @@ -115,7 +116,7 @@ describe RemoteTheme do `cd #{initial_repo} && git add settings.yml` File.delete("#{initial_repo}/settings.yaml") - File.delete("#{initial_repo}/scss/file.scss") + File.delete("#{initial_repo}/stylesheets/file.scss") `cd #{initial_repo} && git commit -am "update"` time = Time.new('2001') diff --git a/spec/models/reviewable_flagged_post_spec.rb b/spec/models/reviewable_flagged_post_spec.rb index 59f447929b..52413d16f5 100644 --- a/spec/models/reviewable_flagged_post_spec.rb +++ b/spec/models/reviewable_flagged_post_spec.rb @@ -44,6 +44,16 @@ RSpec.describe ReviewableFlaggedPost, type: :model do expect(actions.has?(:disagree_and_restore)).to eq(false) end + it "doesn't include deletes for category topics" do + c = Fabricate(:category) + flag = PostActionCreator.spam(user, c.topic.posts.first).reviewable + actions = flag.actions_for(guardian) + expect(actions.has?(:delete_and_ignore)).to eq(false) + expect(actions.has?(:delete_and_ignore_replies)).to eq(false) + expect(actions.has?(:delete_and_agree)).to eq(false) + expect(actions.has?(:delete_and_replies)).to eq(false) + end + it "returns `agree_and_restore` if the post is user deleted" do post.update(user_deleted: true) expect(reviewable.actions_for(guardian).has?(:agree_and_restore)).to eq(true) diff --git a/spec/models/reviewable_spec.rb b/spec/models/reviewable_spec.rb index afcbcdfe98..3cc85cf55a 100644 --- a/spec/models/reviewable_spec.rb +++ b/spec/models/reviewable_spec.rb @@ -151,11 +151,30 @@ RSpec.describe Reviewable, type: :model do it 'Does not filter by status when status parameter is set to all' do rejected_reviewable = Fabricate(:reviewable, target: post, status: Reviewable.statuses[:rejected]) - reviewables = Reviewable.list_for(user, status: :all) - expect(reviewables).to match_array [reviewable, rejected_reviewable] end + + it "supports sorting" do + r0 = Fabricate(:reviewable, score: 100, created_at: 3.months.ago) + r1 = Fabricate(:reviewable, score: 999, created_at: 1.month.ago) + + list = Reviewable.list_for(user, sort_order: 'priority') + expect(list[0].id).to eq(r1.id) + expect(list[1].id).to eq(r0.id) + + list = Reviewable.list_for(user, sort_order: 'priority_asc') + expect(list[0].id).to eq(r0.id) + expect(list[1].id).to eq(r1.id) + + list = Reviewable.list_for(user, sort_order: 'created_at') + expect(list[0].id).to eq(r1.id) + expect(list[1].id).to eq(r0.id) + + list = Reviewable.list_for(user, sort_order: 'created_at_asc') + expect(list[0].id).to eq(r0.id) + expect(list[1].id).to eq(r1.id) + end end end diff --git a/spec/models/theme_field_spec.rb b/spec/models/theme_field_spec.rb index e7489a1dc2..87e182c7f6 100644 --- a/spec/models/theme_field_spec.rb +++ b/spec/models/theme_field_spec.rb @@ -147,6 +147,40 @@ HTML expect(result).to include(".class5") end + it "correctly handles extra JS fields" do + theme = Fabricate(:theme) + js_field = theme.set_field(target: :extra_js, name: "discourse/controllers/discovery.js.es6", value: "import 'discourse/lib/ajax'; console.log('hello');") + hbs_field = theme.set_field(target: :extra_js, name: "discourse/templates/discovery.hbs", value: "{{hello-world}}") + raw_hbs_field = theme.set_field(target: :extra_js, name: "discourse/templates/discovery.raw.hbs", value: "{{hello-world}}") + unknown_field = theme.set_field(target: :extra_js, name: "discourse/controllers/discovery.blah", value: "this wont work") + theme.save! + + expected_js = <<~JS + define("discourse/controllers/discovery", ["discourse/lib/ajax"], function () { + "use strict"; + + var __theme_name__ = "#{theme.name}"; + var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id}); + var themePrefix = function themePrefix(key) { + return "theme_translations.#{theme.id}." + key; + }; + console.log('hello'); + }); + JS + expect(js_field.reload.value_baked).to eq(expected_js.strip) + + expect(hbs_field.reload.value_baked).to include('Ember.TEMPLATES["discovery"]') + expect(raw_hbs_field.reload.value_baked).to include('Discourse.RAW_TEMPLATES["discourse/templates/discovery"]') + expect(unknown_field.reload.value_baked).to eq("") + expect(unknown_field.reload.error).to eq(I18n.t("themes.compile_error.unrecognized_extension", extension: "blah")) + + # All together + expect(theme.javascript_cache.content).to include('Ember.TEMPLATES["discovery"]') + expect(theme.javascript_cache.content).to include('Discourse.RAW_TEMPLATES["discourse/templates/discovery"]') + expect(theme.javascript_cache.content).to include('define("discourse/controllers/discovery"') + expect(theme.javascript_cache.content).to include("var settings =") + end + def create_upload_theme_field!(name) ThemeField.create!( theme_id: 1, diff --git a/spec/models/theme_spec.rb b/spec/models/theme_spec.rb index f1f1db4957..149badb603 100644 --- a/spec/models/theme_spec.rb +++ b/spec/models/theme_spec.rb @@ -367,6 +367,7 @@ HTML var themePrefix = function themePrefix(key) { return 'theme_translations.#{theme.id}.' + key; }; + Discourse._registerPluginCode('1.0', function (api) { try { alert(settings.name);var a = function a() {}; @@ -402,6 +403,7 @@ HTML var themePrefix = function themePrefix(key) { return 'theme_translations.#{theme.id}.' + key; }; + Discourse._registerPluginCode('1.0', function (api) { try { alert(settings.name);var a = function a() {}; diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb index 9a62b088d0..44ff1f1fd2 100644 --- a/spec/models/upload_spec.rb +++ b/spec/models/upload_spec.rb @@ -253,6 +253,13 @@ describe Upload do end end + describe '#base62_sha1' do + it 'should return the right value' do + upload.update!(sha1: "0000c513e1da04f7b4e99230851ea2aafeb8cc4e") + expect(upload.base62_sha1).to eq("1Eg9p8rrCURq4T3a6iJUk0ri6") + end + end + describe '.sha1_from_short_path' do it "should be able to lookup sha1" do path = "/uploads/short-url/3UjQ4jHoyeoQndk5y3qHzm3QVTQ.png" diff --git a/spec/models/user_option_spec.rb b/spec/models/user_option_spec.rb index 36e0e6151b..d02eefd08f 100644 --- a/spec/models/user_option_spec.rb +++ b/spec/models/user_option_spec.rb @@ -18,7 +18,7 @@ describe UserOption do end end - describe "should_be_redirected_to_top" do + describe "defaults" do fab!(:user) { Fabricate(:user) } it "should be redirected to top when there is a reason to" do @@ -30,16 +30,29 @@ describe UserOption do user.user_option.expects(:redirected_to_top).returns(nil) expect(user.user_option.should_be_redirected_to_top).to eq(false) end - end - - describe "defaults" do - fab!(:user) { Fabricate(:user) } it "should not hide the profile and presence by default" do expect(user.user_option.hide_profile_and_presence).to eq(false) end end + describe "site settings" do + it "should apply defaults from site settings" do + + SiteSetting.default_other_enable_quoting = false + SiteSetting.default_other_enable_defer = true + SiteSetting.default_other_external_links_in_new_tab = true + SiteSetting.default_other_dynamic_favicon = true + + user = Fabricate(:user) + + expect(user.user_option.enable_quoting).to eq(false) + expect(user.user_option.enable_defer).to eq(true) + expect(user.user_option.external_links_in_new_tab).to eq(true) + expect(user.user_option.dynamic_favicon).to eq(true) + end + end + describe "#mailing_list_mode" do fab!(:forum_user) { Fabricate(:user) } fab!(:mailing_list_user) { Fabricate(:user) } diff --git a/spec/multisite/s3_store_spec.rb b/spec/multisite/s3_store_spec.rb index 1e4634b889..3c5451f89b 100644 --- a/spec/multisite/s3_store_spec.rb +++ b/spec/multisite/s3_store_spec.rb @@ -118,4 +118,71 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do end end end + + context 'private uploads' do + let(:store) { FileStore::S3Store.new } + let(:client) { Aws::S3::Client.new(stub_responses: true) } + let(:resource) { Aws::S3::Resource.new(client: client) } + let(:s3_bucket) { resource.bucket("some-really-cool-bucket") } + let(:s3_helper) { store.instance_variable_get(:@s3_helper) } + let(:s3_object) { stub } + + before(:each) do + SiteSetting.s3_upload_bucket = "some-really-cool-bucket" + SiteSetting.s3_access_key_id = "s3-access-key-id" + SiteSetting.s3_secret_access_key = "s3-secret-access-key" + SiteSetting.enable_s3_uploads = true + SiteSetting.prevent_anons_from_downloading_files = true + end + + before do + s3_object.stubs(:put).returns(Aws::S3::Types::PutObjectOutput.new(etag: "etag")) + end + + describe "when private uploads are enabled" do + it "returns signed URL with correct path" do + test_multisite_connection('default') do + SiteSetting.authorized_extensions = "pdf|png|jpg|gif" + upload = build_upload + upload.update!(original_filename: "small.pdf", extension: "pdf") + + s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once + s3_bucket.expects(:object).with("uploads/default/original/1X/#{upload.sha1}.pdf").returns(s3_object).at_least_once + s3_object.expects(:presigned_url).with(:get, expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS) + + expect(store.store_upload(uploaded_file, upload)).to eq( + "//some-really-cool-bucket.s3.dualstack.us-east-1.amazonaws.com/uploads/default/original/1X/#{upload.sha1}.pdf" + ) + + expect(store.url_for(upload)).not_to eq(upload.url) + end + end + end + + describe "#update_upload_ACL" do + it "updates correct file for default and second multisite db" do + test_multisite_connection('default') do + upload = build_upload + + s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once + s3_bucket.expects(:object).with("uploads/default/original/1X/#{upload.sha1}.png").returns(s3_object) + s3_object.expects(:acl).returns(s3_object) + s3_object.expects(:put).with(acl: "private").returns(s3_object) + + expect(store.update_upload_ACL(upload)).to be_truthy + end + + test_multisite_connection('second') do + upload = build_upload + + s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once + s3_bucket.expects(:object).with("uploads/second/original/1X/#{upload.sha1}.png").returns(s3_object) + s3_object.expects(:acl).returns(s3_object) + s3_object.expects(:put).with(acl: "private").returns(s3_object) + + expect(store.update_upload_ACL(upload)).to be_truthy + end + end + end + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 3eb1fb20c0..c5dcc9bd0d 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -78,6 +78,10 @@ SiteSetting.automatically_download_gravatars = false SeedFu.seed +# we need this env var to ensure that we can impersonate in test +# this enable integration_helpers sign_in helper +ENV['DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE'] = '1' + module TestSetup # This is run before each test and before each before_all block def self.test_setup(x = nil) diff --git a/spec/requests/admin/groups_controller_spec.rb b/spec/requests/admin/groups_controller_spec.rb index d641fb1d77..c9e9422315 100644 --- a/spec/requests/admin/groups_controller_spec.rb +++ b/spec/requests/admin/groups_controller_spec.rb @@ -12,8 +12,8 @@ RSpec.describe Admin::GroupsController do end describe '#create' do - it 'should work' do - post "/admin/groups.json", params: { + let(:group_params) do + { group: { name: 'testing', usernames: [admin.username, user.username].join(","), @@ -22,6 +22,10 @@ RSpec.describe Admin::GroupsController do membership_request_template: 'Testing', } } + end + + it 'should work' do + post "/admin/groups.json", params: group_params expect(response.status).to eq(200) @@ -32,6 +36,44 @@ RSpec.describe Admin::GroupsController do expect(group.allow_membership_requests).to eq(true) expect(group.membership_request_template).to eq('Testing') end + + context "custom_fields" do + before do + plugin = Plugin::Instance.new + plugin.register_editable_group_custom_field :test + end + + after do + Group.plugin_editable_group_custom_fields.clear + end + + it "only updates allowed user fields" do + params = group_params + params[:group].merge!(custom_fields: { test: :hello1, test2: :hello2 }) + + post "/admin/groups.json", params: params + + group = Group.last + + expect(response.status).to eq(200) + expect(group.custom_fields['test']).to eq('hello1') + expect(group.custom_fields['test2']).to be_blank + end + + it "is secure when there are no registered editable fields" do + Group.plugin_editable_group_custom_fields.clear + params = group_params + params[:group].merge!(custom_fields: { test: :hello1, test2: :hello2 }) + + post "/admin/groups.json", params: params + + group = Group.last + + expect(response.status).to eq(200) + expect(group.custom_fields['test']).to be_blank + expect(group.custom_fields['test2']).to be_blank + end + end end describe '#add_owners' do diff --git a/spec/requests/admin/staff_action_logs_controller_spec.rb b/spec/requests/admin/staff_action_logs_controller_spec.rb index 16d59031f0..7ced305d03 100644 --- a/spec/requests/admin/staff_action_logs_controller_spec.rb +++ b/spec/requests/admin/staff_action_logs_controller_spec.rb @@ -31,6 +31,26 @@ describe Admin::StaffActionLogsController do ) end + it 'generates logs with pages' do + 1.upto(4).each do |idx| + StaffActionLogger.new(Discourse.system_user).log_site_setting_change("title", "value #{idx - 1}", "value #{idx}") + end + + get "/admin/logs/staff_action_logs.json", params: { limit: 3 } + + json = JSON.parse(response.body) + expect(response.status).to eq(200) + expect(json["staff_action_logs"].length).to eq(3) + expect(json["staff_action_logs"][0]["new_value"]).to eq("value 4") + + get "/admin/logs/staff_action_logs.json", params: { limit: 3, page: 1 } + + json = JSON.parse(response.body) + expect(response.status).to eq(200) + expect(json["staff_action_logs"].length).to eq(1) + expect(json["staff_action_logs"][0]["new_value"]).to eq("value 1") + end + context 'When staff actions are extended' do let(:plugin_extended_action) { :confirmed_ham } before { UserHistory.stubs(:staff_actions).returns([plugin_extended_action]) } diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb index 718b735e51..93af224a49 100644 --- a/spec/requests/admin/users_controller_spec.rb +++ b/spec/requests/admin/users_controller_spec.rb @@ -185,6 +185,36 @@ RSpec.describe Admin::UsersController do expect(response.status).to eq(200) end + it "won't delete a category topic" do + c = Fabricate(:category) + cat_post = c.topic.posts.first + put( + "/admin/users/#{user.id}/suspend.json", + params: suspend_params.merge( + post_action: 'delete', + post_id: cat_post.id + ) + ) + cat_post.reload + expect(cat_post.deleted_at).to be_blank + expect(response.status).to eq(200) + end + + it "won't delete a category topic by replies" do + c = Fabricate(:category) + cat_post = c.topic.posts.first + put( + "/admin/users/#{user.id}/suspend.json", + params: suspend_params.merge( + post_action: 'delete_replies', + post_id: cat_post.id + ) + ) + cat_post.reload + expect(cat_post.deleted_at).to be_blank + expect(response.status).to eq(200) + end + it "can delete an associated post and its replies" do reply = PostCreator.create( Fabricate(:user), diff --git a/spec/requests/category_hashtags_controller_spec.rb b/spec/requests/category_hashtags_controller_spec.rb index d31cea66fd..f055998d84 100644 --- a/spec/requests/category_hashtags_controller_spec.rb +++ b/spec/requests/category_hashtags_controller_spec.rb @@ -5,44 +5,49 @@ require 'rails_helper' describe CategoryHashtagsController do describe "check" do describe "logged in" do - before do - sign_in(Fabricate(:user)) + + describe "regular user" do + before do + sign_in(Fabricate(:user)) + end + + it 'only returns the categories that are valid' do + category = Fabricate(:category) + + get "/category_hashtags/check.json", params: { category_slugs: [category.slug, 'none'] } + + expect(response.status).to eq(200) + expect(JSON.parse(response.body)).to eq( + "valid" => [{ "slug" => category.hashtag_slug, "url" => category.url_with_id }] + ) + end + + it 'does not return restricted categories for a normal user' do + group = Fabricate(:group) + private_category = Fabricate(:private_category, group: group) + + get "/category_hashtags/check.json", params: { category_slugs: [private_category.slug] } + + expect(response.status).to eq(200) + expect(JSON.parse(response.body)).to eq("valid" => []) + end end - it 'only returns the categories that are valid' do - category = Fabricate(:category) + describe "admin user" do + it 'returns restricted categories for an admin' do + admin = sign_in(Fabricate(:admin)) + group = Fabricate(:group) + group.add(admin) + private_category = Fabricate(:private_category, group: group) - get "/category_hashtags/check.json", params: { category_slugs: [category.slug, 'none'] } + get "/category_hashtags/check.json", + params: { category_slugs: [private_category.slug] } - expect(response.status).to eq(200) - expect(JSON.parse(response.body)).to eq( - "valid" => [{ "slug" => category.hashtag_slug, "url" => category.url_with_id }] - ) - end - - it 'does not return restricted categories for a normal user' do - group = Fabricate(:group) - private_category = Fabricate(:private_category, group: group) - - get "/category_hashtags/check.json", params: { category_slugs: [private_category.slug] } - - expect(response.status).to eq(200) - expect(JSON.parse(response.body)).to eq("valid" => []) - end - - it 'returns restricted categories for an admin' do - admin = sign_in(Fabricate(:admin)) - group = Fabricate(:group) - group.add(admin) - private_category = Fabricate(:private_category, group: group) - - get "/category_hashtags/check.json", - params: { category_slugs: [private_category.slug] } - - expect(response.status).to eq(200) - expect(JSON.parse(response.body)).to eq( - "valid" => [{ "slug" => private_category.hashtag_slug, "url" => private_category.url_with_id }] - ) + expect(response.status).to eq(200) + expect(JSON.parse(response.body)).to eq( + "valid" => [{ "slug" => private_category.hashtag_slug, "url" => private_category.url_with_id }] + ) + end end end diff --git a/spec/requests/groups_controller_spec.rb b/spec/requests/groups_controller_spec.rb index 7284da58d3..912533484d 100644 --- a/spec/requests/groups_controller_spec.rb +++ b/spec/requests/groups_controller_spec.rb @@ -469,7 +469,7 @@ describe GroupsController do end describe '#update' do - let(:group) do + let!(:group) do Fabricate(:group, name: 'test', users: [user], @@ -478,6 +478,41 @@ describe GroupsController do ) end + context "custom_fields" do + before do + user.update!(admin: true) + sign_in(user) + plugin = Plugin::Instance.new + plugin.register_editable_group_custom_field :test + @group = Fabricate(:group) + end + + after do + Group.plugin_editable_group_custom_fields.clear + end + + it "only updates allowed user fields" do + put "/groups/#{@group.id}.json", params: { group: { custom_fields: { test: :hello1, test2: :hello2 } } } + + @group.reload + + expect(response.status).to eq(200) + expect(@group.custom_fields['test']).to eq('hello1') + expect(@group.custom_fields['test2']).to be_blank + end + + it "is secure when there are no registered editable fields" do + Group.plugin_editable_group_custom_fields.clear + put "/groups/#{@group.id}.json", params: { group: { custom_fields: { test: :hello1, test2: :hello2 } } } + + @group.reload + + expect(response.status).to eq(200) + expect(@group.custom_fields['test']).to be_blank + expect(@group.custom_fields['test2']).to be_blank + end + end + context "when user is group owner" do before do group.add_owner(user) diff --git a/spec/requests/list_controller_spec.rb b/spec/requests/list_controller_spec.rb index 987116c47a..587c6807d0 100644 --- a/spec/requests/list_controller_spec.rb +++ b/spec/requests/list_controller_spec.rb @@ -566,52 +566,22 @@ RSpec.describe ListController do end describe "best_periods_for" do - it "returns yearly for more than 180 days" do - expect(ListController.best_periods_for(nil, :all)).to eq([:yearly]) - expect(ListController.best_periods_for(180.days.ago, :all)).to eq([:yearly]) + it "works" do + expect(ListController.best_periods_for(nil)).to eq([:all]) + expect(ListController.best_periods_for(5.years.ago)).to eq([:all]) + expect(ListController.best_periods_for(2.years.ago)).to eq([:yearly, :all]) + expect(ListController.best_periods_for(6.months.ago)).to eq([:quarterly, :yearly, :all]) + expect(ListController.best_periods_for(2.months.ago)).to eq([:monthly, :quarterly, :yearly, :all]) + expect(ListController.best_periods_for(2.weeks.ago)).to eq([:weekly, :monthly, :quarterly, :yearly, :all]) + expect(ListController.best_periods_for(2.days.ago)).to eq([:daily, :weekly, :monthly, :quarterly, :yearly, :all]) end - it "includes monthly when less than 180 days and more than 35 days" do - (35...180).each do |date| - expect(ListController.best_periods_for(date.days.ago, :all)).to eq([:monthly, :yearly]) - end - end - - it "includes weekly when less than 35 days and more than 8 days" do - (8...35).each do |date| - expect(ListController.best_periods_for(date.days.ago, :all)).to eq([:weekly, :monthly, :yearly]) - end - end - - it "includes daily when less than 8 days" do - (0...8).each do |date| - expect(ListController.best_periods_for(date.days.ago, :all)).to eq([:daily, :weekly, :monthly, :yearly]) - end - end - - it "returns default even for more than 180 days" do - expect(ListController.best_periods_for(nil, :monthly)).to eq([:monthly, :yearly]) - expect(ListController.best_periods_for(180.days.ago, :monthly)).to eq([:monthly, :yearly]) - end - - it "returns default even when less than 180 days and more than 35 days" do - (35...180).each do |date| - expect(ListController.best_periods_for(date.days.ago, :weekly)).to eq([:weekly, :monthly, :yearly]) - end - end - - it "returns default even when less than 35 days and more than 8 days" do - (8...35).each do |date| - expect(ListController.best_periods_for(date.days.ago, :daily)).to eq([:daily, :weekly, :monthly, :yearly]) - end - end - - it "doesn't return default when set to all" do - expect(ListController.best_periods_for(nil, :all)).to eq([:yearly]) - end - - it "doesn't return value twice when matches default" do - expect(ListController.best_periods_for(nil, :yearly)).to eq([:yearly]) + it "supports default period" do + expect(ListController.best_periods_for(nil, :yearly)).to eq([:yearly, :all]) + expect(ListController.best_periods_for(nil, :quarterly)).to eq([:quarterly, :all]) + expect(ListController.best_periods_for(nil, :monthly)).to eq([:monthly, :all]) + expect(ListController.best_periods_for(nil, :weekly)).to eq([:weekly, :all]) + expect(ListController.best_periods_for(nil, :daily)).to eq([:daily, :all]) end end diff --git a/spec/requests/posts_controller_spec.rb b/spec/requests/posts_controller_spec.rb index 8044572b68..61880b4254 100644 --- a/spec/requests/posts_controller_spec.rb +++ b/spec/requests/posts_controller_spec.rb @@ -810,7 +810,10 @@ describe PostsController do it 'queues the post if min_first_post_typing_time is not met' do post "/posts.json", params: { raw: 'this is the test content', - title: 'this is the test title for the topic' + title: 'this is the test title for the topic', + composer_open_duration_msecs: 204, + typing_duration_msecs: 100, + reply_to_post_number: 123 } expect(response.status).to eq(200) @@ -822,6 +825,9 @@ describe PostsController do expect(user).to be_silenced rp = ReviewableQueuedPost.find_by(created_by: user) + expect(rp.payload['typing_duration_msecs']).to eq(100) + expect(rp.payload['composer_open_duration_msecs']).to eq(204) + expect(rp.payload['reply_to_post_number']).to eq(123) expect(rp.reviewable_scores.first.reason).to eq('fast_typer') expect(parsed['pending_post']).to be_present diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb index 2964a9178d..d454dd9f2e 100644 --- a/spec/requests/topics_controller_spec.rb +++ b/spec/requests/topics_controller_spec.rb @@ -2677,7 +2677,6 @@ RSpec.describe TopicsController do end describe "crawler" do - context "when not a crawler" do it "renders with the application layout" do get topic.url @@ -2691,7 +2690,6 @@ RSpec.describe TopicsController do context "when a crawler" do it "renders with the crawler layout, and handles proper pagination" do - page1_time = 3.months.ago page2_time = 2.months.ago page3_time = 1.month.ago @@ -2739,8 +2737,17 @@ RSpec.describe TopicsController do expect(response.headers['Last-Modified']).to eq(page3_time.httpdate) expect(body).to include(' "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36", "HTTP_VIA" => "HTTP/1.0 web.archive.org (Wayback Save Page)" } + body = response.body + + expect(body).to have_tag(:body, with: { class: 'crawler' }) + expect(body).to_not have_tag(:meta, with: { name: 'fragment' }) + end + end + end end describe "#reset_bump_date" do diff --git a/spec/requests/uploads_controller_spec.rb b/spec/requests/uploads_controller_spec.rb index b1d6052218..10b5003a85 100644 --- a/spec/requests/uploads_controller_spec.rb +++ b/spec/requests/uploads_controller_spec.rb @@ -285,12 +285,11 @@ describe UploadsController do context "prevent anons from downloading files" do it "returns 404 when an anonymous user tries to download a file" do - skip("this only works when nginx/apache is asset server") if Discourse::Application.config.public_file_server.enabled upload = upload_file("small.pdf", "pdf") delete "/session/#{user.username}.json" SiteSetting.prevent_anons_from_downloading_files = true - get upload.url + get "/uploads/#{site}/#{upload.sha1}.#{upload.extension}" expect(response.status).to eq(404) end end diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 6fb04028ce..542b81b1e9 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -738,6 +738,18 @@ describe UsersController do expect(response.status).to eq(200) expect(JSON.parse(response.body)['active']).to be_falsy end + + it "won't set the new user's locale to the admin's locale" do + SiteSetting.allow_user_locale = true + admin.update!(locale: :fr) + + post "/u.json", params: post_user_params.merge(active: true, api_key: api_key.key) + expect(response.status).to eq(200) + + json = JSON.parse(response.body) + new_user = User.find(json["user_id"]) + expect(new_user.locale).not_to eq("fr") + end end end @@ -1926,10 +1938,13 @@ describe UsersController do end it 'can successfully pick a custom avatar' do - put "/u/#{user.username}/preferences/avatar/pick.json", params: { - upload_id: upload.id, type: "custom" - } + events = DiscourseEvent.track_events do + put "/u/#{user.username}/preferences/avatar/pick.json", params: { + upload_id: upload.id, type: "custom" + } + end + expect(events.map { |event| event[:event_name] }).to include(:user_updated) expect(response.status).to eq(200) expect(user.reload.uploaded_avatar_id).to eq(upload.id) expect(user.user_avatar.reload.custom_upload_id).to eq(upload.id) @@ -1981,8 +1996,11 @@ describe UsersController do end it 'can successfully select an avatar' do - put "/u/#{user.username}/preferences/avatar/select.json", params: { url: avatar1.url } + events = DiscourseEvent.track_events do + put "/u/#{user.username}/preferences/avatar/select.json", params: { url: avatar1.url } + end + expect(events.map { |event| event[:event_name] }).to include(:user_updated) expect(response.status).to eq(200) expect(user.reload.uploaded_avatar_id).to eq(avatar1.id) expect(user.user_avatar.reload.custom_upload_id).to eq(avatar1.id) diff --git a/spec/serializers/reviewable_flagged_post_serializer_spec.rb b/spec/serializers/reviewable_flagged_post_serializer_spec.rb index 2304423671..9728152243 100644 --- a/spec/serializers/reviewable_flagged_post_serializer_spec.rb +++ b/spec/serializers/reviewable_flagged_post_serializer_spec.rb @@ -12,7 +12,7 @@ describe ReviewableFlaggedPostSerializer do json = ReviewableFlaggedPostSerializer.new(reviewable, scope: Guardian.new(admin), root: nil).as_json expect(json[:cooked]).to eq(p0.cooked) expect(json[:raw]).to eq(p0.raw) - expect(json[:topic_url]).to eq(p0.url) + expect(json[:target_url]).to eq(p0.url) end it "works when the topic is deleted" do diff --git a/spec/serializers/reviewable_serializer_spec.rb b/spec/serializers/reviewable_serializer_spec.rb index 6ce23e0c14..8c2e95f156 100644 --- a/spec/serializers/reviewable_serializer_spec.rb +++ b/spec/serializers/reviewable_serializer_spec.rb @@ -22,9 +22,7 @@ describe ReviewableSerializer do it 'Includes the removed topic id when the topis was deleted' do reviewable.topic.trash!(admin) - json = described_class.new(reviewable.reload, scope: Guardian.new(admin), root: nil).as_json - expect(json[:removed_topic_id]).to eq reviewable.topic_id end @@ -33,4 +31,29 @@ describe ReviewableSerializer do json = ReviewableQueuedPostSerializer.new(reviewable, scope: Guardian.new(admin), root: nil).as_json expect(json['payload']).to be_blank end + + describe "urls" do + + it "links to the flagged post" do + fp = Fabricate(:reviewable_flagged_post) + json = described_class.new(fp, scope: Guardian.new(admin), root: nil).as_json + expect(json[:target_url]).to eq(fp.post.url) + expect(json[:topic_url]).to eq(fp.topic.url) + end + + it "supports deleted topics" do + fp = Fabricate(:reviewable_flagged_post) + fp.topic.trash!(admin) + fp.reload + + json = described_class.new(fp, scope: Guardian.new(admin), root: nil).as_json + expect(json[:topic_url]).to be_blank + end + + it "links to the queued post" do + json = described_class.new(reviewable, scope: Guardian.new(admin), root: nil).as_json + expect(json[:target_url]).to eq(reviewable.topic.url) + expect(json[:topic_url]).to eq(reviewable.topic.url) + end + end end diff --git a/spec/serializers/topic_view_posts_serializer_spec.rb b/spec/serializers/topic_view_posts_serializer_spec.rb index 2c14b08ef7..55be89f36d 100644 --- a/spec/serializers/topic_view_posts_serializer_spec.rb +++ b/spec/serializers/topic_view_posts_serializer_spec.rb @@ -2,28 +2,51 @@ require 'rails_helper' -RSpec.describe TopicViewPostsSerializer do - fab!(:user) { Fabricate(:user) } - fab!(:post) { Fabricate(:post) } - let(:topic) { post.topic } - let(:topic_view) { TopicView.new(topic, user, post_ids: [post.id]) } +describe TopicViewPostsSerializer do - subject do - described_class.new(topic_view, + it 'should return the right attributes' do + + user = Fabricate(:user) + post = Fabricate(:post) + topic = post.topic + + reviewable = Fabricate(:reviewable_flagged_post, created_by: user, target: post, topic: topic) + + ReviewableScore.create!( + reviewable_id: reviewable.id, + user_id: user.id, + reviewable_score_type: 0, + status: ReviewableScore.statuses[:pending] + ) + + ReviewableScore.create!( + reviewable_id: reviewable.id, + user_id: user.id, + reviewable_score_type: 0, + status: ReviewableScore.statuses[:ignored] + ) + + topic_view = TopicView.new(topic, user, post_ids: [post.id]) + + serializer = TopicViewPostsSerializer.new( + topic_view, scope: Guardian.new(Fabricate(:admin)), root: false ) - end - it 'should return the right attributes' do - body = JSON.parse(subject.to_json) + body = JSON.parse(serializer.to_json) posts = body["post_stream"]["posts"] expect(posts.count).to eq(1) expect(posts.first["id"]).to eq(post.id) + + expect(posts.first["reviewable_score_count"]).to eq(2) + expect(posts.first["reviewable_score_pending_count"]).to eq(1) + expect(body["post_stream"]["stream"]).to eq(nil) expect(body["post_stream"]["timeline_lookup"]).to eq(nil) expect(body["post_stream"]["gaps"]).to eq(nil) + end end diff --git a/spec/serializers/user_serializer_spec.rb b/spec/serializers/user_serializer_spec.rb index a02d4e9ca7..7f896a285f 100644 --- a/spec/serializers/user_serializer_spec.rb +++ b/spec/serializers/user_serializer_spec.rb @@ -250,17 +250,4 @@ describe UserSerializer do expect(json[:user_api_keys][2][:id]).to eq(user_api_key_2.id) end end - - context "with missing user profile" do - fab!(:user) { Fabricate(:user) } - - it "does not throw an error" do - id = user.id - UserProfile.delete(id) - user_b = User.find(id) - json = UserSerializer.new(user_b, scope: Guardian.new(user_b), root: false).as_json - - expect(json[:bio_raw]).to eq nil - end - end end diff --git a/spec/serializers/web_hook_user_serializer_spec.rb b/spec/serializers/web_hook_user_serializer_spec.rb index 82ef437f6c..1c7f2ed8a2 100644 --- a/spec/serializers/web_hook_user_serializer_spec.rb +++ b/spec/serializers/web_hook_user_serializer_spec.rb @@ -23,7 +23,7 @@ RSpec.describe WebHookUserSerializer do it 'should only include the required keys' do count = serializer.as_json.keys.count - difference = count - 45 + difference = count - 43 expect(difference).to eq(0), lambda { message = "" diff --git a/spec/services/heat_settings_updater_spec.rb b/spec/services/heat_settings_updater_spec.rb new file mode 100644 index 0000000000..4f86390e50 --- /dev/null +++ b/spec/services/heat_settings_updater_spec.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe HeatSettingsUpdater do + describe '#update' do + subject(:update_settings) { HeatSettingsUpdater.update } + + def expect_default_values + [:topic_views_heat, :topic_post_like_heat].each do |prefix| + [:low, :medium, :high].each do |level| + setting_name = "#{prefix}_#{level}" + expect(SiteSetting.get(setting_name)).to eq(SiteSetting.defaults[setting_name]) + end + end + end + + it 'changes nothing on fresh install' do + expect { + update_settings + }.to_not change { UserHistory.count } + expect_default_values + end + + context 'low activity' do + let!(:hottest_topic1) { Fabricate(:topic, views: 3000, posts_count: 10, like_count: 2) } + let!(:hottest_topic2) { Fabricate(:topic, views: 3000, posts_count: 10, like_count: 2) } + let!(:warm_topic1) { Fabricate(:topic, views: 1500, posts_count: 10, like_count: 1) } + let!(:warm_topic2) { Fabricate(:topic, views: 1500, posts_count: 10, like_count: 1) } + let!(:warm_topic3) { Fabricate(:topic, views: 1500, posts_count: 10, like_count: 1) } + let!(:lukewarm_topic1) { Fabricate(:topic, views: 800, posts_count: 10, like_count: 0) } + let!(:lukewarm_topic2) { Fabricate(:topic, views: 800, posts_count: 10, like_count: 0) } + let!(:lukewarm_topic3) { Fabricate(:topic, views: 800, posts_count: 10, like_count: 0) } + let!(:lukewarm_topic4) { Fabricate(:topic, views: 800, posts_count: 10, like_count: 0) } + let!(:cold_topic) { Fabricate(:topic, views: 100, posts_count: 10, like_count: 0) } + + it "doesn't make settings lower than defaults" do + expect { + update_settings + }.to_not change { UserHistory.count } + expect_default_values + end + + it 'can set back down to minimum defaults' do + [:low, :medium, :high].each do |level| + SiteSetting.set("topic_views_heat_#{level}", 20_000) + SiteSetting.set("topic_post_like_heat_#{level}", 5.0) + end + expect { + update_settings + }.to change { UserHistory.count }.by(6) + expect_default_values + end + end + + context 'similar activity' do + let!(:hottest_topic1) { Fabricate(:topic, views: 3530, posts_count: 100, like_count: 201) } + let!(:hottest_topic2) { Fabricate(:topic, views: 3530, posts_count: 100, like_count: 201) } + let!(:warm_topic1) { Fabricate(:topic, views: 2020, posts_count: 100, like_count: 99) } + let!(:warm_topic2) { Fabricate(:topic, views: 2020, posts_count: 100, like_count: 99) } + let!(:warm_topic3) { Fabricate(:topic, views: 2020, posts_count: 100, like_count: 99) } + let!(:lukewarm_topic1) { Fabricate(:topic, views: 1010, posts_count: 100, like_count: 51) } + let!(:lukewarm_topic2) { Fabricate(:topic, views: 1010, posts_count: 100, like_count: 51) } + let!(:lukewarm_topic3) { Fabricate(:topic, views: 1010, posts_count: 100, like_count: 51) } + let!(:lukewarm_topic4) { Fabricate(:topic, views: 1010, posts_count: 100, like_count: 51) } + let!(:cold_topic) { Fabricate(:topic, views: 100, posts_count: 100, like_count: 1) } + + it "doesn't make small changes" do + expect { + update_settings + }.to_not change { UserHistory.count } + expect_default_values + end + end + + context 'increased activity' do + let!(:hottest_topic1) { Fabricate(:topic, views: 10_100, posts_count: 100, like_count: 230) } + let!(:hottest_topic2) { Fabricate(:topic, views: 10_012, posts_count: 100, like_count: 220) } + let!(:warm_topic1) { Fabricate(:topic, views: 4020, posts_count: 99, like_count: 126) } + let!(:warm_topic2) { Fabricate(:topic, views: 4010, posts_count: 99, like_count: 116) } + let!(:warm_topic3) { Fabricate(:topic, views: 4005, posts_count: 99, like_count: 106) } + let!(:lukewarm_topic1) { Fabricate(:topic, views: 2040, posts_count: 99, like_count: 84) } + let!(:lukewarm_topic2) { Fabricate(:topic, views: 2030, posts_count: 99, like_count: 74) } + let!(:lukewarm_topic3) { Fabricate(:topic, views: 2020, posts_count: 99, like_count: 64) } + let!(:lukewarm_topic4) { Fabricate(:topic, views: 2002, posts_count: 99, like_count: 54) } + let!(:cold_topic) { Fabricate(:topic, views: 100, posts_count: 100, like_count: 1) } + + it 'changes settings when difference is significant' do + expect { + update_settings + }.to change { UserHistory.count }.by(6) + expect(SiteSetting.topic_views_heat_high).to eq(10_000) + expect(SiteSetting.topic_views_heat_medium).to eq(4000) + expect(SiteSetting.topic_views_heat_low).to eq(2000) + expect(SiteSetting.topic_post_like_heat_high).to eq(2.2) + expect(SiteSetting.topic_post_like_heat_medium).to eq(1.07) + expect(SiteSetting.topic_post_like_heat_low).to eq(0.55) + end + + it "doesn't change settings when automatic_topic_heat_values is false" do + SiteSetting.automatic_topic_heat_values = false + expect { + update_settings + }.to_not change { UserHistory.count } + expect_default_values + end + end + end +end diff --git a/spec/services/inline_uploads_spec.rb b/spec/services/inline_uploads_spec.rb new file mode 100644 index 0000000000..0ec00d0753 --- /dev/null +++ b/spec/services/inline_uploads_spec.rb @@ -0,0 +1,496 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe InlineUploads do + before do + @original_asset_host = Rails.configuration.action_controller.asset_host + Rails.configuration.action_controller.asset_host = "https://cdn.discourse.org/stuff" + end + + after do + Rails.configuration.action_controller.asset_host = @original_asset_host + end + + describe '.process' do + describe 'local uploads' do + fab!(:upload) { Fabricate(:upload) } + fab!(:upload2) { Fabricate(:upload) } + fab!(:upload3) { Fabricate(:upload) } + + it "should not correct existing inline uploads" do + md = <<~MD + ![test](#{upload.short_url})haha + [test]#{upload.short_url} + MD + + expect(InlineUploads.process(md)).to eq(md) + + md = <<~MD + ![test](#{upload.short_url}) + [test|attachment](#{upload.short_url}) + MD + + expect(InlineUploads.process(md)).to eq(md) + end + + it "should not escape existing content" do + md = "1 > 2" + + expect(InlineUploads.process(md)).to eq(md) + end + + it "should not escape invalid HTML tags" do + md = "." + + expect(InlineUploads.process(md)).to eq(md) + end + + it "should not correct code blocks" do + md = "`In Code Block`" + + expect(InlineUploads.process(md)).to eq(md) + + md = " In Code Block" + + expect(InlineUploads.process(md)).to eq(md) + end + + it "should not correct links in quotes" do + post = Fabricate(:post) + user = Fabricate(:user) + + md = <<~MD + [quote="#{user.username}, post:#{post.post_number}, topic:#{post.topic.id}"] + some quote + + #{Discourse.base_url}#{upload3.url} + + ![](#{upload.url}) + [/quote] + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + [quote="#{user.username}, post:#{post.post_number}, topic:#{post.topic.id}"] + some quote + + ![](#{upload3.short_url}) + + ![](#{upload.short_url}) + [/quote] + MD + end + + it "should correct markdown linked images" do + md = <<~MD + [![](#{upload.url})](https://somelink.com) + + [![some test](#{upload2.url})](https://somelink.com) + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + [![](#{upload.short_url})](https://somelink.com) + + [![some test](#{upload2.short_url})](https://somelink.com) + MD + end + + it "should correct markdown images with title" do + md = <<~MD + ![](#{upload.url} "some alt") + ![testing](#{upload2.url} 'some alt' ) + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + ![](#{upload.short_url} "some alt") + ![testing](#{upload2.short_url} 'some alt' ) + MD + end + + it "should correct bbcode img URLs to the short version" do + md = <<~MD + [img]#{upload.url}[/img] + + [img] + #{upload2.url} + [/img] + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + ![](#{upload.short_url}) + + ![](#{upload2.short_url}) + MD + end + + it "should correct markdown references" do + md = <<~MD + This is a [some reference] something + + [some reference]: #{Discourse.base_url}#{upload.url} + + + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + This is a [some reference] something + + [some reference]: #{Discourse.base_url}#{upload.short_path} + + ![](#{upload.short_url}) + MD + end + + it "should correct raw image URLs to the short url and paths" do + md = <<~MD + #{Discourse.base_url}#{upload.url} + + #{Discourse.base_url}#{upload.url} #{Discourse.base_url}#{upload2.url} + + #{Discourse.base_url}#{upload3.url} + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + ![](#{upload.short_url}) + + #{Discourse.base_url}#{upload.short_path} #{Discourse.base_url}#{upload2.short_path} + + ![](#{upload3.short_url}) + MD + end + + it "should correct image URLs to the short version" do + md = <<~MD + ![image|690x290](#{upload.short_url}) + + ![IMAge|690x190,60%](#{upload.short_url}) + + ![image](#{upload2.url}) + ![image|100x100](#{upload3.url}) + + some image + some imagesome image + + #{Discourse.base_url}#{upload3.url} #{Discourse.base_url}#{upload3.url} + + + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + ![image|690x290](#{upload.short_url}) + + ![IMAge|690x190,60%](#{upload.short_url}) + + ![image](#{upload2.short_url}) + ![image|100x100](#{upload3.short_url}) + + ![some image](#{upload.short_url} "some title") + ![some image](#{upload2.short_url})![some image](#{upload3.short_url}) + + #{Discourse.base_url}#{upload3.short_path} #{Discourse.base_url}#{upload3.short_path} + + ![|5x4](#{upload.short_url}) + MD + end + + it "should not be affected by an emoji" do + CustomEmoji.create!(name: 'test', upload: upload3) + Emoji.clear_cache + + md = <<~MD + :test: + + ![image|690x290](#{upload.url}) + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + :test: + + ![image|690x290](#{upload.short_url}) + MD + end + + it "should correctly update image sources within anchor or paragraph tags" do + md = <<~MD + + test + + +

+ test +

+ + test + + test + + + + + test + + + +

Test test

+ +
+ test + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + + + ![test|500x500](#{upload.short_url}) + + + +

+ + ![test](#{upload2.short_url}) + +

+ + + + ![test|500x500](#{upload3.short_url}) + + + + + + ![test|500x500](#{upload.short_url}) + + + + + + + ![test|500x500](#{upload.short_url}) + + + +

Test ![test|500x500](#{upload2.short_url})

+ +
+ + ![test|500x500](#{upload2.short_url}) + MD + end + + it "should not be affected by fake HTML tags" do + md = <<~MD + ``` + This is some test + + > some quote + + test2 + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + ``` + This is some some quote + + [test2|attachment](#{upload2.short_url}) + MD + end + + it "should not be affected by an external or invalid links" do + md = <<~MD + invalid + + [test]("https://this.is.some.external.link") + + test + + test2 + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + invalid + + [test]("https://this.is.some.external.link") + + test + + [test2|attachment](#{upload2.short_url}) + MD + end + + it "should correct attachment URLS to the short version when raw contains inline image" do + md = <<~MD + ![image](#{upload.short_url}) ![image](#{upload.short_url}) + + [some complicated.doc %50](#{upload3.url}) + + test2 + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + ![image](#{upload.short_url}) ![image](#{upload.short_url}) + + [some complicated.doc %50](#{upload3.short_url}) + + [test2|attachment](#{upload2.short_url}) + MD + end + + it "should correct attachment URLs to the short version" do + md = <<~MD + + this + is + some + attachment + + + + - test2 + - test2 + - test2 + + test3 + test3test3 + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + [this is some attachment|attachment](#{upload.short_url}) + + - [test2|attachment](#{upload.short_url}) + - [test2|attachment](#{upload2.short_url}) + - [test2|attachment](#{upload3.short_url}) + + [test3|attachment](#{upload.short_url}) + [test3|attachment](#{upload2.short_url})[test3|attachment](#{upload3.short_url}) + MD + end + + it 'should correct full upload url to the shorter version' do + md = <<~MD + Some random text + + ![test](#{upload.short_url}) + [test|attachment](#{upload.short_url}) + + + test + + + `In Code Block` + + In Code Block + + newtest + newtest + + test + test + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + Some random text + + ![test](#{upload.short_url}) + [test|attachment](#{upload.short_url}) + + [test|attachment](#{upload.short_url}) + + `In Code Block` + + In Code Block + + [newtest](#{upload.short_url}) + [newtest](#{upload.short_url}) + + test + test + MD + end + + it 'accepts a block that yields when link does not match an upload in the db' do + url = "#{Discourse.base_url}#{upload.url}" + + md = <<~MD + some image + some image + MD + + upload.destroy! + + InlineUploads.process(md, on_missing: lambda { |link| + expect(link).to eq(url) + }) + end + end + + describe "s3 uploads" do + let(:upload) { Fabricate(:upload_s3) } + let(:upload2) { Fabricate(:upload_s3) } + + before do + SiteSetting.enable_s3_uploads = true + SiteSetting.s3_upload_bucket = "s3-upload-bucket" + SiteSetting.s3_access_key_id = "some key" + SiteSetting.s3_secret_access_key = "some secret key" + SiteSetting.s3_cdn_url = "https://s3.cdn.com" + end + + it "should correct image URLs to the short version" do + md = <<~MD + some image + some image + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + ![some image](#{upload.short_url}) + ![some image](#{upload2.short_url}) + MD + end + + it "should correct markdown references" do + md = <<~MD + This is a [some reference] somethign + + [some reference]: https:#{upload.url} + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + This is a [some reference] somethign + + [some reference]: #{Discourse.base_url}#{upload.short_path} + MD + end + + it "should correct image URLs in multisite" do + begin + Rails.configuration.multisite = true + + md = <<~MD + https:#{upload2.url} https:#{upload2.url} + #{URI.join(SiteSetting.s3_cdn_url, URI.parse(upload2.url).path).to_s} + + some image + some image + MD + + expect(InlineUploads.process(md)).to eq(<<~MD) + #{Discourse.base_url}#{upload2.short_path} #{Discourse.base_url}#{upload2.short_path} + #{Discourse.base_url}#{upload2.short_path} + + ![some image](#{upload.short_url}) + ![some image](#{upload2.short_url}) + MD + ensure + Rails.configuration.multisite = false + end + end + end + end +end diff --git a/spec/services/search_indexer_spec.rb b/spec/services/search_indexer_spec.rb index b92f342945..f8823a9ab6 100644 --- a/spec/services/search_indexer_spec.rb +++ b/spec/services/search_indexer_spec.rb @@ -17,6 +17,16 @@ describe SearchIndexer do SearchIndexer.scrub_html_for_search(html, strip_diacritics: strip_diacritics) end + it 'can correctly inject if http or https links exist' do + + val = "a https://cnn.com?bob=1, http://stuff.com.au?bill=1 b abc.net/xyz=1" + result = SearchIndexer.inject_extra_terms(val) + + expected = "a https://cnn.com?bob=1, cnn com bob=1 http://stuff.com.au?bill=1 stuff com au bill=1 b abc.net/xyz=1 net xyz=1" + + expect(result).to eq(expected) + end + it 'correctly indexes chinese' do SiteSetting.default_locale = 'zh_CN' data = "你好世界" @@ -141,7 +151,7 @@ describe SearchIndexer do topic = post.topic expect(post.post_search_data.raw_data).to eq( - "#{topic.title} #{topic.category.name} https://meta.discourse.org/some.png meta discourse org" + "#{topic.title} #{topic.category.name} https://meta.discourse.org/some.png meta discourse org some png" ) end diff --git a/test/javascripts/acceptance/admin-suspend-user-test.js.es6 b/test/javascripts/acceptance/admin-suspend-user-test.js.es6 index a982bc74a8..152b035b60 100644 --- a/test/javascripts/acceptance/admin-suspend-user-test.js.es6 +++ b/test/javascripts/acceptance/admin-suspend-user-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; acceptance("Admin - Suspend User", { diff --git a/test/javascripts/acceptance/category-chooser-test.js.es6 b/test/javascripts/acceptance/category-chooser-test.js.es6 index 83b1af28db..b6772ab771 100644 --- a/test/javascripts/acceptance/category-chooser-test.js.es6 +++ b/test/javascripts/acceptance/category-chooser-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; acceptance("CategoryChooser", { diff --git a/test/javascripts/acceptance/category-edit-security-test.js.es6 b/test/javascripts/acceptance/category-edit-security-test.js.es6 index 6a4ca5856c..131b26ff9d 100644 --- a/test/javascripts/acceptance/category-edit-security-test.js.es6 +++ b/test/javascripts/acceptance/category-edit-security-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; acceptance("Category Edit - security", { diff --git a/test/javascripts/acceptance/category-edit-test.js.es6 b/test/javascripts/acceptance/category-edit-test.js.es6 index 825cd0e797..690e028ead 100644 --- a/test/javascripts/acceptance/category-edit-test.js.es6 +++ b/test/javascripts/acceptance/category-edit-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import DiscourseURL from "discourse/lib/url"; import { acceptance } from "helpers/qunit-helpers"; diff --git a/test/javascripts/acceptance/composer-actions-test.js.es6 b/test/javascripts/acceptance/composer-actions-test.js.es6 index b70d7491f2..fd6eb58585 100644 --- a/test/javascripts/acceptance/composer-actions-test.js.es6 +++ b/test/javascripts/acceptance/composer-actions-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers"; import { _clearSnapshots } from "select-kit/components/composer-actions"; import { toggleCheckDraftPopup } from "discourse/controllers/composer"; diff --git a/test/javascripts/acceptance/composer-test.js.es6 b/test/javascripts/acceptance/composer-test.js.es6 index b77866a460..b055992135 100644 --- a/test/javascripts/acceptance/composer-test.js.es6 +++ b/test/javascripts/acceptance/composer-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; import { toggleCheckDraftPopup } from "discourse/controllers/composer"; diff --git a/test/javascripts/acceptance/composer-uncategorized-test.js.es6 b/test/javascripts/acceptance/composer-uncategorized-test.js.es6 index bbcc67a350..c65c759c17 100644 --- a/test/javascripts/acceptance/composer-uncategorized-test.js.es6 +++ b/test/javascripts/acceptance/composer-uncategorized-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers"; acceptance("Composer and uncategorized is not allowed", { diff --git a/test/javascripts/acceptance/dashboard-test.js.es6 b/test/javascripts/acceptance/dashboard-test.js.es6 index 1a7675dac9..affe6c0a9f 100644 --- a/test/javascripts/acceptance/dashboard-test.js.es6 +++ b/test/javascripts/acceptance/dashboard-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; acceptance("Dashboard", { diff --git a/test/javascripts/acceptance/group-test.js.es6 b/test/javascripts/acceptance/group-test.js.es6 index 3c73a89a84..3a0d3714a2 100644 --- a/test/javascripts/acceptance/group-test.js.es6 +++ b/test/javascripts/acceptance/group-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance, logIn } from "helpers/qunit-helpers"; acceptance("Group", { @@ -48,7 +49,7 @@ QUnit.test("Anonymous Viewing Group", async assert => { ); assert.ok(count(".user-stream-item") > 0, "it lists stream items"); - await expandSelectKit(".group-dropdown"); + await selectKit(".group-dropdown").expand(); assert.equal( find(".select-kit-row") @@ -70,7 +71,7 @@ QUnit.test("Anonymous Viewing Group", async assert => { await visit("/g"); await visit("/g/discourse"); - await expandSelectKit(".group-dropdown"); + await selectKit(".group-dropdown").expand(); assert.equal( find(".group-dropdown-filter").length, diff --git a/test/javascripts/acceptance/new-topic-test.js.es6 b/test/javascripts/acceptance/new-topic-test.js.es6 index 1ad6fc0595..b83d467a89 100644 --- a/test/javascripts/acceptance/new-topic-test.js.es6 +++ b/test/javascripts/acceptance/new-topic-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance, logIn } from "helpers/qunit-helpers"; acceptance("New Topic"); diff --git a/test/javascripts/acceptance/preferences-test.js.es6 b/test/javascripts/acceptance/preferences-test.js.es6 index e181fcaf0d..ff4319e2c6 100644 --- a/test/javascripts/acceptance/preferences-test.js.es6 +++ b/test/javascripts/acceptance/preferences-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; import User from "discourse/models/user"; @@ -114,12 +115,12 @@ QUnit.test("font size change", async assert => { await visit("/u/eviltrout/preferences/interface"); // Live changes without reload - await expandSelectKit(".text-size .combobox"); - await selectKitSelectRowByValue("larger", ".text-size .combobox"); + await selectKit(".text-size .combobox").expand(); + await selectKit(".text-size .combobox").selectRowByValue("larger"); assert.ok(document.documentElement.classList.contains("text-size-larger")); - await expandSelectKit(".text-size .combobox"); - await selectKitSelectRowByValue("largest", ".text-size .combobox"); + await selectKit(".text-size .combobox").expand(); + await selectKit(".text-size .combobox").selectRowByValue("largest"); assert.ok(document.documentElement.classList.contains("text-size-largest")); assert.equal($.cookie("text_size"), null, "cookie is not set"); @@ -129,16 +130,16 @@ QUnit.test("font size change", async assert => { assert.equal($.cookie("text_size"), null, "cookie is not set"); - await expandSelectKit(".text-size .combobox"); - await selectKitSelectRowByValue("larger", ".text-size .combobox"); + await selectKit(".text-size .combobox").expand(); + await selectKit(".text-size .combobox").selectRowByValue("larger"); await click(".text-size input[type=checkbox]"); await savePreferences(); assert.equal($.cookie("text_size"), "larger|1", "cookie is set"); await click(".text-size input[type=checkbox]"); - await expandSelectKit(".text-size .combobox"); - await selectKitSelectRowByValue("largest", ".text-size .combobox"); + await selectKit(".text-size .combobox").expand(); + await selectKit(".text-size .combobox").selectRowByValue("largest"); await savePreferences(); assert.equal($.cookie("text_size"), null, "cookie is removed"); diff --git a/test/javascripts/acceptance/redirect-to-top-test.js.es6 b/test/javascripts/acceptance/redirect-to-top-test.js.es6 index 2be06cacf1..c1c7e66f85 100644 --- a/test/javascripts/acceptance/redirect-to-top-test.js.es6 +++ b/test/javascripts/acceptance/redirect-to-top-test.js.es6 @@ -3,14 +3,51 @@ import DiscoveryFixtures from "fixtures/discovery_fixtures"; acceptance("Redirect to Top", { pretend(server, helper) { + server.get("/top/weekly.json", () => { + return helper.response(DiscoveryFixtures["/latest.json"]); + }); + server.get("/top/monthly.json", () => { + return helper.response(DiscoveryFixtures["/latest.json"]); + }); server.get("/top/all.json", () => { return helper.response(DiscoveryFixtures["/latest.json"]); }); } }); -function setupUser() { +QUnit.test("redirects categories to weekly top", async assert => { logIn(); + + replaceCurrentUser({ + should_be_redirected_to_top: true, + redirected_to_top: { + period: "weekly", + reason: "Welcome back!" + } + }); + + await visit("/categories"); + assert.equal(currentPath(), "discovery.topWeekly", "it works for categories"); +}); + +QUnit.test("redirects latest to monthly top", async assert => { + logIn(); + + replaceCurrentUser({ + should_be_redirected_to_top: true, + redirected_to_top: { + period: "monthly", + reason: "Welcome back!" + } + }); + + await visit("/latest"); + assert.equal(currentPath(), "discovery.topMonthly", "it works for latest"); +}); + +QUnit.test("redirects root to All top", async assert => { + logIn(); + replaceCurrentUser({ should_be_redirected_to_top: true, redirected_to_top: { @@ -18,22 +55,7 @@ function setupUser() { reason: "Welcome back!" } }); -} -QUnit.test("redirects categories to top", async assert => { - setupUser(); - await visit("/categories"); - assert.equal(currentPath(), "discovery.topAll", "it works for categories"); -}); - -QUnit.test("redirects latest to top", async assert => { - setupUser(); - await visit("/latest"); - assert.equal(currentPath(), "discovery.topAll", "it works for latest"); -}); - -QUnit.test("redirects root to top", async assert => { - setupUser(); await visit("/"); assert.equal(currentPath(), "discovery.topAll", "it works for root"); }); diff --git a/test/javascripts/acceptance/review-test.js.es6 b/test/javascripts/acceptance/review-test.js.es6 index 958f6fe2db..e4b45f43b6 100644 --- a/test/javascripts/acceptance/review-test.js.es6 +++ b/test/javascripts/acceptance/review-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; acceptance("Review", { diff --git a/test/javascripts/acceptance/search-full-test.js.es6 b/test/javascripts/acceptance/search-full-test.js.es6 index a804c9f3b1..ebebff6d23 100644 --- a/test/javascripts/acceptance/search-full-test.js.es6 +++ b/test/javascripts/acceptance/search-full-test.js.es6 @@ -1,4 +1,6 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance, waitFor } from "helpers/qunit-helpers"; + acceptance("Search - Full Page", { settings: { tagging_enabled: true }, loggedIn: true, diff --git a/test/javascripts/acceptance/search-test.js.es6 b/test/javascripts/acceptance/search-test.js.es6 index fe2e364e2e..49a1ae99a7 100644 --- a/test/javascripts/acceptance/search-test.js.es6 +++ b/test/javascripts/acceptance/search-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance, logIn } from "helpers/qunit-helpers"; const emptySearchContextCallbacks = []; diff --git a/test/javascripts/acceptance/share-and-invite-mobile-test.js.es6 b/test/javascripts/acceptance/share-and-invite-mobile-test.js.es6 index 9ab9c767e4..62526e8dcb 100644 --- a/test/javascripts/acceptance/share-and-invite-mobile-test.js.es6 +++ b/test/javascripts/acceptance/share-and-invite-mobile-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; acceptance("Share and Invite modal - mobile", { diff --git a/test/javascripts/acceptance/shared-drafts-test.js.es6 b/test/javascripts/acceptance/shared-drafts-test.js.es6 index f6433aee22..3d15c71907 100644 --- a/test/javascripts/acceptance/shared-drafts-test.js.es6 +++ b/test/javascripts/acceptance/shared-drafts-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; acceptance("Shared Drafts", { loggedIn: true }); diff --git a/test/javascripts/acceptance/tag-groups-test.js.es6 b/test/javascripts/acceptance/tag-groups-test.js.es6 index 95556f09dd..ebfb5c1349 100644 --- a/test/javascripts/acceptance/tag-groups-test.js.es6 +++ b/test/javascripts/acceptance/tag-groups-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; acceptance("Tag Groups", { diff --git a/test/javascripts/acceptance/topic-edit-timer-test.js.es6 b/test/javascripts/acceptance/topic-edit-timer-test.js.es6 index af8ab7e774..07872528ae 100644 --- a/test/javascripts/acceptance/topic-edit-timer-test.js.es6 +++ b/test/javascripts/acceptance/topic-edit-timer-test.js.es6 @@ -1,4 +1,6 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers"; + acceptance("Topic - Edit timer", { loggedIn: true, pretend(server, helper) { diff --git a/test/javascripts/acceptance/topic-footer-buttons-mobile-test.js.es6 b/test/javascripts/acceptance/topic-footer-buttons-mobile-test.js.es6 index ca154fe7bb..ca3483ad28 100644 --- a/test/javascripts/acceptance/topic-footer-buttons-mobile-test.js.es6 +++ b/test/javascripts/acceptance/topic-footer-buttons-mobile-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { withPluginApi } from "discourse/lib/plugin-api"; import { clearTopicFooterButtons } from "discourse/lib/register-topic-footer-button"; import { acceptance } from "helpers/qunit-helpers"; diff --git a/test/javascripts/acceptance/topic-notifications-button-test.js.es6 b/test/javascripts/acceptance/topic-notifications-button-test.js.es6 index 90a41acd71..3f6e028b0e 100644 --- a/test/javascripts/acceptance/topic-notifications-button-test.js.es6 +++ b/test/javascripts/acceptance/topic-notifications-button-test.js.es6 @@ -1,4 +1,6 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; + acceptance("Topic Notifications button", { loggedIn: true, pretend(server, helper) { diff --git a/test/javascripts/acceptance/topic-test.js.es6 b/test/javascripts/acceptance/topic-test.js.es6 index 67f4cae3e4..3623d74c4e 100644 --- a/test/javascripts/acceptance/topic-test.js.es6 +++ b/test/javascripts/acceptance/topic-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; import { IMAGE_VERSION as v } from "pretty-text/emoji/version"; diff --git a/test/javascripts/admin/components/group-list-setting-test.js.es6 b/test/javascripts/admin/components/group-list-setting-test.js.es6 index d64bd9d7d1..e6be2313d3 100644 --- a/test/javascripts/admin/components/group-list-setting-test.js.es6 +++ b/test/javascripts/admin/components/group-list-setting-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; moduleForComponent("group-list", { integration: true }); diff --git a/test/javascripts/components/categories-admin-dropdown-test.js.es6 b/test/javascripts/components/categories-admin-dropdown-test.js.es6 index 1e62bd06d6..198aa5dde5 100644 --- a/test/javascripts/components/categories-admin-dropdown-test.js.es6 +++ b/test/javascripts/components/categories-admin-dropdown-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; moduleForComponent("categories-admin-dropdown", { integration: true }); diff --git a/test/javascripts/components/category-chooser-test.js.es6 b/test/javascripts/components/category-chooser-test.js.es6 index c93a14e584..70ecd7d4f0 100644 --- a/test/javascripts/components/category-chooser-test.js.es6 +++ b/test/javascripts/components/category-chooser-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; moduleForComponent("category-chooser", { diff --git a/test/javascripts/components/category-drop-test.js.es6 b/test/javascripts/components/category-drop-test.js.es6 index f4569c4d98..895a176cf5 100644 --- a/test/javascripts/components/category-drop-test.js.es6 +++ b/test/javascripts/components/category-drop-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; import Category from "discourse/models/category"; diff --git a/test/javascripts/components/category-selector-test.js.es6 b/test/javascripts/components/category-selector-test.js.es6 index c6f82fa1fa..9376b692a3 100644 --- a/test/javascripts/components/category-selector-test.js.es6 +++ b/test/javascripts/components/category-selector-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; import Category from "discourse/models/category"; diff --git a/test/javascripts/components/combo-box-test.js.es6 b/test/javascripts/components/combo-box-test.js.es6 index 274202fa24..94987dbb91 100644 --- a/test/javascripts/components/combo-box-test.js.es6 +++ b/test/javascripts/components/combo-box-test.js.es6 @@ -1,4 +1,6 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; + moduleForComponent("combo-box", { integration: true, beforeEach: function() { diff --git a/test/javascripts/components/d-editor-test.js.es6 b/test/javascripts/components/d-editor-test.js.es6 index 612d67de08..3ab5538a67 100644 --- a/test/javascripts/components/d-editor-test.js.es6 +++ b/test/javascripts/components/d-editor-test.js.es6 @@ -1,5 +1,10 @@ import componentTest from "helpers/component-test"; import { withPluginApi } from "discourse/lib/plugin-api"; +import formatTextWithSelection from "helpers/d-editor-helper"; +import { + setTextareaSelection, + getTextareaSelection +} from "helpers/textarea-selection-helper"; moduleForComponent("d-editor", { integration: true }); @@ -797,12 +802,14 @@ composerTestCase("replace-text event for composer", async function(assert) { .lookup("app-events:main") .trigger("composer:replace-text", "green", "yellow", { forceFocus: true }); - let expect = await formatTextWithSelection(AFTER, CASE.after); // eslint-disable-line no-undef - let actual = await formatTextWithSelection( // eslint-disable-line no-undef - this.value, - getTextareaSelection(textarea) - ); - assert.equal(actual, expect); + Ember.run.next(() => { + let expect = formatTextWithSelection(AFTER, CASE.after); + let actual = formatTextWithSelection( + this.value, + getTextareaSelection(textarea) + ); + assert.equal(actual, expect); + }); }); } })(); diff --git a/test/javascripts/components/list-setting-test.js.es6 b/test/javascripts/components/list-setting-test.js.es6 index 2926c2d481..2242e8459b 100644 --- a/test/javascripts/components/list-setting-test.js.es6 +++ b/test/javascripts/components/list-setting-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; moduleForComponent("list-setting", { integration: true }); diff --git a/test/javascripts/components/mini-tag-chooser-test.js.es6 b/test/javascripts/components/mini-tag-chooser-test.js.es6 index 8ff526f46f..ae52da4c70 100644 --- a/test/javascripts/components/mini-tag-chooser-test.js.es6 +++ b/test/javascripts/components/mini-tag-chooser-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; moduleForComponent("mini-tag-chooser", { diff --git a/test/javascripts/components/multi-select-test.js.es6 b/test/javascripts/components/multi-select-test.js.es6 index fbb3022fc7..62c1c09e5e 100644 --- a/test/javascripts/components/multi-select-test.js.es6 +++ b/test/javascripts/components/multi-select-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; import { withPluginApi } from "discourse/lib/plugin-api"; import { clearCallbacks } from "select-kit/mixins/plugin-api"; diff --git a/test/javascripts/components/pinned-options-test.js.es6 b/test/javascripts/components/pinned-options-test.js.es6 index a7fe2c50fb..25106a2203 100644 --- a/test/javascripts/components/pinned-options-test.js.es6 +++ b/test/javascripts/components/pinned-options-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; import Topic from "discourse/models/topic"; diff --git a/test/javascripts/components/single-select-test.js.es6 b/test/javascripts/components/single-select-test.js.es6 index c92cd9dc09..22fab0a4f9 100644 --- a/test/javascripts/components/single-select-test.js.es6 +++ b/test/javascripts/components/single-select-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; import { withPluginApi } from "discourse/lib/plugin-api"; import { clearCallbacks } from "select-kit/mixins/plugin-api"; diff --git a/test/javascripts/components/tag-drop-test.js.es6 b/test/javascripts/components/tag-drop-test.js.es6 index 7ef094c461..2cfab99b48 100644 --- a/test/javascripts/components/tag-drop-test.js.es6 +++ b/test/javascripts/components/tag-drop-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; import DiscourseURL from "discourse/lib/url"; diff --git a/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 b/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 index adcdc37a80..5cfd498fd5 100644 --- a/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 +++ b/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; import Topic from "discourse/models/topic"; diff --git a/test/javascripts/components/topic-notifications-button-test.js.es6 b/test/javascripts/components/topic-notifications-button-test.js.es6 index b64ff4fd28..63d5df2284 100644 --- a/test/javascripts/components/topic-notifications-button-test.js.es6 +++ b/test/javascripts/components/topic-notifications-button-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; import Topic from "discourse/models/topic"; diff --git a/test/javascripts/components/topic-notifications-options-test.js.es6 b/test/javascripts/components/topic-notifications-options-test.js.es6 index 6d74d71097..25bf0af9b7 100644 --- a/test/javascripts/components/topic-notifications-options-test.js.es6 +++ b/test/javascripts/components/topic-notifications-options-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; import Topic from "discourse/models/topic"; diff --git a/test/javascripts/components/value-list-test.js.es6 b/test/javascripts/components/value-list-test.js.es6 index 7bbbd21a8e..76185bbee8 100644 --- a/test/javascripts/components/value-list-test.js.es6 +++ b/test/javascripts/components/value-list-test.js.es6 @@ -1,3 +1,4 @@ +import selectKit from "helpers/select-kit-helper"; import componentTest from "helpers/component-test"; moduleForComponent("value-list", { integration: true }); diff --git a/test/javascripts/fixtures/concerns/notification-types.js.es6.erb b/test/javascripts/fixtures/concerns/notification-types.js.es6.erb new file mode 100644 index 0000000000..d7a4023b4d --- /dev/null +++ b/test/javascripts/fixtures/concerns/notification-types.js.es6.erb @@ -0,0 +1 @@ +export const NOTIFICATION_TYPES = <%= Notification.types.to_json %>; diff --git a/test/javascripts/fixtures/notification_fixtures.js.es6 b/test/javascripts/fixtures/notification_fixtures.js.es6 index 0ebf8bd678..071df77a0d 100644 --- a/test/javascripts/fixtures/notification_fixtures.js.es6 +++ b/test/javascripts/fixtures/notification_fixtures.js.es6 @@ -1,12 +1,12 @@ /*jshint maxlen:10000000 */ -import { LIKED_CONSOLIDATED_TYPE } from "discourse/widgets/notification-item"; +import { NOTIFICATION_TYPES } from "fixtures/concerns/notification-types"; export default { "/notifications": { notifications: [ { id: 123, - notification_type: 2, + notification_type: NOTIFICATION_TYPES.replied, read: false, post_number: 2, topic_id: 1234, @@ -15,7 +15,7 @@ export default { }, { id: 456, - notification_type: LIKED_CONSOLIDATED_TYPE, + notification_type: NOTIFICATION_TYPES.liked_consolidated, read: false, data: { display_username: "aquaman", count: "5" } } diff --git a/test/javascripts/fixtures/site-fixtures.js.es6 b/test/javascripts/fixtures/site-fixtures.js.es6 index 25d1dc32c0..5dbd4805d4 100644 --- a/test/javascripts/fixtures/site-fixtures.js.es6 +++ b/test/javascripts/fixtures/site-fixtures.js.es6 @@ -1,4 +1,4 @@ -import { LIKED_CONSOLIDATED_TYPE } from "discourse/widgets/notification-item"; +import { NOTIFICATION_TYPES } from "fixtures/concerns/notification-types"; export default { "site.json": { @@ -6,21 +6,7 @@ export default { default_archetype: "regular", disabled_plugins: [], shared_drafts_category_id: 24, - notification_types: { - mentioned: 1, - replied: 2, - quoted: 3, - edited: 4, - liked: 5, - private_message: 6, - invited_to_private_message: 7, - invitee_accepted: 8, - posted: 9, - moved_post: 10, - linked: 11, - granted_badge: 12, - liked_consolidated: LIKED_CONSOLIDATED_TYPE - }, + notification_types: NOTIFICATION_TYPES, post_types: { regular: 1, moderator_action: 2, diff --git a/test/javascripts/helpers/d-editor-helper.js b/test/javascripts/helpers/d-editor-helper.js.es6 similarity index 57% rename from test/javascripts/helpers/d-editor-helper.js rename to test/javascripts/helpers/d-editor-helper.js.es6 index 4245437645..412409e843 100644 --- a/test/javascripts/helpers/d-editor-helper.js +++ b/test/javascripts/helpers/d-editor-helper.js.es6 @@ -1,8 +1,4 @@ -Ember.Test.registerAsyncHelper("formatTextWithSelection", function( - app, - text, - [start, len] -) { +export default function formatTextWithSelection(text, [start, len]) { return [ '"', text.substr(0, start), @@ -12,4 +8,4 @@ Ember.Test.registerAsyncHelper("formatTextWithSelection", function( text.substr(start + len), '"' ].join(""); -}); +} diff --git a/test/javascripts/helpers/select-kit-helper.js b/test/javascripts/helpers/select-kit-helper.js deleted file mode 100644 index 8d6aebb3cf..0000000000 --- a/test/javascripts/helpers/select-kit-helper.js +++ /dev/null @@ -1,290 +0,0 @@ -function checkSelectKitIsNotExpanded(selector) { - if (find(selector).hasClass("is-expanded")) { - // eslint-disable-next-line no-console - console.warn( - "You expected select-kit to be collapsed but it is expanded." - ); - } -} - -function checkSelectKitIsNotCollapsed(selector) { - if (!find(selector).hasClass("is-expanded")) { - // eslint-disable-next-line no-console - console.warn( - "You expected select-kit to be expanded but it is collapsed." - ); - } -} - -Ember.Test.registerAsyncHelper("expandSelectKit", function(app, selector) { - checkSelectKitIsNotExpanded(selector); - click(selector + " .select-kit-header"); -}); - -Ember.Test.registerAsyncHelper("collapseSelectKit", function(app, selector) { - checkSelectKitIsNotCollapsed(selector); - click(selector + " .select-kit-header"); -}); - -Ember.Test.registerAsyncHelper("selectKitFillInFilter", function( - app, - filter, - selector -) { - checkSelectKitIsNotCollapsed(selector); - fillIn(selector + " .filter-input", filter); -}); - -Ember.Test.registerAsyncHelper("selectKitSelectRowByValue", function( - app, - value, - selector -) { - checkSelectKitIsNotCollapsed(selector); - click(selector + " .select-kit-row[data-value='" + value + "']"); -}); - -Ember.Test.registerAsyncHelper("selectKitSelectRowByName", function( - app, - name, - selector -) { - checkSelectKitIsNotCollapsed(selector); - click(selector + " .select-kit-row[data-name='" + name + "']"); -}); - -Ember.Test.registerAsyncHelper("selectKitSelectNoneRow", function( - app, - selector -) { - checkSelectKitIsNotCollapsed(selector); - click(selector + " .select-kit-row.none"); -}); - -Ember.Test.registerAsyncHelper("selectKitSelectRowByIndex", function( - app, - index, - selector -) { - checkSelectKitIsNotCollapsed(selector); - click(find(selector + " .select-kit-row").eq(index)); -}); - -Ember.Test.registerAsyncHelper("keyboardHelper", function( - app, - value, - target, - selector -) { - function createEvent(element, keyCode, options) { - element = element || ".filter-input"; - selector = find(selector).find(element); - options = options || {}; - - var type = options.type || "keydown"; - var event = jQuery.Event(type); - event.keyCode = keyCode; - if (options && options.metaKey) { - event.metaKey = true; - } - - andThen(() => { - find(selector).trigger(event); - }); - } - - switch (value) { - case "enter": - return createEvent(target, 13); - case "backspace": - return createEvent(target, 8); - case "selectAll": - return createEvent(target, 65, { metaKey: true }); - case "escape": - return createEvent(target, 27); - case "down": - return createEvent(target, 40); - case "up": - return createEvent(target, 38); - case "tab": - return createEvent(target, 9); - } -}); - -// eslint-disable-next-line no-unused-vars -function selectKit(selector) { - selector = selector || ".select-kit"; - - function rowHelper(row) { - return { - name: function() { - return row.attr("data-name"); - }, - icon: function() { - return row.find(".d-icon"); - }, - title: function() { - return row.attr("title"); - }, - value: function() { - return row.attr("data-value"); - }, - exists: function() { - return exists(row); - }, - el: function() { - return row; - } - }; - } - - function headerHelper(header) { - return { - value: function() { - return header.attr("data-value"); - }, - name: function() { - return header.attr("data-name"); - }, - label: function() { - return header.text().trim(); - }, - icon: function() { - return header.find(".icon"); - }, - title: function() { - return header.attr("title"); - }, - el: function() { - return header; - } - }; - } - - function filterHelper(filter) { - return { - icon: function() { - return filter.find(".d-icon"); - }, - exists: function() { - return exists(filter); - }, - el: function() { - return filter; - } - }; - } - - return { - expand: function() { - return expandSelectKit(selector); - }, - - collapse: function() { - return collapseSelectKit(selector); - }, - - selectRowByIndex: function(index) { - selectKitSelectRowByIndex(index, selector); - return selectKit(selector); - }, - - selectRowByValue: function(value) { - return selectKitSelectRowByValue(value, selector); - }, - - // Remove when stable is updated to Discourse 2.1 - selectRowByValueAwait: function(value) { - return selectKitSelectRowByValue(value, selector); - }, - - selectRowByName: function(name) { - selectKitSelectRowByValue(name, selector); - return selectKit(selector); - }, - - selectNoneRow: function() { - return selectKitSelectNoneRow(selector); - }, - - fillInFilter: function(filter) { - return selectKitFillInFilter(filter, selector); - }, - - keyboard: function(value, target) { - return keyboardHelper(value, target, selector); - }, - - isExpanded: function() { - return find(selector).hasClass("is-expanded"); - }, - - isFocused: function() { - return find(selector).hasClass("is-focused"); - }, - - isHidden: function() { - return find(selector).hasClass("is-hidden"); - }, - - header: function() { - return headerHelper(find(selector).find(".select-kit-header")); - }, - - filter: function() { - return filterHelper(find(selector).find(".select-kit-filter")); - }, - - rows: function() { - return find(selector).find(".select-kit-row"); - }, - - rowByValue: function(value) { - return rowHelper( - find(selector).find('.select-kit-row[data-value="' + value + '"]') - ); - }, - - rowByName: function(name) { - return rowHelper( - find(selector).find('.select-kit-row[data-name="' + name + '"]') - ); - }, - - rowByIndex: function(index) { - return rowHelper( - find(selector).find(".select-kit-row:eq(" + index + ")") - ); - }, - - el: function() { - return find(selector); - }, - - noneRow: function() { - return rowHelper(find(selector).find(".select-kit-row.none")); - }, - - validationMessage: function() { - var validationMessage = find(selector).find(".validation-message"); - - if (validationMessage.length) { - return validationMessage.html().trim(); - } else { - return null; - } - }, - - selectedRow: function() { - return rowHelper(find(selector).find(".select-kit-row.is-selected")); - }, - - highlightedRow: function() { - return rowHelper(find(selector).find(".select-kit-row.is-highlighted")); - }, - - exists: function() { - return exists(selector); - } - }; -} diff --git a/test/javascripts/helpers/select-kit-helper.js.es6 b/test/javascripts/helpers/select-kit-helper.js.es6 new file mode 100644 index 0000000000..47243a5518 --- /dev/null +++ b/test/javascripts/helpers/select-kit-helper.js.es6 @@ -0,0 +1,245 @@ +function checkSelectKitIsNotExpanded(selector) { + if (find(selector).hasClass("is-expanded")) { + // eslint-disable-next-line no-console + console.warn("You expected select-kit to be collapsed but it is expanded."); + } +} + +function checkSelectKitIsNotCollapsed(selector) { + if (!find(selector).hasClass("is-expanded")) { + // eslint-disable-next-line no-console + console.warn("You expected select-kit to be expanded but it is collapsed."); + } +} + +async function expandSelectKit(selector) { + checkSelectKitIsNotExpanded(selector); + return await click(`${selector} .select-kit-header`); +} + +async function collapseSelectKit(selector) { + checkSelectKitIsNotCollapsed(selector); + return await click(`${selector} .select-kit-header`); +} + +async function selectKitFillInFilter(filter, selector) { + checkSelectKitIsNotCollapsed(selector); + await fillIn(`${selector} .filter-input`, filter); +} + +async function selectKitSelectRowByValue(value, selector) { + checkSelectKitIsNotCollapsed(selector); + await click(`${selector} .select-kit-row[data-value='${value}']`); +} + +async function selectKitSelectRowByName(name, selector) { + checkSelectKitIsNotCollapsed(selector); + await click(`${selector} .select-kit-row[data-name='${name}']`); +} + +async function selectKitSelectNoneRow(selector) { + checkSelectKitIsNotCollapsed(selector); + await click(`${selector} .select-kit-row.none`); +} + +async function selectKitSelectRowByIndex(index, selector) { + checkSelectKitIsNotCollapsed(selector); + await click(find(`${selector} .select-kit-row`).eq(index)); +} + +async function keyboardHelper(value, target, selector) { + target = find(selector).find(target || ".filter-input"); + + if (value === "selectAll") { + // special casing the only one not working with triggerEvent + const event = jQuery.Event("keydown"); + event.keyCode = 65; + event.metaKey = true; + target.trigger(event); + } else { + const mapping = { + enter: { keyCode: 13 }, + backspace: { keyCode: 8 }, + escape: { keyCode: 27 }, + down: { keyCode: 40 }, + up: { keyCode: 38 }, + tab: { keyCode: 9 } + }; + + await triggerEvent(target, "keydown", mapping[value]); + } +} + +function rowHelper(row) { + return { + name() { + return row.attr("data-name"); + }, + icon() { + return row.find(".d-icon"); + }, + title() { + return row.attr("title"); + }, + value() { + return row.attr("data-value"); + }, + exists() { + return exists(row); + }, + el() { + return row; + } + }; +} + +function headerHelper(header) { + return { + value() { + return header.attr("data-value"); + }, + name() { + return header.attr("data-name"); + }, + label() { + return header.text().trim(); + }, + icon() { + return header.find(".icon"); + }, + title() { + return header.attr("title"); + }, + el() { + return header; + } + }; +} + +function filterHelper(filter) { + return { + icon() { + return filter.find(".d-icon"); + }, + exists() { + return exists(filter); + }, + el() { + return filter; + } + }; +} + +export default function selectKit(selector) { + selector = selector || ".select-kit"; + + return { + async expand() { + await expandSelectKit(selector); + }, + + async collapse() { + await collapseSelectKit(selector); + }, + + async selectRowByIndex(index) { + await selectKitSelectRowByIndex(index, selector); + }, + + async selectRowByValue(value) { + await selectKitSelectRowByValue(value, selector); + }, + + async selectKitSelectRowByName(name) { + await selectKitSelectRowByName(name, selector); + }, + + async selectRowByName(name) { + await selectKitSelectRowByValue(name, selector); + }, + + async selectNoneRow() { + await selectKitSelectNoneRow(selector); + }, + + async fillInFilter(filter) { + await selectKitFillInFilter(filter, selector); + }, + + async keyboard(value, target) { + await keyboardHelper(value, target, selector); + }, + + isExpanded() { + return find(selector).hasClass("is-expanded"); + }, + + isFocused() { + return find(selector).hasClass("is-focused"); + }, + + isHidden() { + return find(selector).hasClass("is-hidden"); + }, + + header() { + return headerHelper(find(selector).find(".select-kit-header")); + }, + + filter() { + return filterHelper(find(selector).find(".select-kit-filter")); + }, + + rows() { + return find(selector).find(".select-kit-row"); + }, + + rowByValue(value) { + return rowHelper( + find(selector).find('.select-kit-row[data-value="' + value + '"]') + ); + }, + + rowByName(name) { + return rowHelper( + find(selector).find('.select-kit-row[data-name="' + name + '"]') + ); + }, + + rowByIndex(index) { + return rowHelper( + find(selector).find(".select-kit-row:eq(" + index + ")") + ); + }, + + el() { + return find(selector); + }, + + noneRow() { + return rowHelper(find(selector).find(".select-kit-row.none")); + }, + + validationMessage() { + const validationMessage = find(selector).find(".validation-message"); + + if (validationMessage.length) { + return validationMessage.html().trim(); + } else { + return null; + } + }, + + selectedRow() { + return rowHelper(find(selector).find(".select-kit-row.is-selected")); + }, + + highlightedRow() { + return rowHelper(find(selector).find(".select-kit-row.is-highlighted")); + }, + + exists() { + return exists(selector); + } + }; +} diff --git a/test/javascripts/helpers/textarea-selection-helper.js.es6 b/test/javascripts/helpers/textarea-selection-helper.js.es6 new file mode 100644 index 0000000000..1f914d794e --- /dev/null +++ b/test/javascripts/helpers/textarea-selection-helper.js.es6 @@ -0,0 +1,10 @@ +export function setTextareaSelection(textarea, selectionStart, selectionEnd) { + textarea.selectionStart = selectionStart; + textarea.selectionEnd = selectionEnd; +} + +export function getTextareaSelection(textarea) { + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + return [start, end - start]; +} diff --git a/test/javascripts/helpers/textarea-selection.js b/test/javascripts/helpers/textarea-selection.js deleted file mode 100644 index c406928dfc..0000000000 --- a/test/javascripts/helpers/textarea-selection.js +++ /dev/null @@ -1,15 +0,0 @@ -Ember.Test.registerHelper("setTextareaSelection", function( - app, - textarea, - selectionStart, - selectionEnd -) { - textarea.selectionStart = selectionStart; - textarea.selectionEnd = selectionEnd; -}); - -Ember.Test.registerHelper("getTextareaSelection", function(app, textarea) { - var start = textarea.selectionStart; - var end = textarea.selectionEnd; - return [start, end - start]; -}); diff --git a/test/javascripts/lib/click-track-edit-history-test.js.es6 b/test/javascripts/lib/click-track-edit-history-test.js.es6 index 10d05a9275..e38c5d23db 100644 --- a/test/javascripts/lib/click-track-edit-history-test.js.es6 +++ b/test/javascripts/lib/click-track-edit-history-test.js.es6 @@ -1,6 +1,6 @@ import DiscourseURL from "discourse/lib/url"; import ClickTrack from "discourse/lib/click-track"; -import { logIn } from "helpers/qunit-helpers"; +import { fixture, logIn } from "helpers/qunit-helpers"; QUnit.module("lib:click-track-edit-history", { beforeEach() { diff --git a/test/javascripts/lib/click-track-profile-page-test.js.es6 b/test/javascripts/lib/click-track-profile-page-test.js.es6 index 3496626656..1d51d5f0dd 100644 --- a/test/javascripts/lib/click-track-profile-page-test.js.es6 +++ b/test/javascripts/lib/click-track-profile-page-test.js.es6 @@ -1,6 +1,6 @@ import DiscourseURL from "discourse/lib/url"; import ClickTrack from "discourse/lib/click-track"; -import { logIn } from "helpers/qunit-helpers"; +import { fixture, logIn } from "helpers/qunit-helpers"; QUnit.module("lib:click-track-profile-page", { beforeEach() { diff --git a/test/javascripts/lib/click-track-test.js.es6 b/test/javascripts/lib/click-track-test.js.es6 index 17cf0772b1..a80d25b070 100644 --- a/test/javascripts/lib/click-track-test.js.es6 +++ b/test/javascripts/lib/click-track-test.js.es6 @@ -1,6 +1,6 @@ import DiscourseURL from "discourse/lib/url"; import ClickTrack from "discourse/lib/click-track"; -import { logIn } from "helpers/qunit-helpers"; +import { fixture, asyncTestDiscourse, logIn } from "helpers/qunit-helpers"; QUnit.module("lib:click-track", { beforeEach() { diff --git a/test/javascripts/lib/highlight-text-test.js.es6 b/test/javascripts/lib/highlight-text-test.js.es6 index 1c1ceaec2c..d76b9e0778 100644 --- a/test/javascripts/lib/highlight-text-test.js.es6 +++ b/test/javascripts/lib/highlight-text-test.js.es6 @@ -2,6 +2,7 @@ import { default as highlightText, CLASS_NAME } from "discourse/lib/highlight-text"; +import { fixture } from "helpers/qunit-helpers"; QUnit.module("lib:highlight-text"); diff --git a/test/javascripts/lib/i18n-test.js.es6 b/test/javascripts/lib/i18n-test.js.es6 index b68c871d43..f1c628ab25 100644 --- a/test/javascripts/lib/i18n-test.js.es6 +++ b/test/javascripts/lib/i18n-test.js.es6 @@ -2,6 +2,8 @@ QUnit.module("lib:i18n", { _locale: I18n.locale, _fallbackLocale: I18n.fallbackLocale, _translations: I18n.translations, + _extras: I18n.extras, + _pluralizationRules: Object.assign({}, I18n.pluralizationRules), beforeEach() { I18n.locale = "fr"; @@ -34,6 +36,9 @@ QUnit.module("lib:i18n", { few: "{{count}} FEW", many: "{{count}} MANY", other: "{{count}} OTHER" + }, + days: { + other: "%{count} jours" } } }, @@ -51,12 +56,17 @@ QUnit.module("lib:i18n", { word_count: { one: "1 word", other: "{{count}} words" + }, + days: { + one: "%{count} day", + other: "%{count} days" } } } }; // fake pluralization rules + I18n.pluralizationRules = Object.assign({}, I18n.pluralizationRules); I18n.pluralizationRules.fr = function(n) { if (n === 0) return "zero"; if (n === 1) return "one"; @@ -71,6 +81,8 @@ QUnit.module("lib:i18n", { I18n.locale = this._locale; I18n.fallbackLocale = this._fallbackLocale; I18n.translations = this._translations; + I18n.extras = this._extras; + I18n.pluralizationRules = this._pluralizationRules; } }); @@ -181,6 +193,17 @@ QUnit.test("pluralizations", assert => { }); QUnit.test("fallback", assert => { + assert.equal( + I18n.t("days", { count: 1 }), + "1 day", + "uses fallback locale for missing plural key" + ); + assert.equal( + I18n.t("days", { count: 200 }), + "200 jours", + "uses existing French plural key" + ); + I18n.locale = "fr_FOO"; I18n.fallbackLocale = "fr"; diff --git a/test/javascripts/lib/preload-store-test.js.es6 b/test/javascripts/lib/preload-store-test.js.es6 index b17d8f20e8..c43f2b8dd4 100644 --- a/test/javascripts/lib/preload-store-test.js.es6 +++ b/test/javascripts/lib/preload-store-test.js.es6 @@ -1,4 +1,5 @@ import PreloadStore from "preload-store"; +import { asyncTestDiscourse } from "helpers/qunit-helpers"; QUnit.module("preload-store", { beforeEach() { diff --git a/test/javascripts/lib/to-markdown-test.js.es6 b/test/javascripts/lib/to-markdown-test.js.es6 index f63a004c26..2cb28ba25f 100644 --- a/test/javascripts/lib/to-markdown-test.js.es6 +++ b/test/javascripts/lib/to-markdown-test.js.es6 @@ -166,6 +166,9 @@ QUnit.test("converts img tag", assert => { let html = ``; assert.equal(toMarkdown(html), `![|100x50](${url})`); + html = ``; + assert.equal(toMarkdown(html), `![|100x50](${url} "some title")`); + html = `
description
`; assert.equal(toMarkdown(html), `![description|50x100](${url})`); diff --git a/test/javascripts/lib/tooltip-test.js.es6 b/test/javascripts/lib/tooltip-test.js.es6 index df59cfd7f3..5e69515150 100644 --- a/test/javascripts/lib/tooltip-test.js.es6 +++ b/test/javascripts/lib/tooltip-test.js.es6 @@ -1,4 +1,5 @@ import { registerTooltip, registerHoverTooltip } from "discourse/lib/tooltip"; +import { fixture } from "helpers/qunit-helpers"; // prettier-ignore QUnit.module("lib:tooltip", { diff --git a/test/javascripts/lib/upload-short-url-test.js.es6 b/test/javascripts/lib/upload-short-url-test.js.es6 index ae0434cd02..c18ae0ff9a 100644 --- a/test/javascripts/lib/upload-short-url-test.js.es6 +++ b/test/javascripts/lib/upload-short-url-test.js.es6 @@ -4,6 +4,7 @@ import { resetCache } from "pretty-text/upload-short-url"; import { ajax } from "discourse/lib/ajax"; +import { fixture } from "helpers/qunit-helpers"; QUnit.module("lib:pretty-text/upload-short-url", { beforeEach() { diff --git a/test/javascripts/test_helper.js b/test/javascripts/test_helper.js index deeaf54ed7..ee3ee129d8 100644 --- a/test/javascripts/test_helper.js +++ b/test/javascripts/test_helper.js @@ -30,9 +30,6 @@ //= require sinon/pkg/sinon //= require helpers/assertions -//= require helpers/textarea-selection -//= require helpers/select-kit-helper -//= require helpers/d-editor-helper //= require helpers/qunit-helpers //= require_tree ./fixtures @@ -157,16 +154,30 @@ QUnit.testDone(function() { flushMap(); server.shutdown(); + + window.server = null; + + // ensures any event not removed is not leaking between tests + // most likely in intialisers, other places (controller, component...) + // should be fixed in code + var appEvents = window.Discourse.__container__.lookup("app-events:main"); + var events = appEvents.__proto__._events; + Object.keys(events).forEach(function(eventKey) { + var event = events[eventKey]; + event.forEach(function(listener) { + appEvents.off(eventKey, listener.target, listener.fn); + }); + }); + + // attempts to remove any subscribed message bus callback + window.MessageBus.callbacks.forEach(function(callback) { + window.MessageBus.unsubscribe(callback.channel, callback.func); + }); }); // Load ES6 tests var helpers = require("helpers/qunit-helpers"); -// TODO: Replace with proper imports rather than globals -window.asyncTestDiscourse = helpers.asyncTestDiscourse; -window.controllerFor = helpers.controllerFor; -window.fixture = helpers.fixture; - function getUrlParameter(name) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"); diff --git a/test/javascripts/widgets/post-test.js.es6 b/test/javascripts/widgets/post-test.js.es6 index f3aeec4028..aa5d829194 100644 --- a/test/javascripts/widgets/post-test.js.es6 +++ b/test/javascripts/widgets/post-test.js.es6 @@ -302,8 +302,7 @@ widgetTest( beforeEach() { this.set("args", { canDeleteTopic: false, - yours: true, - firstPost: true + showFlagDelete: true }); }, diff --git a/yarn.lock b/yarn.lock index b552a1915e..e8b5afbe81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1392,9 +1392,9 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.9.1: - version "3.12.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" - integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== dependencies: argparse "^1.0.7" esprima "^4.0.0"