diff --git a/.eslintrc b/.eslintrc index 8706c9f23d..511724fb50 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,14 +5,41 @@ "eol-last": 2 }, "globals": { + "_": "off", + "acceptance": "off", + "asyncRender": "off", + "bootbox": "off", + "click": "off", + "count": "off", + "currentPath": "off", + "currentRouteName": "off", + "currentURL": "off", + "currentUser": "off", + "Discourse": "off", + "exists": "off", + "fillIn": "off", + "find": "off", + "getSettledState": "off", + "hasModule": "off", + "invisible": "off", + "jQuery": "off", + "keyboardHelper": "off", + "keyEvent": "off", "moduleFor": "off", "moduleForComponent": "off", - "testStart": "off", - "testDone": "off", + "pauseTest": "off", + "Pretender": "off", + "query": "off", + "queryAll": "off", + "QUnit": "off", + "sandbox": "off", "sinon": "off", - "currentURL": "off", - "invisible": "off", + "test": "off", + "testDone": "off", + "testStart": "off", + "triggerEvent": "off", "visible": "off", - "count": "off" + "visit": "off", + "waitUntil": "off" } } diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index f1ca33ad32..ac5b712d03 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -61,11 +61,11 @@ jobs: - name: ESLint (core) if: ${{ always() }} - run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern app/assets/javascripts + run: yarn eslint app/assets/javascripts - name: ESLint (core plugins) if: ${{ always() }} - run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern plugins/**/{test,assets}/javascripts + run: yarn eslint plugins - name: Prettier if: ${{ always() }} @@ -73,9 +73,9 @@ jobs: yarn prettier -v yarn prettier --list-different \ "app/assets/stylesheets/**/*.scss" \ - "app/assets/javascripts/**/*.{js,es6}" \ + "app/assets/javascripts/**/*.js" \ "plugins/**/assets/stylesheets/**/*.scss" \ - "plugins/**/assets/javascripts/**/*.{js,es6}" + "plugins/**/assets/javascripts/**/*.js" - name: Ember template lint if: ${{ always() }} diff --git a/Gemfile b/Gemfile index e09839e88a..9cdfbf21a9 100644 --- a/Gemfile +++ b/Gemfile @@ -181,9 +181,14 @@ group :development do gem 'yaml-lint' end -group ENV["ALLOW_DEV_POPULATE"] == "1" ? :production : :development do +if ENV["ALLOW_DEV_POPULATE"] == "1" gem 'discourse_dev_assets' gem 'faker', "~> 2.16" +else + group :development do + gem 'discourse_dev_assets' + gem 'faker', "~> 2.16" + end end # this is an optional gem, it provides a high performance replacement diff --git a/Gemfile.lock b/Gemfile.lock index 0e75936277..6cbb820298 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -129,7 +129,7 @@ GEM sprockets (>= 3.3, < 4.1) ember-source (2.18.2) erubi (1.10.0) - excon (0.87.0) + excon (0.88.0) execjs (2.8.1) exifr (1.3.9) fabrication (2.22.0) @@ -171,23 +171,23 @@ GEM hkdf (0.3.0) htmlentities (4.3.4) http_accept_language (2.1.1) - i18n (1.8.10) + i18n (1.8.11) concurrent-ruby (~> 1.0) - image_optim (0.31.0) + image_optim (0.31.1) exifr (~> 1.2, >= 1.2.2) fspath (~> 3.0) - image_size (>= 1.5, < 3) + image_size (>= 1.5, < 4) in_threads (~> 1.3) progress (~> 3.0, >= 3.0.1) - image_size (2.1.2) + image_size (3.0.1) in_threads (1.5.4) - ipaddr (1.2.2) + ipaddr (1.2.3) jmespath (1.4.0) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.6.0) + json (2.6.1) json-schema (2.8.1) addressable (>= 2.4) json_schemer (0.2.18) @@ -197,12 +197,10 @@ GEM uri_template (~> 0.7) jwt (2.3.0) kgio (2.11.4) - libv8-node (15.14.0.1) - libv8-node (15.14.0.1-arm64-darwin-20) - libv8-node (15.14.0.1-x86_64-darwin-18) - libv8-node (15.14.0.1-x86_64-darwin-19) - libv8-node (15.14.0.1-x86_64-darwin-20) - libv8-node (15.14.0.1-x86_64-linux) + libv8-node (16.10.0.0) + libv8-node (16.10.0.0-x86_64-darwin) + libv8-node (16.10.0.0-x86_64-darwin-19) + libv8-node (16.10.0.0-x86_64-linux) listen (3.7.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) @@ -215,7 +213,7 @@ GEM logstash-event (1.2.02) logstash-logger (0.26.1) logstash-event (~> 1.2) - logster (2.9.7) + logster (2.9.8) loofah (2.12.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -228,8 +226,8 @@ GEM method_source (1.0.0) mini_mime (1.1.2) mini_portile2 (2.6.1) - mini_racer (0.4.0) - libv8-node (~> 15.14.0.0) + mini_racer (0.5.0) + libv8-node (~> 16.10.0.0) mini_scheduler (0.13.0) sidekiq (>= 4.2.3) mini_sql (1.1.3) @@ -254,7 +252,7 @@ GEM racc (~> 1.4) nokogiri (1.12.5-x86_64-linux) racc (~> 1.4) - oauth (0.5.6) + oauth (0.5.8) oauth2 (1.4.7) faraday (>= 0.8, < 2.0) jwt (>= 1.0, < 3.0) @@ -265,7 +263,7 @@ GEM omniauth (1.9.1) hashie (>= 3.4.6) rack (>= 1.6.2, < 3) - omniauth-facebook (8.0.0) + omniauth-facebook (9.0.0) omniauth-oauth2 (~> 1.2) omniauth-github (1.4.0) omniauth (~> 1.5) @@ -278,7 +276,7 @@ GEM omniauth-oauth (1.2.0) oauth omniauth (>= 1.0, < 3) - omniauth-oauth2 (1.7.1) + omniauth-oauth2 (1.7.2) oauth2 (~> 1.4) omniauth (>= 1.9, < 3) omniauth-twitter (1.4.0) @@ -325,7 +323,7 @@ GEM activerecord (~> 6.0) concurrent-ruby railties (~> 6.0) - rails_multisite (3.1.0) + rails_multisite (4.0.0) activerecord (> 5.0, < 7) railties (> 5.0, < 7) railties (6.1.4.1) @@ -381,7 +379,7 @@ GEM rspec-expectations (~> 3.10) rspec-mocks (~> 3.10) rspec-support (~> 3.10) - rspec-support (3.10.2) + rspec-support (3.10.3) rss (0.2.9) rexml rswag-specs (2.4.0) @@ -389,7 +387,7 @@ GEM json-schema (~> 2.2) railties (>= 3.1, < 7.0) rtlit (0.0.5) - rubocop (1.22.1) + rubocop (1.22.3) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) @@ -398,12 +396,12 @@ GEM rubocop-ast (>= 1.12.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.12.0) + rubocop-ast (1.13.0) parser (>= 3.0.1.1) rubocop-discourse (2.4.2) rubocop (>= 1.1.0) rubocop-rspec (>= 2.0.0) - rubocop-rspec (2.5.0) + rubocop-rspec (2.6.0) rubocop (~> 1.19) ruby-prof (1.4.3) ruby-progressbar (1.11.0) @@ -429,7 +427,7 @@ GEM activesupport (>= 3.1) shoulda-matchers (5.0.0) activesupport (>= 5.2.0) - sidekiq (6.2.2) + sidekiq (6.3.1) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) @@ -442,9 +440,9 @@ GEM sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.3.0) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) sshkey (2.0.0) stackprof (0.2.17) @@ -473,7 +471,7 @@ GEM jwt (~> 2.0) xorcist (1.1.2) yaml-lint (0.0.10) - zeitwerk (2.4.2) + zeitwerk (2.5.1) PLATFORMS arm64-darwin-20 diff --git a/app/assets/javascripts/admin/addon/components/admin-backups-logs.js b/app/assets/javascripts/admin/addon/components/admin-backups-logs.js index f691f3ffc6..c707c99b70 100644 --- a/app/assets/javascripts/admin/addon/components/admin-backups-logs.js +++ b/app/assets/javascripts/admin/addon/components/admin-backups-logs.js @@ -33,7 +33,7 @@ export default Component.extend({ } }, - _updateFormattedLogsFunc: function () { + _updateFormattedLogsFunc() { const logs = this.logs; if (logs.length === 0) { return; @@ -48,7 +48,7 @@ export default Component.extend({ } // update the formatted logs & cache index this.setProperties({ - formattedLogs: formattedLogs, + formattedLogs, index: logs.length, }); // force rerender diff --git a/app/assets/javascripts/admin/addon/components/admin-graph.js b/app/assets/javascripts/admin/addon/components/admin-graph.js index 8df160aa85..1107abeb2c 100644 --- a/app/assets/javascripts/admin/addon/components/admin-graph.js +++ b/app/assets/javascripts/admin/addon/components/admin-graph.js @@ -24,7 +24,7 @@ export default Component.extend({ const config = { type: this.type, - data: data, + data, options: { responsive: true, plugins: { diff --git a/app/assets/javascripts/admin/addon/components/admin-theme-editor.js b/app/assets/javascripts/admin/addon/components/admin-theme-editor.js index 36d861779a..6910e15f87 100644 --- a/app/assets/javascripts/admin/addon/components/admin-theme-editor.js +++ b/app/assets/javascripts/admin/addon/components/admin-theme-editor.js @@ -114,7 +114,7 @@ export default Component.extend({ this.fieldAdded(this.currentTargetName, name); }, - toggleMaximize: function () { + toggleMaximize() { this.toggleProperty("maximized"); next(() => this.appEvents.trigger("ace:resize")); }, diff --git a/app/assets/javascripts/admin/addon/components/color-input.js b/app/assets/javascripts/admin/addon/components/color-input.js index 91aeaec6e0..c7a8da4b78 100644 --- a/app/assets/javascripts/admin/addon/components/color-input.js +++ b/app/assets/javascripts/admin/addon/components/color-input.js @@ -40,7 +40,7 @@ export default Component.extend({ }, @observes("hexValue", "brightnessValue", "valid") - hexValueChanged: function () { + hexValueChanged() { const hex = this.hexValue; let text = this.element.querySelector("input.hex-input"); diff --git a/app/assets/javascripts/admin/addon/components/secret-value-list.js b/app/assets/javascripts/admin/addon/components/secret-value-list.js index cd63e3b0ad..ab79dad367 100644 --- a/app/assets/javascripts/admin/addon/components/secret-value-list.js +++ b/app/assets/javascripts/admin/addon/components/secret-value-list.js @@ -63,7 +63,7 @@ export default Component.extend({ }, _addValue(value, secret) { - this.collection.addObject({ key: value, secret: secret }); + this.collection.addObject({ key: value, secret }); this._saveValues(); }, diff --git a/app/assets/javascripts/admin/addon/components/tags-uploader.js b/app/assets/javascripts/admin/addon/components/tags-uploader.js index 1dc3bfc467..dcd57a45fd 100644 --- a/app/assets/javascripts/admin/addon/components/tags-uploader.js +++ b/app/assets/javascripts/admin/addon/components/tags-uploader.js @@ -1,14 +1,15 @@ import Component from "@ember/component"; import I18n from "I18n"; -import UploadMixin from "discourse/mixins/upload"; +import UppyUploadMixin from "discourse/mixins/uppy-upload"; import { alias } from "@ember/object/computed"; import bootbox from "bootbox"; -export default Component.extend(UploadMixin, { +export default Component.extend(UppyUploadMixin, { type: "csv", uploadUrl: "/tags/upload", addDisabled: alias("uploading"), elementId: "tag-uploader", + preventDirectS3Uploads: true, validateUploadedFilesOptions() { return { csvOnly: true }; diff --git a/app/assets/javascripts/admin/addon/components/themes-list-item.js b/app/assets/javascripts/admin/addon/components/themes-list-item.js index edc1783535..b619f160b4 100644 --- a/app/assets/javascripts/admin/addon/components/themes-list-item.js +++ b/app/assets/javascripts/admin/addon/components/themes-list-item.js @@ -1,10 +1,8 @@ import { and, gt } from "@ember/object/computed"; -import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; import { escape } from "pretty-text/sanitizer"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { isTesting } from "discourse-common/config/environment"; -import { schedule } from "@ember/runloop"; const MAX_COMPONENTS = 4; @@ -22,36 +20,6 @@ export default Component.extend({ } }, - init() { - this._super(...arguments); - this.scheduleAnimation(); - }, - - @observes("theme.selected") - triggerAnimation() { - this.animate(); - }, - - scheduleAnimation() { - schedule("afterRender", () => { - this.animate(true); - }); - }, - - animate(isInitial) { - const $container = $(this.element); - const $list = $(this.element.querySelector(".components-list")); - if ($list.length === 0 || isTesting()) { - return; - } - const duration = 300; - if (this.get("theme.selected")) { - this.collapseComponentsList($container, $list, duration); - } else if (!isInitial) { - this.expandComponentsList($container, $list, duration); - } - }, - @discourseComputed( "theme.component", "theme.childThemes.@each.name", @@ -91,54 +59,6 @@ export default Component.extend({ return childrenCount - MAX_COMPONENTS; }, - expandComponentsList($container, $list, duration) { - $container.css("height", `${$container.height()}px`); - $list.css("display", ""); - $container.animate( - { - height: `${$container.height() + $list.outerHeight(true)}px`, - }, - { - duration, - done: () => { - $list.css("display", ""); - $container.css("height", ""); - }, - } - ); - $list.animate( - { - opacity: 1, - }, - { - duration, - } - ); - }, - - collapseComponentsList($container, $list, duration) { - $container.animate( - { - height: `${$container.height() - $list.outerHeight(true)}px`, - }, - { - duration, - done: () => { - $list.css("display", "none"); - $container.css("height", ""); - }, - } - ); - $list.animate( - { - opacity: 0, - }, - { - duration, - } - ); - }, - actions: { toggleChildrenExpanded() { this.toggleProperty("childrenExpanded"); diff --git a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js index 040154d30b..8c2864b69b 100644 --- a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js +++ b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js @@ -1,15 +1,16 @@ import Component from "@ember/component"; import I18n from "I18n"; -import UploadMixin from "discourse/mixins/upload"; +import UppyUploadMixin from "discourse/mixins/uppy-upload"; import { alias } from "@ember/object/computed"; import bootbox from "bootbox"; import discourseComputed from "discourse-common/utils/decorators"; -export default Component.extend(UploadMixin, { +export default Component.extend(UppyUploadMixin, { type: "txt", classNames: "watched-words-uploader", uploadUrl: "/admin/customize/watched_words/upload", addDisabled: alias("uploading"), + preventDirectS3Uploads: true, validateUploadedFilesOptions() { return { skipValidation: true }; diff --git a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-index.js b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-index.js index 5b4ee4ee0a..7b98fdba1d 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-index.js @@ -1,14 +1,39 @@ import Controller from "@ember/controller"; +import { action } from "@ember/object"; import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ - actions: { - revokeKey(key) { - key.revoke().catch(popupAjaxError); - }, + loading: false, - undoRevokeKey(key) { - key.undoRevoke().catch(popupAjaxError); - }, + @action + revokeKey(key) { + key.revoke().catch(popupAjaxError); + }, + + @action + undoRevokeKey(key) { + key.undoRevoke().catch(popupAjaxError); + }, + + @action + loadMore() { + if (this.loading || this.model.loaded) { + return; + } + + const limit = 50; + + this.set("loading", true); + this.store + .findAll("api-key", { offset: this.model.length, limit }) + .then((keys) => { + this.model.addObjects(keys); + if (keys.length < limit) { + this.model.set("loaded", true); + } + }) + .finally(() => { + this.set("loading", false); + }); }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js index 961c1a5c4b..a95c7845c0 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js @@ -10,7 +10,8 @@ import { ajax } from "discourse/lib/ajax"; export default Controller.extend({ userModes: null, - useGlobalKey: false, + scopeModes: null, + globalScopes: null, scopes: null, init() { @@ -20,6 +21,13 @@ export default Controller.extend({ { id: "all", name: I18n.t("admin.api.all_users") }, { id: "single", name: I18n.t("admin.api.single_user") }, ]); + + this.set("scopeModes", [ + { id: "granular", name: I18n.t("admin.api.scopes.granular") }, + { id: "read_only", name: I18n.t("admin.api.scopes.read_only") }, + { id: "global", name: I18n.t("admin.api.scopes.global") }, + ]); + this._loadScopes(); }, @@ -49,14 +57,23 @@ export default Controller.extend({ this.set("userMode", userMode); }, + @action + changeScopeMode(scopeMode) { + this.set("scopeMode", scopeMode); + }, + @action save() { - if (!this.useGlobalKey) { + if (this.scopeMode === "granular") { const selectedScopes = Object.values(this.scopes) .flat() .filterBy("selected"); this.model.set("scopes", selectedScopes); + } else if (this.scopeMode === "read_only") { + this.model.set("scopes", [this.globalScopes.findBy("key", "read")]); + } else if (this.scopeMode === "all") { + this.model.set("scopes", null); } return this.model.save().catch(popupAjaxError); @@ -78,6 +95,10 @@ export default Controller.extend({ _loadScopes() { return ajax("/admin/api/keys/scopes.json") .then((data) => { + // remove global scopes because there is a different dropdown + this.set("globalScopes", data.scopes.global); + delete data.scopes.global; + this.set("scopes", data.scopes); }) .catch(popupAjaxError); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js b/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js index c53adeecf9..7b53b2170d 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js @@ -12,6 +12,9 @@ export default Controller.extend({ uploadLabel: i18n("admin.backups.upload.label"), backupLocation: setting("backup_location"), localBackupStorage: equal("backupLocation", "local"), + enableExperimentalBackupUploader: setting( + "enable_experimental_backup_uploader" + ), @discourseComputed("status.allowRestore", "status.isOperationRunning") restoreTitle(allowRestore, isOperationRunning) { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js b/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js index 5e1ed5bb21..33c8ad620b 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js @@ -70,7 +70,7 @@ export default Controller.extend(bufferedProperty("model"), { }, @observes("model.id") - _resetSaving: function () { + _resetSaving() { this.set("saving", false); this.set("savingStatus", ""); }, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js index 37394fd17f..f4b6df1756 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js @@ -15,11 +15,11 @@ export default Controller.extend({ }, actions: { - revert: function (color) { + revert(color) { color.revert(); }, - undo: function (color) { + undo(color) { color.undo(); }, @@ -68,7 +68,7 @@ export default Controller.extend({ }); }, - save: function () { + save() { this.model.save(); }, @@ -76,7 +76,7 @@ export default Controller.extend({ this.model.updateUserSelectable(this.get("model.user_selectable")); }, - destroy: function () { + destroy() { const model = this.model; return bootbox.confirm( I18n.t("admin.customize.colors.delete_confirm"), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-edit.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-edit.js index 2e1536d181..2c0cd18a70 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-edit.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-edit.js @@ -12,7 +12,7 @@ export default Controller.extend({ editRouteName: "adminCustomizeThemes.edit", showRouteName: "adminCustomizeThemes.show", - setTargetName: function (name) { + setTargetName(name) { const target = this.get("model.targets").find((t) => t.name === name); this.set("currentTarget", target && target.id); }, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-index.js b/app/assets/javascripts/admin/addon/controllers/admin-email-index.js index 18bfd4a8c4..cc808ad094 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-index.js @@ -19,7 +19,7 @@ export default Controller.extend({ @method testEmailAddressChanged **/ @observes("testEmailAddress") - testEmailAddressChanged: function () { + testEmailAddressChanged() { this.set("sentTestEmail", false); }, @@ -29,7 +29,7 @@ export default Controller.extend({ @method sendTestEmail **/ - sendTestEmail: function () { + sendTestEmail() { this.setProperties({ sendingEmail: true, sentTestEmail: false, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-logs-staff-action-logs.js b/app/assets/javascripts/admin/addon/controllers/admin-logs-staff-action-logs.js index 2758a10aa1..670d48c17b 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-logs-staff-action-logs.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-logs-staff-action-logs.js @@ -121,7 +121,7 @@ export default Controller.extend({ }, filterBySubject(subject) { - this.changeFilters({ subject: subject }); + this.changeFilters({ subject }); }, exportStaffActionLogs() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js index b11b397311..d4f354d321 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js @@ -37,7 +37,7 @@ export default Controller.extend({ textArea.remove(); }, - destroy: function (record) { + destroy(record) { return bootbox.confirm( I18n.t("admin.permalink.delete_confirm"), I18n.t("no_value"), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-plugins.js b/app/assets/javascripts/admin/addon/controllers/admin-plugins.js index d19c5584aa..54551537da 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-plugins.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-plugins.js @@ -3,7 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ @discourseComputed - adminRoutes: function () { + adminRoutes() { return this.model .map((p) => { if (p.get("enabled")) { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js b/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js index 2c5212e4ba..9461374085 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js @@ -49,7 +49,7 @@ export default Controller.extend(GrantBadgeController, { let result = { badge: badges[0].badge, granted_at: lastGranted, - badges: badges, + badges, count: badges.length, grouped: true, }; @@ -61,7 +61,7 @@ export default Controller.extend(GrantBadgeController, { }, actions: { - expandGroup: function (userBadge) { + expandGroup(userBadge) { const model = this.model; model.set("expandedBadges", model.get("expandedBadges") || []); model.get("expandedBadges").pushObject(userBadge.badge.id); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-index.js b/app/assets/javascripts/admin/addon/controllers/admin-user-index.js index 22345e8b82..fdd21f7332 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-user-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-user-index.js @@ -592,7 +592,7 @@ export default Controller.extend(CanCheckEmails, { (deletedPosts * 100) / user.get("post_count") ); progressModal.setProperties({ - deletedPercentage: deletedPercentage, + deletedPercentage, }); performDelete(progressModal); } diff --git a/app/assets/javascripts/admin/addon/models/email-settings.js b/app/assets/javascripts/admin/addon/models/email-settings.js index f959df408f..aa4a245f00 100644 --- a/app/assets/javascripts/admin/addon/models/email-settings.js +++ b/app/assets/javascripts/admin/addon/models/email-settings.js @@ -4,7 +4,7 @@ import { ajax } from "discourse/lib/ajax"; const EmailSettings = EmberObject.extend({}); EmailSettings.reopenClass({ - find: function () { + find() { return ajax("/admin/email.json").then(function (settings) { return EmailSettings.create(settings); }); diff --git a/app/assets/javascripts/admin/addon/models/permalink.js b/app/assets/javascripts/admin/addon/models/permalink.js index a2e8445b08..25a3c90e03 100644 --- a/app/assets/javascripts/admin/addon/models/permalink.js +++ b/app/assets/javascripts/admin/addon/models/permalink.js @@ -5,7 +5,7 @@ import { ajax } from "discourse/lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; const Permalink = EmberObject.extend({ - save: function () { + save() { return ajax("/admin/permalinks.json", { type: "POST", data: { @@ -17,16 +17,16 @@ const Permalink = EmberObject.extend({ }, @discourseComputed("category_id") - category: function (category_id) { + category(category_id) { return Category.findById(category_id); }, @discourseComputed("external_url") - linkIsExternal: function (external_url) { + linkIsExternal(external_url) { return !DiscourseURL.isInternal(external_url); }, - destroy: function () { + destroy() { return ajax("/admin/permalinks/" + this.id + ".json", { type: "DELETE", }); @@ -34,12 +34,12 @@ const Permalink = EmberObject.extend({ }); Permalink.reopenClass({ - findAll: function (filter) { - return ajax("/admin/permalinks.json", { data: { filter: filter } }).then( - function (permalinks) { - return permalinks.map((p) => Permalink.create(p)); - } - ); + findAll(filter) { + return ajax("/admin/permalinks.json", { data: { filter } }).then(function ( + permalinks + ) { + return permalinks.map((p) => Permalink.create(p)); + }); }, }); diff --git a/app/assets/javascripts/admin/addon/models/report.js b/app/assets/javascripts/admin/addon/models/report.js index 57d9bb69a5..fb1eef051f 100644 --- a/app/assets/javascripts/admin/addon/models/report.js +++ b/app/assets/javascripts/admin/addon/models/report.js @@ -672,7 +672,7 @@ Report.reopenClass({ Report.fillMissingDates(json.report); } - const model = Report.create({ type: type }); + const model = Report.create({ type }); model.setProperties(json.report); if (json.report.related_report) { diff --git a/app/assets/javascripts/admin/addon/models/screened-email.js b/app/assets/javascripts/admin/addon/models/screened-email.js index 857cca0d63..62e985949d 100644 --- a/app/assets/javascripts/admin/addon/models/screened-email.js +++ b/app/assets/javascripts/admin/addon/models/screened-email.js @@ -9,7 +9,7 @@ const ScreenedEmail = EmberObject.extend({ return I18n.t("admin.logs.screened_actions." + action); }, - clearBlock: function () { + clearBlock() { return ajax("/admin/logs/screened_emails/" + this.id, { type: "DELETE", }); @@ -17,7 +17,7 @@ const ScreenedEmail = EmberObject.extend({ }); ScreenedEmail.reopenClass({ - findAll: function () { + findAll() { return ajax("/admin/logs/screened_emails.json").then(function ( screened_emails ) { diff --git a/app/assets/javascripts/admin/addon/models/screened-ip-address.js b/app/assets/javascripts/admin/addon/models/screened-ip-address.js index a7d29aab31..7389509f89 100644 --- a/app/assets/javascripts/admin/addon/models/screened-ip-address.js +++ b/app/assets/javascripts/admin/addon/models/screened-ip-address.js @@ -42,7 +42,7 @@ const ScreenedIpAddress = EmberObject.extend({ ScreenedIpAddress.reopenClass({ findAll(filter) { return ajax("/admin/logs/screened_ip_addresses.json", { - data: { filter: filter }, + data: { filter }, }).then((screened_ips) => screened_ips.map((b) => ScreenedIpAddress.create(b)) ); diff --git a/app/assets/javascripts/admin/addon/models/screened-url.js b/app/assets/javascripts/admin/addon/models/screened-url.js index f3769c7d2f..9a0d4e1b73 100644 --- a/app/assets/javascripts/admin/addon/models/screened-url.js +++ b/app/assets/javascripts/admin/addon/models/screened-url.js @@ -11,7 +11,7 @@ const ScreenedUrl = EmberObject.extend({ }); ScreenedUrl.reopenClass({ - findAll: function () { + findAll() { return ajax("/admin/logs/screened_urls.json").then(function ( screened_urls ) { diff --git a/app/assets/javascripts/admin/addon/models/web-hook.js b/app/assets/javascripts/admin/addon/models/web-hook.js index 8dd568a9fb..1b74d50b7b 100644 --- a/app/assets/javascripts/admin/addon/models/web-hook.js +++ b/app/assets/javascripts/admin/addon/models/web-hook.js @@ -44,7 +44,7 @@ export default RestModel.extend({ }, groupFinder(term) { - return Group.findAll({ term: term, ignore_automatic: false }); + return Group.findAll({ term, ignore_automatic: false }); }, @discourseComputed("wildcard_web_hook", "web_hook_event_types.[]") diff --git a/app/assets/javascripts/admin/addon/routes/admin-badges.js b/app/assets/javascripts/admin/addon/routes/admin-badges.js index ac4b9e9b53..c966b7a161 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-badges.js +++ b/app/assets/javascripts/admin/addon/routes/admin-badges.js @@ -32,7 +32,7 @@ export default DiscourseRoute.extend({ }); controller.setProperties({ - badgeGroupings: badgeGroupings, + badgeGroupings, badgeTypes: json.badge_types, protectedSystemFields: json.admin_badges.protected_system_fields, badgeTriggers, diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js index 5bcf95d3d2..6c23a36109 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js @@ -2,6 +2,7 @@ import { COMPONENTS, THEMES } from "admin/models/theme"; import I18n from "I18n"; import Route from "@ember/routing/route"; import { scrollTop } from "discourse/mixins/scroll-top"; +import bootbox from "bootbox"; export function showUnassignedComponentWarning(theme, callback) { bootbox.confirm( @@ -39,8 +40,8 @@ export default Route.extend({ }); controller.setProperties({ - model: model, - parentController: parentController, + model, + parentController, allThemes: parentController.get("model"), colorSchemeId: model.get("color_scheme_id"), colorSchemes: parentController.get("model.extras.color_schemes"), diff --git a/app/assets/javascripts/admin/addon/routes/admin-emojis.js b/app/assets/javascripts/admin/addon/routes/admin-emojis.js index 5047bd6f82..6d5b1ee648 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-emojis.js +++ b/app/assets/javascripts/admin/addon/routes/admin-emojis.js @@ -3,7 +3,7 @@ import EmberObject from "@ember/object"; import { ajax } from "discourse/lib/ajax"; export default DiscourseRoute.extend({ - model: function () { + model() { return ajax("/admin/customize/emojis.json").then(function (emojis) { return emojis.map(function (emoji) { return EmberObject.create(emoji); diff --git a/app/assets/javascripts/admin/addon/routes/admin-logs-index.js b/app/assets/javascripts/admin/addon/routes/admin-logs-index.js index db287d0533..1fff3f4068 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-logs-index.js +++ b/app/assets/javascripts/admin/addon/routes/admin-logs-index.js @@ -1,7 +1,7 @@ import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ - redirect: function () { + redirect() { this.transitionTo("adminLogs.staffActionLogs"); }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-logs-screened-emails.js b/app/assets/javascripts/admin/addon/routes/admin-logs-screened-emails.js index 9f3841ee04..1778b561c7 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-logs-screened-emails.js +++ b/app/assets/javascripts/admin/addon/routes/admin-logs-screened-emails.js @@ -1,11 +1,11 @@ import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ - renderTemplate: function () { + renderTemplate() { this.render("admin/templates/logs/screened-emails", { into: "adminLogs" }); }, - setupController: function () { + setupController() { return this.controllerFor("adminLogsScreenedEmails").show(); }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-logs-screened-urls.js b/app/assets/javascripts/admin/addon/routes/admin-logs-screened-urls.js index 99677710ca..230cf85ceb 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-logs-screened-urls.js +++ b/app/assets/javascripts/admin/addon/routes/admin-logs-screened-urls.js @@ -1,11 +1,11 @@ import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ - renderTemplate: function () { + renderTemplate() { this.render("admin/templates/logs/screened-urls", { into: "adminLogs" }); }, - setupController: function () { + setupController() { return this.controllerFor("adminLogsScreenedUrls").show(); }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-site-text-edit.js b/app/assets/javascripts/admin/addon/routes/admin-site-text-edit.js index 40c74fced3..9a9677b687 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-site-text-edit.js +++ b/app/assets/javascripts/admin/addon/routes/admin-site-text-edit.js @@ -24,7 +24,7 @@ export default Route.extend({ controller.setProperties({ siteText, saved: false, - localeFullName: localeFullName, + localeFullName, }); }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-user-fields.js b/app/assets/javascripts/admin/addon/routes/admin-user-fields.js index 25e7eb0188..79c908af64 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-user-fields.js +++ b/app/assets/javascripts/admin/addon/routes/admin-user-fields.js @@ -2,11 +2,11 @@ import DiscourseRoute from "discourse/routes/discourse"; import UserField from "admin/models/user-field"; export default DiscourseRoute.extend({ - model: function () { + model() { return this.store.findAll("user-field"); }, - setupController: function (controller, model) { + setupController(controller, model) { controller.setProperties({ model, fieldTypes: UserField.fieldTypes() }); }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-users-index.js b/app/assets/javascripts/admin/addon/routes/admin-users-index.js index 90a3f8a271..02d9cce444 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-users-index.js +++ b/app/assets/javascripts/admin/addon/routes/admin-users-index.js @@ -1,7 +1,7 @@ import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ - redirect: function () { + redirect() { this.transitionTo("adminUsersList"); }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-users-list-index.js b/app/assets/javascripts/admin/addon/routes/admin-users-list-index.js index e2e45b16a3..29ae18db79 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-users-list-index.js +++ b/app/assets/javascripts/admin/addon/routes/admin-users-list-index.js @@ -1,7 +1,7 @@ import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ - beforeModel: function () { + beforeModel() { this.transitionTo("adminUsersList.show", "active"); }, }); diff --git a/app/assets/javascripts/admin/addon/templates/api-keys-index.hbs b/app/assets/javascripts/admin/addon/templates/api-keys-index.hbs index ac5dbf7c6b..f5608434ad 100644 --- a/app/assets/javascripts/admin/addon/templates/api-keys-index.hbs +++ b/app/assets/javascripts/admin/addon/templates/api-keys-index.hbs @@ -5,67 +5,71 @@ label="admin.api.new_key"}} {{#if model}} -
| {{i18n "admin.api.key"}} | -{{i18n "admin.api.description"}} | -{{i18n "admin.api.user"}} | -{{i18n "admin.api.created"}} | -{{i18n "admin.api.last_used"}} | -- - - {{#each model as |k|}} - |
|---|---|---|---|---|---|
| - {{#if k.revoked_at}}{{d-icon "times-circle"}}{{/if}} - {{k.truncatedKey}} - | -- {{k.shortDescription}} - | -
- {{i18n "admin.api.user"}}
- {{#if k.user}}
- {{#link-to "adminUser" k.user}}
- {{avatar k.user imageSize="small"}}
- {{/link-to}}
- {{else}}
- {{i18n "admin.api.all_users"}}
- {{/if}}
- |
-
- {{i18n "admin.api.created"}}
- {{format-date k.created_at}}
- |
-
- {{i18n "admin.api.last_used"}}
- {{#if k.last_used_at}}
- {{format-date k.last_used_at}}
- {{else}}
- {{i18n "admin.api.never_used"}}
- {{/if}}
- |
- - {{d-button action=(route-action "show" k) icon="far-eye" title="admin.api.show_details"}} - {{#if k.revoked_at}} - {{d-button - action=(action "undoRevokeKey") - actionParam=k icon="undo" - title="admin.api.undo_revoke"}} - {{else}} - {{d-button - class="btn-danger" - action=(action "revokeKey") - actionParam=k - icon="times" - title="admin.api.revoke"}} - {{/if}} - | -
| {{i18n "admin.api.key"}} | +{{i18n "admin.api.description"}} | +{{i18n "admin.api.user"}} | +{{i18n "admin.api.created"}} | +{{i18n "admin.api.last_used"}} | ++ + + {{#each model as |k|}} + |
|---|---|---|---|---|---|
| + {{#if k.revoked_at}}{{d-icon "times-circle"}}{{/if}} + {{k.truncatedKey}} + | ++ {{k.shortDescription}} + | +
+ {{i18n "admin.api.user"}}
+ {{#if k.user}}
+ {{#link-to "adminUser" k.user}}
+ {{avatar k.user imageSize="small"}}
+ {{/link-to}}
+ {{else}}
+ {{i18n "admin.api.all_users"}}
+ {{/if}}
+ |
+
+ {{i18n "admin.api.created"}}
+ {{format-date k.created_at}}
+ |
+
+ {{i18n "admin.api.last_used"}}
+ {{#if k.last_used_at}}
+ {{format-date k.last_used_at}}
+ {{else}}
+ {{i18n "admin.api.never_used"}}
+ {{/if}}
+ |
+ + {{d-button action=(route-action "show" k) icon="far-eye" title="admin.api.show_details"}} + {{#if k.revoked_at}} + {{d-button + action=(action "undoRevokeKey") + actionParam=k icon="undo" + title="admin.api.undo_revoke"}} + {{else}} + {{d-button + class="btn-danger" + action=(action "revokeKey") + actionParam=k + icon="times" + title="admin.api.revoke"}} + {{/if}} + | +
{{i18n "admin.api.none"}}
{{/if}} diff --git a/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs b/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs index 2b9e40cf80..7f703c3bc2 100644 --- a/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs +++ b/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs @@ -36,12 +36,18 @@ {{/admin-form-row}} {{/if}} - {{#admin-form-row label="admin.api.use_global_key"}} - {{input type="checkbox" checked=useGlobalKey}} + {{#admin-form-row label="admin.api.scope_mode"}} + {{combo-box content=scopeModes value=scopeMode onChange=(action "changeScopeMode")}} + + {{#if (eq scopeMode "read_only")}} +{{i18n "admin.api.scopes.descriptions.global.read"}}
+ {{else if (eq scopeMode "global")}} +{{i18n "admin.api.scopes.global_description"}}
+ {{/if}} {{/admin-form-row}} - {{#unless useGlobalKey}} -{{i18n "admin.api.scopes.description"}}
| {{i18n "admin.permalink.url"}} | @@ -21,7 +25,14 @@ {{#each model as |pl|}}||
|---|---|---|
| {{d-button title="admin.permalink.copy_to_clipboard" icon="far-clipboard" action=(action "copyUrl" pl)}} {{pl.url}} | ++ {{flat-button + title="admin.permalink.copy_to_clipboard" + icon="far-clipboard" + action=(action "copyUrl" pl) + }} + {{pl.url}} + |
{{#if pl.topic_id}}
{{pl.topic_title}}
diff --git a/app/assets/javascripts/admin/addon/templates/watched-words-action.hbs b/app/assets/javascripts/admin/addon/templates/watched-words-action.hbs
index 6860ac16d3..f074dc3ede 100644
--- a/app/assets/javascripts/admin/addon/templates/watched-words-action.hbs
+++ b/app/assets/javascripts/admin/addon/templates/watched-words-action.hbs
@@ -9,7 +9,11 @@
icon="download"
label="admin.watched_words.download"}}
- {{watched-word-uploader uploading=uploading actionKey=actionNameKey done=(action "uploadComplete")}}
+ {{watched-word-uploader
+ id="watched-word-uploader"
+ uploading=uploading
+ actionKey=actionNameKey
+ done=(action "uploadComplete")}}
{{d-button
class="watched-word-test"
diff --git a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
index 8a01d633f0..e6c740b353 100644
--- a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
+++ b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
@@ -20,6 +20,8 @@ const REPLACEMENTS = {
"d-drop-collapsed": "caret-right",
"d-unliked": "far-heart",
"d-liked": "heart",
+ "d-post-share": "link",
+ "d-topic-share": "link",
"notification.mentioned": "at",
"notification.group_mentioned": "users",
"notification.quoted": "quote-right",
diff --git a/app/assets/javascripts/discourse-common/addon/utils/decorator-alias.js b/app/assets/javascripts/discourse-common/addon/utils/decorator-alias.js
index dd4299c700..e67bc9938a 100644
--- a/app/assets/javascripts/discourse-common/addon/utils/decorator-alias.js
+++ b/app/assets/javascripts/discourse-common/addon/utils/decorator-alias.js
@@ -11,7 +11,7 @@ export default function decoratorAlias(fn, errorMessage) {
enumerable: desc.enumerable,
configurable: desc.configurable,
writable: desc.writable,
- initializer: function () {
+ initializer() {
let value = extractValue(desc);
return fn.apply(null, params.concat(value));
},
diff --git a/app/assets/javascripts/discourse-common/addon/utils/decorators.js b/app/assets/javascripts/discourse-common/addon/utils/decorators.js
index 4fad373bf8..a7f1d0a53d 100644
--- a/app/assets/javascripts/discourse-common/addon/utils/decorators.js
+++ b/app/assets/javascripts/discourse-common/addon/utils/decorators.js
@@ -50,7 +50,7 @@ export function readOnly(target, name, desc) {
writable: false,
enumerable: desc.enumerable,
configurable: desc.configurable,
- initializer: function () {
+ initializer() {
let value = extractValue(desc);
return value.readOnly();
},
diff --git a/app/assets/javascripts/discourse-common/addon/utils/handle-descriptor.js b/app/assets/javascripts/discourse-common/addon/utils/handle-descriptor.js
index 3987adffa6..d871e454eb 100644
--- a/app/assets/javascripts/discourse-common/addon/utils/handle-descriptor.js
+++ b/app/assets/javascripts/discourse-common/addon/utils/handle-descriptor.js
@@ -6,7 +6,7 @@ export default function handleDescriptor(target, key, desc, params = []) {
enumerable: desc.enumerable,
configurable: desc.configurable,
writeable: desc.writeable,
- initializer: function () {
+ initializer() {
let computedDescriptor;
if (desc.writable) {
diff --git a/app/assets/javascripts/discourse-common/addon/utils/macro-alias.js b/app/assets/javascripts/discourse-common/addon/utils/macro-alias.js
index 5b66901872..ed60022e68 100644
--- a/app/assets/javascripts/discourse-common/addon/utils/macro-alias.js
+++ b/app/assets/javascripts/discourse-common/addon/utils/macro-alias.js
@@ -5,7 +5,7 @@ function handleDescriptor(target, property, desc, fn, params = []) {
enumerable: desc.enumerable,
configurable: desc.configurable,
writable: desc.writable,
- initializer: function () {
+ initializer() {
return fn(...params);
},
};
diff --git a/app/assets/javascripts/discourse/app/components/avatar-uploader.js b/app/assets/javascripts/discourse/app/components/avatar-uploader.js
index 0620aa0d9f..47ea3a1d84 100644
--- a/app/assets/javascripts/discourse/app/components/avatar-uploader.js
+++ b/app/assets/javascripts/discourse/app/components/avatar-uploader.js
@@ -1,8 +1,8 @@
import Component from "@ember/component";
-import UploadMixin from "discourse/mixins/upload";
+import UppyUploadMixin from "discourse/mixins/uppy-upload";
import discourseComputed from "discourse-common/utils/decorators";
-export default Component.extend(UploadMixin, {
+export default Component.extend(UppyUploadMixin, {
type: "avatar",
tagName: "span",
imageIsNotASquare: false,
diff --git a/app/assets/javascripts/discourse/app/components/basic-topic-list.js b/app/assets/javascripts/discourse/app/components/basic-topic-list.js
index 3343512568..9c948b50f2 100644
--- a/app/assets/javascripts/discourse/app/components/basic-topic-list.js
+++ b/app/assets/javascripts/discourse/app/components/basic-topic-list.js
@@ -19,7 +19,7 @@ export default Component.extend({
},
@observes("topicList.[]")
- _topicListChanged: function () {
+ _topicListChanged() {
this._initFromTopicList(this.topicList);
},
diff --git a/app/assets/javascripts/discourse/app/components/bookmark-list.js b/app/assets/javascripts/discourse/app/components/bookmark-list.js
index 30b7197c4c..09062748c0 100644
--- a/app/assets/javascripts/discourse/app/components/bookmark-list.js
+++ b/app/assets/javascripts/discourse/app/components/bookmark-list.js
@@ -7,7 +7,7 @@ import I18n from "I18n";
import { Promise } from "rsvp";
import { action } from "@ember/object";
import bootbox from "bootbox";
-import showModal from "discourse/lib/show-modal";
+import { openBookmarkModal } from "discourse/controllers/bookmark";
export default Component.extend({
classNames: ["bookmark-list-wrapper"],
@@ -19,6 +19,11 @@ export default Component.extend({
bookmark
.destroy()
.then(() => {
+ this.appEvents.trigger(
+ "bookmarks:changed",
+ null,
+ bookmark.attachedTo()
+ );
this._removeBookmarkFromList(bookmark);
resolve(true);
})
@@ -51,17 +56,19 @@ export default Component.extend({
@action
editBookmark(bookmark) {
- let controller = showModal("bookmark", {
- model: {
- postId: bookmark.post_id,
- id: bookmark.id,
- reminderAt: bookmark.reminder_at,
- name: bookmark.name,
+ openBookmarkModal(bookmark, {
+ onAfterSave: (savedData) => {
+ this.appEvents.trigger(
+ "bookmarks:changed",
+ savedData,
+ bookmark.attachedTo()
+ );
+ this.reload();
+ },
+ onAfterDelete: () => {
+ this.reload();
},
- title: "post.bookmarks.edit",
- modalClass: "bookmark-with-reminder",
});
- controller.set("afterSave", this.reload);
},
@action
diff --git a/app/assets/javascripts/discourse/app/components/bookmark.js b/app/assets/javascripts/discourse/app/components/bookmark.js
index 35b8a31a4e..deadd61cc9 100644
--- a/app/assets/javascripts/discourse/app/components/bookmark.js
+++ b/app/assets/javascripts/discourse/app/components/bookmark.js
@@ -17,7 +17,7 @@ import { TIME_SHORTCUT_TYPES } from "discourse/lib/time-shortcut";
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import bootbox from "bootbox";
-import discourseComputed, { on } from "discourse-common/utils/decorators";
+import discourseComputed, { bind, on } from "discourse-common/utils/decorators";
import { formattedReminderTime } from "discourse/lib/bookmark";
import { and, notEmpty } from "@ember/object/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
@@ -65,7 +65,7 @@ export default Component.extend({
_itsatrap: new ItsATrap(),
});
- this.registerOnCloseHandler(this._onModalClose.bind(this));
+ this.registerOnCloseHandler(this._onModalClose);
this._loadBookmarkOptions();
this._bindKeyboardShortcuts();
@@ -201,6 +201,7 @@ export default Component.extend({
post_id: this.model.postId,
id: this.model.id || response.id,
name: this.model.name,
+ topic_id: this.model.topicId,
});
},
@@ -238,12 +239,15 @@ export default Component.extend({
}
},
- _onModalClose(initiatedByCloseButton) {
+ @bind
+ _onModalClose(closeOpts) {
// we want to close without saving if the user already saved
// manually or deleted the bookmark, as well as when the modal
// is just closed with the X button
this._closeWithoutSaving =
- this._closeWithoutSaving || initiatedByCloseButton;
+ this._closeWithoutSaving ||
+ closeOpts.initiatedByCloseButton ||
+ closeOpts.initiatedByESC;
if (!this._closeWithoutSaving && !this._savingBookmarkManually) {
this._saveBookmark().catch((e) => this._handleSaveError(e));
diff --git a/app/assets/javascripts/discourse/app/components/composer-body.js b/app/assets/javascripts/discourse/app/components/composer-body.js
index 3eb735066d..035da9505f 100644
--- a/app/assets/javascripts/discourse/app/components/composer-body.js
+++ b/app/assets/javascripts/discourse/app/components/composer-body.js
@@ -207,7 +207,6 @@ export default Component.extend(KeyEnterEscape, {
willDestroyElement() {
this._super(...arguments);
- this.appEvents.off("composer:resize", this, this.resize);
if (this._visualViewportResizing()) {
window.visualViewport.removeEventListener("resize", this.viewportResize);
}
diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js
index b6c3d3456a..a98df73ad1 100644
--- a/app/assets/javascripts/discourse/app/components/composer-editor.js
+++ b/app/assets/javascripts/discourse/app/components/composer-editor.js
@@ -38,6 +38,18 @@ import { loadOneboxes } from "discourse/lib/load-oneboxes";
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
import userSearch from "discourse/lib/user-search";
+// original string ``
+// group 1 `image|foo=bar`
+// group 2 `690x220`
+// group 3 `, 50%`
+// group 4 '|bar=baz'
+// group 5 'upload://1TjaobgKObzpU7xRMw2HuUc87vO.png "image title"'
+
+// Notes:
+// Group 3 is optional. group 4 can match images with or without a markdown title.
+// All matches are whitespace tolerant as long it's still valid markdown.
+// If the image is inside a code block, we'll ignore it `(?!(.*`))`.
+const IMAGE_MARKDOWN_REGEX = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
const REBUILD_SCROLL_MAP_EVENTS = ["composer:resized", "composer:typed-reply"];
let uploadHandlers = [];
@@ -48,7 +60,14 @@ export function addComposerUploadHandler(extensions, method) {
});
}
export function cleanUpComposerUploadHandler() {
- uploadHandlers = [];
+ // we cannot set this to uploadHandlers = [] because that messes with
+ // the references to the original array that the component has. this only
+ // really affects tests, but without doing this you could addComposerUploadHandler
+ // in a beforeEach function in a test but then it's not adding to the
+ // existing reference that the component has, because an earlier test ran
+ // cleanUpComposerUploadHandler and lost it. setting the length to 0 empties
+ // the array but keeps the reference
+ uploadHandlers.length = 0;
}
let uploadProcessorQueue = [];
@@ -499,41 +518,40 @@ export default Component.extend(ComposerUpload, {
$input.stop(true).animate({ scrollTop }, 100, "linear");
},
- _renderUnseenMentions($preview, unseen) {
+ _renderUnseenMentions(preview, unseen) {
// 'Create a New Topic' scenario is not supported (per conversation with codinghorror)
// https://meta.discourse.org/t/taking-another-1-7-release-task/51986/7
fetchUnseenMentions(unseen, this.get("composer.topic.id")).then(() => {
- linkSeenMentions($preview, this.siteSettings);
- this._warnMentionedGroups($preview);
- this._warnCannotSeeMention($preview);
+ linkSeenMentions(preview, this.siteSettings);
+ this._warnMentionedGroups(preview);
+ this._warnCannotSeeMention(preview);
});
},
- _renderUnseenHashtags($preview) {
- const unseen = linkSeenHashtags($preview);
+ _renderUnseenHashtags(preview) {
+ const unseen = linkSeenHashtags(preview);
if (unseen.length > 0) {
fetchUnseenHashtags(unseen).then(() => {
- linkSeenHashtags($preview);
+ linkSeenHashtags(preview);
});
}
},
- _warnMentionedGroups($preview) {
+ _warnMentionedGroups(preview) {
schedule("afterRender", () => {
let found = this.warnedGroupMentions || [];
- $preview.find(".mention-group.notify").each((idx, e) => {
- if (this._isInQuote(e)) {
+ preview?.querySelectorAll(".mention-group.notify")?.forEach((mention) => {
+ if (this._isInQuote(mention)) {
return;
}
- const $e = $(e);
- let name = $e.data("name");
+ let name = mention.dataset.name;
if (found.indexOf(name) === -1) {
this.groupsMentioned([
{
- name: name,
- user_count: $e.data("mentionable-user-count"),
- max_mentions: $e.data("max-mentions"),
+ name,
+ user_count: mention.dataset.mentionableUserCount,
+ max_mentions: mention.dataset.maxMentions,
},
]);
found.push(name);
@@ -544,7 +562,7 @@ export default Component.extend(ComposerUpload, {
});
},
- _warnCannotSeeMention($preview) {
+ _warnCannotSeeMention(preview) {
const composerDraftKey = this.get("composer.draftKey");
if (composerDraftKey === Composer.NEW_PRIVATE_MESSAGE_KEY) {
@@ -554,9 +572,8 @@ export default Component.extend(ComposerUpload, {
schedule("afterRender", () => {
let found = this.warnedCannotSeeMentions || [];
- $preview.find(".mention.cannot-see").each((idx, e) => {
- const $e = $(e);
- let name = $e.data("name");
+ preview?.querySelectorAll(".mention.cannot-see")?.forEach((mention) => {
+ let name = mention.dataset.name;
if (found.indexOf(name) === -1) {
// add a delay to allow for typing, so you don't open the warning right away
@@ -565,8 +582,9 @@ export default Component.extend(ComposerUpload, {
this,
() => {
if (
- $preview.find('.mention.cannot-see[data-name="' + name + '"]')
- .length > 0
+ preview?.querySelectorAll(
+ `.mention.cannot-see[data-name="${name}"]`
+ )?.length > 0
) {
this.cannotSeeMention([{ name }]);
found.push(name);
@@ -582,24 +600,15 @@ export default Component.extend(ComposerUpload, {
},
_registerImageScaleButtonClick($preview) {
- // original string ``
- // group 1 `image|foo=bar`
- // group 2 `690x220`
- // group 3 `, 50%`
- // group 4 '|bar=baz'
- // group 5 'upload://1TjaobgKObzpU7xRMw2HuUc87vO.png "image title"'
-
- // Notes:
- // Group 3 is optional. group 4 can match images with or without a markdown title.
- // All matches are whitespace tolerant as long it's still valid markdown.
- // If the image is inside a code block, we'll ignore it `(?!(.*`))`.
- const imageScaleRegex = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
$preview.off("click", ".scale-btn").on("click", ".scale-btn", (e) => {
- const index = parseInt($(e.target).parent().attr("data-image-index"), 10);
+ const index = parseInt(
+ $(e.target).closest(".button-wrapper").attr("data-image-index"),
+ 10
+ );
const scale = e.target.attributes["data-scale"].value;
const matchingPlaceholder = this.get("composer.reply").match(
- imageScaleRegex
+ IMAGE_MARKDOWN_REGEX
);
if (matchingPlaceholder) {
@@ -607,7 +616,7 @@ export default Component.extend(ComposerUpload, {
if (match) {
const replacement = match.replace(
- imageScaleRegex,
+ IMAGE_MARKDOWN_REGEX,
``
);
@@ -615,7 +624,7 @@ export default Component.extend(ComposerUpload, {
"composer:replace-text",
matchingPlaceholder[index],
replacement,
- { regex: imageScaleRegex, index }
+ { regex: IMAGE_MARKDOWN_REGEX, index }
);
}
}
@@ -625,6 +634,58 @@ export default Component.extend(ComposerUpload, {
});
},
+ _registerImageAltTextButtonClick($preview) {
+ $preview
+ .off("click", ".alt-text-edit-btn")
+ .on("click", ".alt-text-edit-btn", (e) => {
+ const parentContainer = $(e.target).closest(
+ ".alt-text-readonly-container"
+ );
+ const altText = parentContainer.find(".alt-text");
+ const correspondingInput = parentContainer.find(".alt-text-input");
+
+ $(e.target).hide();
+ altText.hide();
+ correspondingInput.val(altText.text());
+ correspondingInput.show();
+ e.preventDefault();
+ });
+
+ $preview
+ .off("keypress", ".alt-text-input")
+ .on("keypress", ".alt-text-input", (e) => {
+ if (e.key === "[" || e.key === "]") {
+ e.preventDefault();
+ }
+
+ if (e.key === "Enter") {
+ const index = parseInt(
+ $(e.target).closest(".button-wrapper").attr("data-image-index"),
+ 10
+ );
+ const matchingPlaceholder = this.get("composer.reply").match(
+ IMAGE_MARKDOWN_REGEX
+ );
+ const match = matchingPlaceholder[index];
+ const replacement = match.replace(
+ IMAGE_MARKDOWN_REGEX,
+ ``
+ );
+
+ this.appEvents.trigger("composer:replace-text", match, replacement);
+
+ const parentContainer = $(e.target).closest(
+ ".alt-text-readonly-container"
+ );
+ const altText = parentContainer.find(".alt-text");
+ const altTextButton = parentContainer.find(".alt-text-edit-btn");
+ altText.show();
+ altTextButton.show();
+ $(e.target).hide();
+ }
+ });
+ },
+
@on("willDestroyElement")
_composerClosed() {
this._unbindMobileUploadButton();
@@ -688,6 +749,14 @@ export default Component.extend(ComposerUpload, {
}
},
+ _findMatchingUploadHandler(fileName) {
+ return this.uploadHandlers.find((handler) => {
+ const ext = handler.extensions.join("|");
+ const regex = new RegExp(`\\.(${ext})$`, "i");
+ return regex.test(fileName);
+ });
+ },
+
actions: {
importQuote(toolbarEvent) {
this.importQuote(toolbarEvent);
@@ -732,26 +801,29 @@ export default Component.extend(ComposerUpload, {
});
},
- previewUpdated($preview) {
+ previewUpdated(preview) {
+ // cache jquery objects for functions still using jquery
+ const $preview = $(preview);
+
// Paint mentions
- const unseenMentions = linkSeenMentions($preview, this.siteSettings);
+ const unseenMentions = linkSeenMentions(preview, this.siteSettings);
if (unseenMentions.length) {
discourseDebounce(
this,
this._renderUnseenMentions,
- $preview,
+ preview,
unseenMentions,
450
);
}
- this._warnMentionedGroups($preview);
- this._warnCannotSeeMention($preview);
+ this._warnMentionedGroups(preview);
+ this._warnCannotSeeMention(preview);
// Paint category and tag hashtags
- const unseenHashtags = linkSeenHashtags($preview);
+ const unseenHashtags = linkSeenHashtags(preview);
if (unseenHashtags.length > 0) {
- discourseDebounce(this, this._renderUnseenHashtags, $preview, 450);
+ discourseDebounce(this, this._renderUnseenHashtags, preview, 450);
}
// Paint oneboxes
@@ -765,7 +837,7 @@ export default Component.extend(ComposerUpload, {
}
const paintedCount = loadOneboxes(
- $preview[0],
+ preview,
ajax,
this.get("composer.topic.id"),
this.get("composer.category.id"),
@@ -781,7 +853,7 @@ export default Component.extend(ComposerUpload, {
discourseDebounce(this, paintFunc, 450);
// Short upload urls need resolution
- resolveAllShortUrls(ajax, this.siteSettings, $preview[0]);
+ resolveAllShortUrls(ajax, this.siteSettings, preview);
if (this._enableAdvancedEditorPreviewSync()) {
this._syncScroll(
@@ -792,8 +864,9 @@ export default Component.extend(ComposerUpload, {
}
this._registerImageScaleButtonClick($preview);
+ this._registerImageAltTextButtonClick($preview);
- this.trigger("previewRefreshed", $preview[0]);
+ this.trigger("previewRefreshed", preview);
this.afterRefresh($preview);
},
},
diff --git a/app/assets/javascripts/discourse/app/components/composer-messages.js b/app/assets/javascripts/discourse/app/components/composer-messages.js
index b41309e959..a888c2829c 100644
--- a/app/assets/javascripts/discourse/app/components/composer-messages.js
+++ b/app/assets/javascripts/discourse/app/components/composer-messages.js
@@ -83,7 +83,7 @@ export default Component.extend({
!topic.archived &&
!topic.closed &&
!topic.deleted,
- topic: topic,
+ topic,
});
},
diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js
index a7a879d5b4..2c64f01570 100644
--- a/app/assets/javascripts/discourse/app/components/d-editor.js
+++ b/app/assets/javascripts/discourse/app/components/d-editor.js
@@ -319,7 +319,7 @@ export default Component.extend(TextareaTextManipulation, {
}
if (isTesting()) {
- this.element.addEventListener("paste", this.paste.bind(this));
+ this.element.addEventListener("paste", this.paste);
}
},
@@ -341,6 +341,8 @@ export default Component.extend(TextareaTextManipulation, {
if (isTesting()) {
this.element.removeEventListener("paste", this.paste);
}
+
+ this._cachedCookFunction = null;
},
@discourseComputed()
@@ -394,8 +396,8 @@ export default Component.extend(TextareaTextManipulation, {
const cookedElement = document.createElement("div");
cookedElement.innerHTML = cooked;
- linkSeenHashtags($(cookedElement));
- linkSeenMentions($(cookedElement), this.siteSettings);
+ linkSeenHashtags(cookedElement);
+ linkSeenMentions(cookedElement, this.siteSettings);
resolveCachedShortUrls(this.siteSettings, cookedElement);
loadOneboxes(
cookedElement,
@@ -427,7 +429,7 @@ export default Component.extend(TextareaTextManipulation, {
}
if (this.previewUpdated) {
- this.previewUpdated($(preview));
+ this.previewUpdated(preview);
}
});
});
diff --git a/app/assets/javascripts/discourse/app/components/d-modal-body.js b/app/assets/javascripts/discourse/app/components/d-modal-body.js
index 1e46c4442a..4231de24df 100644
--- a/app/assets/javascripts/discourse/app/components/d-modal-body.js
+++ b/app/assets/javascripts/discourse/app/components/d-modal-body.js
@@ -10,7 +10,7 @@ export default Component.extend({
this._super(...arguments);
this._modalAlertElement = document.getElementById("modal-alert");
if (this._modalAlertElement) {
- this._modalAlertElement.innerHTML = "";
+ this._clearFlash();
}
let fixedParent = $(this.element).closest(".d-modal.fixed-modal");
diff --git a/app/assets/javascripts/discourse/app/components/date-input.js b/app/assets/javascripts/discourse/app/components/date-input.js
index 9cb022d024..ca6140d1df 100644
--- a/app/assets/javascripts/discourse/app/components/date-input.js
+++ b/app/assets/javascripts/discourse/app/components/date-input.js
@@ -1,9 +1,9 @@
+/* global Pikaday:true */
import discourseComputed, { on } from "discourse-common/utils/decorators";
import Component from "@ember/component";
import I18n from "I18n";
import { Promise } from "rsvp";
import { action } from "@ember/object";
-/* global Pikaday:true */
import loadScript from "discourse/lib/load-script";
import { schedule } from "@ember/runloop";
@@ -144,9 +144,16 @@ export default Component.extend({
}
},
- @discourseComputed()
- placeholder() {
- return I18n.t("dates.placeholder");
+ @discourseComputed("_placeholder")
+ placeholder: {
+ get(_placeholder) {
+ return _placeholder || I18n.t("dates.placeholder");
+ },
+
+ set(value) {
+ this.set("_placeholder", value);
+ return value;
+ },
},
_opts() {
diff --git a/app/assets/javascripts/discourse/app/components/edit-category-tab.js b/app/assets/javascripts/discourse/app/components/edit-category-tab.js
index 45721b4370..e96720db36 100644
--- a/app/assets/javascripts/discourse/app/components/edit-category-tab.js
+++ b/app/assets/javascripts/discourse/app/components/edit-category-tab.js
@@ -39,7 +39,7 @@ export default Component.extend({
});
},
- _addToCollection: function () {
+ _addToCollection() {
this.panels.addObject(this.tabClassName);
},
@@ -50,7 +50,7 @@ export default Component.extend({
},
actions: {
- select: function () {
+ select() {
this.set("selectedTab", this.tab);
if (!this.newCategory) {
diff --git a/app/assets/javascripts/discourse/app/components/edit-category-topic-template.js b/app/assets/javascripts/discourse/app/components/edit-category-topic-template.js
index b4d8d7a721..b9125ebf1e 100644
--- a/app/assets/javascripts/discourse/app/components/edit-category-topic-template.js
+++ b/app/assets/javascripts/discourse/app/components/edit-category-topic-template.js
@@ -8,7 +8,7 @@ export default buildCategoryPanel("topic-template", {
showInsertLinkButton: false,
@observes("activeTab")
- _activeTabChanged: function () {
+ _activeTabChanged() {
if (this.activeTab) {
schedule("afterRender", () =>
this.element.querySelector(".d-editor-input").focus()
diff --git a/app/assets/javascripts/discourse/app/components/emoji-uploader.js b/app/assets/javascripts/discourse/app/components/emoji-uploader.js
index 371137e4fa..474aba66dc 100644
--- a/app/assets/javascripts/discourse/app/components/emoji-uploader.js
+++ b/app/assets/javascripts/discourse/app/components/emoji-uploader.js
@@ -1,12 +1,12 @@
import Component from "@ember/component";
-import UploadMixin from "discourse/mixins/upload";
+import UppyUploadMixin from "discourse/mixins/uppy-upload";
import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { notEmpty } from "@ember/object/computed";
const DEFAULT_GROUP = "default";
-export default Component.extend(UploadMixin, {
+export default Component.extend(UppyUploadMixin, {
type: "emoji",
uploadUrl: "/admin/customize/emojis",
hasName: notEmpty("name"),
@@ -15,10 +15,10 @@ export default Component.extend(UploadMixin, {
emojiGroups: null,
newEmojiGroups: null,
tagName: null,
+ preventDirectS3Uploads: true,
didReceiveAttrs() {
this._super(...arguments);
-
this.set("newEmojiGroups", this.emojiGroups);
},
@@ -27,10 +27,6 @@ export default Component.extend(UploadMixin, {
return !this.hasName || this.uploading;
},
- uploadOptions() {
- return { sequentialUploads: true };
- },
-
@action
createEmojiGroup(group) {
this.setProperties({
diff --git a/app/assets/javascripts/discourse/app/components/highlight-search.js b/app/assets/javascripts/discourse/app/components/highlight-search.js
index b0f8cc5e9c..268bd2ca39 100644
--- a/app/assets/javascripts/discourse/app/components/highlight-search.js
+++ b/app/assets/javascripts/discourse/app/components/highlight-search.js
@@ -7,7 +7,7 @@ export default Component.extend({
@on("didInsertElement")
@observes("highlight")
- _highlightOnInsert: function () {
+ _highlightOnInsert() {
const term = this.highlight;
highlightSearch(this.element, term);
},
diff --git a/app/assets/javascripts/discourse/app/components/invite-panel.js b/app/assets/javascripts/discourse/app/components/invite-panel.js
index dd55ebeed2..17ea67436e 100644
--- a/app/assets/javascripts/discourse/app/components/invite-panel.js
+++ b/app/assets/javascripts/discourse/app/components/invite-panel.js
@@ -351,12 +351,11 @@ export default Component.extend({
if (this.isInviteeGroup) {
return this.inviteModel
.createGroupInvite(this.invitee.trim())
- .then((data) => {
+ .then(() => {
model.setProperties({ saving: false, finished: true });
- this.get("inviteModel.details.allowed_groups").pushObject(
- EmberObject.create(data.group)
- );
- this.appEvents.trigger("post-stream:refresh");
+ this.inviteModel.reload().then(() => {
+ this.appEvents.trigger("post-stream:refresh");
+ });
})
.catch(onerror);
} else {
diff --git a/app/assets/javascripts/discourse/app/components/pick-files-button.js b/app/assets/javascripts/discourse/app/components/pick-files-button.js
index a451c67e80..9ab6465bcd 100644
--- a/app/assets/javascripts/discourse/app/components/pick-files-button.js
+++ b/app/assets/javascripts/discourse/app/components/pick-files-button.js
@@ -1,8 +1,9 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { empty } from "@ember/object/computed";
-import { bind, default as computed } from "discourse-common/utils/decorators";
+import computed, { bind } from "discourse-common/utils/decorators";
import I18n from "I18n";
+import bootbox from "bootbox";
export default Component.extend({
classNames: ["pick-files-button"],
diff --git a/app/assets/javascripts/discourse/app/components/reviewable-item.js b/app/assets/javascripts/discourse/app/components/reviewable-item.js
index d7173eb3ec..30eeff40fc 100644
--- a/app/assets/javascripts/discourse/app/components/reviewable-item.js
+++ b/app/assets/javascripts/discourse/app/components/reviewable-item.js
@@ -4,7 +4,7 @@ import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
import bootbox from "bootbox";
import { dasherize } from "@ember/string";
-import discourseComputed from "discourse-common/utils/decorators";
+import discourseComputed, { bind } from "discourse-common/utils/decorators";
import optionalService from "discourse/lib/optional-service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { set } from "@ember/object";
@@ -113,6 +113,7 @@ export default Component.extend({
return _components[type];
},
+ @bind
_performConfirmed(action) {
let reviewable = this.reviewable;
@@ -264,7 +265,7 @@ export default Component.extend({
title: "review.reject_reason.title",
model: this.reviewable,
}).setProperties({
- performConfirmed: this._performConfirmed.bind(this),
+ performConfirmed: this._performConfirmed,
action,
});
} else if (customModal) {
@@ -272,7 +273,7 @@ export default Component.extend({
title: `review.${customModal}.title`,
model: this.reviewable,
}).setProperties({
- performConfirmed: this._performConfirmed.bind(this),
+ performConfirmed: this._performConfirmed,
action,
});
} else {
diff --git a/app/assets/javascripts/discourse/app/components/scrolling-post-stream.js b/app/assets/javascripts/discourse/app/components/scrolling-post-stream.js
index da490bb0b3..8234c21b36 100644
--- a/app/assets/javascripts/discourse/app/components/scrolling-post-stream.js
+++ b/app/assets/javascripts/discourse/app/components/scrolling-post-stream.js
@@ -77,7 +77,12 @@ export default MountWidget.extend({
if (this.isDestroyed || this.isDestroying) {
return;
}
- if (isWorkaroundActive()) {
+
+ if (
+ isWorkaroundActive() ||
+ document.webkitFullscreenElement ||
+ document.fullscreenElement
+ ) {
return;
}
diff --git a/app/assets/javascripts/discourse/app/components/share-source.js b/app/assets/javascripts/discourse/app/components/share-source.js
index ae9f40730c..972ffe3249 100644
--- a/app/assets/javascripts/discourse/app/components/share-source.js
+++ b/app/assets/javascripts/discourse/app/components/share-source.js
@@ -1,8 +1,9 @@
import Component from "@ember/component";
export default Component.extend({
tagName: "",
+
actions: {
- share: function (source) {
+ share(source) {
this.action(source);
},
},
diff --git a/app/assets/javascripts/discourse/app/components/suggested-topics.js b/app/assets/javascripts/discourse/app/components/suggested-topics.js
index 02616fdaa7..a054bb0a76 100644
--- a/app/assets/javascripts/discourse/app/components/suggested-topics.js
+++ b/app/assets/javascripts/discourse/app/components/suggested-topics.js
@@ -37,12 +37,12 @@ export default Component.extend({
const inboxFilter = suggestedGroupName ? "group" : "user";
const unreadCount = this.pmTopicTrackingState.lookupCount("unread", {
- inboxFilter: inboxFilter,
+ inboxFilter,
groupName: suggestedGroupName,
});
const newCount = this.pmTopicTrackingState.lookupCount("new", {
- inboxFilter: inboxFilter,
+ inboxFilter,
groupName: suggestedGroupName,
});
@@ -54,7 +54,7 @@ export default Component.extend({
BOTH: hasBoth,
UNREAD: unreadCount,
NEW: newCount,
- username: username,
+ username,
groupName: suggestedGroupName,
groupLink: this._groupLink(username, suggestedGroupName),
basePath: getURL(""),
diff --git a/app/assets/javascripts/discourse/app/components/topic-footer-buttons.js b/app/assets/javascripts/discourse/app/components/topic-footer-buttons.js
index e0167ebf7d..ab1430ae50 100644
--- a/app/assets/javascripts/discourse/app/components/topic-footer-buttons.js
+++ b/app/assets/javascripts/discourse/app/components/topic-footer-buttons.js
@@ -1,7 +1,9 @@
import { alias, and, or } from "@ember/object/computed";
+import { computed } from "@ember/object";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import { getTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
+import { getTopicFooterDropdowns } from "discourse/lib/register-topic-footer-dropdown";
export default Component.extend({
elementId: "topic-footer-buttons",
@@ -18,17 +20,25 @@ export default Component.extend({
return this.siteSettings.enable_personal_messages && isPM;
},
- buttons: getTopicFooterButtons(),
+ inlineButtons: getTopicFooterButtons(),
+ inlineDropdowns: getTopicFooterDropdowns(),
- @discourseComputed("buttons.[]")
- inlineButtons(buttons) {
- return buttons.filter((button) => !button.dropdown);
- },
+ inlineActionables: computed(
+ "inlineButtons.[]",
+ "inlineDropdowns.[]",
+ function () {
+ return this.inlineButtons
+ .filterBy("dropdown", false)
+ .concat(this.inlineDropdowns)
+ .sortBy("priority")
+ .reverse();
+ }
+ ),
// topic.assigned_to_user is for backward plugin support
- @discourseComputed("buttons.[]", "topic.assigned_to_user")
- dropdownButtons(buttons) {
- return buttons.filter((button) => button.dropdown);
+ @discourseComputed("inlineButtons.[]", "topic.assigned_to_user")
+ dropdownButtons(inlineButtons) {
+ return inlineButtons.filter((button) => button.dropdown);
},
@discourseComputed("topic.isPrivateMessage")
diff --git a/app/assets/javascripts/discourse/app/components/topic-list-item.js b/app/assets/javascripts/discourse/app/components/topic-list-item.js
index 9933e98880..0fcbbd38f1 100644
--- a/app/assets/javascripts/discourse/app/components/topic-list-item.js
+++ b/app/assets/javascripts/discourse/app/components/topic-list-item.js
@@ -159,16 +159,16 @@ export default Component.extend({
return classes.join(" ");
},
- hasLikes: function () {
+ hasLikes() {
return this.get("topic.like_count") > 0;
},
- hasOpLikes: function () {
+ hasOpLikes() {
return this.get("topic.op_like_count") > 0;
},
@discourseComputed
- expandPinned: function () {
+ expandPinned() {
const pinned = this.get("topic.pinned");
if (!pinned) {
return false;
diff --git a/app/assets/javascripts/discourse/app/components/topic-navigation.js b/app/assets/javascripts/discourse/app/components/topic-navigation.js
index 77d1dee893..8e5c07ad9c 100644
--- a/app/assets/javascripts/discourse/app/components/topic-navigation.js
+++ b/app/assets/javascripts/discourse/app/components/topic-navigation.js
@@ -13,6 +13,7 @@ const MIN_WIDTH_TIMELINE = 924,
MIN_HEIGHT_TIMELINE = 325;
export default Component.extend(PanEvents, {
+ classNameBindings: ["info.topicProgressExpanded:topic-progress-expanded"],
composerOpen: null,
info: null,
isPanning: false,
diff --git a/app/assets/javascripts/discourse/app/components/topic-progress.js b/app/assets/javascripts/discourse/app/components/topic-progress.js
index a6fea0ea89..41ff05f96b 100644
--- a/app/assets/javascripts/discourse/app/components/topic-progress.js
+++ b/app/assets/javascripts/discourse/app/components/topic-progress.js
@@ -1,13 +1,14 @@
-import discourseComputed, { observes } from "discourse-common/utils/decorators";
+import discourseComputed, { bind } from "discourse-common/utils/decorators";
import Component from "@ember/component";
import I18n from "I18n";
import { alias } from "@ember/object/computed";
-import { scheduleOnce } from "@ember/runloop";
+import { later, scheduleOnce } from "@ember/runloop";
export default Component.extend({
elementId: "topic-progress-wrapper",
- classNameBindings: ["docked"],
+ classNameBindings: ["docked", "withTransitions"],
docked: false,
+ withTransitions: null,
progressPosition: null,
postStream: alias("topic.postStream"),
_streamPercentage: null,
@@ -68,128 +69,119 @@ export default Component.extend({
return readPos < stream.length - 1 && readPos > position;
},
- @observes("postStream.stream.[]")
- _updateBar() {
- scheduleOnce("afterRender", this, this._updateProgressBar);
- },
-
_topicScrolled(event) {
if (this.docked) {
- this.set("progressPosition", this.get("postStream.filteredPostsCount"));
- this._streamPercentage = 1.0;
+ this.setProperties({
+ progressPosition: this.get("postStream.filteredPostsCount"),
+ _streamPercentage: 100,
+ });
} else {
- this.set("progressPosition", event.postIndex);
- this._streamPercentage = event.percent;
+ this.setProperties({
+ progressPosition: event.postIndex,
+ _streamPercentage: (event.percent * 100).toFixed(2),
+ });
}
+ },
- this._updateBar();
+ @discourseComputed("_streamPercentage")
+ progressStyle(_streamPercentage) {
+ return `--progress-bg-width: ${_streamPercentage || 0}%`;
},
didInsertElement() {
this._super(...arguments);
this.appEvents
- .on("composer:will-open", this, this._dock)
- .on("composer:resized", this, this._dock)
- .on("composer:closed", this, this._dock)
- .on("topic:scrolled", this, this._dock)
+ .on("composer:resized", this, this._composerEvent)
.on("topic:current-post-scrolled", this, this._topicScrolled);
- const prevEvent = this.prevEvent;
- if (prevEvent) {
- scheduleOnce("afterRender", this, this._topicScrolled, prevEvent);
- } else {
- scheduleOnce("afterRender", this, this._updateProgressBar);
+ if (this.prevEvent) {
+ scheduleOnce("afterRender", this, this._topicScrolled, this.prevEvent);
}
- scheduleOnce("afterRender", this, this._dock);
+ scheduleOnce("afterRender", this, this._startObserver);
+
+ // start CSS transitions a tiny bit later
+ // to avoid jumpiness on initial topic load
+ later(this._addCssTransitions, 500);
},
willDestroyElement() {
this._super(...arguments);
+ this._topicBottomObserver?.disconnect();
this.appEvents
- .off("composer:will-open", this, this._dock)
- .off("composer:resized", this, this._dock)
- .off("composer:closed", this, this._dock)
- .off("topic:scrolled", this, this._dock)
+ .off("composer:resized", this, this._composerEvent)
.off("topic:current-post-scrolled", this, this._topicScrolled);
},
- _updateProgressBar() {
- if (this.isDestroyed || this.isDestroying) {
+ @bind
+ _addCssTransitions() {
+ if (this.isDestroying || this.isDestroyed) {
return;
}
+ this.set("withTransitions", true);
+ },
- const $topicProgress = $(this.element.querySelector("#topic-progress"));
- // speeds up stuff, bypass jquery slowness and extra checks
- if (!this._totalWidth) {
- this._totalWidth = $topicProgress[0].offsetWidth;
- }
-
- // Only show percentage once we have one
- if (!this._streamPercentage) {
- return;
- }
-
- const totalWidth = this._totalWidth;
- const progressWidth = (this._streamPercentage || 0) * totalWidth;
- const borderSize = progressWidth === totalWidth ? "0px" : "1px";
-
- const $bg = $topicProgress.find(".bg");
- if ($bg.length === 0) {
- const style = `border-right-width: ${borderSize}; width: ${progressWidth}px`;
- $topicProgress.append(` ',
// A callback for generating the remote link from the `link` and `title`
- generateUrl: function(link, title) {
+ generateUrl(link, title) {
return "http://twitter.com/intent/tweet?url=" + encodeURIComponent(link) + "&text=" + encodeURIComponent(title);
},
// If provided, handle by custom javascript rather than default url open
- clickHandler: function(link, title){
+ clickHandler(link, title) {
alert("Hello!")
},
diff --git a/app/assets/javascripts/discourse/app/lib/timeframes-builder.js b/app/assets/javascripts/discourse/app/lib/timeframes-builder.js
index 6fd5138209..cdfd0a3d2b 100644
--- a/app/assets/javascripts/discourse/app/lib/timeframes-builder.js
+++ b/app/assets/javascripts/discourse/app/lib/timeframes-builder.js
@@ -6,7 +6,7 @@ const TIMEFRAME_BASE = {
};
function buildTimeframe(opts) {
- return jQuery.extend({}, TIMEFRAME_BASE, opts);
+ return Object.assign({}, TIMEFRAME_BASE, opts);
}
const TIMEFRAMES = [
diff --git a/app/assets/javascripts/discourse/app/lib/transform-post.js b/app/assets/javascripts/discourse/app/lib/transform-post.js
index d1463a9635..37a1e4940f 100644
--- a/app/assets/javascripts/discourse/app/lib/transform-post.js
+++ b/app/assets/javascripts/discourse/app/lib/transform-post.js
@@ -1,6 +1,7 @@
import I18n from "I18n";
import { isEmpty } from "@ember/utils";
import { userPath } from "discourse/lib/url";
+import getURL from "discourse-common/lib/get-url";
const _additionalAttributes = [];
@@ -52,7 +53,7 @@ export function transformBasicPost(post) {
created_at: post.created_at,
updated_at: post.updated_at,
canDelete: post.can_delete,
- canPermanentlyDelete: post.can_permanently_delete,
+ canPermanentlyDelete: false,
showFlagDelete: false,
canRecover: post.can_recover,
canEdit: post.can_edit,
@@ -147,6 +148,7 @@ export default function transformPost(
postAtts.linkCounts = post.link_counts;
postAtts.actionCode = post.action_code;
postAtts.actionCodeWho = post.action_code_who;
+ postAtts.actionCodePath = getURL(post.action_code_path || `/t/${topic.id}`);
postAtts.topicUrl = topic.get("url");
postAtts.isSaving = post.isSaving;
postAtts.staged = post.staged;
@@ -262,7 +264,8 @@ export default function transformPost(
postAtts.canRecoverTopic = postAtts.isDeleted && details.can_recover;
postAtts.canDeleteTopic = !postAtts.isDeleted && details.can_delete;
postAtts.expandablePost = topic.expandable_first_post;
- postAtts.canPermanentlyDeleteTopic = details.can_permanently_delete;
+ postAtts.canPermanentlyDelete =
+ postAtts.isDeleted && details.can_permanently_delete;
// Show a "Flag to delete" message if not staff and you can't
// otherwise delete it.
@@ -279,6 +282,8 @@ export default function transformPost(
!post.deleted_at &&
currentUser &&
(currentUser.staff || !post.user_deleted);
+ postAtts.canPermanentlyDelete =
+ postAtts.isDeleted && post.can_permanently_delete;
}
_additionalAttributes.forEach((a) => (postAtts[a] = post[a]));
diff --git a/app/assets/javascripts/discourse/app/lib/uppy-checksum-plugin.js b/app/assets/javascripts/discourse/app/lib/uppy-checksum-plugin.js
index 977a01659b..0b54cf851d 100644
--- a/app/assets/javascripts/discourse/app/lib/uppy-checksum-plugin.js
+++ b/app/assets/javascripts/discourse/app/lib/uppy-checksum-plugin.js
@@ -1,6 +1,7 @@
import { UploadPreProcessorPlugin } from "discourse/lib/uppy-plugin-base";
import { Promise } from "rsvp";
import { HUGE_FILE_THRESHOLD_BYTES } from "discourse/mixins/uppy-upload";
+import { bind } from "discourse-common/utils/decorators";
export default class UppyChecksum extends UploadPreProcessorPlugin {
static pluginId = "uppy-checksum";
@@ -33,6 +34,7 @@ export default class UppyChecksum extends UploadPreProcessorPlugin {
return true;
}
+ @bind
_generateChecksum(fileIds) {
if (!this._canUseSubtleCrypto()) {
return this._skipAll(fileIds, true);
@@ -85,14 +87,14 @@ export default class UppyChecksum extends UploadPreProcessorPlugin {
}
_hasCryptoCipher() {
- return window.crypto && window.crypto.subtle && window.crypto.subtle.digest;
+ return window.crypto?.subtle?.digest;
}
install() {
- this._install(this._generateChecksum.bind(this));
+ this._install(this._generateChecksum);
}
uninstall() {
- this._uninstall(this._generateChecksum.bind(this));
+ this._uninstall(this._generateChecksum);
}
}
diff --git a/app/assets/javascripts/discourse/app/lib/uppy-media-optimization-plugin.js b/app/assets/javascripts/discourse/app/lib/uppy-media-optimization-plugin.js
index 15ba5aa419..b8e037fbaa 100644
--- a/app/assets/javascripts/discourse/app/lib/uppy-media-optimization-plugin.js
+++ b/app/assets/javascripts/discourse/app/lib/uppy-media-optimization-plugin.js
@@ -1,5 +1,6 @@
import { UploadPreProcessorPlugin } from "discourse/lib/uppy-plugin-base";
import { Promise } from "rsvp";
+import { bind } from "discourse-common/utils/decorators";
export default class UppyMediaOptimization extends UploadPreProcessorPlugin {
static pluginId = "uppy-media-optimization";
@@ -15,6 +16,7 @@ export default class UppyMediaOptimization extends UploadPreProcessorPlugin {
this.runParallel = opts.runParallel || false;
}
+ @bind
_optimizeFile(fileId) {
let file = this._getFile(fileId);
@@ -42,13 +44,15 @@ export default class UppyMediaOptimization extends UploadPreProcessorPlugin {
});
}
+ @bind
_optimizeParallel(fileIds) {
- return Promise.all(fileIds.map(this._optimizeFile.bind(this)));
+ return Promise.all(fileIds.map(this._optimizeFile));
}
+ @bind
async _optimizeSerial(fileIds) {
let optimizeTasks = fileIds.map((fileId) => () =>
- this._optimizeFile.call(this, fileId)
+ this._optimizeFile(fileId)
);
for (const task of optimizeTasks) {
@@ -58,17 +62,17 @@ export default class UppyMediaOptimization extends UploadPreProcessorPlugin {
install() {
if (this.runParallel) {
- this._install(this._optimizeParallel.bind(this));
+ this._install(this._optimizeParallel);
} else {
- this._install(this._optimizeSerial.bind(this));
+ this._install(this._optimizeSerial);
}
}
uninstall() {
if (this.runParallel) {
- this._uninstall(this._optimizeParallel.bind(this));
+ this._uninstall(this._optimizeParallel);
} else {
- this._uninstall(this._optimizeSerial.bind(this));
+ this._uninstall(this._optimizeSerial);
}
}
}
diff --git a/app/assets/javascripts/discourse/app/lib/user-search.js b/app/assets/javascripts/discourse/app/lib/user-search.js
index 6b6900db79..5fc019ab6d 100644
--- a/app/assets/javascripts/discourse/app/lib/user-search.js
+++ b/app/assets/javascripts/discourse/app/lib/user-search.js
@@ -55,7 +55,7 @@ function performSearch(
}
let data = {
- term: term,
+ term,
topic_id: topicId,
category_id: categoryId,
include_groups: includeGroups,
@@ -65,7 +65,7 @@ function performSearch(
topic_allowed_users: allowedUsers,
include_staged_users: includeStagedUsers,
last_seen_users: lastSeenUsers,
- limit: limit,
+ limit,
};
if (customUserSearchOptions) {
diff --git a/app/assets/javascripts/discourse/app/lib/utilities.js b/app/assets/javascripts/discourse/app/lib/utilities.js
index 654eaeb353..e075a40f81 100644
--- a/app/assets/javascripts/discourse/app/lib/utilities.js
+++ b/app/assets/javascripts/discourse/app/lib/utilities.js
@@ -95,13 +95,11 @@ export function avatarImg(options, customGetURL) {
title = ` title='${escaped}' aria-label='${escaped}'`;
}
- return ` |
| Heading 1 | Head 2 |
|---|---|
| ipsum |
| Heading 1 |
|---|
| Lorem | sit amet |
var helloWorld = () => {
alert(' hello \t\t world ');
@@ -253,23 +253,23 @@ helloWorld();
helloWorld();consectetur.`;
output = `Lorem ipsum dolor sit amet, \`var helloWorld = () => {\n alert(' hello \t\t world ');\n return;\n}\nhelloWorld();\`consectetur.`;
- assert.equal(toMarkdown(html), output);
+ assert.strictEqual(toMarkdown(html), output);
});
test("converts blockquote tag", function (assert) {
let html = "Lorem ipsum"; let output = "> Lorem ipsum"; - assert.equal(toMarkdown(html), output); + assert.strictEqual(toMarkdown(html), output); html = "
Lorem ipsum
"; output = "> Lorem ipsum\n\n> dolor sit amet"; - assert.equal(toMarkdown(html), output); + assert.strictEqual(toMarkdown(html), output); html = "dolor sit amet
\nLorem ipsum\n"; output = "> Lorem ipsum\n> > dolor\n> > > sit\n> > amet"; - assert.equal(toMarkdown(html), output); + assert.strictEqual(toMarkdown(html), output); }); test("converts ol list tag", function (assert) { @@ -287,7 +287,7 @@ helloWorld();consectetur.`; `; const markdown = `Testing\n\n1. Item 1\n2. Item 2\n 100. Sub Item 1\n 101. Sub Item 2\n3. Item 3`; - assert.equal(toMarkdown(html), markdown); + assert.strictEqual(toMarkdown(html), markdown); }); test("converts list tag from word", function (assert) { @@ -334,7 +334,7 @@ helloWorld();consectetur.`; Item 4 List`; const markdown = `Sample\n\n* **Item 1**\n * *Item 2*\n * Item 3\n* Item 4\n\nList`; - assert.equal(toMarkdown(html), markdown); + assert.strictEqual(toMarkdown(html), markdown); }); test("keeps mention/hash class", function (assert) { @@ -347,7 +347,7 @@ helloWorld();consectetur.`; const markdown = `User mention: @discourse\n\nGroup mention: @discourse-group\n\nCategory link: #foo\n\nSub-category link: #foo:bar`; - assert.equal(toMarkdown(html), markdown); + assert.strictEqual(toMarkdown(html), markdown); }); test("keeps emoji and removes click count", function (assert) { @@ -360,7 +360,7 @@ helloWorld();consectetur.`; const markdown = `A [link](http://example.com) with click count and :boom: emoji.`; - assert.equal(toMarkdown(html), markdown); + assert.strictEqual(toMarkdown(html), markdown); }); test("keeps emoji syntax for custom emoji", function (assert) { @@ -372,7 +372,7 @@ helloWorld();consectetur.`; const markdown = `:custom_emoji:`; - assert.equal(toMarkdown(html), markdown); + assert.strictEqual(toMarkdown(html), markdown); }); test("converts image lightboxes to markdown", function (assert) { @@ -383,11 +383,11 @@ helloWorld();consectetur.`; `; let markdown = ``; - assert.equal(toMarkdown(html), markdown); + assert.strictEqual(toMarkdown(html), markdown); html = `dolor
sitamet
`;
- assert.equal(toMarkdown(html), markdown);
+ assert.strictEqual(toMarkdown(html), markdown);
html = `
"
+ text: "Trupi"
colors:
title: "Ngjyrat"
copy_name_prefix: "Kopje e"
@@ -2704,7 +2700,6 @@ sq:
with_time: %{username} at %{time}
emoji:
title: "Emoji"
- help: "Add new emoji that will be available to everyone. (PROTIP: drag & drop multiple files at once)"
add: "Add New Emoji"
uploading: "Duke ngarkuar..."
name: "Emri"
diff --git a/config/locales/client.sr.yml b/config/locales/client.sr.yml
index 719475cd37..716f42a664 100644
--- a/config/locales/client.sr.yml
+++ b/config/locales/client.sr.yml
@@ -1988,12 +1988,6 @@ sr:
text: "Header"
footer:
text: "Footer"
- head_tag:
- text: ""
- title: "HTML koji će biti umetnut pre oznake"
- body_tag:
- text: "" + text: "النص الأساسي" yaml: text: "YAML" title: "تحديد إعدادات السمة بتنسيق YAML" @@ -5626,7 +5622,6 @@ ar: grant_existing_holders: منح شارات إضافة لحاملي الشارات الحاليين emoji: title: "الرمز التعبيري" - help: "إضافة رمز تعبيري جديد سيكون متاحًا للجميع. (تلميح احترافي: يمكنك سحب وإفلات عدة ملفات في الوقت نفسه)" add: "إضافة رمز تعبيري جديد" uploading: "جارٍ التحميل..." name: "الاسم" @@ -5637,7 +5632,6 @@ ar: embedding: get_started: "إذا كنت تريد تضمين Discourse في موقع آخر، فابدأ بإضافة مضيفه." confirm_delete: "هل تريد بالتأكيد حذف هذا المضيف؟" - sample: "استخدم رمز HTML التالي في موقعك لإنشاء موضوعات discourse وتضمينها. استبدل REPLACE_ME بالرابط الأساسي للصفحة التي تضمِّنه فيها." title: "التضمين" host: "المضيفون المسموح بهم" class_name: "اسم الفئة" diff --git a/config/locales/client.be.yml b/config/locales/client.be.yml index f45ae45b1e..b8653f4340 100644 --- a/config/locales/client.be.yml +++ b/config/locales/client.be.yml @@ -1563,8 +1563,8 @@ be: add: "Дадаць" header: text: "Header" - head_tag: - text: "<" + body_tag: + text: "цела" colors: title: "<......" copy_name_prefix: "капіяваць з" diff --git a/config/locales/client.bg.yml b/config/locales/client.bg.yml index 804f0e1d70..5d3ab526b5 100644 --- a/config/locales/client.bg.yml +++ b/config/locales/client.bg.yml @@ -2444,12 +2444,8 @@ bg: text: "Футър" embedded_scss: text: "ембеднато CSS" - head_tag: - text: "" - title: "HTML код който ще бъде поставен преди тага" body_tag: - text: "" - title: "HTML код който ще бъде поставен преди тага" + text: "Тяло" colors: title: "Цветове" copy_name_prefix: "Копие на" @@ -2991,7 +2987,6 @@ bg: csv_has_unmatched_users: "Следните записи са в CSV файла, но те не могат да бъдат съчетани със съществуващи потребители и следователно няма да получат значката:" emoji: title: "Емотикони " - help: "Добави нови емотикони, които ще бъдат достъпни до всички. (Съвет: Може да дръпнете и пуснете много файлове наведнъж). " add: "Добави нова емотикона " uploading: "Качва се..." name: "Име " @@ -3001,7 +2996,6 @@ bg: embedding: get_started: "Ако искате да внедрите Discourse в друг сайт, започнете с добавянето на хоста му." confirm_delete: "Сигурни ли сте, че искате да изтриете този хост?" - sample: "Използвайте следния HTML код в сайта си, за да създадете и вградите discourse теми. Сменете REPLACE_ME с URL адреса на страницата която искате да вградите." title: "Ембедване" host: "Позволени хостове" edit: "редактирай" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index bbe1933958..fd49707b0d 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -3596,12 +3596,8 @@ bs_BA: embedded_scss: text: "Ugrađeni CSS" title: "Unesite prilagođeni CSS za isporuku s ugrađenom verzijom komentara" - head_tag: - text: "" - title: "HTML koji će biti umetnut prije tag" body_tag: - text: "" - title: "HTML koji će biti umetnut prije tag" + text: "Tijelo" yaml: text: "YAML" title: "Definirajte postavke teme u YAML formatu" @@ -4303,7 +4299,6 @@ bs_BA: badge_query_examples_title: "Primjeri upita za oznaku" emoji: title: "Emoji" - help: "Dodajte novi emotikon koji će biti dostupan svima. (PROTIP: povlačenje i ispuštanje više datoteka odjednom)" add: "Dodaj novi emoji" uploading: "Učitava se..." name: "Ime" diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml index 57c29c0223..027b34a6b6 100644 --- a/config/locales/client.ca.yml +++ b/config/locales/client.ca.yml @@ -3379,12 +3379,8 @@ ca: embedded_scss: text: "CSS incrustat" title: "Introduïu el CSS personalitzat que es lliurarà amb la versió incrustada dels comentaris" - head_tag: - text: "" - title: "HTML que s'inclourà abans de l'etiqueta " body_tag: - text: "" - title: "HTML que s'inclourà abans de l'etiqueta " + text: "Cos" yaml: text: "YAML" title: "Definiu la configuració de l'aparença en format YAML" @@ -4098,7 +4094,6 @@ ca: badge_query_examples_title: "Exemples de consulta d'insígnies" emoji: title: "Emoji" - help: "Afegeix un nou emoji que estarà disponible per a tothom. (CONSELL: arrossegueu i deixeu anar molts fitxers de cop)" add: "Afegeix emoji nou" uploading: "Carregant..." name: "Nom" @@ -4108,7 +4103,6 @@ ca: embedding: get_started: "Si voleu incrustar Discourse en un altre web, comenceu afegint-hi l'amfitrió." confirm_delete: "Esteu segur que voleu suprimir aquest amfitrió?" - sample: "Feu servir el següent codi HTML en el vostre lloc web per a crear i incrustar temes de Discourse. Reemplaceu REEMPLAÇA'M amb l'URL canònic de la pàgina en la qual ho esteu incrustant." title: "Incrustant" host: "Amfitrions permesos" class_name: "Nom de la classe" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 0c1ee1756b..e3d7eabb45 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -3215,12 +3215,8 @@ cs: embedded_scss: text: "Vloženo CSS" title: "Zadat vlastní CSS k doručení s vloženou verzí kometářů" - head_tag: - text: "" - title: "HTML které bude vloženo před štítkem" body_tag: - text: "" - title: "HTML, který bude vložený před štítek" + text: "Tělo" yaml: text: "YAML" title: "Definovat nastavení motivu ve formátu YAML" @@ -3845,7 +3841,6 @@ cs: with_time: %{username} at %{time} emoji: title: "Emoji" - help: "Vložte nové emoji, které bude dostupné pro všechny na fóru. (Protip: můžete přetáhnout několik souborů najednou.)" add: "Vložit nový Emoji" uploading: "Nahrává se..." name: "Název" @@ -3855,7 +3850,6 @@ cs: embedding: get_started: "Pokud chceš zabudovat Discourse do jiných stránek, začni s přidáním hostu." confirm_delete: "Opravdu chcete smazat tento host?" - sample: "Abyste vytvořili a zabudovali témata z discourse, použijte následující HTML kód na vašem webu. Nahraď REPLACE_ME celkovým URL stránky, do které je zabudováváš." title: "Zabudování" host: "Povolené hosty" class_name: "název třídy" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index ac82b7ed5a..aa42f1b40d 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -4070,12 +4070,8 @@ da: %{example} Præfiksering af egenskabsnavne anbefales stærkt for at undgå konflikter med plugins og/eller kernen. - head_tag: - text: "" - title: "HTML som vil blive sat ind før tagget " body_tag: - text: "" - title: "HTML som vil blive sat ind før tagget " + text: "Brødtekst" yaml: text: "YAML" title: "Definér tema indstillinger i YAML-format" @@ -4580,7 +4576,7 @@ da: title: "Fremskridt med sletning af indlæg" description: "Sletter indlæg ..." confirmation: - cancel: "Afbryd" + cancel: "Annuller" merge: button: "Flet" prompt: @@ -4591,7 +4587,7 @@ da:
Alle emner, indlæg, beskeder og andet indhold oprettet af @%{username} overføres.
target_username_placeholder: "Brugernavn på ny ejer" transfer_and_delete: "Overfør & Slet @%{username}" - cancel: "Afbryd" + cancel: "Annuller" progress: title: "Flet fremgang" confirmation: @@ -4604,7 +4600,7 @@ da:
For at fortsætte skriv: %{text}
text: "overfør @%{username} til @%{targetUsername}" transfer_and_delete: "Overfør & Slet @%{username}" - cancel: "Afbryd" + cancel: "Annuller" merging_user: "Fletter bruger..." merge_failed: "Der opstod en fejl under sammenfletning af brugere." delete_forbidden_because_staff: "Admins og moderatorer kan ikke slettes." @@ -4710,7 +4706,7 @@ da: save: "Gem" edit: "Ret" delete: "Slet" - cancel: "Afbryd" + cancel: "Annuller" delete_confirm: "Er du sikker på at du vil slette det brugerfelt?" options: "Indstillinger" required: @@ -4906,7 +4902,6 @@ da: grant_existing_holders: Tildel yderligere emblemer til eksisterende emblemindehavere emoji: title: "Humørikon" - help: "Tilføj en ny emoji, som vil være tilgængelig for alle. (PROTIP: drag & drop flere filer på én gang)" add: "Tilføj ny emoji" uploading: "Overfører…" name: "Navn" @@ -4917,7 +4912,6 @@ da: embedding: get_started: "Hvis du vil indlejre Discourse på et andet website, skal du starte med at tilføje dets server." confirm_delete: "Er du sikker på at du vil slette denne server?" - sample: "Tilføj denne HTML til dit site for at oprette og indlejre emner fra discourse. Erstat REPLACE_ME med den kanoniske URL for den side du indlejrer den på," title: "Indlejring" host: "Tilladte servere" class_name: "Klasse Navn" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 72c0ba0bfd..d9a1396586 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -1143,9 +1143,9 @@ de: tags: "Schlagwörter" warnings: "Offizielle Warnungen" read_more_in_group: "Möchtest Du mehr lesen? Entdecke andere Nachrichten in %{groupLink}." - read_more: "Möchtest Du mehr lesen? Durchsuche andere Nachrichten in persönliche Nachrichten." + read_more: "Möchtest Du mehr lesen? Durchsuche andere Nachrichten in persönliche Nachrichten." read_more_group_pm_MF: "Dort { UNREAD, plural, =0 {} one { ist # ungelesen } other { sind # ungelesen } } { NEW, plural, =0 {} one { {BOTH, select, true{und } false {ist } other{}} # neue Nachricht} other { {BOTH, select, true{und } false {sind } other{}} # neue Nachrichten} } verbleibend, oder durchsuche andere Nachrichten in {groupLink}" - read_more_personal_pm_MF: "Dort { UNREAD, plural, =0 {} one { ist # ungelesen } other { sind # ungelesen } } { NEW, plural, =0 {} one { {BOTH, select, true{und } false {ist } other{}} # neue Nachricht} other { {BOTH, select, true{und } false {sind } other{}} # neue Nachrichten} } verbleibend oder durchsuche andere persönliche Nachrichten" + read_more_personal_pm_MF: "Dort { UNREAD, plural, =0 {} one { ist # ungelesen } other { sind # ungelesen } } { NEW, plural, =0 {} one { {BOTH, select, true{und } false {ist } other{}} # neue Nachricht} other { {BOTH, select, true{und } false {sind } other{}} # neue Nachrichten} } verbleibend oder durchsuche andere persönliche Nachrichten" preferences_nav: account: "Konto" security: "Sicherheit" @@ -3622,6 +3622,8 @@ de: no_read_topics_title: "Du hast noch keine Themen gelesen" no_read_topics_body: "Sobald Du mit dem Lesen von Diskussionen beginnst, siehst Du hier eine Liste. Zum Lesen suche nach Themen, die Du in Top oder Kategorien oder suche nach dem Stichwort %{searchIcon}" no_group_messages_title: "Keine Gruppennachrichten gefunden" + fullscreen_table: + expand_btn: "Tabelle erweitern" admin_js: type_to_filter: "zum Filtern hier eingeben …" admin: @@ -4221,11 +4223,11 @@ de: Eigenschaften sollten mit einem Präfix versehen werden, um Konflikte mit Discourse und/oder Plug-ins zu vermeiden. head_tag: - text: "" - title: "HTML, das vor dem -Tag eingefügt wird" + text: "Kopf" + title: "HTML, das vor dem Head-Tag eingefügt wird" body_tag: - text: "" - title: "HTML, das vor dem -Tag eingefügt wird" + text: "Body" + title: "HTML, das vor dem Body-Tag eingefügt wird" yaml: text: "YAML" title: "Definiere Theme-Einstellungen im YAML-Format" @@ -5077,7 +5079,6 @@ de: grant_existing_holders: Gewähre weiteren Abzeichen an bestehende Abzeicheninhaber emoji: title: "Emoji" - help: "Neues Emoji hinzufügen, das für alle verfügbar sein wird. (TIPP: Per Drag-and-drop kannst du gleichzeitig mehrere Dateien hinzufügen)" add: "Neues Emoji hinzufügen" uploading: "Wird hochgeladen …" name: "Name" @@ -5088,7 +5089,6 @@ de: embedding: get_started: "Wenn du Discourse in einer anderen Website einbetten möchtest, beginne mit dem Hinzufügen des Hosts." confirm_delete: "Möchtest du wirklich diesen Host löschen?" - sample: "Verwende den folgenden HTML-Code auf deiner Website, um Discourse-Themen zu erstellen und einzubetten. Ersetze REPLACE_ME mit der URL der Seite, auf der du ihn einbettest." title: "Einbettung" host: "Erlaubte Hosts" class_name: "Klassenname" diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml index 3d2c722833..325c1c775a 100644 --- a/config/locales/client.el.yml +++ b/config/locales/client.el.yml @@ -3643,12 +3643,8 @@ el: color_definitions: text: "Ορισμοί χρωμάτων" title: "Εισαγωγή προσαρμοσμένων ορισμών χρωμάτων (μόνο για προχωρημένους χρήστες)" - head_tag: - text: "" - title: "HTML το οποίο θα προστίθεται πριν το tag" body_tag: - text: "" - title: "HTML το οποίο θα προστίθεται πριν το tag" + text: "Body" yaml: text: "YAML" title: "Ορισμός ρυθμίσεων θέματος σε μορφή YAML" @@ -4409,7 +4405,6 @@ el: replace_owners: Αφαιρέστε το σήμα από προηγούμενους κατόχους emoji: title: "Emoji" - help: "Πρόσθεσε ένα νέο emoji που θα είναι διαθέσιμο σε όλους. (Συμβουλή: μπορείς να σύρεις πολλά αρχεία ταυτόχρονα)" add: "Προσθήκη νέου emoji" uploading: "Ανεβαίνει..." name: "Όνομα" @@ -4420,7 +4415,6 @@ el: embedding: get_started: "Εάν θέλεις να ενσωματώσεις το Discourse σε μια άλλη ιστοσελίδα, ξεκίνα με το να προσθέσεις το host του." confirm_delete: "Είσαι βέβαιος πως θέλεις να διαγράψεις το host;" - sample: "Χρησιμοποιήστε τον ακόλουθο HTML κώδικα στον ιστότοπό σας για να ενθέσετε θέματα. Αντικαταστήστε REPLACE_ME με το κανονικό URL της σελίδας στην οποία θα ενθέσετε το θέμα. " title: "Ενσωμάτωση" host: "Επιτρεπόμενα hosts" class_name: "Class Name" diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 2054b24c4d..a50b1a6e19 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1219,7 +1219,7 @@ en: tags: "Tags" warnings: "Official Warnings" read_more_in_group: "Want to read more? Browse other messages in %{groupLink}." - read_more: "Want to read more? Browse other messages in personal messages." + read_more: "Want to read more? Browse other messages in personal messages." read_more_group_pm_MF: "There { UNREAD, plural, @@ -1249,7 +1249,7 @@ en: =0 {} one { {BOTH, select, true{and } false {is } other{}} # new message} other { {BOTH, select, true{and } false {are } other{}} # new messages} - } remaining, or browse other personal messages" + } remaining, or browse other personal messages" preferences_nav: account: "Account" @@ -2234,6 +2234,9 @@ en: reload: "Reload" ignore: "Ignore" + image_alt_text: + aria_label: Alt text for image + notifications: tooltip: regular: @@ -3765,6 +3768,9 @@ en: changed: "tags changed:" tags: "Tags" choose_for_topic: "optional tags" + choose_for_topic_required: + one: "select at least %{count} tag..." + other: "select at least %{count} tags..." info: "Info" default_info: "This tag isn't restricted to any categories, and has no synonyms. To add restrictions, put this tag in a tag group." category_restricted: "This tag is restricted to categories you don't have permission to access." @@ -3938,6 +3944,9 @@ en: no_group_messages_title: "No group messages found" + fullscreen_table: + expand_btn: "Expand Table" + # This section is exported to the javascript for i18n in the admin section admin_js: type_to_filter: "type to filter..." @@ -4138,6 +4147,7 @@ en: no_description: (no description) all_api_keys: All API Keys user_mode: User Level + scope_mode: Scope impersonate_all_users: Impersonate any user single_user: "Single User" user_placeholder: Enter username @@ -4148,12 +4158,15 @@ en: delete: Permanently Delete not_shown_again: This key will not be displayed again. Make sure you take a copy before continuing. continue: Continue - use_global_key: Global Key (allows all actions) scopes: description: | When using scopes, you can restrict an API key to a specific set of endpoints. You can also define which parameters will be allowed. Use commas to separate multiple values. title: Scopes + granular: Granular + read_only: Read-only + global: Global + global_description: API key has no restriction and all endpoints are accessible. resource: Resource action: Action allowed_parameters: Allowed Parameters @@ -4161,6 +4174,8 @@ en: any_parameter: (any parameter) allowed_urls: Allowed URLs descriptions: + global: + read: Restrict API key to read-only endpoints. topics: read: Read a topic or a specific post in it. RSS is also supported. write: Create a new topic or post to an existing one. @@ -4552,11 +4567,11 @@ en: Prefixing the property names is highly recommended to avoid conflicts with plugins and/or core. head_tag: - text: "" - title: "HTML that will be inserted before the tag" + text: "Head" + title: "HTML that will be inserted before the head tag" body_tag: - text: "" - title: "HTML that will be inserted before the tag" + text: "Body" + title: "HTML that will be inserted before the body tag" yaml: text: "YAML" title: "Define theme settings in YAML format" @@ -5434,7 +5449,7 @@ en: emoji: title: "Emoji" - help: "Add new emoji that will be available to everyone. (PROTIP: drag & drop multiple files at once)" + help: "Add new emoji that will be available to everyone. Drag and drop multiple files at once without entering a name to create emojis using their file names." add: "Add New Emoji" uploading: "Uploading..." name: "Name" @@ -5446,7 +5461,7 @@ en: embedding: get_started: "If you'd like to embed Discourse on another website, begin by adding its host." confirm_delete: "Are you sure you want to delete that host?" - sample: "Use the following HTML code into your site to create and embed discourse topics. Replace REPLACE_ME with the canonical URL of the page you are embedding it on." + sample: "Paste the following HTML code into your site to create and embed discourse topics. Replace REPLACE_ME with the canonical URL of the page you are embedding it on." title: "Embedding" host: "Allowed Hosts" class_name: "Class Name" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index f69b66fa0a..382321e190 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -283,6 +283,7 @@ es: bookmarks: created: "Has guardado esta publicación en marcadores. %{name}" not_bookmarked: "guarda esta publicación en marcadores" + remove_reminder_keep_bookmark: "Quitar recordatorio y mantener marcador" created_with_reminder: "Has marcado esta publicación con un recordatorio %{date}. %{name}" remove: "Eliminar marcador" delete: "Eliminar marcador" @@ -1142,7 +1143,6 @@ es: tags: "Etiquetas" warnings: "Advertencias oficiales" read_more_in_group: "¿Quieres leer más? Consulta otros mensajes de %{groupLink}." - read_more: "¿Quieres leer más? Echa un vistazo a otros mensajes dentro de tus mensajes personales." preferences_nav: account: "Cuenta" security: "Seguridad" @@ -2158,6 +2158,7 @@ es: tags: "Etiquetas" in: "en" in_this_topic: "en este tema" + in_this_topic_tooltip: "cambiar a buscar en todos los temas" in_topics_posts: "en todos los temas y publicaciones" enter_hint: "o pulsa Intro" in_posts_by: "en publicaciones por %{username}" @@ -2185,7 +2186,7 @@ es: label: Publicado por aria_label: Filtrar por autor in_category: - label: Categorizado + label: Categoría in_group: label: En el grupo with_badge: @@ -3618,6 +3619,8 @@ es: no_read_topics_title: "Todavía no has leído ningún tema" no_read_topics_body: "Cuando empieces a leer temas, los verás aquí en una lista. Puedes empezar a buscar temas que te interesen en Destacados, la lista de Categorías o buscando palabras %{searchIcon}" no_group_messages_title: "No hay ningún mensaje en el grupo" + fullscreen_table: + expand_btn: "Expandir tabla" admin_js: type_to_filter: "filtrar opciones..." admin: @@ -4216,11 +4219,9 @@ es: Se recomienda poner prefijos a los nombres de propiedad para evitar conflictos con plugins o el core head_tag: - text: "" - title: "HTML a insertar antes de la etiqueta " + text: "Cabecera" body_tag: - text: "" - title: "HTML a insertar antes de la etiqueta " + text: "Cuerpo" yaml: text: "YAML" title: "Definir ajustes al tema en formato YAML" @@ -5072,7 +5073,6 @@ es: grant_existing_holders: Conceder una vez más la medalla a las cuentas que ya la tengan emoji: title: "Emoji" - help: "Añade emojis nuevos que estarán disponibles para todos. (CONSEJO: arrasta varios archivos a la vez)" add: "Añadir emoji nuevo" uploading: "Subiendo..." name: "Nombre" @@ -5083,7 +5083,6 @@ es: embedding: get_started: "Si quieres insertar Discourse en otro sitio web, empieza por añadir su host." confirm_delete: "¿Seguro que quieres eliminar este host?" - sample: "Usa el siguiente código HTML en tu sitio para crear e insertar temas. Reempalza REPLACE_ME con la URL canónica de la página donde lo quieres incrustar." title: "Incrustado" host: "Hosts permitidos" class_name: "Nombre de clase" diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml index 3337d90df3..3feda31cbd 100644 --- a/config/locales/client.et.yml +++ b/config/locales/client.et.yml @@ -2899,12 +2899,8 @@ et: text: "Jalus" embedded_scss: text: "Sängitatud CSS" - head_tag: - text: "" - title: "HTML, mida lisatakse enne silti" body_tag: - text: "" - title: "HTML, mida lisatakse enne silti" + text: "Sisu" yaml: text: "YAML" colors: @@ -3485,7 +3481,6 @@ et: with_time: %{username} ajal %{time} emoji: title: "Emotikon" - help: "Lisa uued emotikonid kõigile kasutamiseks. (PRO-vihje: pukseeri korraga mitu faili)" add: "Lisa uus emotikon" uploading: "Laen üles..." name: "Nimi" @@ -3495,7 +3490,6 @@ et: embedding: get_started: "Kui Sa soovid lisada Discourse teise veebisaidi sisse ehk sängitada, alusta lisades oma teenusepakkuja" confirm_delete: "Oled Sa kindel, et soovid kustutada selle teenusepakkuja?" - sample: "Kasuta järgnevat HTML koodi oma saidil, et luua ja lisada/sängitada discourse teemad oma saidile. Asenda REPLACE_ME kanoonilise lehe URL-viitega, kuhu Sa seda lisad." title: "Sängitamine" host: "Lubatud hostid" class_name: "Klassi nimi" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index e525caa2d4..11a94f1789 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -1807,6 +1807,8 @@ fa_IR: label: "فعالسازی بالا آوردن مبحث در لیست" desc: "بدون تغییر زمان اخرین پاسخ، پاسخ دهید." ignore: "چشم پوشی" + image_alt_text: + aria_label: متن Alt برای عکس notifications: tooltip: regular: @@ -3480,12 +3482,8 @@ fa_IR: embedded_scss: text: "کد CSS جاسازی شده" title: "کد CSS سفارشی مد نظرتان را اینجا وارد کنید" - head_tag: - text: "head>" - title: "کد HTML که قبل از برچسب اضافه میشود" body_tag: - text: "body>" - title: "کد HTML که قبل از برچسب اضافه میشود" + text: "بدنه" yaml: text: "YAML" colors: @@ -4076,7 +4074,6 @@ fa_IR: with_time: %{username} در %{time} emoji: title: "شکلک" - help: "اضافه کردن شکلک های جدید که در دسترس همگان خواهد بود.(PROTIP: کشیدن و رها کردن چند فایل در یک بار)" add: "افزودن شکلک جدید" uploading: "در حال بار گذاری ..." name: "نام" @@ -4086,7 +4083,6 @@ fa_IR: embedding: get_started: "اگر مایل هستید که Discourse را بر روی یک وبسایت دیگر جاساز کنید, با اضافه کردن میزبان یا همان هاست آن وبسایت شروع کنید." confirm_delete: "آیا مطمئن هستید که میخواهید آن میزبان را حذف کنید؟" - sample: "از این کد HTML در سایت خود استفاده کنید تا بتوانید مبحث های Discourse ایجاد کنید یا جاساز کنید, این کد REPLACE_ME را به URL استاندارد صفحه ای که بر روی آن جاسازی میکنید تغییر دهید." title: "جاسازی" host: "میزبان های مجاز" class_name: "نام کلاس" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 1c9774c646..27f1537f70 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -4098,12 +4098,8 @@ fi: %{example} Etuliitteen lisääminen CSS-kuvauksiin on erittäin suositeltavaa, jotta vältät ristiriidat lisäosien tai ydinsovelluksen kanssa. - head_tag: - text: "" - title: "HTML, joka sijoitetaan ennen -tunnistetta" body_tag: - text: "" - title: "HTML, joka sijoitetaan ennen -tunnistetta" + text: "Leipäteksti" yaml: text: "YAML" title: "Määritä teeman asetukset YAML-muodossa" @@ -4938,7 +4934,6 @@ fi: replace_owners: Poista kunniamerkki aiemmilta omistajilta emoji: title: "Emoji" - help: "Lisää uusi emoji, joka on kaikkien käytettävissä. (Vinkki: voit vetää ja pudottaa useita tiedostoja kerralla)" add: "Lisää uusi emoji" uploading: "Ladataan..." name: "Nimi" @@ -4949,7 +4944,6 @@ fi: embedding: get_started: "Jos haluat upottaa Discoursen toiselle sivustolle, aloita lisäämällä sen isäntä." confirm_delete: "Oletko varma, että haluat poistaa tämän isännän?" - sample: "Käytä alla olevaa HTML-koodia sivustollasi, jotta voit luoda ja upottaa discourse-ketjuja. Korvaa REPLACE_ME upotettavan sen sivun kanonisella URL-osoitteella, jolle upotat." title: "Upottaminen" host: "Sallitut isännät" class_name: "Luokan nimi" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index a16ada026c..9d7163cd51 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -4096,12 +4096,8 @@ fr: %{example} Il est fortement recommandé de préfixer les noms des propriétés CSS pour éviter tout conflit avec Discourse ou ses extensions. - head_tag: - text: "" - title: "HTML qui sera inséré avant la balise " body_tag: - text: "" - title: "HTML qui sera inséré avant la balise " + text: "Corps" yaml: text: "YAML" title: "Définir les paramètres du thème au format YAML" @@ -4936,7 +4932,6 @@ fr: replace_owners: Retirer le badge des propriétaires précédents emoji: title: "Émoji" - help: "Ajoutez de nouveaux émojis qui seront disponibles pour tout le monde. (Conseil : glissez-déposez plusieurs fichiers en même temps)" add: "Ajouter un nouvel émoji" uploading: "Envoi en cours…" name: "Nom" @@ -4947,7 +4942,6 @@ fr: embedding: get_started: "Si vous souhaitez intégrer Discourse dans un autre site, commencez par ajouter son hôte." confirm_delete: "Voulez-vous vraiment supprimer cet hôte ?" - sample: "Introduire le code HTML suivant dans votre site pour créer et intégrer des sujets Discourse. Remplacer REPLACE_ME avec l'URL de la page dans laquelle vous souhaitez l'intégrer." title: "Intégration externe" host: "Hôtes autorisés" class_name: "Nom de classe" diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml index 2590db6297..42c757aacc 100644 --- a/config/locales/client.gl.yml +++ b/config/locales/client.gl.yml @@ -3970,12 +3970,8 @@ gl: } Prefixar os nomes da propiedade resulta altamente recomendábel para evitarmos conflitos con complementos e/ou o núcleo. - head_tag: - text: "" - title: "HTML que se inserirá antes da etiqueta " body_tag: - text: "" - title: "HTML que se inserirá antes da etiqueta " + text: "Corpo" yaml: text: "YAML" title: "Definir os axustes do tema en formato YAML" @@ -4784,7 +4780,6 @@ gl: replace_owners: Retirar a insignia dos propietarios anteriores emoji: title: "Emoji" - help: "Engada un novo emoji que estará dispoñíbel para todos. (Suxestión: arrastre e solte múltiples ficheiros dunha vez)" add: "Engadir novo emoji" uploading: "Cargando..." name: "Nome" @@ -4795,7 +4790,6 @@ gl: embedding: get_started: "Se quere incorporar o Discourse noutro sitio web, comece engadindo o seu servidor." confirm_delete: "Confirma a eliminación deste servidor?" - sample: "Utilice o seguinte código HTML no seu sitio para crear e incorporar temas do Discourse. Substitúa REPLACE_ME polo URL canónico da páxina na que o está a incorporar." title: "Incorporando" host: "Servidores permitidos" class_name: "Nome da clase" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 0e3c66bb49..78cd0a2e7e 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -1096,15 +1096,15 @@ he: notifications: "התראות" statistics: "סטטיסטיקות" desktop_notifications: - label: "התראות" + label: "התראות חיות" not_supported: "התראות לא נתמכות בדפדפן זה. עמך הסליחה." perm_default: "הפעלת התראות" perm_denied_btn: "הרשאות נדחו" perm_denied_expl: "דחית הרשאה לקבלת התראות. יש לאפשר התראות בהגדרות הדפדפן שלך." - disable: "כבוי התראות" - enable: "אפשר התראות" + disable: "השבתת התראות" + enable: "הפעלת התראות" each_browser_note: 'הערה: עליך לשנות הגדרה זו בכל דפדפן שבו אתה משתמש. כל ההודעות יושבתו במצב "אל תפריע", ללא קשר להגדרה זו.' - consent_prompt: "האם ברצונך לקבל התראות כשאנשים מגיבים לפוסטים שלך?" + consent_prompt: "לקבל התראות חיות כשמתקבלות תגובות לפוסטים שלך?" dismiss: "דחה" dismiss_notifications: "בטלו הכל" dismiss_notifications_tooltip: "סימון כל ההתראות שלא נקראו כהתראות שנקראו" @@ -1182,7 +1182,7 @@ he: watched_first_post_tags_instructions: "אתם תיודעו לגבי הפוסט הראשון בכל נושא חדש בתגיות אלו." muted_categories: "מושתק" muted_categories_instructions: "לא תקבל הודעה בנוגע לנושאים חדשים בקטגוריות אלה, והם לא יופיעו בקטגוריות או בדפים האחרונים." - muted_categories_instructions_dont_hide: "לא תישלחנה אליך התראות על שום דבר בנוגע לנושאים בקטגוריות האלו." + muted_categories_instructions_dont_hide: "לא תישלחנה אליך התראות על שום דבר שנוגע לנושאים בקטגוריות האלו." regular_categories: "רגילות" regular_categories_instructions: "הקטגוריות האלו תופענה תחת רשימות הנושאים „אחרונים” ו־„מובילים”." no_category_access: "בתור פיקוח יש לך גישה מוגבלת לקטגוריות, שמירה מושבתת." @@ -1244,7 +1244,7 @@ he: tags: "תגיות" warnings: "אזהרות רשמיות" read_more_in_group: "מעניין אותך לקרוא עוד? אפשר לעיין בהודעות אחרות ב־%{groupLink}." - read_more: "מעניין אותך לקרוא עוד? אפשר לעיין בהודעות אחרות בהודעות הפרטיות." + read_more: "מעניין אותך לקרוא עוד? אפשר לעיין בהודעות אחרות בהודעות הפרטיות." preferences_nav: account: "חשבון" security: "אבטחה" @@ -1404,7 +1404,7 @@ he: other: "נשלח לך הודעה בדוא״ל רק אם לא הופעת ב־%{count} הדקות האחרונות." associated_accounts: title: "חשבונות מקושרים" - connect: "התחבר" + connect: "התחברות" revoke: "בטל" cancel: "ביטול" not_connected: "(לא מחובר)" @@ -1871,7 +1871,7 @@ he: awaiting_activation: "החשבון שלך ממתין להפעלה, נא להשתמש בקישור „שכחתי ססמה” כדי לשלוח הודעת הפעלה נוספת." awaiting_approval: "החשבון שלך טרם אושר על ידי חבר סגל. תישלח אליך הודעה בדוא״ל כשהוא יאושר." requires_invite: "סליחה, גישה לפורום הזה היא בהזמנה בלבד." - not_activated: "אינך יכול להתחבר עדיין. שלחנו לך דואר אלקטרוני להפעלת החשבון לכתובת: %{sentTo}. יש לעקוב אחר ההוראות בדואר כדי להפעיל את החשבון." + not_activated: "עוד אין לך אפשרות להיכנס. שלחנו אליך הודעת הפעלה בדוא״ל אל %{sentTo}. נא לעקוב אחר ההוראות שבהודעה כדי להפעיל את החשבון שלך." not_allowed_from_ip_address: "הכניסה מכתובת IP זו אסורה." admin_not_allowed_from_ip_address: "הכניסה לניהול מכתובת IP זו אסורה." resend_activation_email: "יש ללחוץ כאן לשליחת דואר אלקטרוני חוזר להפעלת החשבון." @@ -2182,6 +2182,8 @@ he: desc: "הגב מבלי לשנות את תאריך התגובה האחרונה" reload: "רענון" ignore: "התעלמות" + image_alt_text: + aria_label: כיתוב חלופי לתמונה notifications: tooltip: regular: @@ -3900,6 +3902,8 @@ he: no_topics_title: "עדיין לא פתחת אף נושא" no_read_topics_title: "טרם קראת נושאים" no_group_messages_title: "לא נמצאו הודעות קבוצתיות" + fullscreen_table: + expand_btn: "הרחבת טבלה" admin_js: type_to_filter: "הקלידו לסינון..." admin: @@ -4510,11 +4514,11 @@ he: מומלץ מאוד להוסיף קידומת לשמות המאפיינים כדי להימנע מסתירות מול תוספים ו/או הליבה. head_tag: - text: "head>" - title: "HTML שיוכנס לפני התג head>" + text: "Head" + title: "HTML שיתווסף לפני התגית head" body_tag: - text: "body>" - title: "HTML שיוכנס לפני התג body>" + text: "Body" + title: "HTML שיתווסף לפני התגית body" yaml: text: "YAML" title: "עריכת הגדרות ערכת העיצוב בתצורת YAML" @@ -5375,7 +5379,7 @@ he: grant_existing_holders: הענקת עיטורים נוספים למחזיקים בעיטורים קיימים emoji: title: "אמוג׳י" - help: "הוספת אמוג׳י חדשים שיהיו זמינים לכולם. (עצה למקצוענים: עדיף לגרור לכאן כמה קבצים בבת אחת)" + help: "הוספת אמוג׳י חדש שיהיה זמין לכולם. אפשר לגרור ולשחרר לכאן מגוון קבצים בבת אחת ללא מילוי שם ליצירת אמוג׳ים לפי שמות הקבצים שלהם." add: "הוספת אמוג׳י חדש" uploading: "בהליכי העלאה..." name: "שם" @@ -5386,7 +5390,7 @@ he: embedding: get_started: "כדי להטמיע את Discourse באתר אחר, יש להתחיל בהוספת השרת שלו." confirm_delete: "האם ברצונכם להסיר את הhost הזה? " - sample: "יש להשתמש בקוד ה־HTML הבא באתר שלך כדי ליצור ולהטמיע נושאים של Discourse. עליך להחליף את REPLACE_ME בכתובת היחסית של העמוד בו תתבצע ההטמעה." + sample: "יש להדביק את קוד ה־HTML הבא באתר שלך כדי ליצור ולהטמיע נושאים של Discourse. עליך להחליף את REPLACE_ME בכתובת היחסית של העמוד בו תתבצע ההטמעה." title: "שילוב (embedding)" host: "שרתים מורשים" class_name: "שם מחלקה" diff --git a/config/locales/client.hu.yml b/config/locales/client.hu.yml index c041b1de34..0e888afd07 100644 --- a/config/locales/client.hu.yml +++ b/config/locales/client.hu.yml @@ -283,6 +283,7 @@ hu: bookmarks: created: "Könyvjelzőzte ezt a bejegyzést. %{name}" not_bookmarked: "bejegyzés könyvjelzőzése" + remove_reminder_keep_bookmark: "Emlékeztető eltávolítása és könyvjelző megtartása" created_with_reminder: "Könyvjelzőzte ezt a bejegyzést, és emlékeztetőt állított be ekkorra: %{date}. %{name}" remove: "Könyvjelző eltávolítása" delete: "Könyvjelző törlése" @@ -1085,6 +1086,7 @@ hu: watched_first_post_tags_instructions: "Csak az új témakörök legelső hozzászólásáról fog értesítést kapni ezekben a címkékben." muted_categories: "Némított" muted_categories_instructions: "Semmilyen értesítést nem fog kapni a kategória témaköreiről, és a kategóriák vagy a legújabbak között sem fog megjelenni." + muted_categories_instructions_dont_hide: "Nem fog értesítést kapni az új témákról ezekben a kategóriákban." regular_categories: "Átlagos" no_category_access: "Moderátorként korlátozott kategória-hozzáférése van, a mentés tiltott." delete_account: "Saját fiók törlése" @@ -1140,9 +1142,7 @@ hu: tags: "Címkék" warnings: "Hivatalos figyelmeztetések" read_more_in_group: "Szeretne többet olvasni? Nézzen meg más témákat itt: %{groupLink}." - read_more: "Szeretne többet olvasni? Böngésszen a többi üzenet között a személyes üzenetekben." read_more_group_pm_MF: "{ UNREAD, plural, =0 {} one {# olvasatlan } other {# olvasatlan } } { NEW, plural, =0 {} one { {BOTH, select, true{és} false{} other{}} # új üzenet van hátra, vagy böngésszen } other { {BOTH, select, true{és} false{Böngésszen} other{}} # új üzenet van hátra, vagy böngésszen} } más üzeneteket ebben a csoportban: {groupLink}" - read_more_personal_pm_MF: "{ UNREAD, plural, =0 {} one {# olvasatlan } other {# olvasatlan } } { NEW, plural, =0 {} one { {BOTH, select, true{és} false{} other{}} # új üzenet van hátra, vagy böngésszen } other { {BOTH, select, true{és} false{Böngésszen} other{}} # új üzenet van hátra, vagy böngésszen} } más személyes üzeneteket" preferences_nav: account: "Fiók" security: "Biztonság" @@ -1281,6 +1281,7 @@ hu: auth_override_instructions: "Az e-mail-címet a hitelesítésszolgáltató tudja frissíteni." no_secondary: "Nincsenek másodlagos e-mail-címek" instructions: "Sosem jelenik meg nyilvánosan." + admin_note: "Megjegyzés: Ha egy rendszergazda felhasználó megváltoztatja egy nem rendszergazda felhasználó e-mail-címét, az azt jelzi, hogy a felhasználó elveszítette az eredeti e-mail-fiókját, ezért jelszó-visszaállítási e-mail lesz küldve az új címére. A felhasználó e-mail-címe addig nem változik meg, míg be nem fejezi a jelszó-visszaállítási folyamatot." ok: "E-mailt fogunk küldeni a megerősítéshez" required: "Adjon meg egy e-mail-címet" invalid: "Adjon meg egy érvényes e-mail-címet" @@ -1297,10 +1298,15 @@ hu: revoke: "Visszavonás" cancel: "Mégse" not_connected: "(nincs csatlakoztatva)" + confirm_modal_title: "%{provider}-fiók összekapcsolása" + confirm_description: + disconnect: "A meglévő „%{account_description}” %{provider}-fiókja le lesz választva." + account_specific: "Az Ön „%{account_description}” %{provider}-fiókja lesz használva a hitelesítéshez." name: title: "Név" instructions: "a teljes neve (nem kötelező)" instructions_required: "Az Ön teljes neve" + required: "Adjon meg egy nevet" too_short: "A neve túl rövid" ok: "A neve megfelelő." username: @@ -1314,6 +1320,8 @@ hu: too_long: "A felhasználóneve túl hosszú" checking: "A felhasználónév elérhetőségének ellenőrzése…" prefilled: "Az e-mail-cím megfelel ennek a regisztrált felhasználónévnek" + required: "Adjon meg egy felhasználónevet" + edit: "Felhasználónév szerkesztése" locale: title: "A felület nyelve" instructions: "A felhasználói felület nyelve. A lap frissítése után fog módosulni." @@ -1482,6 +1490,10 @@ hu: bulk_invite: none: "Nincs megjeleníthető meghívó ezen az oldalon." text: "Tömeges meghívás" + instructions: | +
Hívjon meg gyorsan felhasználókat egy listáról, hogy gyorsan összeállítsa a közösséget. Készítsen egy CSV-fájlt amely legalább sort tartalmaz a meghívandó felhasználók e-mail-címével. A következő vesszővel elválasztott információk adhatók meg, ha csoportokhoz is szeretné rendelni az embereket, vagy egy konkrét témához akarja őket küldeni az első bejelentkezésükkor.
+
email@example.com,első_csoport_neve;második_csoport_neve;témaazonosító
+
Minden a feltöltött CSV-fájlban lévő e-mail-címre egy meghívó lesz küldve, és ezeket majd később kezelheti.
progress: "%{progress}% feltöltve…" success: "A fájl sikeresen feltöltve. Értesítést fog kapni., ha a folyamat befejeződött." error: "Sajnáljuk, a fájlnak CSV formátumúnak kell lennie." @@ -1493,6 +1505,7 @@ hu: same_as_email: "A jelszava megegyezik az e-mail-címével." ok: "A jelszava megfelelőnek tűnik." instructions: "legalább %{count} karakter" + required: "Adjon meg egy jelszót" summary: title: "Összefoglaló" stats: "Statisztikák" @@ -1547,11 +1560,16 @@ hu: avatar: title: "Profilkép" header_title: "profil, üzenetek, könyvjelzők és beállítások" + name_and_description: "%{name} – %{description}" + edit: "Profilkép szerkesztése" title: title: "Cím" none: "(egyik sem)" + instructions: "a felhasználónév után jelenik meg" flair: + title: "Színesítő" none: "(egyik sem)" + instructions: "a profilkép mellett megjelenített ikon" primary_group: title: "Elsődleges csoport" none: "(egyik sem)" @@ -1584,7 +1602,9 @@ hu: fixed: "Oldal betöltése" modal: close: "bezárás" + dismiss_error: "Hiba elvetése" close: "Bezárás" + assets_changed_confirm: "Ez az oldal most kapott egy szoftverfrissítést. Beszerzi most a legújabb verziót?" logout: "Kijelentkezett." refresh: "Frissítés" home: "Kezdőlap" @@ -1592,14 +1612,27 @@ hu: enabled: "Az oldal írásvédett módban van. Nyugodtan folytathatja a böngészést, de a válasz, kedvelés és egyéb műveletek egyelőre le vannak tiltva." login_disabled: "A belépés le van tiltva, amíg az oldal írásvédett módban van." logout_disabled: "A kilépés le van tiltva, amíg az oldal írásvédett módban van." + too_few_topics_and_posts_notice_MF: >- + Kezdjük el a beszélgetést! Jelenleg {currentTopics, plural, one {# téma} other {# téma}} és {currentPosts, plural, one {# bejegyzés} other {# bejegyzés}} található. A látogatóknak több olvasnivalóra van szükségük – azt javasoljuk, hogy legalább {requiredTopics, plural, one {# téma} other {# téma}} és {requiredPosts, plural, one {# bejegyzés} other {# bejegyzés}} legyen. Csak a stáb láthatja ezt az üzenetet. + too_few_topics_notice_MF: >- + Kezdjük el a beszélgetést! Jelenleg {currentTopics, plural, one {# téma} other {# téma}} található. A látogatóknak több olvasnivalóra van szükségük – azt javasoljuk, hogy legalább {requiredTopics, plural, one {# téma} other {# téma}} legyen. Csak a stáb láthatja ezt az üzenetet. + too_few_posts_notice_MF: >- + Kezdjük el a beszélgetést! Jelenleg {currentPosts, plural, one {# bejegyzés} other {# bejegyzés}} található. A látogatóknak több olvasnivalóra van szükségük – azt javasoljuk, hogy legalább {requiredPosts, plural, one {# bejegyzés} other {# bejegyzés}} legyen. Csak a stáb láthatja ezt az üzenetet. + logs_error_rate_notice: + reached_hour_MF: "{relativeAge} – {rate, plural, one {# hiba/óra} other {# hiba/óra}}, elérte az oldal beállításiban megadott {limit, plural, one {# hiba/órás} other {# hiba/órás}} korlátot." + reached_minute_MF: "{relativeAge} – {rate, plural, one {# hiba/perc} other {# hiba/perc}}, elérte az oldal beállításiban megadott {limit, plural, one {# hiba/perces} other {# hiba/perces}} korlátot." + exceeded_hour_MF: "{relativeAge} – {rate, plural, one {# hiba/óra} other {# hiba/óra}}, túllépte az oldal beállításiban megadott {limit, plural, one {# hiba/órás} other {# hiba/órás}} korlátot." + exceeded_minute_MF: "{relativeAge} – {rate, plural, one {# hiba/perc} other {# hiba/perc}}, túllépte az oldal beállításiban megadott {limit, plural, one {# hiba/perces} other {# hiba/perces}} korlátot." learn_more: "tudjon meg többet…" first_post: Első bejegyzés mute: Némítás unmute: Némítás feloldása last_post: Közzétett + local_time: "Helyi idő" time_read: Olvasott time_read_recently: "%{time_read} mostanában" time_read_tooltip: "%{time_read} olvasással töltött idő" + time_read_recently_tooltip: "%{time_read} olvasással töltött idő (%{recent_time_read} az elmúlt 60 napban)" last_reply_lowercase: utolsó válasz replies_lowercase: one: válasz @@ -1608,6 +1641,7 @@ hu: sign_up: "Regisztráció" hide_session: "Emlékeztessen holnap" hide_forever: "nem, köszönöm" + hidden_for_session: "Rendben, holnap megkérdezzük. Bármikor használhatja a „Bejelentkezés” gombot, hogy fiókot készítsen magának." intro: "Szia! Úgy tűnik, tetszik neked a fórumunk, de még nem regisztráltál fiókot." value_prop: "Onnantól, hogy létrehozol egy fiókot, a rendszer emlékezni fog arra, hogy mit olvastál, így mindig oda térhetsz vissza, ahol korábban abbahagytad. Értesítéseket is kapsz, itt is és e-mailben is, valahányszor valaki válaszol neked. A bejegyzéseket pedig kedvelheted is. :heartpulse:" summary: @@ -1615,8 +1649,11 @@ hu: description: one: "%{count} válasz van." other: "%{count} válasz van." + description_time_MF: "{replyCount, plural, one {is # válasz} other {are # válasz}} található, a becsült olvasási idő {readingTime, plural, one {# perc} other {# perc}}." enable: "Téma összefoglalása" disable: "Összes bejegyzés megjelenítése" + short_label: "Összefoglalás" + short_title: "A téma összefoglalásának megjelenítése: a legérdekesebb bejegyzéseket a közösség határozta meg" deleted_filter: enabled_description: "Ez a téma törölt bejegyzéseket is tartalmaz, melyek el lettek rejtve." disabled_description: "A téma törölt bejegyzései is megjelennek." @@ -1644,6 +1681,7 @@ hu: disclaimer: "Csak akkor regisztráljon, ha elfogadja az adatvédelmi szabályzatot és a szolgáltatási feltételeket." title: "Regisztráció" failed: "Valami hiba történt, talán ez az e-mail cím már regisztrálva van. Próbálta már a jelszó-emlékeztetőt?" + associate: "Már van fiókja? Jelentkezzen be hogy hozzákapcsolja a(z) %{provider}-fiókját." forgot_password: title: "Jelszó-visszaállítás" action: "Elfelejtettem a jelszavamat" @@ -1673,13 +1711,23 @@ hu: logging_in_as: Bejelentkezés mint %{email} confirm_button: Bejelentkezés befejezése login: + header_title: "Üdvözlünk újra" subheader_title: "Jelentkezzen be a fiókjába" + title: "Bejelentkezés" username: "Felhasználó" password: "Jelszó" second_factor_title: "Kétfaktoros hitelesítés" second_factor_description: "Írja be a hitelesítési kódot az alkalmazásából:" + second_factor_backup: "Bejelentkezés biztonsági kóddal" + second_factor_backup_title: "Kétfaktoros biztonsági kód" second_factor_backup_description: "Írja be valamelyik biztonsági kódját:" + second_factor: "Bejelentkezés hitelesítő alkalmazással" + security_key_description: "Ha előkészítette a fizikai biztonsági kulcsot, nyomja meg a lenti „Hitelesítés biztonsági kulccsal” gombot." + security_key_alternative: "Próbálkozás más módon" + security_key_authenticate: "Hitelesítés biztonsági kulccsal" + security_key_not_allowed_error: "A biztonsági hardverkulcsos hitelesítési folyamata során időtúllépés történt, vagy megszakították." security_key_no_matching_credential_error: "A megadott biztonsági kulcsban nem található megfelelő hitelesítő adat." + security_key_support_missing_error: "Jelenlegi eszköze vagy böngészője nem támogatja a biztonsági kulcsok használatát. Használjon más módszert." email_placeholder: "E-mail/felhasználónév" caps_lock_warning: "A Caps Lock be van kapcsolva" error: "Ismeretlen hiba" @@ -1714,17 +1762,26 @@ hu: twitter: name: "Twitter" title: "Twitterrel" + sr_title: "Bejelentkezés a Twitterrel" instagram: name: "Instagram" title: "Instagrammal" + sr_title: "Bejelentkezés az Instagrammal" facebook: name: "Facebook" title: "Facebookkal" + sr_title: "Bejelentkezés a Facebookkal" github: name: "GitHub" title: "GitHubbal" + sr_title: "Bejelentkezés a GitHubbal" discord: name: "Discord" + title: "Discorddal" + sr_title: "Bejelentkezés a Discorddal" + second_factor_toggle: + totp: "Helyette hitelesítő alkalmazás használata" + backup_code: "Helyette biztonsági kód használata" invites: accept_title: "Meghívás" emoji: "boríték emodzsi" @@ -1756,18 +1813,28 @@ hu: shift: "Shift" ctrl: "Ctrl" alt: "Alt" + enter: "Enter" conditional_loading_section: loading: Betöltés… category_row: topic_count: one: "%{count} téma van ebben a kategóriában" other: "%{count} téma van ebben a kategóriában" + plus_subcategories_title: + one: "%{name} és egy alkategória" + other: "%{name} és %{count} alkategória" + plus_subcategories: + one: "+ %{count} alkategória" + other: "+ %{count} alkategória" select_kit: delete_item: "%{name} törlése" filter_by: "Szűrő: %{name}" select_to_filter: "Válaszd ki a szűrni kívánt értéket" default_header_text: Kiválasztás… no_content: Nem található egyezés + results_count: + one: "%{count} találat" + other: "%{count} találat" filter_placeholder: Keresés… filter_placeholder_with_any: Keresés vagy létrehozás… create: "Létrehozás: „%{content}”" @@ -1816,10 +1883,16 @@ hu: whisper: "suttogás" unlist: "nem listázott" add_warning: "Ez egy hivatalos figyelmeztetés." + toggle_whisper: "Suttogás be/ki" + toggle_unlisted: "Nem listázott állapot be/ki" posting_not_on_topic: "Melyik témakörre szeretnél válaszolni?" saved_local_draft_tip: "helyben mentett" similar_topics: "A témaköröd hasonlít a..." drafts_offline: "vázlatok offline" + edit_conflict: "szerkesztési ütközés" + group_mentioned_limit: + one: "Figyelem!Megemlítette a(z) %{group} csoportot, azonban ennek a csoportnak több mint %{count} tagja van, amely több, mint a rendszergazda által beállított megemlítési korlát. Senki nem lesz értesítve." + other: "Figyelem!Megemlítette a(z) %{group} csoportot, azonban ennek a csoportnak több mint %{count} tagja van, amely több, mint a rendszergazda által beállított megemlítési korlát. Senki nem lesz értesítve." group_mentioned: one: "A(z) %{group} csoport megemlítésével %{count} embert fog értesíteni – biztos benne?" other: "A(z) %{group} csoport megemlítésével %{count} embert fog értesíteni – biztos benne?" @@ -1827,12 +1900,28 @@ hu: category: "Megemlítette %{username} felhasználót, de nem lesz értesítve, mivel nincs hozzáférése ehhez a témához. Hozzá kell adnia egy olyan csoporthoz, amely hozzáfér ehhez a kategóriához." private: "Említetted %{username} de nem lesznek értesítve mivel képtelenek megnézni ezt a személyes üzenetet. Megkell hívnod őket ehez a személyes üzenethez" duplicate_link: "Úgy tűnik, hogy a %{domain} re mutató linkjét @%{username} már ban közzétette a témában, válasz: %{ago} - Biztosan újra el akarja küldeni?" + reference_topic_title: "Válasz: %{title}" error: - title_missing: "A címet kötelező megadni" - category_missing: "Ki kéne választanod egy kategóriát" + title_missing: "A cím kötelező" + title_too_short: + one: "A címnek legalább %{count} karakteresnek kell lennie" + other: "A címnek legalább %{count} karakteresnek kell lennie" + title_too_long: + one: "A cím legfeljebb %{count} karakteres lehet" + other: "A cím legfeljebb %{count} karakteres lehet" + post_missing: "A bejegyzés nem lehet üres" + post_length: + one: "A bejegyzésnek legalább %{count} karakteresnek kell lennie" + other: "A bejegyzésnek legalább %{count} karakteresnek kell lennie" + try_like: "Próbálta már a %{heart} gombot?" + category_missing: "Választania kell egy kategóriát" + tags_missing: + one: "Választania kell legalább %{count} címkét" + other: "Választania kell legalább %{count} címkét" + topic_template_not_modified: "Adjon hozzá részleteket és konkrétumokat a témájához a témasablon szerkesztésével." save_edit: "Módosítások mentése" - overwrite_edit: "Módosítások mentése" - reply_original: "Válasz az eredeti témakörre" + overwrite_edit: "Módosítások felülírása" + reply_original: "Válasz az eredeti témára" reply_here: "Válasz ide" reply: "Válasz" cancel: "Mégse" @@ -1841,13 +1930,14 @@ hu: create_whisper: "Suttogás" create_shared_draft: "Megosztott vázlat létrehozása" edit_shared_draft: "Megosztott vázlat szerkesztése" + title: "Vagy nyomjon %{modifier}Entert" users_placeholder: "Felhasználó hozzáadása" - title_placeholder: "Mi lesz a témája ennek a beszélgetésnek, röviden?" - title_or_link_placeholder: "Adj címet vagy másolj ide egy linket" - edit_reason_placeholder: "miért szerkesztesz?" - topic_featured_link_placeholder: "Írja be a link címét!" + title_placeholder: "Mi lesz a témája ennek a beszélgetésnek egy rövid mondatban?" + title_or_link_placeholder: "Írja be a címet, vagy illesszen be ide egy hivatkozást" + edit_reason_placeholder: "miért szerkeszt?" + topic_featured_link_placeholder: "Írja be a címmel megjelenített hivatkozást." remove_featured_link: "Hivatkozás eltávolítása a témából." - reply_placeholder: "Ide írhatsz. A feltöltéshez húzz- vagy illessz be képet! A formázáshoz használhatsz Markdown-, BBCode- vagy HTML kódokat is." + reply_placeholder: "Ide írjon. A formázáshoz használhat Markdownt, BBCode-ot vagy HTML-t. Idehúzhat vagy beilleszthet képeket." reply_placeholder_no_images: "Ide írj. Használhatsz Markdown-t, BBCode-ot, vagy HTML-t a formázáshoz." reply_placeholder_choose_category: "Válassz egy kategóriát, mielőtt elkezdesz írni!" view_new_post: "Nézd meg az új bejegyzésedet." @@ -1855,6 +1945,8 @@ hu: saved: "Elmentve!" saved_draft: "Vázlat közzététele folyamatban. Koppintson a folytatáshoz." uploading: "Feltöltés..." + show_preview: "előnézet megjelenítése" + hide_preview: "előnézet elrejtése" quote_post_title: "Teljes bejegyzés idézése" bold_label: "B" bold_title: "Félkövér" @@ -1866,6 +1958,7 @@ hu: link_description: "itt add meg a link leírását" link_dialog_title: "Hiperhivatkozás beszúrása" link_optional_text: "alternatív cím" + link_url_placeholder: "Illesszen be egy URL-t, vagy gépeljen a témák kereséséhez" blockquote_title: "Idézetblokk" blockquote_text: "Idézetblokk" code_title: "Előformázott szöveg" @@ -1876,35 +1969,55 @@ hu: olist_title: "Számozott lista" ulist_title: "Pontozott lista" list_item: "Listaelem" + toggle_direction: "Irány be/ki" help: "Markdown szerkesztési súgó" + collapse: "szerkesztő panel minimalizálása" + open: "szerkesztő panel megnyitása" abandon: "szerkesztő bezárása és a vázlat elvetése" + enter_fullscreen: "belépés a teljes képernyős szerkesztőben" + exit_fullscreen: "kilépés a teljes képernyős szerkesztőből" + show_toolbar: "szerkesztő eszköztár megjelenítése" + hide_toolbar: "szerkesztő eszköztár elrejtése" modal_ok: "Rendben" modal_cancel: "Mégse" cant_send_pm: "Nem tud üzenetet küldeni %{username} számára" yourself_confirm: title: "Elfelejtett címzettet hozzáadni?" body: "Jelenleg ez az üzenet csak saját magadnak lett elküldve" + slow_mode: + error: "Ez a téma lassú módban van. Nemrég már tett közzé bejegyzést; %{timeLeft} múlva teheti meg újra." admin_options_title: "A témakör opcionális szervezői beállításai" composer_actions: reply: Válasz draft: Vázlat edit: Szerkesztés reply_to_post: + label: Válasz %{postUsername} bejegyzésére desc: Válasz egy adott bejegyzésre reply_as_new_topic: label: Válasz hivatkozott témaként desc: Egy új téma létrehozása ezzel a témával kapcsolatban confirm: Új témavázlatot mentett, amely automatikusan felül lesz írva, ha kapcsolt témát hoz létre. + reply_as_new_group_message: + label: Válasz új csoportos üzenetként + desc: Új üzenet létrehozása azonos címzettekkel reply_as_private_message: label: Új üzenet desc: Új személyes üzenet létrehozása reply_to_topic: label: Válasz a témára + desc: Válasz a témára, nem konkrét bejegyzésre + toggle_whisper: + label: Suttogás be/ki + desc: A suttogások csak a stáb tagjai számára láthatók create_topic: label: "Új téma" shared_draft: label: "Megosztott vázlat" desc: "Témavázlat készítése, amely csak az engedélyezett felhasználók számára látható" + toggle_topic_bump: + desc: "Válasz a legutóbbi válasz dátumának módosítása nélkül" + reload: "Újratöltés" ignore: "Letiltás" notifications: tooltip: @@ -1914,6 +2027,9 @@ hu: message: one: "%{count} olvasatlan üzenet" other: "%{count} olvasatlan üzenet" + high_priority: + one: "%{count} olvasatlan, magas prioritású értesítés" + other: "%{count} olvasatlan, magas prioritású értesítés" title: "értesítések a @név megemlítésekről, a hozzászólásaira adott válaszokról, üzenetekről stb." none: "Jelenleg nem lehet betölteni az értesítéseket." empty: "Nem található értesítés." @@ -1939,12 +2055,18 @@ hu: invited_to_private_message: "
%{username} %{description}" invited_to_topic: "%{username} %{description}" invitee_accepted: "%{username} elfogadta a meghívásodat" + moved_post: "%{username} áthelyezte: %{description}" linked: "%{username} %{description}" granted_badge: "Új jelvény: „%{description}”" topic_reminder: "%{username} %{description}" watching_first_post: "Új téma %{description}" + membership_request_accepted: "Tagság elfogadva itt: „%{group_name}”" + membership_request_consolidated: + one: "%{count} nyitott tagsági kérés ennél: „%{group_name}”" + other: "%{count} nyitott tagsági kérés ennél: „%{group_name}”" reaction: "%{username}%{description}" reaction_2: "%{username}, %{username2} %{description}" + votes_released: "%{description} – kész" group_message_summary: one: "%{count} Üzenet a %{group_name} postaládában" other: "%{count} Üzenetek a %{group_name} postaládában" @@ -1961,21 +2083,36 @@ hu: confirm_body: "Siker! Értesítések engedélyezve." custom: "Értesítés %{username} felhasználótól itt: %{site_title}" titles: + mentioned: "megemlítve" + replied: "új válasz" + quoted: "idézve" + edited: "szerkesztve" + liked: "új kedvelés" private_message: "új személyes üzenet" invited_to_private_message: "meghívott privát üzenetre" + invitee_accepted: "meghívás elfogadva" + posted: "új bejegyzés" + moved_post: "bejegyzés áthelyezve" linked: "linkelve" bookmark_reminder: "könyvjelző emlékeztető" bookmark_reminder_with_name: "könyvjelző emlékeztető - %{name}" granted_badge: "kiadott jelvény" + invited_to_topic: "meghívva a témához" + group_mentioned: "csoport megemlítve" group_message_summary: "új csoportüzenetek" watching_first_post: "új téma" + topic_reminder: "téma emlékeztető" + liked_consolidated: "új kedvelés" post_approved: "bejegyzés jóváhagyva" + membership_request_consolidated: "új tagsági kérés" + reaction: "új reakció" upload_selector: uploading: "Feltöltés" - select_file: "File kiválasztása." - default_image_alt_text: Kép + processing: "Feltöltés feldolgozása" + select_file: "Válaszon fájlt" + default_image_alt_text: kép search: - sort_by: "Rendezés " + sort_by: "Rendezés" relevance: "Relevancia" latest_post: "Utolsó bejegyzés" latest_topic: "Legutóbbi téma" @@ -1983,78 +2120,147 @@ hu: most_liked: "Legtöbbet kedvelt" select_all: "Összes kijelölése" clear_all: "Összes törlése" - too_short: "Keresési kifejezésed túl rövid." + too_short: "A keresési kifejezés túl rövid." + open_advanced: "Speciális keresés megnyitása" + clear_search: "Keresés törlése" + sort_or_bulk_actions: "Találatok rendezése vagy tömeges kiválasztása" + result_count: + one: "%{count} találat erre:%{term}" + other: "%{count}%{plus} találat erre:%{term}" title: "Keresés" full_page_title: "Keresés" no_results: "Nincs találat." no_more_results: "Nincs több találat." post_format: "#%{post_number}, szerző: %{username}" results_page: "Találatok erre: „%{term}”" + more_results: "Több találat is van. Szűkítse a keresési feltételeket." + cant_find: "Nem találja, amit keres?" start_new_topic: "Esetleg indítson egy új témát?" or_search_google: "Vagy keressen a Google-lel:" search_google: "Inkább keressen a Google-lel:" search_google_button: "Google" search_button: "Keresés" + search_term_label: "adja meg a keresési kulcsszót" categories: "Kategóriák" tags: "Címkék" + in: "ebben:" + in_this_topic: "ebben a témában" + in_this_topic_tooltip: "váltás az összes téma keresésére" + in_topics_posts: "az összes témában és bejegyzésben" + enter_hint: "vagy nyomjon Entert" + in_posts_by: "%{username} bejegyzéseiben" type: + default: "Témák/bejegyzések" users: "Felhasználók" categories: "Kategóriák" + categories_and_tags: "Kategóriák/címkék" context: user: "Keresés @%{username} bejegyzései között" category: "Keresés a(z) #%{category} kategóriában" + tag: "A(z) #%{tag} címke keresése" topic: "Keresés ebben a témában" private_messages: "Üzenetek keresése" + tips: + category_tag: "szűrés kategória vagy címke szerint" + author: "szűrés a bejegyzés szerzője szerint" + in: "szűrés metaadat szerint (pl.: in:title, in:personal, in:pinned)" + status: "szűrés a téma állapota szerint" + full_search: "teljes oldalas keresés indítása" + full_search_key: "%{modifier} + Enter" advanced: + title: Speciális szűrők posted_by: label: 'Szerző:' + aria_label: Szűrés a bejegyzés szerzője szerint in_category: label: Kategorizált + in_group: + label: Csoportban with_badge: label: Jelvénnyel with_tags: label: Címkézett + aria_label: Szűrés címkék használatával filters: label: 'Csak akkor jelenítsen meg egy témát/választ, ha:' title: A keresett kifejezés megtalálható a címben likes: kedveltem + posted: közzétettem benne created: én hoztam létre watching: figyelem tracking: követem private: az üzeneteim között található bookmarks: könyvjelzőztem first: az a legelső bejegyzés + pinned: kitűzött seen: olvastam + unseen: nem olvastam + wiki: wiki bejegyzések + images: képeket tartalmaz all_tags: A fenti címkék mindegyike statuses: - open: nyitott - closed: zárolt + label: Ahol a témák + open: nyitottak + closed: zároltak + public: nyilvános archived: archivált noreplies: nincsenek válaszok + single_user: egyetlen felhasználót tartalmaznak post: count: label: Bejegyzések + min: + placeholder: legalább + aria_label: szűrés a bejegyzések legkisebb száma szerint + max: + placeholder: legfeljebb + aria_label: szűrés a bejegyzések legnagyobb száma szerint time: label: Közzétett + aria_label: Szűrés közzétételi dátum szerint before: előtte after: utána views: label: Megtekintések + min_views: + placeholder: legalább + aria_label: szűrés a megtekintések legkisebb száma szerint + max_views: + placeholder: legfeljebb + aria_label: szűrés a megtekintések legnagyobb száma szerint + additional_options: + label: "Szűrés hozzászólásszám és témamegtekintés szerint" + hamburger_menu: "menü" new_item: "új" go_back: "visszalépés" - not_logged_in_user: "felhasználói oldal összesítéssel a jelenleg aktivitásokról és beállításokról" + not_logged_in_user: "felhasználói oldal a jelenlegi tevékenységek és beállítások összesítésével" current_user: "ugrás a felhasználói oldalára" + view_all: "összes %{tab} megtekintése" topics: new_messages_marker: "utolsó látogatás" bulk: select_all: "Összes kiválasztása" clear_all: "Összes törlése" - unlist_topics: "Nem listázott témák" + unlist_topics: "Témák listázásnak megszüntetése" relist_topics: "Újralistázott témák" + reset_read: "Olvasott állapot visszaállítása" delete: "Témák törlése" dismiss: "Elvetés" dismiss_read: "Olvasatlan üzenetek elvetése" + dismiss_read_with_selected: + one: "%{count} olvasatlan elvetése" + other: "%{count} olvasatlan elvetése" dismiss_button: "Elvetés…" + dismiss_button_with_selected: + one: "Elvetés (%{count})…" + other: "Elvetés (%{count})…" + dismiss_tooltip: "Csak az új hozzászólások elvetése vagy a témák követésének leállítása" + also_dismiss_topics: "Ezen témák követésének leállítása, így sosem fognak olvasatlanként megjelenni" + dismiss_new: "Újak elvetése" + dismiss_new_with_selected: + one: "Újak elvetése (%{count})" + other: "Újak elvetése (%{count})" + toggle: "témák csoportos kiválasztása be/ki" actions: "Csoportos műveletek" change_category: "Kategória beállítása…" close_topics: "Témák lezárása" @@ -2066,41 +2272,61 @@ hu: selected: one: "Kiválasztott %{count} témát." other: "Kiválasztott %{count} témát." + change_tags: "Címkék cseréje" + append_tags: "Címkék hozzáfűzése" + choose_new_tags: "Válasszon új címkéket ezekhez a témákhoz:" + choose_append_tags: "Válasszon új hozzáfűzendő címkéket ezekhez a témákhoz:" + changed_tags: "Azon témák címkéi megváltoztak." + remove_tags: "Összes címke eltávolítása" confirm_remove_tags: one: "Az összes címke eltávolításra kerül erről a témáról. Biztos benne?" other: "Az összes címke eltávolításra kerül %{count} témáról. Biztos benne?" + progress: + one: "Folyamat: %{count} téma" + other: "Folyamat: %{count} téma" none: unread: "Nincsenek olvasatlan témái." + unseen: "Nincsenek nem látott témái." new: "Nincsenek új témái." read: "Még egy témát sem olvasott el." posted: "Még egy témához sem szólt hozzá." + latest: "Naprakész lett!" bookmarks: "Még nem adott hozzá témát a könyvjelzőihez." category: "Nincsenek témakörök a %{category} kategóriában." top: "Nincsenek top témák" educate: - new: '
Az új témák fognak itt megjelenni. Alapértelmezésként itt jelennek meg a friss témák, és egy ilyen jelet találsz majd azok mellett, amelyeket ráadásul az elmúlt két napban tettek közzé.
A beállításaidat ezen az oldalon tudod módosítani.
' + new: '
Az új témák fognak itt megjelenni. Alapértelmezésként itt jelennek meg a friss témák, és egy ilyen jelet talál majd azok mellett, amelyek az elmúlt két napban lettek közzétéve.
A beállításait ezen az oldalon tudja módosítani.
'
bottom:
latest: "Nincs több friss téma."
- posted: "Nincsen több közzétett téma"
+ posted: "Nincsen több közzétett téma."
read: "Nincs több olvasott téma."
- new: "Nincs több új témakör."
- unread: "Nincs több olvasatlan témakör."
- category: "Nincs több %{category} téma."
- tag: "Nincs több %{tag} téma."
- top: "Nincsenek több top témák"
- bookmarks: "Nincs több témakör a könyvjelzők között."
+ new: "Nincs több új téma."
+ unread: "Nincs több olvasatlan téma."
+ unseen: "Nincs több nem látott téma."
+ category: "Nincs több „%{category}” kategóriájú téma."
+ tag: "Nincs több „%{tag}” címkéjű téma."
+ top: "Nincsenek több népszerű téma"
+ bookmarks: "Nincs több könyvjelzőzött téma."
topic:
+ filter_to:
+ one: "%{count} bejegyzés a témában"
+ other: "%{count} bejegyzés a témában"
create: "Új téma"
create_long: "Új téma létrehozása"
open_draft: "Vázlat megnyitása"
- private_message: "Üzenj "
+ private_message: "Üzenet indítása"
archive_message:
- help: "Az üzenet az archivumba adása"
+ help: "Üzenet áthelyezése az archívumba"
title: "Archívum"
move_to_inbox:
title: "Áthelyezés a bejövő üzenetek közé"
+ help: "Üzenet áthelyezése a Beérkezett üzenetek közé"
edit_message:
+ help: "Az üzenet első bejegyzésének szerkesztése"
title: "Szerkesztés"
+ defer:
+ help: "Megjelölés olvasatlanként"
+ title: "Elhalasztás"
list: "Témák"
new: "új téma"
unread: "olvasatlan"
@@ -2110,34 +2336,43 @@ hu:
unread_topics:
one: "%{count} olvasatlan téma"
other: "%{count} olvasatlan téma"
- title: "Témakör"
+ title: "Téma"
invalid_access:
- title: "Privát témakör"
- description: "Sajnáljuk, de nincs hozzáférésed ehhez a témához!"
- login_required: "Be kell jelentkezned, hogy megtekinthesd ezt a témakört!"
+ title: "A téma privát"
+ description: "Sajnáljuk, de nincs hozzáférése ehhez a témához!"
+ login_required: "Be kell jelentkeznie, hogy megtekinthesse ezt a témát!"
server_error:
- title: "Nem sikerült betölteni a témakört"
+ title: "A téma betöltése sikertelen"
description: "Sajnos nem tudtuk betölteni a témát, valószínűleg kapcsolódási probléma miatt. Kérjük, próbáld újra. Ha a probléma továbbra is fennáll, értesíts minket."
not_found:
- title: "Nem létező témakör"
- description: "Sajnáljuk, de nem tudtuk megtalálni ezt a témakört. Talán egy moderátor kitörölte volna?"
+ title: "A téma nem található"
+ description: "Sajnos a téma nem található. Lehet, hogy egy moderátor törölte?"
unread_posts:
- one: "%{count} hozzászólást nem olvastál a témában"
- other: "%{count} hozzászólást nem olvastál a témában"
+ one: "%{count} nem olvasott bejegyzés van ebben a témában"
+ other: "%{count} nem olvasott bejegyzés van ebben a témában"
likes:
one: "%{count} kedvelés van a témában"
other: "%{count} kedvelés van a témában"
- back_to_list: "Vissza a témakörök listájára"
- options: "Témakör beállításai"
- show_links: "linkek megjelenítése ebben a témakörben"
- read_more_in_category: "Szeretnél még többet olvasni? Nézz meg más témakat itt, %{catLink} vagy itt: %{latestLink}."
- read_more: "Szeretnél mégtöbbet olvasni? %{catLink} vagy %{latestLink}."
- browse_all_categories: Böngéssz a kategóriák között
+ back_to_list: "Vissza a témalistához"
+ options: "Témabeállítások"
+ show_links: "a témában szereplő hivatkozások megjelenítése"
+ collapse_details: "téma részleteinek összecsukása"
+ expand_details: "téma részleteinek kibontása"
+ read_more_in_category: "Szeretne többet olvasni? Böngésszen más témákat itt: %{catLink} vagy %{latestLink}."
+ read_more: "Szeretne többet olvasni? %{catLink} vagy %{latestLink}."
+ unread_indicator: "Egyetlen tag sem olvasta még a téma utolsó bejegyzését."
+ read_more_MF: "{ UNREAD, plural, =0 {} one {# olvasatlan} other {# olvasatlan} } { NEW, plural, =0 {} one { {BOTH, select, true{és} false {} other{}} # új téma} other { {BOTH, select, true{és} false {} other{}} # új téma} } van hátra, vagy {CATEGORY, select, true {böngésszen más témákat itt: {catLink}} false {{latestLink}} other {}}"
+ bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}"
+ browse_all_categories: Összes kategória böngészése
+ browse_all_tags: Összes címke böngészése
view_latest_topics: legújabb témák megtekintése
- jump_reply_up: ugrás régebbi válaszhoz
- jump_reply_down: ugrás újabb válaszhoz
- deleted: "Ez a témakör ki lett törölve"
+ suggest_create_topic: Készen áll, hogy új beszélgetést kezdjen?
+ jump_reply_up: ugrás a korábbi válaszhoz
+ jump_reply_down: ugrás a későbbi válaszhoz
+ deleted: "A téma törölve lett"
slow_mode_update:
+ title: "Lassú mód"
+ select: "A felhasználók ennyi időnként csak egyszer tehetnek közzé bejegyzést:"
enable: "Engedélyezés"
remove: "Letiltás"
hours: "Óráig:"
@@ -2946,6 +3181,8 @@ hu:
no_drafts_title: "Még nem kezdett el egyetlen vázlatot sem"
no_drafts_body: "Még nem áll készen a bejegyzés létrehozására? Automatikusan mentve lesz egy új vázlat, és itt fel lesz sorolva, ha egy új témát, választ vagy személyes üzenetet kezd el írni. Válassza a Mégse gombot, hogy elvesse, vagy mentse a vázlatot a későbbi folytatáshoz."
no_likes_others: "Nincsenek kedvelt bejegyzések."
+ fullscreen_table:
+ expand_btn: "Táblázat kibontása"
admin_js:
type_to_filter: "Írj ide a szűréshez.."
admin:
@@ -3295,9 +3532,11 @@ hu:
embedded_scss:
text: "Ágyazott CSS"
head_tag:
- text: ""
+ text: "Fej"
+ title: "A „head” címke előtt beillesztendő HTML"
body_tag:
- text: ""
+ text: "Törzs"
+ title: "A „body” címke előtt beillesztendő HTML"
yaml:
text: "YAML"
title: "Téma beállításainak megadása YAML formátumban"
@@ -3829,7 +4068,6 @@ hu:
badge_query_examples_title: "Jelvény lekérdezési példák"
emoji:
title: "Emodzsi"
- help: "Adjon hozzá egy új emodzsit, amely mindenki számára elérhető lesz. (PROTIP: fogjon és vigyen több fájlt egyszerre)"
add: "Új emodzsi hozzáadása"
uploading: "Feltöltés…"
name: "Név"
diff --git a/config/locales/client.hy.yml b/config/locales/client.hy.yml
index 8e1c3a4b69..6986c89b39 100644
--- a/config/locales/client.hy.yml
+++ b/config/locales/client.hy.yml
@@ -3420,12 +3420,8 @@ hy:
embedded_scss:
text: "Զետեղված CSS"
title: "Մուտքագրեք մասնավոր CSS՝ մեկնաբանությունների զետեղված տարբերակի վրա կիրառելու համար"
- head_tag:
- text: ""
- title: "HTML, որը պետք է մուտք արվի մինչև թեգը"
body_tag:
- text: ""
- title: "HTML, որը պետք է մուտք արվի մինչև թեգը"
+ text: "Մարմին"
yaml:
text: "YAML"
title: "Սահմանել թեմայի կարգավորումները YAML ֆորմատով"
@@ -4174,7 +4170,6 @@ hy:
replace_owners: Հեռացնել նախորդ սեփականատիրոջ կրծքանշանը
emoji:
title: "Էմոջի"
- help: "Ավելացրեք նոր էմոջի, որը հասանելի կլինի բոլորին: (ՀՈՒՇՈՒՄ՝ քաշեք և գցեք բազմակի ֆայլեր միանգամից)"
add: "Ավելացնել Նոր Էմոջի"
uploading: "Վերբեռնում..."
name: "Անուն"
@@ -4185,7 +4180,6 @@ hy:
embedding:
get_started: "Եթե Դուք ցանկանում եք զետեղել Discourse-ը մեկ այլ կայքում, սկսեք նրա հոսթի ավելացմամբ:"
confirm_delete: "Դուք համոզվա՞ծ եք, որ ցանկանում եք ջնջել այդ հոսթը:"
- sample: "Օգտագործեք հետևյալ HTML կոդը Ձեր կայքում՝ discourse-ի թեմաներ ստեղծելու և զետեղելու համար: Փոխարինեք REPLACE_ME տեքստը կանոնավոր URL-ով այն էջի վրա, որտեղ Դուք ներկառուցում եք այն:"
title: "Զետեղում"
host: "Թույլատրված Հոսթերը"
class_name: "Class-ի Անունը"
diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml
index 10f95e6bcc..eb9a3940de 100644
--- a/config/locales/client.id.yml
+++ b/config/locales/client.id.yml
@@ -39,6 +39,7 @@ id:
long_date_without_year_with_linebreak: "MMM D
LT"
long_date_with_year_with_linebreak: "MMM D, 'YY
LT"
wrap_ago: "%{date} yang lalu"
+ wrap_on: "pada %{date}"
tiny:
half_a_minute: "< 1m"
less_than_x_seconds:
@@ -92,6 +93,8 @@ id:
previous_month: "Bulan Lalu"
next_month: "Bulan Depan"
placeholder: tanggal
+ from_placeholder: "dari tanggal"
+ to_placeholder: "sampai saat ini"
share:
topic_html: 'Topik: %{topicTitle}'
post: "post #%{postNumber}"
@@ -156,6 +159,7 @@ id:
cn_northwest_1: "Cina (Ningxia)"
eu_central_1: "EU (Frankfurt)"
eu_north_1: "EU (Stockholm)"
+ eu_south_1: "Uni Eropa (Milan)"
eu_west_1: "EU (Ireland)"
eu_west_2: "Eropa (London)"
eu_west_3: "EU (Paris)"
@@ -166,6 +170,7 @@ id:
us_gov_west_1: "AWS GovCloud (US-West)"
us_west_1: "US West (N. California)"
us_west_2: "US West (Oregon)"
+ clear_input: "Hapus masukan"
edit: "Ubah judul dan kategori topik ini"
expand: "Perluas"
not_implemented: "Maaf, fitur ini belum diimplementasikan."
@@ -1770,6 +1775,8 @@ id:
enable: "Aktifkan"
disable: "Nonaktifkan"
add: "Menambahkan"
+ body_tag:
+ text: "Konten"
colors:
undo: "batalkan perintah"
revert: "dibalik"
diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml
index 8a1b8d35e0..0d3b58fabe 100644
--- a/config/locales/client.it.yml
+++ b/config/locales/client.it.yml
@@ -1143,7 +1143,7 @@ it:
tags: "Etichette"
warnings: "Avvertimenti ufficiali"
read_more_in_group: "Vuoi continuare a leggere? Sfoglia altri messaggi in %{groupLink}."
- read_more: "Vuoi saperne di più? Sfoglia altri messaggi nei messaggi personali."
+ read_more: "Vuoi saperne di più? Leggi altri messaggi in messaggi personali."
preferences_nav:
account: "Account"
security: "Sicurezza"
@@ -2022,6 +2022,8 @@ it:
desc: "Rispondi senza cambiare la data dell'ultima risposta"
reload: "Ricarica"
ignore: "Ignora"
+ image_alt_text:
+ aria_label: Testo alternativo per l'immagine
notifications:
tooltip:
regular:
@@ -3573,6 +3575,8 @@ it:
no_activity_others: "Nessuna attività."
no_replies_others: "Nessuna risposta."
no_likes_others: "Nessun messaggio con \"Mi piace\"."
+ fullscreen_table:
+ expand_btn: "Espandi Tabella"
admin_js:
type_to_filter: "digita per filtrare..."
admin:
@@ -4170,11 +4174,11 @@ it:
Si consiglia di assegnare un prefisso ai nomi delle proprietà per evitare conflitti con i plugin e/o il kernel.
head_tag:
- text: ""
- title: "HTML che sarà inserito prima del tag "
+ text: "Intestazione"
+ title: "L'HTML sarà inserito prima dell'etichetta di intestazione"
body_tag:
- text: ""
- title: "HTML che deve essere inserito prima del tag "
+ text: "Corpo"
+ title: "HTML che sarà inserito prima dell'etichetta del corpo"
yaml:
text: "YAML"
title: "Definisci le impostazioni del tema in formato YAML"
@@ -5016,7 +5020,7 @@ it:
grant_existing_holders: Assegna distintivi aggiuntivi ai titolari di distintivi già esistenti
emoji:
title: "Emoji"
- help: "Aggiungi nuovi emoji da mettere a disposizione per tutti. (Suggerimento: trascina e rilascia più file in una volta sola)"
+ help: "Aggiungi nuove emoji che saranno disponibili per tutti. Trascina e rilascia più file contemporaneamente senza inserire un nome per creare emoji utilizzando i nomi dei file."
add: "Aggiungi Nuovo Emoji"
uploading: "In caricamento..."
name: "Nome"
@@ -5027,7 +5031,7 @@ it:
embedding:
get_started: "Se lo desideri, puoi incorporare Discourse in un altro sito web. Comincia aggiungendo il nome dell'host."
confirm_delete: "Sicuro di voler cancellare questo host?"
- sample: "Utilizza il seguente codice HTML nel tuo sito per creare e incorporare gli argomenti di Discourse. Sostituisci REPLACE_ME con l'URL canonico della pagina in cui lo stai incorporando."
+ sample: "Incolla il seguente codice HTML nel tuo sito per creare e incorporare argomenti Discourse. Sostituire REPLACE_ME con l'URL canonico della pagina su cui si sta incorporando."
title: "Integrato"
host: "Host Abilitati"
class_name: "Nome Classe"
diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml
index 258d08453f..8320bc94a5 100644
--- a/config/locales/client.ja.yml
+++ b/config/locales/client.ja.yml
@@ -3947,12 +3947,8 @@ ja:
%{example}
プラグインやコアとの競合を避けるには、プロパティ名に接頭辞を付けることを強くお勧めします。
- head_tag:
- text: ""
- title: " タグの前に挿入される HTML"
body_tag:
- text: ""
- title: " タグの前に挿入される HTML"
+ text: "本文"
yaml:
text: "YAML"
title: "YAML 形式でテーマ設定を定義してください"
@@ -4787,7 +4783,6 @@ ja:
grant_existing_holders: 既存のバッジ保有者に追加のバッジを付与する
emoji:
title: "絵文字"
- help: "みんなが利用できる新しい絵文字を追加します。(ヒント: 複数のファイルを一度にドラッグアンドドロップできます)"
add: "新しい絵文字を追加"
uploading: "アップロード中..."
name: "名前"
@@ -4798,7 +4793,6 @@ ja:
embedding:
get_started: "ほかのウェブサイトに Discourse を埋め込む場合は、まずホストを追加してください。"
confirm_delete: "このホストを削除してもよろしいですか?"
- sample: "次の HTML コードをサイトに使用して、Discourse のトピックを作成して埋め込みます。REPLACE_ME を埋め込み先の正規 URL に置き換えてください。"
title: "埋め込み"
host: "許可されたホスト"
class_name: "クラス名"
diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml
index a96269a59a..f2ead6798d 100644
--- a/config/locales/client.ko.yml
+++ b/config/locales/client.ko.yml
@@ -81,7 +81,7 @@ ko:
x_days:
other: "%{count}일 전"
x_months:
- other: "%{count}달 전"
+ other: "%{count}개월 전"
x_years:
other: "%{count}년 전"
later:
@@ -257,6 +257,7 @@ ko:
bookmarks:
created: "이 글을 북마크했습니다. %{name}"
not_bookmarked: "이 글을 북마크"
+ remove_reminder_keep_bookmark: "알림 제거 및 북마크 유지"
created_with_reminder: "알림 %{date}으로 이 글을 북마크했습니다. %{name}"
remove: "북마크 삭제"
delete: "북마크 삭제"
@@ -1085,6 +1086,7 @@ ko:
failed_to_move: "선택한 메시지를 이동하지 못했습니다. (네트워크가 다운되었을 수 있음)"
tags: "태그"
warnings: "공식 경고"
+ read_more_in_group: "더 읽을거리가 필요하신가요? %{groupLink}에서 다른 메시지를 찾아보십시오."
preferences_nav:
account: "계정"
security: "보안"
@@ -1241,6 +1243,7 @@ ko:
not_connected: "(연결되지 않음)"
confirm_modal_title: "%{provider} 계정 연결"
confirm_description:
+ disconnect: "기존 %{provider} 계정 '%{account_description}' 연결이 해제됩니다."
account_specific: "사용자님의 %{provider} 계정 '%{account_description}'이 인증에 사용됩니다."
generic: "%{provider} 계정이 인증에 사용됩니다."
name:
@@ -1582,6 +1585,7 @@ ko:
enable: "이 글 요약"
disable: "모든 글 표시"
short_label: "요약하기"
+ short_title: "이 글의 요약: 커뮤니티의 인기 게시물"
deleted_filter:
enabled_description: "이 글에는 숨겨진 삭제 된 게시물이 있습니다."
disabled_description: "글에 삭제 된 게시물이 표시됩니다."
@@ -1852,6 +1856,7 @@ ko:
create_whisper: "귓속말"
create_shared_draft: "공유 초안 만들기"
edit_shared_draft: "공유 초안 편집"
+ title: "또는 %{modifier}Enter 키를 누릅니다."
users_placeholder: "사용자 추가"
title_placeholder: "이야기 나누고자 하는 내용을 한문장으로 적는다면?"
title_or_link_placeholder: "여기에 제목을 입력하거나 링크를 붙여 넣으세요."
@@ -2059,6 +2064,7 @@ ko:
categories: "카테고리"
tags: "태그"
in_this_topic: "이 글에서"
+ in_this_topic_tooltip: "모든 글 검색으로 전환"
in_topics_posts: "모든 글과 댓글에서"
enter_hint: "또는 Enter 키를 누르십시오."
in_posts_by: "%{username}님의 글에서"
@@ -2713,6 +2719,7 @@ ko:
just_the_post: "아니오, 글만 삭제합니다."
admin: "관리자 기능"
permanently_delete: "영구적으로 삭제"
+ permanently_delete_confirmation: "이 게시물을 영구적으로 삭제하시겠습니까? 복구할 수 없습니다."
wiki: "위키 만들기"
unwiki: "위키 제거하기"
convert_to_moderator: "스태프 색상 추가하기"
@@ -3427,12 +3434,15 @@ ko:
no_replies_title: "아직 어떤 글에도 댓글을 달지 않았습니다."
no_replies_others: "답글이 없습니다."
no_drafts_title: "초안을 시작하지 않았습니다."
+ no_drafts_body: "아직 글을 작성할 준비가 되지 않았습니까? 글, 댓글 또는 개인 메시지 작성을 시작할 때마다 새 초안을 자동으로 저장하고 여기에 나열합니다. 취소 버튼을 클릭해 삭제하거나 나중에 계속하려면 초안을 저장하세요."
no_likes_title: "아직 어떤 글에도 좋아요를 클릭하지 않으셨습니다."
no_likes_body: "참여와 기여를 시작하는 가장 좋은 방법은 다른 사용자의 글을 읽고 %{heartIcon} 을 클릭하는 것입니다!"
no_likes_others: "좋아요를 받은 게시글이 없습니다"
no_topics_title: "아직 작성한 글이 없습니다."
no_read_topics_title: "아직 읽은 글이 없습니다."
no_group_messages_title: "그룹 메시지가 없습니다."
+ fullscreen_table:
+ expand_btn: "테이블 확장"
admin_js:
type_to_filter: "필터를 입력하세요"
admin:
@@ -4025,11 +4035,11 @@ ko:
플러그인 및/또는 코어와의 충돌을 피하기 위해 속성 이름 앞에 붙이는 것이 좋습니다.
head_tag:
- text: ""
- title: " 태그 전에 들어갈 HTML"
+ text: "헤드"
+ title: "헤드 태그 앞에 삽입될 HTML"
body_tag:
- text: ""
- title: " 태그 전에 들어갈 HTML"
+ text: "바디"
+ title: "본문 태그 앞에 삽입될 HTML"
yaml:
text: "YAML"
title: "YAML 형식으로 테마 설정 정의"
@@ -4874,7 +4884,7 @@ ko:
grant_existing_holders: 기존 배지 소지자에게 추가 배지 부여
emoji:
title: "이모티콘"
- help: "모든 사람이 사용할 수 있는 새 이모티콘을 추가합니다.(PROTIP: 한 번에 여러 파일을 드래그 앤 드롭)"
+ help: "모든 사람이 사용할 수 있는 새 이모티콘을 추가합니다.이름을 입력하지 않고 여러 파일을 한 번에 끌어다 놓아 파일 이름을 사용하여 이모티콘을 만들 수 있습니다."
add: "새 이모티콘 추가"
uploading: "업로드 중..."
name: "이름"
@@ -4885,7 +4895,6 @@ ko:
embedding:
get_started: "다른 웹사이트에 Discourse를 임베드하려면 호스트 추가부터 하세요"
confirm_delete: "정말로 host를 삭제할까요?"
- sample: "Discourse 토픽을 웹사이트에 삽입(embed)하기 위해 다음 HTML코드를 이용하세요. REPLACE_ME 부분을 당신이 삽입하려는 웹사이트의 정식URL로 교체 하면 됩니다."
title: "삽입(Embedding)"
host: "허용 Host"
class_name: "클래스 이름"
diff --git a/config/locales/client.lt.yml b/config/locales/client.lt.yml
index 65f78a6370..4e0745a122 100644
--- a/config/locales/client.lt.yml
+++ b/config/locales/client.lt.yml
@@ -3377,6 +3377,8 @@ lt:
no_topics_title: "Dar nepradėjote jokių temų"
no_read_topics_title: "Dar neskaitėte jokių temų"
no_group_messages_title: "Grupės pranešimų nerasta"
+ fullscreen_table:
+ expand_btn: "Išskleisti lentelę"
admin_js:
type_to_filter: "įrašyk kažką dėl filtro..."
admin:
@@ -3874,12 +3876,8 @@ lt:
text: "Įterptas CSS"
color_definitions:
text: "Spalvų apibrėžimai"
- head_tag:
- text: ""
- title: "HTML, kuris bus įstatytas prieš tag'ą"
body_tag:
- text: ""
- title: "HTML, kuris bus įstatytas prieš tag'ą"
+ text: "Turinys"
yaml:
text: "YAML"
colors:
@@ -4634,7 +4632,6 @@ lt:
grant_existing_holders: Suteikite papildomų ženklelių esamiems ženklelių savininkams
emoji:
title: "Šypsenėlės"
- help: "Pridėti naują šypsenėlę, kurią galės matyti visi (patarimas: kelis failus kelk tuo pačiu metu)"
add: "Pridėti Naują Šypsenėlę"
uploading: "Įkeliama..."
name: "Vardas"
@@ -4644,7 +4641,6 @@ lt:
embedding:
get_started: "Norėčiau pridėti Discourse prie kito puslapio. Pradėk tiesiog pridėdamas valdytoją."
confirm_delete: "Ar tikrai nori ištrinti šį šeimininką?"
- sample: "Use the following HTML code into your site to create and embed discourse topics. Replace REPLACE_ME with the canonical URL of the page you are embedding it on."
title: "Įterpimai"
host: "Šeimininkai"
class_name: "Klasės pavadinimas"
diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml
index 0178bc7749..4bbf3b2bf1 100644
--- a/config/locales/client.lv.yml
+++ b/config/locales/client.lv.yml
@@ -3134,6 +3134,8 @@ lv:
add: "Pievienot"
scss:
text: "CSS"
+ body_tag:
+ text: "Ķermenis"
colors:
undo: "atsaukt"
revert: "atgriezt"
@@ -3658,7 +3660,6 @@ lv:
with_time: %{username} %{time}
emoji:
title: "Smaidiņi"
- help: "Pievienot jaunu smaidiņu, kas būs pieejams visiem."
add: "Pievienot Jaunu Smaidiņu"
uploading: "Notiek ielāde..."
name: "Nosaukums"
@@ -3668,7 +3669,6 @@ lv:
embedding:
get_started: "Ja tu vēlies ieguldīt Discourse citā mājas lapā, sāc ar \"hosta\" pievienošanu.\n\nIf you'd like to embed Discourse on another website, begin by adding its host."
confirm_delete: "Vai tiešām vēlies dzēst šo?"
- sample: "(Use the following HTML code into your site to create and embed discourse topics. Replace REPLACE_ME with the canonical URL of the page you are embedding it on.)"
title: "Ieguldīšana"
host: "Atļautie lietotāji"
class_name: "Klases Nosaukums"
diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml
index e91ddcc8a2..056d6a85eb 100644
--- a/config/locales/client.nb_NO.yml
+++ b/config/locales/client.nb_NO.yml
@@ -3458,6 +3458,8 @@ nb_NO:
no_activity_others: "Ingen aktivitet."
no_replies_others: "Ingen svar."
no_likes_others: "Ingen likte innlegg. "
+ fullscreen_table:
+ expand_btn: "Utvid tabell"
admin_js:
type_to_filter: "skriv for å filtrere…"
admin:
@@ -3946,12 +3948,8 @@ nb_NO:
embedded_scss:
text: "Innebygd CSS"
title: "Skriv inn egendefinert CSS for å levere med innebygde versjoner av kommentarer"
- head_tag:
- text: ""
- title: "HTML som har blitt smettet inn før -taggen"
body_tag:
- text: ""
- title: "HTML som vil bli smettet inn før -taggen"
+ text: "Body"
yaml:
text: "YAML"
title: "Definer draktinnstillinger i YAML-format"
@@ -4640,7 +4638,6 @@ nb_NO:
with_time: %{username} - %{time}
emoji:
title: "Emoji"
- help: "Legg til en ny emoji som vil være tilgjengelig for alle. (PROFFTIPS: Dra og slipp flere filer samtidig)"
add: "Legg til ny Emoji"
uploading: "Laster opp…"
name: "Navn"
@@ -4650,7 +4647,6 @@ nb_NO:
embedding:
get_started: "Hvis du vil bygge inn Discourse på en annen nettside, begynn med å legge til dens vert."
confirm_delete: "Er du sikker på at du vil slette den verten?"
- sample: "Bruk følgende HTML-kode på siden din for å bygge inn Discourse-emner. Erstatt ERSTATT_MEG med riktig nettadresse til siden du bygger den inn i."
title: "Innbygging"
host: "Tillatte verter"
class_name: "Klassenavn"
diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml
index df9c6b95ec..b76d15508b 100644
--- a/config/locales/client.nl.yml
+++ b/config/locales/client.nl.yml
@@ -183,6 +183,7 @@ nl:
cn_northwest_1: "China (Ningxia)"
eu_central_1: "EU (Frankfurt)"
eu_north_1: "EU (Stockholm)"
+ eu_south_1: "EU (Milaan)"
eu_west_1: "EU (Ierland)"
eu_west_2: "EU (Londen)"
eu_west_3: "EU (Parijs)"
@@ -240,6 +241,8 @@ nl:
character_count:
one: "%{count} teken"
other: "%{count} tekens"
+ period_chooser:
+ aria_label: "Filteren op periode"
related_messages:
title: "Gerelateerde berichten"
see_all: 'Alle berichten van @%{username}bekijken...'
@@ -267,6 +270,7 @@ nl:
contact_info: "Neem in het geval van een kritieke kwestie of dringende vraagstukken in verband met deze website contact met ons op via %{contact_info}."
bookmarked:
title: "Bladwijzer maken"
+ edit_bookmark: "Bladwijzer bewerken"
clear_bookmarks: "Bladwijzers wissen"
help:
bookmark: "Klik om een bladwijzer voor het eerste bericht van dit topic te maken"
@@ -300,6 +304,7 @@ nl:
copied: "gekopieerd!"
drafts:
label: "Concepten"
+ label_with_count: "Concepten (%{count})"
resume: "Hervatten"
remove: "Verwijderen"
remove_confirmation: "Weet u zeker dat u dit concept wilt verwijderen?"
@@ -362,6 +367,7 @@ nl:
placeholder: "typ hier de titel, URL of ID van het bericht"
review:
order_by: "Sorteren op"
+ date_filter: "Geplaatst tussen"
in_reply_to: "in reactie op"
explain:
why: "leg uit waarom dit item in de wachtrij is beland"
@@ -623,6 +629,7 @@ nl:
title: "Beheren"
name: "Naam"
full_name: "Volledige naam"
+ add_members: "Gebruikers toevoegen"
invite_members: "Uitnodigen"
delete_member_confirm: "'%{username}' uit de groep '%{group}' verwijderen?"
profile:
@@ -634,7 +641,16 @@ nl:
email:
title: "E-mailadres"
status: "%{old_emails} / %{total_emails} e-mails via IMAP gesynchroniseerd."
+ test_settings: "Test Instellingen"
+ save_settings: "Instellingen opslaan"
+ last_updated: "Laatst bijgewerkt:"
last_updated_by: "door"
+ smtp_settings_valid: "SMTP instellingen geldig."
+ smtp_title: "SMTP"
+ imap_title: "IMAP"
+ imap_additional_settings: "Extra instellingen"
+ prefill:
+ gmail: "GMail"
credentials:
title: "Referenties"
smtp_server: "SMTP-server"
@@ -977,6 +993,7 @@ nl:
description: "Onboarding-tips en badges voor nieuwe gebruikers overslaan"
not_first_time: "Niet uw eerste keer?"
skip_link: "Deze tips overslaan"
+ read_later: "Ik zal het later lezen."
theme_default_on_all_devices: "Dit het standaardthema maken op al mijn apparaten"
color_scheme_default_on_all_devices: "Standaard kleurenschema(’s) op al mijn apparaten instellen"
color_scheme: "Kleurenschema"
@@ -1073,6 +1090,7 @@ nl:
rejected_posts: "geweigerde berichten"
messages:
inbox: "Postvak IN"
+ personal: "Persoonlijk"
latest: "Nieuwste"
sent: "Verzonden"
unread: "Ongelezen"
@@ -1089,6 +1107,7 @@ nl:
move_to_archive: "Archiveren"
failed_to_move: "Het verplaatsen van geselecteerde berichten is niet gelukt (misschien is uw netwerkverbinding verbroken)"
tags: "Tags"
+ warnings: "Officiële waarschuwingen"
preferences_nav:
account: "Account"
security: "Beveiliging"
@@ -1696,21 +1715,27 @@ nl:
google_oauth2:
name: "Google"
title: "met Google"
+ sr_title: "Inloggen met Google"
twitter:
name: "Twitter"
title: "met Twitter"
+ sr_title: "Inloggen met Twitter"
instagram:
name: "Instagram"
title: "met Instagram"
+ sr_title: "Inloggen met Instagram"
facebook:
name: "Facebook"
title: "met Facebook"
+ sr_title: "Inloggen met Facebook"
github:
name: "GitHub"
title: "met GitHub"
+ sr_title: "Inloggen met GitHub"
discord:
name: "Discord"
title: "met Discord"
+ sr_title: "Inloggen met Discord"
second_factor_toggle:
totp: "Een authenticator-app gebruiken"
backup_code: "Een back-upcode gebruiken"
@@ -1765,6 +1790,9 @@ nl:
select_to_filter: "Een waarde selecteren om te filteren"
default_header_text: Selecteren...
no_content: Geen overeenkomsten gevonden
+ results_count:
+ one: "%{count} resultaat"
+ other: "%{count} resultaten"
filter_placeholder: Zoeken...
filter_placeholder_with_any: Zoeken of aanmaken...
create: "Aanmaken: '%{content}'"
@@ -2055,6 +2083,7 @@ nl:
select_all: "Alles selecteren"
clear_all: "Alles wissen"
too_short: "Uw zoekterm is te kort."
+ clear_search: "Zoekopdracht wissen"
result_count:
one: "%{count} resultaat voor%{term}"
other: "%{count}%{plus} resultaten voor%{term}"
@@ -2073,15 +2102,23 @@ nl:
search_button: "Zoeken"
categories: "Categorieën"
tags: "Tags"
+ in: "in"
+ in_this_topic: "in dit onderwerp"
+ enter_hint: "of druk op Enter"
+ in_posts_by: "In berichten van @%{username}"
type:
+ default: "Onderwerpen/berichten"
users: "Gebruikers"
categories: "Categorieën"
+ categories_and_tags: "Categorieën/tags"
context:
user: "Berichten van @%{username} doorzoeken"
category: "De categorie #%{category} doorzoeken"
tag: "De tag #%{tag} doorzoeken"
topic: "Dit topic doorzoeken"
private_messages: "Berichten doorzoeken"
+ tips:
+ category_tag: "filtert op categorie of tag"
advanced:
posted_by:
label: Geplaatst door
@@ -2133,8 +2170,13 @@ nl:
label: Weergaven
min_views:
placeholder: minimaal
+ aria_label: filteren op minimale weergaven
max_views:
placeholder: maximaal
+ aria_label: filteren op maximale weergaven
+ additional_options:
+ label: "Filteren op aantal berichten en onderwerpweergaven"
+ hamburger_menu: "menu"
new_item: "nieuw"
go_back: "terug"
not_logged_in_user: "gebruikerspagina met samenvatting van huidige activiteit en voorkeuren"
@@ -2151,6 +2193,9 @@ nl:
delete: "Topics verwijderen"
dismiss: "Negeren"
dismiss_read: "Alle ongelezen negeren"
+ dismiss_read_with_selected:
+ one: "%{count} ongelezen onderwerp negeren"
+ other: "%{count} ongelezen onderwerpen negeren"
dismiss_button: "Negeren..."
dismiss_tooltip: "Alleen nieuwe berichten negeren of het volgen van topics stoppen"
also_dismiss_topics: "Het volgen van deze topics stoppen, zodat ze nooit meer als ongelezen verschijnen."
@@ -2800,6 +2845,7 @@ nl:
bookmarks:
create: "Bladwijzer maken"
edit: "Bladwijzer bewerken"
+ edit_for_topic: "Bewerk bladwijzer voor onderwerp"
created: "Gemaakt"
updated: "Bijgewerkt"
name: "Naam"
@@ -4019,12 +4065,8 @@ nl:
%{example}
Het toevoegen van voorvoegsels aan de eigenschapsnamen wordt sterk aanbevolen om conflicten met plug-ins en/of core te voorkomen.
- head_tag:
- text: ""
- title: "HTML die voor de -tag wordt ingevoegd"
body_tag:
- text: ""
- title: "HTML die voor de -tag wordt ingevoegd"
+ text: "Inhoud"
yaml:
text: "YAML"
title: "Thema-instellingen definiëren in YAML-indeling"
@@ -4840,9 +4882,9 @@ nl:
upload_csv: Een CSV met e-mailadressen of gebruikersnamen uploaden
aborted: Upload een CSV dat e-mailadressen of gebruikersnamen bevat
replace_owners: De badge van vorige eigenaren verwijderen
+ grant_existing_holders: Extra badges toekennen aan bestaande badgehouders
emoji:
title: "Emoji"
- help: "Nieuwe emoji toevoegen die voor iedereen beschikbaar zal zijn. (PROTIP: versleep meerdere bestanden tegelijk)"
add: "Nieuwe emoji toevoegen"
uploading: "Uploaden..."
name: "Naam"
@@ -4853,7 +4895,6 @@ nl:
embedding:
get_started: "Als u Discourse in een andere website wilt inbedden, begin dan door de host ervan toe te voegen."
confirm_delete: "Weet u zeker dat u die host wilt verwijderen?"
- sample: "Gebruik de volgende HTML-code in uw website om Discourse-topics te maken en in te bedden. Vervang REPLACE_ME door de canonieke URL van de pagina waarin u het topic wilt inbedden."
title: "Inbedding"
host: "Toegestane hosts"
class_name: "Klassenaam"
diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml
index aaf8bd3459..b49352f15a 100644
--- a/config/locales/client.pl_PL.yml
+++ b/config/locales/client.pl_PL.yml
@@ -1247,7 +1247,7 @@ pl_PL:
tags: "Tagi"
warnings: "Oficjalne ostrzeżenia"
read_more_in_group: "Chcesz przeczytać więcej? Przeglądaj inne wiadomości w %{groupLink}."
- read_more: "Chcesz przeczytać więcej? Przeglądaj inne wiadomości w wiadomościach prywatnych."
+ read_more: "Chcesz przeczytać więcej? Przeglądaj inne wiadomości w wiadomościach osobistych."
preferences_nav:
account: "Konto"
security: "Bezpieczeństwo"
@@ -4538,12 +4538,8 @@ pl_PL:
%{example}
Zdecydowanie zalecamy dodawanie przedrostków do nazw zmiennych, aby uniknąć konfliktów z wtyczkami i/lub silnikiem forum.
- head_tag:
- text: ""
- title: "HTML, który zostanie umieszczony przed tagiem "
body_tag:
- text: ""
- title: "HTML, który zostanie umieszczony przed tagiem "
+ text: "Body"
yaml:
text: "YAML"
title: "Zdefiniuj ustawienia motywu w formacie YAML"
@@ -5407,7 +5403,6 @@ pl_PL:
grant_existing_holders: Przyznaj dodatkowe odznaki obecnym posiadaczom odznak
emoji:
title: "Emoji"
- help: "Dodaj nowe emoji. (PROTIP: przeciągnij i upuść kilka plików na raz)"
add: "Dodaj nowe Emoji"
uploading: "Przesyłanie…"
name: "Nazwa"
@@ -5418,7 +5413,6 @@ pl_PL:
embedding:
get_started: "Jeśli chcesz osadzić Discourse na innej stronie, rozpocznij podając jej host."
confirm_delete: "Czy na pewno chcesz usunąć ten host?"
- sample: "Użyj poniższego kodu HTML na swojej stronie, aby osadzić tematy z Discourse. Zastąp REPLACE_ME domyślnym adresem URL strony na której osadzasz."
title: "Osadzanie"
host: "Dozwolone hosty"
class_name: "Nazwa klasy"
diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml
index a11723ef14..65e5686daf 100644
--- a/config/locales/client.pt.yml
+++ b/config/locales/client.pt.yml
@@ -3882,12 +3882,8 @@ pt:
%{example}
Prefixar os nomes das propriedades é altamente recomendado para evitar conflitos com plugins e/ou o core.
- head_tag:
- text: ""
- title: "HTML que será introduzido antes da tag "
body_tag:
- text: ""
- title: "HTML que será introduzido antes da tag "
+ text: "Corpo"
yaml:
text: "YAML"
title: "Defina as configurações do tema no formato YAML"
@@ -4516,7 +4512,6 @@ pt:
badge_query_examples_title: "Exemplos de queries de crachás"
emoji:
title: "Emoji"
- help: "Adicionar novo emoji que irá estar disponível para todos. (PROTIP: arraste múltiplos ficheiros de uma só vez)"
add: "Adicionar Novo Emoji"
uploading: "A carregar…"
name: "Nome"
@@ -4526,7 +4521,6 @@ pt:
embedding:
get_started: "Se deseja incorporar o Discourse noutro sítio, comece por adicionar o seu servidor."
confirm_delete: "Tem certeza que deseja eliminar este servidor?"
- sample: "Utilize o seguinte código HTML no seu sítio para criar e incorporar tópicos do discourse. Substitua REPLACE_ME pelo URL canónico da página onde está a incorporá-los."
title: "Incorporação"
host: "Servidores Permitidos"
edit: "editar"
diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml
index bb39d4eb20..ad71fc0079 100644
--- a/config/locales/client.pt_BR.yml
+++ b/config/locales/client.pt_BR.yml
@@ -272,6 +272,7 @@ pt_BR:
help:
bookmark: "Clique para adicionar a primeira postagem deste tópico aos favoritos"
edit_bookmark: "Clique para editar o favorito neste tópico"
+ edit_bookmark_for_topic: "Clique para editar o favorito para este tópico"
unbookmark: "Clique para remover todos os favoritos neste tópico"
unbookmark_with_reminder: "Clique para remover todos os favoritos e lembretes neste tópico."
bookmarks:
@@ -2122,16 +2123,25 @@ pt_BR:
search_button: "Pesquisar"
categories: "Categorias"
tags: "Etiquetas"
+ in: "em"
+ in_this_topic: "neste tópico"
+ enter_hint: "ou pressione Enter"
+ in_posts_by: "em postagens por %{username}"
type:
+ default: "Tópicos/postagens"
users: "Usuários"
categories: "Categorias"
+ categories_and_tags: "Categorias/etiquetas"
context:
user: "Pesquisar postagens de @%{username}"
category: "Pesquisar a categoria #%{category}"
tag: "Pesquisar a etiqueta #%{tag}"
topic: "Pesquisar este tópico"
private_messages: "Pesquisar mensagens"
+ tips:
+ full_search_key: "%{modifier} + Enter"
advanced:
+ title: Filtros avançados
posted_by:
label: Postado por
in_category:
@@ -2691,7 +2701,9 @@ pt_BR:
deleted_by_author_simple: "(tópico excluído pelo(a) autor(a))"
post:
quote_reply: "Citação"
+ quote_reply_shortcut: "Ou pressione q"
quote_edit: "Editar"
+ quote_edit_shortcut: "Ou pressione e"
quote_share: "Compartilhar"
edit_reason: "Motivo:"
post_number: "postagem %{number}"
@@ -2870,7 +2882,9 @@ pt_BR:
button: "HTML"
bookmarks:
create: "Criar favorito"
+ create_for_topic: "Criar favorito para o tópico"
edit: "Editar favorito"
+ edit_for_topic: "Editar favorito para o tópico"
created: "Criado"
updated: "Atualizado"
name: "Nome"
@@ -4115,12 +4129,8 @@ pt_BR:
%{example}
É altamente recomendável adicionar os nomes das propriedades ao prefixo para evitar conflitos com plug-ins e/ou núcleo.
- head_tag:
- text: ""
- title: "O HTML que será inserido antes da etiqueta"
body_tag:
- text: ""
- title: "O HTML que será inserido antes da etiqueta"
+ text: "Corpo"
yaml:
text: "YAML"
title: "Definir configurações de tema no formato YAML"
@@ -4961,7 +4971,6 @@ pt_BR:
grant_existing_holders: Conceder emblemas adicionais a titulares de emblema existentes
emoji:
title: "Emoji"
- help: "Adicionar novo emoji que estará disponível para todos(as). (DICA: arraste e solte diversos arquivos de uma vez)"
add: "Adicionar novo emoji"
uploading: "Enviando..."
name: "Nome"
@@ -4972,7 +4981,6 @@ pt_BR:
embedding:
get_started: "Se você deseja incorporar Discourse em outro site, começe adicionando seu host."
confirm_delete: "Você tem certeza que deseja excluir este host?"
- sample: "Use o seguinte código HTML no seu site para criar e incorporar tópicos do Discourse. Substitua REPLACE_ME pela URL canônica da página na qual você está incorporando."
title: "Incorporar"
host: "Hosts permitidos"
class_name: "Nome da classe"
diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml
index fc0b99da62..124d063742 100644
--- a/config/locales/client.ro.yml
+++ b/config/locales/client.ro.yml
@@ -3109,12 +3109,8 @@ ro:
text: "Subsol"
embedded_scss:
text: "CSS încorporat"
- head_tag:
- text: "1"
- title: "HTML care va fi inserat înainte de 1 tag"
body_tag:
- text: "1"
- title: "HTML care va fi inserat înaintea de tag-ul "
+ text: "Corp"
colors:
title: "Culori"
copy_name_prefix: "Copie a"
@@ -3704,7 +3700,6 @@ ro:
with_time: %{username} la %{time}
emoji:
title: "Emoji"
- help: "Adaugă un nou \"emoji\" care va fi disponibil pentru toţi. (PROTIP: trage şi adaugă mai multe fişiere odată)"
add: "Adaugă un emoji nou"
uploading: "Se încarcă..."
name: "Nume"
@@ -3714,7 +3709,6 @@ ro:
embedding:
get_started: "Dacă dorești să încorporezi Discourse pe un alt website, începe prin a-i adăuga gazda."
confirm_delete: "Ești sigur că vrei să ștergi acest host?"
- sample: "Folosește următorul cod HTML în site-ul tău pentru a crea și pentru a încorpora subiecte Discourse. Înlocuiește ÎNLOCUIEȘTE_MĂ cu URL-ul canonic al paginii pe care dorești să o încorporezi."
title: "Embedding"
host: "Host-uri permise"
class_name: "Numele clasei"
diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml
index 35ce5bddf9..6cfe2bd42e 100644
--- a/config/locales/client.ru.yml
+++ b/config/locales/client.ru.yml
@@ -2194,6 +2194,8 @@ ru:
desc: "Ответить без изменения даты последнего ответа"
reload: "Обновить"
ignore: "Игнорировать"
+ image_alt_text:
+ aria_label: Альт-текст для изображения
notifications:
tooltip:
regular:
@@ -2353,7 +2355,7 @@ ru:
topic: "Искать в этой теме"
private_messages: "Искать в личных сообщениях"
tips:
- category_tag: "фильтры по категориям или тегам"
+ category_tag: "фильтры по разделам или тегам"
author: "фильтры по автору сообщения"
in: "фильтры по метаданным (например, in:title, in:personal, in:pinned)"
status: "фильтры по статусу темы"
@@ -3934,6 +3936,8 @@ ru:
no_read_topics_title: "Вы пока ещё не прочли ни одной темы"
no_read_topics_body: "Когда вы начнете читать обсуждения, список читаемых тем появится здесь. Чтобы начать читать, поищите интересующие вас темы в разделеОбсуждаемые, в других разделах форума, либо выполните поиск по ключевому слову %{searchIcon}"
no_group_messages_title: "Групповые сообщения не найдены"
+ fullscreen_table:
+ expand_btn: "Развернуть таблицу"
admin_js:
type_to_filter: "Фильтрация настроек..."
admin:
@@ -4537,11 +4541,11 @@ ru:
title: "Укажите собственные цвета (только для опытных пользователей)"
placeholder: "\nИспользуйте эту таблицу стилей для добавления цветов в список пользовательских свойств CSS.\n\nПример: \n\n%{example}\n\nНастоятельно рекомендуется добавлять префиксы к именам свойств, чтобы избежать конфликтов с плагинами и / или движком форума."
head_tag:
- text: ""
- title: "HTML для размещения перед тегом "
+ text: "Head"
+ title: "HTML, который будет вставлен перед тегом 'head'"
body_tag:
- text: ""
- title: "HTML для размещения перед тегом "
+ text: "Body"
+ title: "HTML, который будет вставлен перед тегом 'body'"
yaml:
text: "YAML"
title: "Определить настройки темы в формате YAML"
@@ -5405,7 +5409,7 @@ ru:
grant_existing_holders: Предоставление награждённым дополнительных наград
emoji:
title: "Эмодзи"
- help: "Добавить новые эмодзи, которые будут доступны всем. (Подсказка: можно перетаскивать несколько файлов за раз)"
+ help: "Добавьте новые эмодзи, которые будут доступны всем. Перетащите сразу несколько файлов без указания имени, чтобы создать эмодзи, используя их имена файлов."
add: "Добавить новую иконку"
uploading: "Загрузка..."
name: "Название"
@@ -5416,7 +5420,7 @@ ru:
embedding:
get_started: "Для встраивания на другой сайт необходимо добавить соответствующий хост."
confirm_delete: "Вы действительно хотите удалить это поле?"
- sample: "Используйте следующий HTML-код на своём сайте, для возможности создания связанных тем. Замените REPLACE_ME канонической ссылкой страницы, куда производится встраивание."
+ sample: "Используйте следующий HTML-код на своём сайте для создания связанных тем. Замените REPLACE_ME канонической ссылкой страницы, в которую производится встраивание."
title: "Встраивание"
host: "Разрешённые хосты"
class_name: "Имя класса"
diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml
index 022f3ec224..47f5da78be 100644
--- a/config/locales/client.sk.yml
+++ b/config/locales/client.sk.yml
@@ -2564,12 +2564,8 @@ sk:
text: "Päta"
embedded_scss:
text: "Vnorené CSS"
- head_tag:
- text: ""
- title: "HTML, ktoré bude vložené pred tag"
body_tag:
- text: ""
- title: "HTML, ktoré bude vložené pred tag"
+ text: "Telo"
colors:
title: "Farby"
copy_name_prefix: "Kópia"
@@ -3094,7 +3090,6 @@ sk:
with_time: %{username} v čase %{time}
emoji:
title: "Emoji"
- help: "Pridaj nové emoji, ktoré bude dostupné pre všetkých (TIP: môžete pretiahnuť viac súborov naraz)"
add: "Pridaj nové Emoji"
uploading: "Upload prebieha..."
name: "Meno"
@@ -3104,7 +3099,6 @@ sk:
embedding:
get_started: "Pokiaľ chcete vložiť Discourse na inú stránku, začnite pridaním jej hostiteľa."
confirm_delete: "Ste si istý, že chcete zmazať tohoto hostiteľa?"
- sample: "Použite nasledovný HTML kód vo Vašej stránke pre vytvorenie vloženej témy Discourse. Nahraďte REPLACE_ME kanonickou URL adresou stránky, do ktorej to vkladáte."
title: "Vkladám"
host: "Povolení hostitelia"
edit: "uprav"
diff --git a/config/locales/client.sl.yml b/config/locales/client.sl.yml
index 9ab205b141..079982a91f 100644
--- a/config/locales/client.sl.yml
+++ b/config/locales/client.sl.yml
@@ -3721,12 +3721,6 @@ sl:
text: "CSS"
header:
text: "Glava"
- head_tag:
- text: ""
- title: "HTML, ki bo vstavljen pred oznako"
- body_tag:
- text: ""
- title: "HTML, ki bo vstavljen pred oznako"
colors:
title: "Barve"
copy_name_prefix: "Kopija od"
@@ -4275,7 +4269,6 @@ sl:
badge_query_examples_title: "Primeri poizvedb SQL za značke"
emoji:
title: "Emoji"
- help: "Dodaj nov emoji, ki bo na voljo vsem. (lahko prestavite in spustite več datotek naenkrat)"
add: "Dodaj nov emoji"
uploading: "Nalagam..."
name: "Ime"
diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml
index e0f92304d5..dbcc6c97e2 100644
--- a/config/locales/client.sq.yml
+++ b/config/locales/client.sq.yml
@@ -2180,12 +2180,8 @@ sq:
text: "Fundi i faqes"
embedded_scss:
text: "CSS e ngjitur"
- head_tag:
- text: ""
- title: "HTML që do të vendoset para mbylles së tagut
" body_tag: - text: "" - title: "HTML që do të vendoset para mbylljes së tagut
"
- title: "HTML koji će biti umetnut pre %{username} u %{time}
emoji:
title: "Emoji"
- help: "Dodajte nove Emoji-e koji će biti dostupni svima. (Savet: povuci i ispusti više datoteka odjednom)"
add: "Dodaj Novi Emoji"
uploading: "Učitavanje..."
name: "Ime"
diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml
index e719e612b9..575e04ac58 100644
--- a/config/locales/client.sv.yml
+++ b/config/locales/client.sv.yml
@@ -1142,9 +1142,9 @@ sv:
tags: "Taggar"
warnings: "Officiella varningar"
read_more_in_group: "Vill du läsa mer? Bläddra bland andra meddelanden i %{groupLink}."
- read_more: "Vill du läsa mer? Bläddra genom andra meddelanden i personliga meddelanden."
+ read_more: "Vill du läsa mer? Bläddra bland andra meddelanden i personliga meddelanden."
read_more_group_pm_MF: "Det { UNREAD, plural, =0 {} one { finns # oläst } other { finns # olästa } } { NEW, plural, =0 {} one { {BOTH, select, true{och } false {finns } other{}} # nytt meddelande} other { {BOTH, select, true{och } false {finns } other{}} # nya meddelanden} } kvar, eller bläddra bland andra meddelanden i {groupLink}"
- read_more_personal_pm_MF: "Där { UNREAD, plural, =0 {} one { är # oläst } other { är # olästa } } { NEW, plural, =0 {} one { {BOTH, select, true{och } false {är } other{}} # nytt meddelande} other { {BOTH, select, true{och } false {är } other{}} # nya meddelanden} } kvar, eller bläddra genom andra personliga meddelanden"
+ read_more_personal_pm_MF: "Det { UNREAD, plural, =0 {} one { finns # oläst } other { finns # olästa } } { NEW, plural, =0 {} one { {BOTH, select, true{och } false {finns } other{}} # nytt meddelande} other { {BOTH, select, true{och } false {finns } other{}} # nya meddelanden} } kvar, eller bläddra bland andra personliga meddelanden"
preferences_nav:
account: "Konto"
security: "Säkerhet"
@@ -2034,6 +2034,8 @@ sv:
desc: "Svara utan att ändra senaste svarsdatum"
reload: "Ladda om"
ignore: "Ignorera"
+ image_alt_text:
+ aria_label: Alternativ text för bild
notifications:
tooltip:
regular:
@@ -3622,6 +3624,8 @@ sv:
no_read_topics_title: "Du har inte läst några ämnen än"
no_read_topics_body: "När du börjar läsa diskussioner ser du en lista här. Börja läsa genom att leta efter ämnen som du tycker är intressanta i Topp eller Kategorier eller sök efter sökord %{searchIcon}"
no_group_messages_title: "Inga gruppmeddelanden hittades"
+ fullscreen_table:
+ expand_btn: "Expandera tabell"
admin_js:
type_to_filter: "skriv för att filtrera..."
admin:
@@ -4221,11 +4225,11 @@ sv:
Användning av prefix för egenskapsnamnen rekommenderas starkt för att undvika konflikter mellan tillägg och/eller grundläggande program.
head_tag:
- text: ""
- title: "HTML som kommer att sättas in före taggen"
+ text: "Huvud"
+ title: "HTML som kommer att infogas före huvudtaggen"
body_tag:
- text: ""
- title: "HTML som kommer att infogas in före taggen "
+ text: "Huvuddel"
+ title: "HTML som kommer att infogas innan kroppstaggen"
yaml:
text: "YAML"
title: "Definiera temainställningar i YAML-format"
@@ -5077,7 +5081,7 @@ sv:
grant_existing_holders: Utfärda ytterligare utmärkelser till befintliga utmärkelseinnehavare
emoji:
title: "Emoji"
- help: "Lägg till en ny emoji för andra att använda. (TIPS: dra och släpp flera filer på en och samma gång)"
+ help: "Lägg till nya emoji som kommer att vara tillgängliga för alla. Dra och släpp flera filer samtidigt utan att ange ett namn för att skapa emojis med deras filnamn."
add: "Lägg till ny emoji"
uploading: "Laddar upp..."
name: "Namn"
@@ -5088,7 +5092,7 @@ sv:
embedding:
get_started: "Börja genom att lägga till värden, om du vill bädda in Discourse på en annan hemsida. "
confirm_delete: "Är du säker på att du vill ta bort värden?"
- sample: "Använd följande HTML-kod på din webbplats för att skapa och bädda in discourse-ämnen. Byt ut REPLACE_ME mot den kanoniska URL-en för sidan som du bäddar in den i."
+ sample: "Klistra in följande HTML-kod på din webbplats för att skapa och bädda in discourse-ämnen. Byt ut REPLACE_ME mot den vägledande URL-en för sidan som du bäddar in den i."
title: "Inbäddning"
host: "Tillåtna värdar"
class_name: "Klassnamn"
diff --git a/config/locales/client.sw.yml b/config/locales/client.sw.yml
index 9928975ad6..511182703f 100644
--- a/config/locales/client.sw.yml
+++ b/config/locales/client.sw.yml
@@ -2632,12 +2632,8 @@ sw:
title: "Ruhusu HTLM kuonekana kwenye ukurasa wa kijachini"
embedded_scss:
text: "CSS Iliyoambatanishwa"
- head_tag:
- text: ""
- title: "HTML itakayowekwa kabla ya lebo "
body_tag:
- text: ""
- title: "HTML itakayowekwa kabla ya lebo "
+ text: "Mwili"
yaml:
text: "YAML"
title: "Tambulisha mipangilio ya mpango kwenye umbiza wa YAML"
@@ -3242,7 +3238,6 @@ sw:
with_time: %{username} kwenye %{time}
emoji:
title: "Emoji"
- help: "Ongeza emoji mpya ambayo itapatikana kwa kila mtu. (USHAURI: unaweza kuweka zaidi ya faili moja mara moja)"
add: "Ongeza Emoji Mpya"
uploading: "Inaongezwa"
name: "Jina"
diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml
index d6a40530c9..1280899a99 100644
--- a/config/locales/client.te.yml
+++ b/config/locales/client.te.yml
@@ -1561,12 +1561,8 @@ te:
text: "హెడర్"
footer:
text: "ఫుటరు"
- head_tag:
- text: ""
- title: " కొస ముందు ఉంచే హెచ్ టీ యం యల్"
body_tag:
- text: ""
- title: " కొస ముందు ఉంచే హెచ్ టీ యం యల్"
+ text: "శరీరం"
colors:
title: "రంగులు"
copy_name_prefix: "దీనికి నకలు"
diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml
index b5c04a6749..132e058ab1 100644
--- a/config/locales/client.th.yml
+++ b/config/locales/client.th.yml
@@ -2665,10 +2665,6 @@ th:
text: "Footer"
embedded_scss:
text: "Embedded CSS"
- head_tag:
- text: ""
- body_tag:
- text: ""
colors:
title: "สี"
undo: "เลิกทำ"
diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml
index eab53f25aa..e24427ba29 100644
--- a/config/locales/client.tr_TR.yml
+++ b/config/locales/client.tr_TR.yml
@@ -1140,6 +1140,8 @@ tr_TR:
tags: "Etiketler"
warnings: "Resmi Uyarılar"
read_more_in_group: "Daha fazla okumak ister misin? %{groupLink} bağlantısındaki diğer iletilere göz atın."
+ read_more: "Daha fazla okumak isterseniz kişisel mesajlar altında diğer mesajlara göz atın."
+ read_more_personal_pm_MF: "{ UNREAD, plural, =0 {} one {# adet okunmamış} other {# adet okunmamış }} mesaj { NEW, plural, =0 {} one { {BOTH, select, true{ve } false {var } other{}} # adet yeni mesaj} other { {BOTH, select, true{ve } false {var } other{}} # adet yeni mesaj} } var, ya dakişisel mesajlara göz atın"
preferences_nav:
account: "Hesap"
security: "Güvenlik"
@@ -2124,7 +2126,7 @@ tr_TR:
sort_by: "Sırala"
relevance: "Uygunluk"
latest_post: "Son Gönderi"
- latest_topic: "En son konu"
+ latest_topic: "En Son Konu"
most_viewed: "En Çok Görüntülenen"
most_liked: "En Çok Beğenilen"
select_all: "Tümünü Seç"
@@ -2156,6 +2158,7 @@ tr_TR:
in_this_topic: "bu konuda"
in_this_topic_tooltip: "tüm konuları aramaya geç"
in_topics_posts: "tüm konularda ve gönderilerde"
+ enter_hint: "veya Enter tuşuna basın"
in_posts_by: "@%{username} kullancısına ait gönderilerde"
type:
default: "Konular/yazılar"
@@ -2370,7 +2373,7 @@ tr_TR:
read_more_in_category: "Daha fazlası için %{catLink} kategorisine göz atabilir ya da %{latestLink}yebilirsin."
read_more: "Daha fazla okumak mı istiyorsun? %{catLink} ya da %{latestLink}."
unread_indicator: "Bu konunun son mesajını henüz hiç üye okumamış."
- read_more_MF: "{ UNREAD, plural, =0 {0 okunmamış mesaj} one {1 okunmamış mesaj } other { # okunmamış mesaj } }ve{ NEW, plural, =0 {0 yeni mesaj} one { {BOTH, select, true{} false {} other{}}1 yeni mesaj} other { {BOTH, select, true{} false {} other{}} # yeni mesaj}} var. {CATEGORY, select, true {{catLink} kategorisine göz atabilirsin.} false {{latestLink}} other {}}"
+ read_more_MF: "{ UNREAD, plural, =0 {0 okunmamış mesaj} one { 1 okunmamış mesaj } other { # okunmamış mesaj } } { NEW, plural, =0 {0 yeni mesaj} one { {BOTH, select, true{ve } false {} other{}} 1 yeni mesaj} other { {BOTH, select, true{ve } false {} other{}} # yeni mesaj}} var. Ya da {CATEGORY, select, true {{catLink} kategorisine göz atabilirsin} false {{latestLink}} other {}}."
bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}"
browse_all_categories: Bütün kategorilere göz at
browse_all_tags: Tüm etiketlere göz atın
@@ -3608,6 +3611,8 @@ tr_TR:
no_topics_title: "Henüz herhangi bir konu başlatmadın"
no_read_topics_title: "Henüz herhangi bir konu okumadın"
no_group_messages_title: "Grup mesajı bulunamadı"
+ fullscreen_table:
+ expand_btn: "Tabloyu Genişlet"
admin_js:
type_to_filter: "filtrelemek için yaz..."
admin:
@@ -4206,11 +4211,11 @@ tr_TR:
Eklentiler ve/veya ana kod ile çakışmaları önlemek için özellik adlarının önüne önek eklenmesi şiddetle tavsiye edilir.
head_tag:
- text: ""
- title: " etiketinden önce eklenecek HTML"
+ text: "Head"
+ title: "Head etiketinden önce eklenecek HTML"
body_tag:
- text: ""
- title: " etiketinden önce eklenecek HTML"
+ text: "İçerik"
+ title: "Body etiketinden önce eklenecek HTML"
yaml:
text: "YAML"
title: "Tema ayarlarını YAML formatında tanımla"
@@ -5062,7 +5067,6 @@ tr_TR:
grant_existing_holders: Mevcut rozet sahiplerine ek rozetler ver
emoji:
title: "Emoji"
- help: "Herkese açık yeni bir emoji ekle. (PROTIP: birden çok dosyayı tek seferde sürükleyip bırakabilirsin)"
add: "Yeni Emoji Ekle"
uploading: "Yükleniyor..."
name: "İsim"
@@ -5073,7 +5077,6 @@ tr_TR:
embedding:
get_started: "Eğer Discourse'u başka bir web sitesine yerleştirmek istiyorsan bu sitenin sunucusunu ekleyerek başla."
confirm_delete: "Bu sunucuyu silmek istediğine emin misin?"
- sample: "Discourse konuları oluşturmak ve yerleştirmek için aşağıdaki HTML kodunu sitende kullan. REPLACE_ME'yi Discourse'u yerleştirdiğin sayfanın tam URL'i ile değiştir."
title: "Yerleştirme"
host: "İzin Verilen Sunucular"
class_name: "Sınıf adı"
diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml
index e44c842294..bc9a9642a5 100644
--- a/config/locales/client.uk.yml
+++ b/config/locales/client.uk.yml
@@ -335,6 +335,7 @@ uk:
bookmarks:
created: "Ви додали повідомлення в закладки. %{name}"
not_bookmarked: "додати цей допис до закладок"
+ remove_reminder_keep_bookmark: "Вилучити нагадування, але зберегти закладку"
created_with_reminder: "Ви додали цей допис в закладку із нагадуванням %{date}. %{name}"
remove: "Вилучити закладку"
delete: "Видалити закладку"
@@ -751,6 +752,7 @@ uk:
settings_required: "Усі налаштування є обов’язковими. Будь ласка, заповніть усі поля перед підтвердженням."
smtp_settings_valid: "Настройки SMTP дійсні."
smtp_title: "SMTP"
+ smtp_instructions: "Коли ви ввімкнете SMTP для групи, всі вихідні повідомлення електронної пошти, надіслані з групи, буде надіслано через вказані тут параметри SMTP, замість стандартного поштового сервера, налаштованого для інших листів."
imap_title: "IMAP"
imap_additional_settings: "Додаткові налаштування"
imap_alpha_warning: "Попередження. Це функція на альфа-тестуванні. Офіційно підтримується лише Gmail. Використовуйте на свій страх і ризик!"
@@ -1241,7 +1243,6 @@ uk:
warnings: "Офіційні попередження"
read_more_in_group: "Бажаєте дізнатися більше? Перегляньте інші повідомлення в %{groupLink}."
read_more_group_pm_MF: "У вас { UNREAD, plural, =0 {} one { # непрочитане } few { # непрочитаних } other { # непрочитаних } } { NEW, plural, =0 {} one { {BOTH, select, true{и } false { } other{}} # нове повідомлення} few { {BOTH, select, true{і } false { } other{}} # нових повідомлень} other { {BOTH, select, true{і } false { } other{}} # нових повідомлень} }, ви також можете продивитись інші повідомлення в групі {groupLink}"
- read_more_personal_pm_MF: "У вас { UNREAD, plural, =0 {} one { # непрочитане } few { # непрочитаних } other { # непрочитаних } } { NEW, plural, =0 {} one { {BOTH, select, true{і } false { } other{}} # нове приватне повідомлення} few { {BOTH, select, true{і } false { } other{}} # нових приватних повідомлень} other { {BOTH, select, true{і } false { } other{}} # нових приватних повідомлень} }, ви також можете продивитись інші приватні повідомлення"
preferences_nav:
account: "Обліковий запис"
security: "Безпека"
@@ -4530,11 +4531,10 @@ uk:
Рекомендується вводити префікси до назв властивостей, щоб уникнути конфліктів з плагінами та / або ядром.
head_tag:
- text: ""
- title: "HTML-код, який буде вставлений перед міткою "
+ title: "HTML-код, який буде вставлений перед тегом "
body_tag:
- text: ""
- title: "HTML-код, який буде вставлений перед міткою "
+ text: "Тіло"
+ title: "HTML-код, який буде вставлений перед тегом "
yaml:
text: "YAML"
title: "Визначити налаштування теми в форматі YAML"
@@ -4869,6 +4869,7 @@ uk:
clear_all: Скинути все
clear_all_confirm: "Ви впевнені, що хочете видалити всі контрольні слова на %{action}?"
invalid_regex: 'Контрольоване слово "%{word}" є недійсним регулярним виразом.'
+ regex_warning: 'Контролюємі слова є регулярні вирази і вони автоматично не включають межі слів. Якщо ви хочете, щоб регулярний вираз відповідав цілим словам, додайте
+ @test
+ #test
+ tdiscourset
+ \b на початку і в кінці вашого регулярного виразу.'
actions:
block: "Заблокувати"
censor: "Цензура"
@@ -5397,7 +5398,7 @@ uk:
grant_existing_holders: Надати додаткові нагороди існуючим власникам нагород
emoji:
title: "Іконки"
- help: "Додати нові смайли-emoji, які будуть доступні всім. (Підказка: можна перетягувати кілька файлів за раз)"
+ help: "Додайте нові смайли, які будуть доступні для всіх. Перетягуйте декілька файлів одночасно, не вводячи назву, щоб створити емодзі, використовуючи їхні імена файлів."
add: "Додати нову іконку"
uploading: "Завантаження…"
name: "Ім'я"
@@ -5408,7 +5409,7 @@ uk:
embedding:
get_started: "Для вбудовування на Інший сайт необхідно додати відповідний хост."
confirm_delete: "Ви впевнені, що хочете видалити цей хост?"
- sample: "Використовуйте наступний HTML-код на своєму сайті, для можливості створення пов’язаних тем. Замініть REPLACE_ME канонічної посиланням сторінки, куди проводиться вбудовування."
+ sample: "Використовуйте наступний HTML-код на своєму сайті, для створення пов’язаних тем. Замініть REPLACE_ME канонічним посиланням сторінки, в яку проводиться вбудовування."
title: "Взаємодія"
host: "Дозволені Хости"
class_name: "Назва класу"
diff --git a/config/locales/client.ur.yml b/config/locales/client.ur.yml
index 48fccc326b..ceaec164a6 100644
--- a/config/locales/client.ur.yml
+++ b/config/locales/client.ur.yml
@@ -3556,12 +3556,8 @@ ur:
embedded_scss:
text: "اَیمبَیڈ کیا ہوا CSS"
title: "تبصرے کے اَیمبَیڈ شدہ ورژن کے ساتھ فراہم کیے جانے والا اپنی مرضی کا CSS درج کریں"
- head_tag:
- text: ""
- title: "HTML جو ٹیگ سے پہلے داخل کی جائے گی"
body_tag:
- text: "body>"
- title: "HTML جو ٹیگ سے پہلے داخل کی جائے گی"
+ text: "متن"
yaml:
text: "YAML"
title: "تھیم ترتیبات کی وضاحت YAML فارمَیٹ میں کریں"
@@ -4270,7 +4266,6 @@ ur:
badge_query_examples_title: "بَیج قُوَیری کی مثالیں"
emoji:
title: "اِیمَوجی"
- help: "نئی اِیمَوجی شامل کریں جو سب کے لئے دستیاب ہوگی۔ (پیشہ وَرَانہ ٹِپ: ایک بار میں ایک سے زیادہ فائلوں کو ڈرَیگ & ڈراپ کریں)"
add: "نئی اِیمَوجی شامل کریں"
uploading: "اَپ لوڈ کیا جا رہا ہے..."
name: "نام"
@@ -4280,7 +4275,6 @@ ur:
embedding:
get_started: "اگر آپ ڈِسکورس کو ایک اور ویب سائٹ پر اَیمبَیڈ کرنا چاہتے ہیں، تو ہوسٹ شامل کر کے شروع کریں۔"
confirm_delete: "کیا آپ واقعی اس ہَوسٹ کو حذف کرنا چاہتے ہیں؟"
- sample: "ڈِسکورس ٹاپک بنانے اور اَیمبَیڈ کرنے کیلئے اپنی ویب سائٹ میں درج ذیل HTML کوڈ استعمال کریں۔ جس پیج پر آپ اسے اَیمبَیڈ کر رہے ہیں اُس کا canonical URL REPLACE_ME کی جگہ اِستعمال کریں۔"
title: "اَیمبَیڈ کرنا"
host: "اجازت یافتہ ہَوسٹ"
class_name: "کلاس کا نام"
diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml
index f05c8935dc..6646132989 100644
--- a/config/locales/client.vi.yml
+++ b/config/locales/client.vi.yml
@@ -3784,12 +3784,8 @@ vi:
color_definitions:
text: "Màu sắc"
title: "Nhập màu tùy chỉnh (chỉ dành cho người dùng nâng cao)"
- head_tag:
- text: ""
- title: "HTML sẻ thêm trước thẻ "
body_tag:
- text: ""
- title: "HTML sẽ thêm trước thẻ "
+ text: "Thân"
yaml:
text: "YAML"
title: "Xác định cài đặt chủ đề ở định dạng YAML"
@@ -4546,7 +4542,6 @@ vi:
replace_owners: Xóa huy hiệu khỏi chủ sở hữu trước đó
emoji:
title: "Emoji"
- help: "Thêm emoji mới có sẵn cho tất cả mọi người. (MẸO: kéo & thả nhiều file cùng lúc)"
add: "Thêm emoji mới"
uploading: "Đang tải lên..."
name: "Tên"
@@ -4556,7 +4551,6 @@ vi:
embedding:
get_started: "Nếu bạn muốn nhúng Discourse trên một website khác, bắt đầu bằng cách thêm host."
confirm_delete: "Bạn muốn xóa host này?"
- sample: "Sử dụng mã HTML sau vào website để tạo và nhúng các chủ đề. Thay thế REPLACE_ME với Canonical URL của trang bạn muốn nhúng."
title: "Nhúng"
host: "Cho phép Host"
class_name: "Tên lớp"
diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml
index eaf3008079..6653ef002c 100644
--- a/config/locales/client.zh_CN.yml
+++ b/config/locales/client.zh_CN.yml
@@ -1089,7 +1089,6 @@ zh_CN:
tags: "标签"
warnings: "官方警告"
read_more_in_group: "想阅读更多?浏览%{groupLink}或中的其他话题。"
- read_more: "想阅读更多? 请在 个人消息中浏览其他消息。"
preferences_nav:
account: "帐户"
security: "安全性"
@@ -4016,12 +4015,8 @@ zh_CN:
%{example}
强烈建议为属性名称添加前缀以避免与插件和/或核心冲突。
- head_tag:
- text: ""
- title: "将在 标记前插入的 HTML"
body_tag:
- text: ""
- title: "将在 标记前插入的 HTML"
+ text: "正文"
yaml:
text: "YAML"
title: "使用 YAML 格式定义主题设置"
@@ -4867,7 +4862,6 @@ zh_CN:
grant_existing_holders: 向现有徽章持有者授予额外的徽章
emoji:
title: "表情符号"
- help: "添加可供所有人使用的新表情符号。(高级提示:一次拖放多个文件)"
add: "添加新表情符号"
uploading: "正在上传…"
name: "名称"
@@ -4878,7 +4872,6 @@ zh_CN:
embedding:
get_started: "如果您想将 Discourse 嵌入另一个网站,请先添加其主机。"
confirm_delete: "确定要删除该主机吗?"
- sample: "在您的站点中使用以下 HTML 代码来创建和嵌入 Discourse 话题。将 REPLACE_ME 替换为您嵌入它的页面的规范 URL。"
title: "嵌入"
host: "允许的主机"
class_name: "类名"
diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml
index dcb2465329..8ea4c75ca4 100644
--- a/config/locales/client.zh_TW.yml
+++ b/config/locales/client.zh_TW.yml
@@ -970,7 +970,7 @@ zh_TW:
move_to_archive: "封存"
failed_to_move: "移動所選郵件失敗(請檢查網路連線)"
tags: "標籤"
- read_more: "想看更多?在個人訊息瀏覽其他訊息。"
+ read_more: "想看更多?到個人訊息瀏覽其他訊息。"
preferences_nav:
account: "帳號"
security: "安全性"
@@ -3231,12 +3231,8 @@ zh_TW:
embedded_scss:
text: "嵌入的 CSS"
title: "輸入用於嵌入回應的自定義 CSS 樣式"
- head_tag:
- text: ""
- title: "會在標籤前被插入的 HTML"
body_tag:
- text: ""
- title: "會在標籤前被插入的 HTML"
+ text: "內容"
yaml:
text: "YAML"
title: "用 YAML 格式定義佈景主題的設定"
@@ -3918,7 +3914,6 @@ zh_TW:
badge_query_examples_title: "查詢徽章範例"
emoji:
title: "Emoji"
- help: "新增新的emoji供所有人使用。(提示:一次拖放多個檔案)"
add: "新增emoji"
uploading: "正在上傳..."
name: "名稱"
@@ -3928,7 +3923,6 @@ zh_TW:
embedding:
get_started: "如果你想要將 Discourse 嵌入至其他網站,添加他們的主機地址。"
confirm_delete: "你確定要刪除此主機?"
- sample: "使用下列 HTML 代碼至你的站點開啟和嵌入 Discourse 話題。把REPLACE_ME 替換成你將嵌入至的網址。"
title: "嵌入"
host: "允許的主機"
class_name: "階級名稱"
diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml
index 93a3585055..e61ce82a11 100644
--- a/config/locales/server.ar.yml
+++ b/config/locales/server.ar.yml
@@ -285,7 +285,6 @@ ar:
topic_invite:
failed_to_invite: "لا يمكن دعوة المستخدم إلى هذا الموضوع دون عضوية في إحدى المجموعات التالية: %{group_names}."
user_exists: "عذرًا، لقد تمت دعوة هذا المستخدم بالفعل. لا يمكنك دعوة مستخدام إلى موضوع أكثر من مرة."
- muted_invitee: "عذرًا، لقد كتمك هذا المستخدم."
muted_topic: "عذرًا، لقد كتم هذا المستخدم ذلك الموضوع."
receiver_does_not_allow_pm: "عذرًا، لا يسمح لك هذا المستخدم بإرسال رسائل خاصة إليه."
sender_does_not_allow_pm: "عذرًا، أنت لا تسمح لهذا المستخدم بإرسال رسائل خاصة إليك."
@@ -1658,7 +1657,6 @@ ar:
summary_likes_required: "الحد الأدنى من عدد الإعجابات في الموضوع قبل تفعيل \"تلخيص هذا الموضوع\". سيتم تطبيق التغييرات على هذا الإعداد بأثر رجعي في غضون أسبوع."
summary_percent_filter: "يظهر أعلى % من المنشورات عندما يضغط المستخدم على \"تلخيص هذا الموضوع\""
summary_max_results: "الحد الأقصى لعدد المنشورات التي تم إرجاعها بواسطة \"تلخيص هذا الموضوع\""
- enable_personal_messages: "السماح للمستخدمين من مستوى الثقة 1 (قابل للإعداد عبر الحد الأدنى لمستوى الثقة لإرسال الرسائل) بإنشاء رسائل والرد على الرسائل. لاحظ أن فريق العمل يمكنه دائمًا إرسال الرسائل بغض النظر عن أي شيء."
enable_system_message_replies: "يسمح للمستخدمين بالرد على رسائل النظام، حتى إذا كانت الرسائل الشخصية متوقفة."
enable_long_polling: "يمكن لناقل الرسائل المُستخدَم في الإشعارات استخدام الاستقصاء الطويل"
enable_chunked_encoding: "تفعيل استجابات الترميز المقسَّمة بواسطة الخادم. تعمل هذه الميزة على معظم الإعدادات، ولكن قد يتم تخزين بعض الخوادم الوكيلة مؤقتًا، مما يتسبب في تأخير الاستجابات"
@@ -1988,7 +1986,6 @@ ar:
topic_view_duration_hours: "عد مرة عرض الموضوع الجديد مرة واحدة لكل IP/مستخدم كل N ساعة"
user_profile_view_duration_hours: "عد عرض الملف الشخصي للمستخدم الجديد مرة واحدة لكل IP/مستخدم كل N ساعة"
levenshtein_distance_spammer_emails: "عدد الأحرف المختلفة الذي سيسمح بمطابقة جزئية عند مطابقة الرسائل الإلكترونية غير المرغوب فيها"
- max_new_accounts_per_registration_ip: "التوقُّف عن قبول عمليات الاشتراك الجديدة من عنوان IP هذا إذا كان هناك بالفعل (n) حساب من مستوى الثقة 0 منه (ولم يكن لفريق العمل أو من المستوى الثقة 2 أو أعلى)"
min_ban_entries_for_roll_up: "عند النقر على الزر \"تجميع\"، سيتم إنشاء إدخال حظر جديد في الشبكة الفرعية إذا كان هناك (N) من الإدخالات على الأقل."
max_age_unmatched_emails: "حذف إدخالات البريد الإلكتروني الخاضعة للمراقبة غير المتطابقة بعد (N) يوم"
max_age_unmatched_ips: "حذف إدخالات عناوين IP الخاضعة للمراقبة غير المتطابقة بعد (N) يوم"
@@ -4421,10 +4418,8 @@ ar:
label: "اسم مجتمعك"
placeholder: "استراحة جين"
site_description:
- label: "صِف مجتمعك في جملة واحدة قصيرة"
placeholder: "مكان لجين وأصدقائها لمناقشة أشياء رائعة"
short_site_description:
- label: "صِف مجتمعك في بضع كلمات"
placeholder: "أفضل مجتمع على الإطلاق"
introduction:
title: "مقدمة"
diff --git a/config/locales/server.be.yml b/config/locales/server.be.yml
index 7ae833e9d5..db6d898ed4 100644
--- a/config/locales/server.be.yml
+++ b/config/locales/server.be.yml
@@ -933,7 +933,6 @@ be:
summary_score_threshold: "Мінімальная колькасць балаў, неабходныя для пасады, якія будуць уключаны ў «Сумаваць гэтую тэму»"
summary_percent_filter: "Калі карыстальнік націскае «Сумаваць гэтую тэму», паказваюць верхнюю% паведамленняў"
summary_max_results: "Максімум паведамлення якiя вяртаюцца «Абагульніць Гэтую тэму»"
- enable_personal_messages: "Дазволіць траставы ўзровень 1 (наладжваецца праз мін ўзровень даверу для адпраўкі паведамленняў) карыстальнікам ствараць паведамленні і адказваць на паведамленні. Звярніце ўвагу, што супрацоўнікі заўсёды могуць адпраўляць паведамленні незалежна ад таго, што."
enable_system_message_replies: "Дазваляе карыстальнікам адказваць на сістэмныя паведамленні, нават калі асабістыя паведамленні адключаныя"
enable_long_polling: "Шына паведамленняў выкарыстоўваюцца для апавяшчэння можа выкарыстоўваць доўгі апытанне"
long_polling_base_url: "Базавы URL выкарыстоўваецца для апытання (калі CDN абслугоўвае дынамічны кантэнт, не забудзьцеся ўсталяваць гэта паходжанне цягнуць), напрыклад: HTTP:"
@@ -1146,7 +1145,6 @@ be:
topic_view_duration_hours: "Граф новага віду тэмы адзін раз у IP"
user_profile_view_duration_hours: "Граф новы від профіляў карыстальнікаў адзін раз у IP"
levenshtein_distance_spammer_emails: "Пры супастаўленні спамерскіх лістоў, колькасць знакаў розніцы, што будзе па-ранейшаму дазваляе невыразны матч."
- max_new_accounts_per_registration_ip: "Калі ўжо ёсць (п) узровень даверу 0 рахункаў з гэтага IP (і ніхто не з'яўляецца супрацоўнікам або TL2 або вышэй), спыніць прыём новых падпісак з гэтага IP."
min_ban_entries_for_roll_up: "Пры націску на кнопку Roll Up, створыць новую запіс аб забароне падсеткі, калі ёсць па меншай меры (N) запісаў."
max_age_unmatched_emails: "Выдаліць няпарнага Экранаваныя запісу па электроннай пошце пасля (N) дзён."
max_age_unmatched_ips: "Выдаліць няпарнага Экранаваныя запісу IP пасля (N) дзён."
@@ -2403,10 +2401,8 @@ be:
label: "Імя вашага супольнасці"
placeholder: "Hangout Джэйн"
site_description:
- label: "Апішыце сваю супольнасць у адным кароткім сказе"
placeholder: "Месца для Джэйн і яе сяброў, каб абмеркаваць цікавыя рэчы"
short_site_description:
- label: "Апішыце сваю супольнасць у некалькіх словах"
placeholder: "Лепшае супольнасць калі-небудзь"
introduction:
title: "ўвядзенне"
diff --git a/config/locales/server.bg.yml b/config/locales/server.bg.yml
index f4d2f97fdf..4e35aae1f1 100644
--- a/config/locales/server.bg.yml
+++ b/config/locales/server.bg.yml
@@ -803,7 +803,6 @@ bg:
privacy_policy_url: "Ако имате документ \"Декларация за поверителност\", който се хоства някъде другаде и желаете да го използвате, въведете пълния път до URL адреса тук. "
allowed_spam_host_domains: "Списък с домейните изключени от спам хост теста. Новите потребители никога няма да бъдат ограничавани в създаването на публикации с линкове към тези домейни."
levenshtein_distance_spammer_emails: "Колко символа разлика в съвпадението може да има, когато се проверява имейла за спам, за да има приблизително съвпадение."
- max_new_accounts_per_registration_ip: "Ако вече има (n) в ниво на доверие 0 акаунта от това IP (и никой не е от екипа или от по-високо ниво на доверие 2), спри да приемаш нови регистрации от този IP адрес."
min_ban_entries_for_roll_up: "Когато щракнете на бутона Сливане, ще създадете нов бан на подмрежа, ако в същата има повече от (N) записа."
max_age_unmatched_emails: "Изтриване на несъвпадащите пресяти имейл записи след (N) дни."
max_age_unmatched_ips: "Изтрий несъчетаните пресяти IP вписвания след (N) дни."
diff --git a/config/locales/server.ca.yml b/config/locales/server.ca.yml
index 346d7eea66..310ae161ef 100644
--- a/config/locales/server.ca.yml
+++ b/config/locales/server.ca.yml
@@ -1251,7 +1251,6 @@ ca:
summary_likes_required: "Nombre mínim de 'M'agrada' en un tema abans que sigui activat 'Resumeix aquest tema'. Els canvis en aquesta configuració s'aplicaran retroactivament dins d'una setmana."
summary_percent_filter: "Quan un usuari fa clic en 'Resumeix aquest tema', mostra el % superior de les publicacions"
summary_max_results: "Nombre màxim de publicacions retornades per \"Resumeix aquest tema\""
- enable_personal_messages: "Permet que els usuaris amb nivell de confiança 1 puguin crear missatges i respondre-hi (configurable amb nivell de confiança mínim per a enviar missatges). Observeu que l'equip responsable sempre pot enviar tota mena de missatges."
enable_system_message_replies: "Permet als usuaris respondre als missatges del sistema, fins i tot si els missatges personals estan desactivats"
enable_long_polling: "El bus de missatges emprat per a notificar pot utilitzar 'long polling'"
long_polling_base_url: "URL base emprat per a 'long polling' (quan una xarxa de CDN serveix contingut dinàmic, assegureu-vos que ho heu configurat com a 'origin pull'), p. ex. http://origin.site.com"
@@ -1505,7 +1504,6 @@ ca:
topic_view_duration_hours: "Compta una nova vista de tema una vegada per IP/usuari cada N hores"
user_profile_view_duration_hours: "Compta una nova vista de perfil d'usuari una vegada per IP/usuari cada N hores."
levenshtein_distance_spammer_emails: "En cercar coincidències de correus brossa, diferència de nombre de caràcters que encara permetrà una coincidència difusa (fuzzy match)."
- max_new_accounts_per_registration_ip: "Si ja hi ha (n) comptes amb nivell de confiança o d'aquesta IP (i cap no és membre de l'equip responsable o té nivell 2 o superior), deixa d'acceptar registres des d'aquesta IP."
min_ban_entries_for_roll_up: "En clicar el botó Agrupa es crearà una nova entrada de prohibició de subxarxa si hi ha almenys (N) entrades."
max_age_unmatched_emails: "Suprimeix entrades no coincidents de correus sota supervisió després de (N) dies."
max_age_unmatched_ips: "Suprimeix entrades no coincidents d'entrades IP sota supervisió després de (N) dies."
@@ -3006,10 +3004,8 @@ ca:
label: "El nom de la vostra comunitat"
placeholder: "Cau de la Jane"
site_description:
- label: "Descriviu la vostra comunitat amb una frase curta"
placeholder: "Un lloc on la Jane i les seves amistats poden conversar sobre temes interessants"
short_site_description:
- label: "Descriviu la vostra comunitat en poques paraules"
placeholder: "La millor comunitat de tots els temps"
introduction:
title: "Introducció"
diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml
index 6acd0a86c4..b81a320d42 100644
--- a/config/locales/server.da.yml
+++ b/config/locales/server.da.yml
@@ -241,7 +241,6 @@ da:
topic_invite:
failed_to_invite: "Brugeren kan ikke inviteres til dette emne uden et gruppemedlemskab i en af følgende grupper: %{group_names}."
user_exists: "Beklager, men brugeren er allerede inviteret. Du kan kun invitere en bruger til et emne én gang."
- muted_invitee: "Beklager, denne bruger har slukket for dig."
muted_topic: "Beklager, denne bruger ønsker tavshed i dette emne."
receiver_does_not_allow_pm: "Beklager, denne bruger tillader dig ikke at sende private beskeder til sig."
sender_does_not_allow_pm: "Beklager, du tillader ikke denne bruger sender dig private beskeder."
@@ -1374,7 +1373,6 @@ da:
summary_likes_required: "Minimum antal 'Synes godt om' i et emne, før 'Opsummer dette emne' er aktiveret. Ændringer af denne indstilling anvendes med tilbagevirkende kraft inden for en uge."
summary_percent_filter: "Når en bruger kllikker 'Opsummer dette Emne' vises top % af indlæg"
summary_max_results: "Maksimum antal indlæg returneret af 'Opsummer dette emne'"
- enable_personal_messages: "Tillad Trust Level 1 (kan konfigureres minimum trust level til at sende beskeder), brugere til at sende og svare på beskeder. Bemærk at moderatorer beskeder, uanset hvad."
enable_system_message_replies: "Tillader brugere at svare på systemmeddelelser, også selvom personlige meddelelser er deaktiveret"
enable_long_polling: "Message bus til underretninger kan bruge long polling"
long_polling_base_url: "URL anvendt for afsteminger (når CDN leverer dynamisk indhold, så sæt dette til oprindelig / orginal) f.eks: http://origin.site.com"
@@ -1584,7 +1582,6 @@ da:
tos_url: "Hvis du hoster dine forretningsbetingelser et andet sted kan du indtaste den fulde URL her."
privacy_policy_url: "Hvis du hoster din privatlivspolitik et andet sted kan du indtaste den fulde URL her."
staff_like_weight: "Hvor meget vægt der gives for personalets 'Synes godt om' (ikke-personale har en vægt på 1.)"
- max_new_accounts_per_registration_ip: "Hvis der allerede er (n) kontoer med tillidsniveau 0 fra en IP (og ingen er en del af hjælperteamet eller på TL2 eller højere), så accepteres nye kontooprettelser fra denne IP ikke."
num_flaggers_to_close_topic: "Minimum antal anmeldelser fra unikke brugere, der skal til for automatisk at sætte et emne på pause for ingriben"
auto_respond_to_flag_actions: "Aktivér automatisk besvarelse, når en anmeldelse fjernes."
high_trust_flaggers_auto_hide_posts: "Nye brugerindlæg skjules automatisk efter at være blevet anmeldt som spam af en TL3+ bruger"
@@ -3601,10 +3598,8 @@ da:
label: "Dit fællesskabs navn"
placeholder: "Janes Sted"
site_description:
- label: "Beskriv dit fællesskab med en kort sætning"
placeholder: "Et sted hvor Jane og hendes venner kan diskutere fede emner"
short_site_description:
- label: "Beskriv dit fællesskab med nogle få ord"
placeholder: "Bedste fællesskab nogensinde"
introduction:
title: "Introduktion"
diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml
index 07d968275c..d1f2c30bdd 100644
--- a/config/locales/server.de.yml
+++ b/config/locales/server.de.yml
@@ -250,7 +250,6 @@ de:
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}"
user_exists: "Entschuldige, dieser Benutzer ist bereits eingeladen worden. Du kannst einen Benutzer nur einmal zu einem Thema einladen."
- muted_invitee: "Entschuldige, dieser Benutzer hat dich stummgeschaltet."
muted_topic: "Entschuldige, dieser Benutzer hat dieses Thema stummgeschaltet."
receiver_does_not_allow_pm: "Entschuldige, dieser Benutzer erlaubt es dir nicht, ihm private Nachrichten zu senden."
sender_does_not_allow_pm: "Entschuldige, du erlaubst diesem Benutzer nicht, dir private Nachrichten zu senden."
@@ -1790,7 +1789,7 @@ de:
topic_view_duration_hours: "Zähle einen neuen Themenaufruf einmal pro IP/Benutzer alle N Stunden"
user_profile_view_duration_hours: "Zähle einen neuen Profilaufruf einmal pro IP/Benutzer alle N Stunden"
levenshtein_distance_spammer_emails: "E-Mail-Adressen, die sich um so viele Zeichen unterscheiden, werden beim Abgleich mit Adressen der Spammer dennoch als identisch betrachtet."
- max_new_accounts_per_registration_ip: "Keine neuen Registrierungen von einer IP-Adresse annehmen, wenn bereits (n) Benutzerkonten mit Vertrauensstufe 0 zugeordnet sind (und keines davon ein Team-Mitglied ist oder Vertrauensstufe 2 oder höher hat)."
+ max_new_accounts_per_registration_ip: "Keine neuen Registrierungen von einer IP-Adresse annehmen, wenn bereits (n) Benutzerkonten mit Vertrauensstufe 0 zugeordnet sind (und keines davon ein Team-Mitglied ist oder Vertrauensstufe 2 oder höher hat). 0 setzen um die Begrenzung zu deaktivieren."
min_ban_entries_for_roll_up: "Wenn du auf die Schaltfläche „Zusammenfassen“ klickst, wird ein neuer Subnetz-Block-Eintrag erstellt, wenn es mindestens (N) Einträge gibt."
max_age_unmatched_emails: "Gefilterte E-Mail-Adressen nach (N) Tagen ohne Treffer löschen."
max_age_unmatched_ips: "Gefilterte IP-Adressen nach (N) Tagen ohne Treffer löschen."
@@ -1926,7 +1925,8 @@ de:
permalink_normalizations: "Diesen regulären Ausdruck anwenden, bevor Permalinks verarbeitet werden; Beispiel: /(topic.*)\\?.*/\\1 wird Query-Strings von Themen-Routen entfernen. Format: regulärer Ausdruck + String, benutze \\1 usw., um Teilausdrücke zu verwenden"
global_notice: "Zeigt allen Besuchern eine DRINGENDE NOTFALL-Meldung in Form eines nicht ausblendbaren, global sichtbaren Banners an. Leere den Inhalt, um sie wieder auszublenden (HTML ist erlaubt)."
disable_system_edit_notifications: "Unterdrückt Bearbeitungsbenachrichtigungen durch den System-Benutzer, wenn die „download_remote_images_to_local“-Einstellung aktiviert ist."
- disable_category_edit_notifications: "Deaktiviere Benachrichtigungen zum Bearbeiten von Kategorien für Themen."
+ disable_category_edit_notifications: "Deaktiviere Benachrichtigungen über Kategorie-Anpassungen von Themen."
+ disable_tags_edit_notifications: "Deaktiviere Benachrichtigungen über Schlagwort-Anpassungen von Themen."
notification_consolidation_threshold: "Anzahl von „Gefällt mir“- oder Mitgliedschaftsanfragen-Benachrichtigungen, bevor die Benachrichtigungen zu einer einzelnen zusammengefasst werden. Zum Deaktivieren auf 0 setzen."
likes_notification_consolidation_window_mins: "Zeitfenster in Minuten, in dem mehrere „Gefällt mir“-Benachrichtigungen zu einer einzelnen Benachrichtigung zusammengefasst werden, sobald der Schwellenwert erreicht wird. Der Schwellenwert kann mittels `SiteSetting.notification_consolidation_threshold` eingestellt werden."
automatically_unpin_topics: "Themen automatisch loslösen, wenn ein Benutzer das Ende erreicht."
@@ -4367,10 +4367,8 @@ de:
label: "Name deiner Community"
placeholder: "Erikas Stammtisch"
site_description:
- label: "Beschreibe deine Community in einem kurzen Satz"
placeholder: "Ein Ort für Erika und ihre Bekannten, um coole Sachen zu besprechen"
short_site_description:
- label: "Beschreibe deine Community in wenigen Worten"
placeholder: "Beste Community aller Zeiten"
introduction:
title: "Einführung"
diff --git a/config/locales/server.el.yml b/config/locales/server.el.yml
index 77297228a0..d5c837eb4e 100644
--- a/config/locales/server.el.yml
+++ b/config/locales/server.el.yml
@@ -950,7 +950,6 @@ el:
force_https: "Αναγκάζει το site σας να χρησιμοποιεί μόνο HTTPS. ΠΡΟΣΟΧΗ: Μην το ενεργοποιήσετε, μέχρι να επαληθεύσετε ότι το HTTPS είναι πλήρως εγκατεστημένο και λειτουργεί απολύτως παντού! Ελέγξατε το CDN σας, όλες τις κοινωνικές συνδέσεις και τα εξωτερικά σας λογότυπα / εξαρτήσεις για να βεβαιωθείτε ότι είναι όλα συμβατά με HTTPS;"
summary_score_threshold: "Η ελάχιστη βαθμολογία που απαιτείται από μια ανάρτηση για να συμπεριληφθεί στο «Συνοψίστε αυτό το θέμα»"
summary_percent_filter: "Όταν ο χρήστης επιλέξει «Συνοψίστε αυτό το θέμα», δείξε το κορυφαίο % των αναρτήσεων"
- enable_personal_messages: "Επίτρεψε στους χρήστες επιπέδου εμπιστοσύνης 1 (ρυθμιζόμενο μέσω min trust level to send messages) να δημιουργήσουν μηνύματα και να απαντήσουν σε μηνύματα. Σημειώστε ότι οι συνεργάτες μπορούν πάντα να στέλνουν μηνύματα."
enable_long_polling: "Η αρτηρία μηνυμάτων που χρησιμοποιείτε για ειδοποιήσεις μπορεί να χρησιμοποιήσει μακρυά μέθοδο εξέτασης."
long_polling_base_url: "Base URL που χρησιμοποιείτε για μακρύ ψήφισμα (όταν ένα CDN εξυπηρετεί δυναμικό περιεχόμενο, βεβαιωθείτε ότι το ορίσατε σε έλξη προέλευσης ) π.χ.: http://origin.site.com"
long_polling_interval: "Χρονικό διάστημα που ο διακομιστής θα πρέπει να περιμένει πριν απαντήσει στους πελάτες όταν δεν υπάρχουν δεδομένα για την αποστολή (μόνο συνδεδεμένοι χρήστες )"
@@ -1130,7 +1129,6 @@ el:
topic_view_duration_hours: "Μετρήστε μια νέα προβολή νήματος μία φορά ανά IP/User κάθε N ώρες"
user_profile_view_duration_hours: "Μετρήστε μια νέα προβολή προφίλ χρήστη μία φορά ανά IP/User κάθε N ώρες"
levenshtein_distance_spammer_emails: "Στην αντιστοίχιση διευθύνσεων email των σπάμερ, η διαφορά στον αριθμό των χαρακτήρων η οποία θα επιτρέπει μια ασαφή αντιστοίχιση."
- max_new_accounts_per_registration_ip: "Αν ήδη υπάρχουν (n) λογαριασμοί επιπέδου εμπιστοσύνης 0 από αυτή την IP διεύθυνση (και κανείς από αυτούς δεν είναι συνεργάτης ή τουλάχιστον στο ΕΕ2), σταμάτα να αποδέχεσαι νέες εγγραφές από αυτή την IP διεύθυνση. "
min_ban_entries_for_roll_up: "Το πάτημα του πλήκτρου Κύλιση προς τα πάνω, θα δημιουργήσει μία απαγόρευση εισαγωγής νέου υποδικτύου αν υπάρχουν τουλάχιστον (Ν) εισαγωγές. "
max_age_unmatched_emails: "Διαγράψτε τις αταίριαστες ελεγχόμενες εγγραφές email μετά από (N) μέρες."
max_age_unmatched_ips: "Διαγράψτε τις αταίριαστες ελεγχόμενες εγγραφές IP μετά από (N) μέρες."
@@ -2546,7 +2544,6 @@ el:
label: "Το όνομα της κοινότητάς σου"
placeholder: "Το μέρος που συχνάζει η Jane"
site_description:
- label: "Περίγραψε την κοινότητά σου σε μια σύντομη πρόταση"
placeholder: "Ένα μέρος για την Jane και τις φίλες της να συζητήσουν ωραία θέματα"
introduction:
title: "Εισαγωγή"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index da3b53354a..62492771d6 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -270,7 +270,6 @@ en:
topic_invite:
failed_to_invite: "The user cannot be invited into this topic without a group membership in either one of the following groups: %{group_names}."
user_exists: "Sorry, that user has already been invited. You may only invite a user to a topic once."
- muted_invitee: "Sorry, that user muted you."
muted_topic: "Sorry, that user muted this topic."
receiver_does_not_allow_pm: "Sorry, that user does not allow you to send them private messages."
sender_does_not_allow_pm: "Sorry, you do not allow that user to send you private messages."
@@ -1548,7 +1547,7 @@ en:
summary_max_results: "Maximum posts returned by 'Summarize This Topic'"
summary_timeline_button: "Show a 'Summarize' button in the timeline"
- enable_personal_messages: "Allow trust level 1 (configurable via min trust level to send messages) users to create messages and reply to messages. Note that staff can always send messages no matter what."
+ enable_personal_messages: "Allow trust level 1 (configurable via min trust to send messages) users to create messages and reply to messages. Note that staff can always send messages no matter what."
enable_system_message_replies: "Allows users to reply to system messages, even if personal messages are disabled"
enable_long_polling: "Message bus used for notification can use long polling"
enable_chunked_encoding: "Enable chunked encoding responses by the server. This feature works on most setups however some proxies may buffer, causing responses to be delayed"
@@ -1950,7 +1949,7 @@ en:
user_profile_view_duration_hours: "Count a new user profile view once per IP/User every N hours"
levenshtein_distance_spammer_emails: "When matching spammer emails, number of characters difference that will still allow a fuzzy match."
- max_new_accounts_per_registration_ip: "If there are already (n) trust level 0 accounts from this IP (and none is a staff member or at TL2 or higher), stop accepting new signups from that IP."
+ max_new_accounts_per_registration_ip: "If there are already (n) trust level 0 accounts from this IP (and none is a staff member or at TL2 or higher), stop accepting new signups from that IP. Set to 0 to disable the limit."
min_ban_entries_for_roll_up: "When clicking the Roll up button, will create a new subnet ban entry if there are at least (N) entries."
max_age_unmatched_emails: "Delete unmatched screened email entries after (N) days."
@@ -2131,6 +2130,8 @@ en:
disable_category_edit_notifications: "Disable category edit notifications on topics."
+ disable_tags_edit_notifications: "Disable tags edit notifications on topics."
+
notification_consolidation_threshold: "Number of liked or membership request notifications received before the notifications are consolidated into a single one. Set to 0 to disable."
likes_notification_consolidation_window_mins: "Duration in minutes where liked notifications are consolidated into a single notification once the threshold has been reached. The threshold can be configured via `SiteSetting.notification_consolidation_threshold`."
@@ -2164,7 +2165,7 @@ en:
warn_reviving_old_topic_age: "When someone starts replying to a topic where the last reply is older than this many days, a warning will be displayed. Disable by setting to 0."
autohighlight_all_code: "Force apply code highlighting to all preformatted code blocks even when they didn't explicitly specify the language."
highlighted_languages: "Included syntax highlighting rules. (Warning: including too many languages may impact performance) see: https://highlightjs.org/static/demo for a demo"
- show_copy_button_on_codeblocks: "Add a button to codeblocks to copy the block contents to the user's clipboard. This feature is not supported on Internet Explorer."
+ show_copy_button_on_codeblocks: "Add a button to codeblocks to copy the block contents to the user's clipboard."
embed_any_origin: "Allow embeddable content regardless of origin. This is required for mobile apps with static HTML."
embed_topics_list: "Support HTML embedding of topics lists"
@@ -4810,10 +4811,10 @@ en:
label: "Your community’s name"
placeholder: "Jane’s Hangout"
site_description:
- label: "Describe your community in one short sentence"
+ label: "Describe your community in one short sentence (used in search results and social media)"
placeholder: "A place for Jane and her friends to discuss cool stuff"
short_site_description:
- label: "Describe your community in few words"
+ label: "Describe your community in few words (used for the homepage title)"
placeholder: "Best community ever"
introduction:
diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml
index 177b5441f3..3ea356d725 100644
--- a/config/locales/server.es.yml
+++ b/config/locales/server.es.yml
@@ -250,7 +250,6 @@ es:
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}."
user_exists: "Lo sentimos, ese usuario ya fue invitado. Solo se puede invitar a un usuario a un tema una vez."
- muted_invitee: "Lo sentimos, el usuario te ha silenciado."
muted_topic: "Lo sentimos, el usuario ha silenciado este tema."
receiver_does_not_allow_pm: "Lo sentimos, el usuario no permite que le envíes mensajes privados."
sender_does_not_allow_pm: "Lo sentimos, no permites que el usuario te mande mensajes privados."
@@ -1458,7 +1457,6 @@ es:
summary_percent_filter: "Cuando un usuario hace clic en «resumen de este tema», mostrar el % de las publicaciones destacadas"
summary_max_results: "Cantidad máxima de publicaciones devueltas en «resumen de este tema»"
summary_timeline_button: "Mostrar un botón para «Resumir» en la línea de tiempo"
- enable_personal_messages: "Permitir a los usuarios con nivel de confianza 1 (configurable a través del mínimo nivel de confianza para enviar mensajes) crear mensajes y responder a ellos. Ten en cuenta que el personal siempre puede enviar mensajes pase lo que pase."
enable_system_message_replies: "Permite a los usuarios responder a los mensajes del sistema, incluso si los mensajes personales están desactivados."
enable_long_polling: "Los mensajes usados para notificaciones pueden usar el long polling"
enable_chunked_encoding: "Activar respuestas en lotes del servidor. Esta funcionalidad debería funcionar en casi todos los entornos, pero algunos proxies pueden causar que las respuestas tarden"
@@ -1789,7 +1787,6 @@ es:
topic_view_duration_hours: "Contar una visita a un nuevo tema por IP/Usuario cada N horas"
user_profile_view_duration_hours: "Contar una nueva visita de perfil por IP/Usuario cada N horas"
levenshtein_distance_spammer_emails: "Al revisar coincidencias por correos electrónicos de spammers, cantidad de caracteres diferentes que permiten una coincidencia parcial."
- max_new_accounts_per_registration_ip: "Si ya hay (n) cuentas con nivel de confianza 0 desde esta IP (y ninguna es de un miembro del equipo o de nivel de confianza 2 o superior), prohibir registros nuevos desde esa IP."
min_ban_entries_for_roll_up: "Al hacer clic en el botón agrupar, se creará un nuevo rango de entradas prohibidas si hay al menos (N) entradas."
max_age_unmatched_emails: "Eliminar entradas de correos electrónicos prohibidos que no coincidan después de (N) días."
max_age_unmatched_ips: "Eliminar entradas de IP prohibidos que no coincidan después de (N) días."
@@ -1925,6 +1922,7 @@ es:
permalink_normalizations: "Aplicar la siguiente expresión regular antes de hacer coincidir los permalinks, por ejemplo: /(topic.*)\\?.*/\\1 despojará las cadenas de consulta de las rutas de los temas. El formato es regex+string usa \\1 etc. para acceder a capturas"
global_notice: "Mostrar un banner global de URGENCIA o EMERGENCIA que no se pueda ocultar para todos los visitantes. Deja este campo en blanco para ocultarlo (se permite HTML)."
disable_system_edit_notifications: "Desactivar editar notificaciones por el usuario del sistema cuando «download_remote_images_to_local» este activo."
+ disable_category_edit_notifications: "Desactivar que se manden notificaciones al cambiar la categoría de los temas."
notification_consolidation_threshold: "Cantidad de me gusta o notificaciones de solicitud de membresía recibidas antes de que las notificaciones se consoliden en una sola. Establece el valor en 0 para desactivar."
likes_notification_consolidation_window_mins: "Minutos durante los que las notificaciones se consolidan en una sola notificación una vez que se haya alcanzado el limite. El límite se puede configurar en «SiteSetting.notification_consolidation_threshold»."
automatically_unpin_topics: "Desanclar automáticamente cuando el usuario llega al final del tema."
@@ -4366,10 +4364,8 @@ es:
label: "El nombre de tu comunidad"
placeholder: "Reunión de Juana"
site_description:
- label: "Describe tu comunidad en una frase corta"
placeholder: "Un sitio para que Juana y sus amigos discutan cosas interesantes"
short_site_description:
- label: "Describe tu comunidad en pocas palabras"
placeholder: "La mejor comunidad del mundo"
introduction:
title: "Introducción"
diff --git a/config/locales/server.et.yml b/config/locales/server.et.yml
index 4a4d5c182a..866fd68aed 100644
--- a/config/locales/server.et.yml
+++ b/config/locales/server.et.yml
@@ -1024,7 +1024,6 @@ et:
label: "Sinu kogukonna nimi"
placeholder: "Jane ajaviitenurgake"
site_description:
- label: "Kirjelda oma kogukonda ühe lühikese lausega"
placeholder: "Koht Janele ja tema sõpradele lahedate arutelude jaoks"
introduction:
title: "Sissejuhatus"
diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml
index 2d68606323..e849ccd769 100644
--- a/config/locales/server.fa_IR.yml
+++ b/config/locales/server.fa_IR.yml
@@ -907,7 +907,6 @@ fa_IR:
force_https: "اجبار به استفاده از HTTPS ، تا زمانی که HTTPS را کامل تنظیم نکردهاید این گزینه را فعال نکنید. آیا تمام CDN ها و شبکههای اجتماعی جهت ورود را بررسی کردید و تمام لوگوها و وابستگیها بدون مشکل با HTTPS کار میکنند؟"
summary_score_threshold: "حداقل امتیاز برای یک نوشته که بتواند شامل \" خلاصه این موضوع\" شود"
summary_percent_filter: "وقتی کاربر بر روی ' خلاصه این موضوع' کلیک کرد٬ % بهترین نوشتهها را نشان بده"
- enable_personal_messages: "اجازه ارسال پیام به کاربران سطح اعتماد 1 (قابل تنظیم با حداقل سطح اعتماد برای ارسال پیام). توجه کنید که همکاران در هر شرایطی میتوانند پیام ارسال کنند."
enable_long_polling: "message bus استفاده شده برای آگاه سازی می تواند برای رای گیری طولانی استفاده شود. "
long_polling_base_url: " URL پایه استفاده شده برای رای گیری طولانی (وقتی CDN خدمت محتوای پویا می دهد٬ از تنظیم بودن منشا این کشش مطمئن شوید) برای نمونه : http://origin.site.com"
long_polling_interval: "مدت زمانی که سرور قبل پاسخ دادن به مشتریها باید صبر کند، وقتی در آنجا داده ای برای ارسال نیست (فقط کاربران وارد شده)"
@@ -1080,7 +1079,6 @@ fa_IR:
topic_view_duration_hours: "بازدیدهای موضوعات را به ازای هر آیپی/کاربر در N ساعت محاسبه کن"
user_profile_view_duration_hours: "بازدیدهای پروفایل کاربران را به ازای هر آیپی/کاربر در N ساعت محاسبه کن"
levenshtein_distance_spammer_emails: "هنگامی که تطبیق ایمیل هرزنامه باشد٬ تعداد نویسههای متفاوت که هنوز هم به یک تطبق مبهم اجازه خواهد داد."
- max_new_accounts_per_registration_ip: "اگر در حال حاضر (n) حساب کاربری با سطح اعتماد 0 از این IP وجود دارد(و هیچ یک از کارمندان عضو یا با سطح اعتماد 2 یا بالاتر نیستند) ثبت نام را از این IP را متوقف کن. "
min_ban_entries_for_roll_up: "وقتی بر روی کلید جمع کردن کلیک کنید٬ یک ممنوعیت زیرشبکه ورودی جدید ساخته می شود اگر در آنجا حداقل (N) ورودی باشد. "
max_age_unmatched_emails: "ایمیل های ورودی همسان نشده نمایش داده شده بعد از (N) روز پاک شوند."
max_age_unmatched_ips: "IP ورودی همسان نشده نمایش داده شده بعد از (N) روز پاک شوند. "
@@ -2231,7 +2229,6 @@ fa_IR:
label: "نام انجمن شما"
placeholder: "انجمن آزموده"
site_description:
- label: "انجمن خود را در یک جمله کوتاه توضیح دهید"
placeholder: "محلی برای آموزش انواع زبانهای برنامه نویسی"
introduction:
title: "مقدمه"
diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml
index 2879a738ee..dc35ee9f45 100644
--- a/config/locales/server.fi.yml
+++ b/config/locales/server.fi.yml
@@ -249,7 +249,6 @@ fi:
topic_invite:
failed_to_invite: "Käyttäjää ei voi kutsua ketjuun, jollei hän ole jonkun näistä ryhmistä jäsen: %{group_names}."
user_exists: "Pahoittelut, tämä käyttäjä on jo kutsuttu. Voit kutsua toisen käyttäjän ketjuun vain yhden kerran."
- muted_invitee: "Valitettavasti tämä käyttäjä on vaimentanut sinut."
muted_topic: "Valitettavasti käyttäjä on vaimentanut tämän ketjun."
receiver_does_not_allow_pm: "Tämä käyttäjä ei salli sinun lähettää hänelle yksityisviestejä."
sender_does_not_allow_pm: "Et salli tämän käyttäjän lähettää sinulle yksityisviestejä."
@@ -1446,7 +1445,6 @@ fi:
summary_likes_required: "Vähimmäismäärä tykkäyksiä ketjussa ennen kuin Näytä ketjun tiivistelmä otetaan käyttöön. Muutokset tähän asetukseen otetaan käyttöön taannehtivasti viikon kuluessa."
summary_percent_filter: "Kun käyttäjä klikkaa 'Näytä ketjun tiivistelmä', näytä paras % viesteistä"
summary_max_results: "Maksimimäärä viestejä, jotka näytetään ketjun tiivistelmässä"
- enable_personal_messages: "Salli luottamustason 1 saavuttaneiden käyttäjien (määritettävissä viestien lähettämiseen vaadittavassa vähimmäisluottamustasossa) lähettää yksityisviestejä ja vastata niihin. Huomioi, että henkilökunta voi aina lähettää yksityisviestejä."
enable_system_message_replies: "Sallii käyttäjien vastata järjestelmän viesteihin, vaikka yksityisviestit eivät olisikaan käytössä"
enable_long_polling: "Ilmoitusten käyttämä viestiväylä voi käyttää long pollingia"
enable_chunked_encoding: "Ota käyttöön palvelimen joukkokoodausvastaukset. Tämä ominaisuus toimii useimmissa määrityksissä, mutta jotkin välityspalvelimet saattavat puskuroida, mikä aiheuttaa vastausten viivästymisen"
@@ -1769,7 +1767,6 @@ fi:
topic_view_duration_hours: "Laske uusi ketjun katselu kerran per IP/käyttäjä joka N:s tunti"
user_profile_view_duration_hours: "Laske uusi profiilin katselu kerran per IP/käyttäjä joka N:s tunti"
levenshtein_distance_spammer_emails: "Verrattaessa sähköpostiosoitteita tunnettuihin roskapostittajiin, näin monen merkin ero saa vielä aikaan sumean osuman."
- max_new_accounts_per_registration_ip: "Jos samasta IP-osoitteesta on jo (n) luottamustason 0 käyttäjätiliä (eikä yhtään henkilökunnan tai vähintään LT2), lakkaa hyväksymästä uusia rekisteröitymisiä tästä IP:stä."
min_ban_entries_for_roll_up: "Kun Kokoa-painiketta painetaan, luodaan IP-porttikielloista aliverkon kattavia, kieltoja jos kieltoja on asettu vähintään (N) määrä."
max_age_unmatched_emails: "Poista osumattomat seulotut sähköpostiosoitteet (N) päivän jälkeen."
max_age_unmatched_ips: "Poista osumattomat seulotut IP-osoitteet (N) päivän jälkeen."
@@ -4262,10 +4259,8 @@ fi:
label: "Yhteisön nimi"
placeholder: "Jennin mesta"
site_description:
- label: "Kuvaile yhteisöä yhdellä lyhyellä lauseella"
placeholder: "Paikka, jossa Jenni ja hänen ystävänsä voivat keskustella kivoista jutuista"
short_site_description:
- label: "Kuvaile yhteisöä muutamalla sanalla lyhyesti"
placeholder: "Paras yhteisö ikinä missään"
introduction:
title: "Johdanto"
diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml
index daeca741ed..54239c5f93 100644
--- a/config/locales/server.fr.yml
+++ b/config/locales/server.fr.yml
@@ -249,7 +249,6 @@ fr:
topic_invite:
failed_to_invite: "L'utilisateur ne peut pas être invité dans ce sujet sans être membre d'un des groupes suivants : %{group_names}."
user_exists: "Nous sommes désolés, cet utilisateur a déjà été invité. Vous ne pouvez inviter un utilisateur qu'une seule fois par sujet."
- muted_invitee: "Nous sommes désolés, cet utilisateur vous a mis(e) en sourdine."
muted_topic: "Nous sommes désolés, cet utilisateur a mis ce sujet en sourdine."
receiver_does_not_allow_pm: "Nous sommes désolés, cet utilisateur ne vous autorise pas à lui envoyer des messages directs."
sender_does_not_allow_pm: "Nous sommes désolés, vous n'autorisez pas cet utilisateur à vous envoyer des messages directs."
@@ -1446,7 +1445,6 @@ fr:
summary_likes_required: "Nombre minimal de « J'aime » dans un sujet avant que la fonctionnalité « Résumer ce sujet » soit activée. Les modifications de ce paramètre sont appliquées rétroactivement sur une semaine."
summary_percent_filter: "Quand un utilisateur clique sur « Résumer ce sujet », montrer le top % des messages"
summary_max_results: "Nombre maximal de messages inclus dans le résultat de « Résumer ce sujet »"
- enable_personal_messages: "Autoriser les utilisateurs de niveau de confiance 1 à créer des messages directs et à y répondre (configurable via le niveau de confiance minimal pour envoyer des messages directs). Notez que les responsables peuvent toujours envoyer des messages directs."
enable_system_message_replies: "Permettre aux utilisateurs de répondre aux messages système, même si les messages directs sont désactivés."
enable_long_polling: "Utiliser les requêtes longues pour le flux de notifications."
enable_chunked_encoding: "Activer les réponses d'encodage par bloc par le serveur. Cette fonctionnalité fonctionne dans la plupart des configurations, mais certains proxys peuvent mettre les réponses en mémoire tampon et risquent donc de les retarder"
@@ -1769,7 +1767,6 @@ fr:
topic_view_duration_hours: "Compter la vue d'un sujet une seule fois par IP ou par utilisateur toutes les N heures"
user_profile_view_duration_hours: "Compter la vue d'un profil d'utilisateur une seule fois par IP ou par utilisateur qui visite toutes les N heures"
levenshtein_distance_spammer_emails: "Une adresse courriel sera attribuée à un spammeur connu même si elle diffère par ce nombre de caractères."
- max_new_accounts_per_registration_ip: "S'il y a déjà (n) comptes avec un niveau de confiance 0 en provenance de cette adresse IP (et aucun n'est un responsable ou avec un niveau de confiance 2 et plus), ne plus accepter de nouvelles inscriptions depuis cette adresse IP."
min_ban_entries_for_roll_up: "En cliquant sur le bouton Consolider, une liste d'au moins (N) adresses interdites sera remplacée par une plage de sous réseau."
max_age_unmatched_emails: "Effacer les adresses courriel sous surveillance sans correspondance après (N) jours"
max_age_unmatched_ips: "Effacer les adresses IP sous surveillance sans correspondance après (N) jours"
@@ -4273,10 +4270,8 @@ fr:
label: "Nom de votre communauté"
placeholder: "Le repaire de Jeanne"
site_description:
- label: "Décrivez votre communauté en une courte phrase"
placeholder: "Un endroit de discussion pour Jeanne et ses amis"
short_site_description:
- label: "Décrivez votre communauté en quelques mots"
placeholder: "Meilleure communauté de tous les temps"
introduction:
title: "Introduction"
diff --git a/config/locales/server.gl.yml b/config/locales/server.gl.yml
index 3ea5ad9ec8..356eb108cf 100644
--- a/config/locales/server.gl.yml
+++ b/config/locales/server.gl.yml
@@ -235,7 +235,6 @@ gl:
topic_invite:
failed_to_invite: "O usuario non pode ser invitado a este tema por non ser membro de ningún destes grupos: %{group_names}."
user_exists: "Este usuario xa foi invitado. Só se pode invitar a un usuario a un tema unha única vez."
- muted_invitee: "Sentímolo, ese usuario silenciouno."
muted_topic: "Sentímolo, ese usuario silenciou este tema."
receiver_does_not_allow_pm: "Sentímolo, ese usuario non lle permite enviarlles mensaxes privadas."
sender_does_not_allow_pm: "Sentímolo, vostede non permite que ese usuario lle envíe mensaxes privadas."
@@ -1399,7 +1398,6 @@ gl:
summary_likes_required: "Número mínimo de gústame nun tema antes de que «Resumir este tema» estea activado. Os cambios neste axuste aplicaranse retroactivamente despois dunha semana."
summary_percent_filter: "Cando un usuario preme en «Resumir este tema» mostrar a porcentaxe de publicacións destacadas"
summary_max_results: "Número máximo de publicacións que devolve a opción «Resumir este tema»"
- enable_personal_messages: "Permitirlles aos usuarios con nivel de confianza 1 (configurable a través de nivel mínimo de confianza para enviar mensaxes) crear mensaxes e respondelas. Repare en que o equipo sempre pode enviar mensaxes pasar o que pasar."
enable_system_message_replies: "Permitirlles aos usuarios responder mensaxes do sistema, mesmo aínda que as mensaxes persoais estean desactivadas"
enable_long_polling: "O bus de mensaxes utilizado para a notificación pode utilizar «long polling»"
enable_chunked_encoding: "Active as respostas mediante codificación fragmentaria do servidor. Esta funcionalidade serve na maioría das configuracións, pero algúns servidores intermedios poden retelas, o que provocaría o atraso nas respostas"
@@ -1710,7 +1708,6 @@ gl:
topic_view_duration_hours: "Contar unha visita a un novo tema por IP/usuario cada N horas"
user_profile_view_duration_hours: "Contar unha visita de perfil por IP/usuario cada N horas"
levenshtein_distance_spammer_emails: "Ao revisar coincidencias en correos electrónicos de remitentes non desexados, número de caracteres diferentes que permiten una coincidencia parcial."
- max_new_accounts_per_registration_ip: "Se hai xa (n) contas con nivel de confianza 0 desde este IP (e ningunha é dun membro do equipo ou de nivel de confianza 2 ou superior), prohibir rexistros novos desde ese IP."
min_ban_entries_for_roll_up: "Ao premer no botón Pregar, crearase unha nova entrada de veto de subrede se hai polo menos (N) entradas."
max_age_unmatched_emails: "Eliminar entradas de correos electrónicos prohibidos que non coincidan despois de (N) días."
max_age_unmatched_ips: "Eliminar entradas de IP prohibidas que non coincidan despois de (N) días."
@@ -4094,10 +4091,8 @@ gl:
label: "O nome da súa comunidade"
placeholder: "Reunión de Helena"
site_description:
- label: "Describa a súa comunidade cunha frase curta"
placeholder: "Un lugar para que Helena e as súas amizades discutan sobre cousas interesantes"
short_site_description:
- label: "Describa a súa comunidade nunhas poucas palabras"
placeholder: "A mellor comunidade do mundo"
introduction:
title: "Introdución"
diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml
index 70bdedae21..71e5968e73 100644
--- a/config/locales/server.he.yml
+++ b/config/locales/server.he.yml
@@ -120,7 +120,7 @@ he:
silenced_user_error: "מתרחש כאשר השולח הושתק."
bad_destination_address: "מתרחש כאשר אף אחת מהכתובות בשדות אל או עותק אינה תואמת לאף כתובת דוא״ל נכנסת מוגדרת."
strangers_not_allowed_error: "מתרחש כאשר משתמשים מנסים ליצור נושא חדש בקטגוריה בה הם אינם חברים."
- insufficient_trust_level_error: "מתרחש כאשר משתמשים מנסים ליצור נושא חדש בקטגוריה בה אין להם את רמת האמון/הרשאה הנדרשת."
+ insufficient_trust_level_error: "קורה כאשר משתמשים מנסים ליצור נושא חדש בקטגוריה בה אין להם את דרגת האמון/הרשאה הנדרשת."
reply_user_not_matching_error: "מתרחש כאשר תגובה מגיעה מכתובת דוא״ל שונה מזו שאליה נשלחה ההתראה."
topic_not_found_error: "מתרחש כאשר הגיעה תגובה אבל הנושא התואם נמחק."
topic_closed_error: "מתרחש כאשר הגיעה תגובה אבל הנושא התואם נסגר."
@@ -267,7 +267,6 @@ he:
topic_invite:
failed_to_invite: "לא ניתן להזמין את המשתמש לנושא הזה בהעדר חברות באחת הקבוצות הבאות: %{group_names}"
user_exists: "כבר נשלחה הזמנה למשתמש זה. ניתן להזמין משתמש לנושא פעם אחת בלבד, עמך הסליחה."
- muted_invitee: "הושתקת על ידי המשתמש הזה, עמך הסליחה."
muted_topic: "המשתמש הזה השתיק את הנושא הזה, עמך הסליחה."
receiver_does_not_allow_pm: "משתמש זה לא מאשר לך לשלוח אליו הודעות פרטיות, עמך הסליחה."
sender_does_not_allow_pm: "אסרת על משתמש זה לשלוח אליך הודעות פרטיות, עמך הסליחה."
@@ -687,7 +686,7 @@ he:
trust_levels:
admin: "ניהול"
staff: "סגל"
- change_failed_explanation: "ניסיתם להוריד ברמה את %{user_name} ל- '%{new_trust_level}'. אולם רמת האמון שלהם היא כבר '%{current_trust_level}'. %{user_name} ישאר/תישאר ב-'%{current_trust_level}' - אם ברצונכם להוריד את המשתמש/ת נעלו קודם את רמת האמון"
+ change_failed_explanation: "ניסית להוריד את דרגת האמון של %{user_name} לכדי ‚%{new_trust_level}’. למרות שדרגת האמון של המשתמש היא כבר ‚%{current_trust_level}’. דרגת האמון של %{user_name} תישאר ‚%{current_trust_level}’ - כדי להוריד משתמש בדרגה יש לנעול את דרגת האמון תחילה"
post:
image_placeholder:
broken: "תמונה זו פגומה"
@@ -1046,7 +1045,7 @@ he:
unwatch_category: "הפסיקו לצפות בכל הנושאים בקטגוריה %{category}"
mailing_list_mode: "כבו את מצב ״רשימת תפוצה״"
all: "לא לשלוח לי הודעות דוא״ל מאת %{sitename}"
- different_user_description: "התחברת כמשתמש אחר מזה שאליו נשלחה הודעת דוא״ל. נא להתנתק או לעבור למצב אלמוני ולנסות שוב."
+ different_user_description: "נכנסת כמשתמש אחר מזה שאליו נשלחה הודעת דוא״ל. נא להתנתק או לעבור למצב אלמוני ולנסות שוב."
not_found_description: "לא הצלחנו למצוא את ביטול המינוי הזה עמך הסליחה. יכול להיות שהקישור בהודעה שקיבלת ישן מדי או שתוקפו פג?"
log_out: "התנתקות"
submit: "שמירת העדפות"
@@ -1073,7 +1072,7 @@ he:
confirm_title: להמשיך אל %{site_name}
logging_in_as: כניסה למערכת בתור %{username}
confirm_button: סיום הכניסה
- no_trust_level: "מצטערים, אין לכם את רמת האמון הנדרשת כדי לגשת ל API של המשתמשים"
+ no_trust_level: "אין לך את דרגת האמון הנדרשת כדי לגשת ל־API של המשתמשים, סליחה"
generic_error: "לא הצלחנו לייצר את מפתחות ה־API של המשתמש, ייתכן שיכולת זו הושבתה על ידי הנהלת האתר, עמך הסליחה"
scopes:
message_bus: "עדכונים חיים"
@@ -1530,14 +1529,14 @@ he:
email_subject: "תבנית נושא בהתאמה אישית להודעות דוא״ל תקניות. יש לעיין ב־https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801"
detailed_404: "מספק פרטים נוספים למשתמשים בנוגע לסיבה שבגינה אין להם גישה לנושא מסוים. לתשומת לבך: מדובר בתצורה לא מאובטחת כיוון שמשתמשים ידעו אם כתובת מקשרת לנושא תקף."
enforce_second_factor: "מאלץ משתמשים להפעיל אימות דו־שלבי. יש לבחור ב‚הכול’ כדי לאלץ את כל המשתמשים. יש לבחור ב‚סגל’ כדי לאכוף על חברי סגל בלבד."
- force_https: "הכריחו את אתרכם להשתמש אך ורק ב HTTPS. אזהרה: אל תאפשרו זאת עד שתוודאו ש HTTPS מותקן ועובד ממש בכל המקרים! וידאתם את הגדרות ה CDN שלכם, כל שירותי ההתחברות, וכל הלוגואים / תלויות החיצוניים - כדי לוודא שכולם עובדים גם כן עם HTTPS?"
+ force_https: "אילוץ האתר שלך להשתמש ב־HTTPS בלבד. אזהרה: אין להפעיל זאת עד שווידאת ש־HTTPS מוגדר ועובד לחלוטין בכל מקום! בדקת שה־CDN, הכניסה דרך רשתות חברתיות ולוגואים / תלויות חיצוניות גם כן תומכים ב־HTTPS?"
summary_score_threshold: "הניקוד המינימלי הנדרש כדי שפוסט ייכלל ב\"סיכום נושא זה\""
summary_posts_required: "כמות פוסטים מזערית בנושא בטרם מופעל ‚תקציר לנושא זה’. שינויים בהגדרה זו יחולו רטרואקטיבית תוך שבוע."
summary_likes_required: "כמות לייקים מזערית בנושא בטרם מופעל ‚תקציר לנושא זה’. שינויים בהגדרה זו יחולו רטרואקטיבית תוך שבוע."
summary_percent_filter: "כאשר משתמש/ת מקליקים על \"סיכום נושא זה\", הציגו את % הפוסטים הראשונים"
summary_max_results: "מספר הפוסטים המרבי שיוחזר על ידי ‚סיכום הנושא הזה’"
summary_timeline_button: "הצגת כפתור ‚סיכום’ בציר הזמן"
- enable_personal_messages: "לאפשר למשתמשים בדרגת אמון 1 (ניתן להגדרה דרך דרגת אמון מזערית לשליחת הודעות) ליצור הודעות ולענות להודעות. נא לשים לב שהסגל תמיד יכול לשלוח הודעות, ללא קשר."
+ enable_personal_messages: "לאפשר למשתמשים בדרגת אמון 1 (ניתן להגדרה דרך אמון מזערי לשליחת הודעות) ליצור הודעות ולענות להודעות. נא לשים לב שהסגל תמיד יכול לשלוח הודעות, ללא קשר."
enable_system_message_replies: "מאפשר למשתמשים להגיב להודעות מערכת אפילו כשהודעות אישיות מושבתות"
enable_long_polling: "באס הודעות שמשמש להתראות יכול להשתמש בתשאול ארוך (long polling)"
enable_chunked_encoding: "הפעלת תגובות קידוד מחולקות מצד השרת. תכונה זו עובדת ברוב התצורות אך חלק מהמתווכים עשויים לכלוא כצעד ביניים, מה שעלול לגרום להאטה"
@@ -1553,7 +1552,7 @@ he:
max_topics_in_first_day: "הכמות המקסימלית של נושאים שמשתמשים מורשים ליצור ב 24 השעות הראשונות לאחר הפוסט הראשון שלהם"
max_replies_in_first_day: "הכמות המקסימלית של תגובות שמשתמשים מורשים ליצור ב 24 השעות הראשונות אחרי יצירת הפוסט הראשון שלהם"
tl2_additional_likes_per_day_multiplier: "להגדיל את כמות הלייקים האפשרית ביום עבור tl2 (משתמש) באמצעות הכפלה במספר זה. "
- tl3_additional_likes_per_day_multiplier: "להגדיל את כמות הלייקים האפשרית ביום עבור רמת-אמון 3 (רגיל) באמצעות הכפלה במספר זה."
+ tl3_additional_likes_per_day_multiplier: "הכפלה במספר הזה מגדילה את מגבלת הלייקים ליום עבור דרגת אמון 3 (רגיל)"
tl4_additional_likes_per_day_multiplier: "להגדיל את כמות הלייקים האפשרית ביום עבור tl4 (מנהיג) באמצעות הכפלה במספר זה. "
tl2_additional_edits_per_day_multiplier: "להגדיל את כמות העריכות האפשרית ביום עבור דרגת אמון 2 (חברים) באמצעות הכפלה במספר זה"
tl3_additional_edits_per_day_multiplier: "להגדיל את כמות העריכות האפשרית ביום עבור דרגת אמון 3 (רגיל) באמצעות הכפלה במספר זה"
@@ -1722,8 +1721,8 @@ he:
max_personal_messages_per_day: "מספר ההודעות האישיות החדשות המרבי שמשתמשים יכולים ליצור ביום."
max_invites_per_day: "מספר מקסימלי של הזמנות שיכולים משתמשים לשלוח ביום."
max_topic_invitations_per_day: "מספר מירבי של הזמנות לנושא שמשתמשים יכולים לשלוח ביום. "
- max_logins_per_ip_per_hour: "מספר מקסימלי של התחברויות מורשות לכל כתובת IP בשעה"
- max_logins_per_ip_per_minute: "מספר מקסימלי של התחברויות מורשות לכתובת IP לדקה"
+ max_logins_per_ip_per_hour: "מספר כניסות מרבי מורשה לכל כתובת IP בשעה"
+ max_logins_per_ip_per_minute: "מספר כניסות מרבי מורשה לכל כתובת IP בדקה"
max_post_deletions_per_minute: "מספר הפוסטים המרבי שמשתמש יכול למחוק בדקה אחת. 0 ישבית מחיקת פוסטים."
max_post_deletions_per_day: "מספר הפוסטים המרבי שמשתמש יכול למחוק ביום אחד. 0 ישבית מחיקת פוסטים."
invite_link_max_redemptions_limit: "כמות הניצולים המרבית שמורשית לקישורים הזמנה לא יכולה לעבור את הערך הזה."
@@ -1768,34 +1767,34 @@ he:
composer_media_optimization_image_encode_quality: "איכות קידוד JPEG המשמשת בתהליך הקידוד מחדש."
min_ratio_to_crop: "יחס לחיתוך תמונות גבוהות. יש להקליד את התוצאה ברוחב / גובה."
simultaneous_uploads: "מספר הקבצים המרבי שניתן לגרור ולהשליך אל מחבר ההודעות"
- default_invitee_trust_level: "ברירת מחדל של רמת אמון (0-4) של משתמשים מוזמנים."
- default_trust_level: "רמת אמון (0-4) לכל המשתמשים החדשים. אזהרה! שינוי של משתנה זה שם אתכם בסיכון רציני לספאם."
- tl1_requires_topics_entered: "כמה נושאים משתמשים חדשים צריכים להתחיל עד שישודרגו לרמת אמון 1."
- tl1_requires_read_posts: "כמה פוסטים משתמשים צריכים לקרוא לפני שישודרגו לרמת אמון 1."
- tl1_requires_time_spent_mins: "כמה דקות משתמשים חדשים צריכים לקרוא פוסטים לפני שישודרגו לרמת אמון 1."
- tl2_requires_topics_entered: "כמה נושאים משתמשים חדשים צריכים להתחיל עד שישודרגו לרמת אמון 2."
- tl2_requires_read_posts: "כמה פוסטים משתמשים צריכים לקרוא לפני שישודרגו לרמת אמון 2."
- tl2_requires_time_spent_mins: "כמה דקות משתמשים חדשים צריכים לקרוא פוסטים לפני שישודרגו לרמת אמון 2."
- tl2_requires_days_visited: "כמה ימים שונים משתמשים צריכים לבקר באתר לפני שישודרגו לרמת אמון 2."
- tl2_requires_likes_received: "כמה \"לייקים\" משתמשים צריכים לקבל לפני שישודרגו לרמת אמון 2."
- tl2_requires_likes_given: "כמה לייקים משתמשים צריכים לתת לפני שישודרגו לרמת אמון 2."
- tl2_requires_topic_reply_count: "על כמה נושאים משתמשים צריכים לענות לפני שישודרגו לרמת אמון 2."
- tl3_time_period: "דרישות פרק זמן של רמת אמון 3 (בימים)"
- tl3_requires_days_visited: "מספר מינימלי של ימים שמשתמש צריך לבקר באתר ב (תקופת זמן רמת-אמון-3) הימים האחרונים כדי להיות מועמד לקידום לרמת אמון 3. קבעו ליותר מתקופת זמן של רמת-אמון-3 כדי לנטרל קידום ל רמת-אמון-3. (0 או יותר)."
- tl3_requires_topics_replied_to: "מספר מינימלי של נושאים שמשתמש צריך להגיב עליהם ב (תקופת זמן רמת-אמון-3) הימים האחרונים כדי להיות מועמד לקידום לרמת אמון 3. (0 או יותר)"
- tl3_requires_topics_viewed: "אחוז הנושאים שנוצרו במהלך (תקופת זמן רמת-אמון-3) הימים האחרונים שמשתמש צריך לצפות בהם כדי שיוכל להיות מועמד לקידום לרמת אמון 3. (0 עד 100)"
- tl3_requires_topics_viewed_cap: "המספר המקסימלי הנדרש של נושאים לצפייה ב (תקופת זמן רמת-אמון-3) הימים האחרונים."
- tl3_requires_posts_read: "אחוז הנושאים שנוצרו ב (תקופת זמן רמת-אמון-3) הימים האחרונים שעל משתמש לצפות בהם כדי להיות מועמד לרמת אמון 3. (0 עד 100)"
- tl3_requires_posts_read_cap: "המספר המקסימלי הנדרש של פוסטים לקריאה ב (תקופת זמן רמת-אמון-3) הימים האחרונים."
- tl3_requires_topics_viewed_all_time: "מספר מינימלי של נושאים שמשתמשים צריכים לעיין בהם על מנת שיתאפשר להם להיות משודרגים לרמת אמון 3."
- tl3_requires_posts_read_all_time: "מספר מינימלי של פוסטים שמשתמשים קראו על מנת שיוכלו להיות משודרגים לרמת אמון 3."
- tl3_requires_max_flagged: "אסור שלמשתמש ידוגלו למעלה מ x פוסטים על ידי x משתמשים שונים ב (תקופת זמן רמת-אמון-3) הימים האחרונים כדי להיות מועמד לקידום לרמת אמון 3, כאשר x הוא ערך ההגדרה. (0 או יותר)"
- tl3_promotion_min_duration: "מספר הימים המינימלי ששדרוג לרמת אמון 3 אורך לפני שניתן להוריד משתמשים בדרגה בחזרה לרמת אמון 2."
- tl3_requires_likes_given: "המספר המינימלי של לייקים שצריכים להנתן ב (תקופת זמן רמת-אמון-3) הימים האחרונים כדי להיות מועמד לקידום לרמת אמון 3."
- tl3_requires_likes_received: "המספר המינימלי של לייקים שחייבים להתקבל ב (תקופת זמן רמת-אמון-3) הימים האחרונים כדי להיות מועמדים לקידום לרמת אמון 3."
- tl3_links_no_follow: "מניעת הסרת rel=nofollow מקישורים שמפורסמים על ידי משתמשים ברמת אמון 3."
+ default_invitee_trust_level: "דרגת האמון כבררת המחדל למשתמשים שהוזמנו (0-4)"
+ default_trust_level: "דרגת האמון כבררת המחדל (0-4) לכל המשתמשים החדשים. אזהרה! שינוי הערך הזה חושף אותך בפני סכנה ממשית לספאם."
+ tl1_requires_topics_entered: "לכמה נושאים על משתמש חדש להיכנס בטרם קידומו לדרגת אמון 1."
+ tl1_requires_read_posts: "כמה פוסטים על משתמש חדש לקרוא בטרם קידומו לדרגת אמון 1."
+ tl1_requires_time_spent_mins: "כמה דקות קריאה של פוסטים על משתמש חדש לצבור בטרם קידומו לדרגת אמון 1."
+ tl2_requires_topics_entered: "לכמה נושאים על משתמש להיכנס בטרם קידומו לדרגת אמון 2."
+ tl2_requires_read_posts: "כמה פוסטים על משתמש לקרוא בטרם קידומו לדרגת אמון 2."
+ tl2_requires_time_spent_mins: "כמה דקות קריאה של פוסטים על משתמש לצבור בטרם קידומו לדרגת אמון 2."
+ tl2_requires_days_visited: "כמה ימים לא רצופים על משתמש לבקר באתר בטרם קידומות לדרגת אמון 2."
+ tl2_requires_likes_received: "כמה לייקים על משתמש לקבל בטרם קידומו לדרגת אמון 2."
+ tl2_requires_likes_given: "כמה לייקים על משתמש להעניק בטרם קידומו לדרגת אמון 2."
+ tl2_requires_topic_reply_count: "על כמה נושאים על משתמש לענות בטרם קידומו לדרגת אמון 2."
+ tl3_time_period: "דרישות ותק (בימים) לדרגת אמון 3"
+ tl3_requires_days_visited: "מספר הימים המזערי שעל משתמש לבקר באתר במשך (תקופת זמן עם דרגת אמון 3) ימים כדי לעמוד בתנאי הקידום לדרגת אמון 3. אפשר להגדיר לתקופת זמן ממושכת יותר מאשר דרגת אמון 3 כדי להשבית את הקידומים לדרגת אמון 3. (0 ומעלה)"
+ tl3_requires_topics_replied_to: "מספר הנושאים המזערי שעל משתמש להגיב בהם במשך (תקופת זמן עם דרגת אמון 3) ימים כדי לעמוד בתנאי הקידום לדרגת אמון 3. (0 ומעלה)"
+ tl3_requires_topics_viewed: "אחוז הנושאים שנוצרו במהלך (תקופה עם דרגת אמון 3) ימים שעל משתמש לצפות בהם כדי לעמוד בתנאי הקידום לדרגת אמון 3. (0 עד 100)"
+ tl3_requires_topics_viewed_cap: "המספר המרבי הדרוש של צפיות בנושאים במשך (תקופה בדרגת אמון 3) ימים."
+ tl3_requires_posts_read: "אחוז הפוסטים שנוצרו במהלך (תקופה עם דרגת אמון 3) ימים שעל משתמש לצפות בהם כדי לעמוד בתנאי הקידום לדרגת אמון 3. (0 עד 100)"
+ tl3_requires_posts_read_cap: "המספר המרבי הדרוש של פוסטים שנקראו במשך (תקופה בדרגת אמון 3) ימים."
+ tl3_requires_topics_viewed_all_time: "מספר הנושאים הכולל המזערי שעל משתמש לצפות בהם כדי לעמוד בתנאי הקידום לדרגת אמון 3."
+ tl3_requires_posts_read_all_time: "מספר הפוסטים הכולל המזערי שעל משתמש לקרוא כדי לעמוד בתנאי הקידום לדרגת אמון 3."
+ tl3_requires_max_flagged: "אסור שלמשתמש יהיו יותר מ־x פוסטים שמסומנים בדגל על ידי x משתמשים שונים במשך (משך זמן דרגת אמון 3) הימים האחרונים כדי לעמוד בתנאי הקידום לדרגת אמון 3, כאשר x הוא ערך ההגדרה הזאת. (0 ומעלה)"
+ tl3_promotion_min_duration: "מספר הימים המזערי שקידום לרמת אמון 3 נמשך לפני שניתן להוריד משתמשים בחזרה לדרגת אמון 2."
+ tl3_requires_likes_given: "מספר הלייקים המזערי שיש להעניק במשך (פרק זמן בדרגת אמון 3) הימים האחרונים כדי לעמוד בתנאי הקידום לדרגת אמון 3."
+ tl3_requires_likes_received: "מספר הלייקים המזערי שיש לקבל במשך (פרק זמן בדרגת אמון 3) הימים האחרונים כדי לעמוד בתנאי הקידום לדרגת אמון 3."
+ tl3_links_no_follow: "מניעת הסרת rel=nofollow מקישורים שמפורסמים על ידי משתמשים בדרגת אמון 3."
trusted_users_can_edit_others: "לאפשר למשתמשים בדרגת אמון גבוהה לערוך תוכן של משתמשים אחרים"
- min_trust_to_create_topic: "רמת האמון המינימלית הנדרשת כדי לייצר נושא חדש."
+ min_trust_to_create_topic: "דרגת האמון המזערית הנדרשת ליצירת נושא חדש."
allow_flagging_staff: "אם אפשרות זו פעילה, משתמשים יכולים לסמן בדגל פוסטים שפורסמו על ידי חשבונות סגל."
min_trust_to_edit_wiki_post: "דרגת האמון המזערית הנדרשת לעריכת פוסט המסומן כוויקי."
min_trust_to_edit_post: "דרגת האמון המזערית הנדרשת לעריכת פוסטים."
@@ -1868,7 +1867,7 @@ he:
topic_view_duration_hours: "ספרו צפיות חדשות בנושא פעם אחת לכל IP/משתמש לכל N שעות"
user_profile_view_duration_hours: "ספרו צפיות בפרופיל משתמש פעם אחת לכל IP/משתמש בכל N שעות"
levenshtein_distance_spammer_emails: "כאשר מתאימים דוא\"ל של ספאמרים, מספר ההבדלים בתווים שעדיין מאפשרים התאמה מטושטשת."
- max_new_accounts_per_registration_ip: "אם ישנם כבר (n) חשבונות עם רמת אמון 0 מכתובת IP זו (ואף אחד מהם אינו חבר צוות, או בעל/ת רמת אמון 2 ומעלה), הפסיקו קבלת הרשמות מכתובת IP זו."
+ max_new_accounts_per_registration_ip: "אם יש כבר (n) חשבונות בדרגת אמון 0 מכתובת IP זו (ואף אחד מהם אינו חבר צוות, או בדרגת אמון 2 ומעלה), לא יתקבלו עוד הרשמות מכתובת IP זו. הגדרה ל־0 תשבית את המגבלה."
min_ban_entries_for_roll_up: "בעת לחיצה על לחצן הגלילה למעלה, ייוצר איסור כניסת משנה (subnet ban entry) חדשה אם יש לפחות (N) ערכים."
max_age_unmatched_emails: "למחוק רשומות דוא״ל במעקב שלא קיבלו התאמה לאחר (N) ימים."
max_age_unmatched_ips: "מחק ערכי IP לא תואמים שמוצגים לאחר (N) ימים."
@@ -1932,7 +1931,7 @@ he:
pop3_polling_delete_from_server: "למחוק הודעות דוא״ל מהשרת. לתשומת לבך: השבתת אפשרות זו מאלצת אותך לפנות את תיבת הדוא״ל באופן ידני"
log_mail_processing_failures: "לתעד את כל שגיאות עיבוד הדוא״ל אל /logs"
email_in: 'לאפשר למשתמשים לפרסם נושאים חדשים באמצעות דוא״ל (נדרש תשאול ידני או דרך pop3). יש להגדיר את הכתובות בלשונית ה„הגדרות” שבכל קטגוריה.'
- email_in_min_trust: "רמת האמון המינימלית הנדרשת למשתמשים כדי שיוכלו להעלות נושאים חדשים באמצעות הדוא\"ל."
+ email_in_min_trust: "דרגת האמון המזערית הנדרשת למשתמש כדי לפרסם נושאים חדשים דרך הדוא״ל."
email_in_authserv_id: "מזהה השירות מבצע בדיקות אימות על הודעות דוא״ל נכנסות. יש לעיין ב־https://meta.discourse.org/t/134358 לקבלת הנחיות כיצד להגדיר זאת."
email_in_spam_header: "כותרת הודעת הדוא״ל לאיתור ספאם."
enable_imap: "הפעלת IMAP לסנכרון הודעות קבוצתיות."
@@ -1977,7 +1976,7 @@ he:
enable_category_group_moderation: "לאפשר לקבוצות לסקור תוכן בקטגוריות מסוימות"
group_in_subject: "ניתן להגדיר %%{optional_pm} בנושא הודעת הדוא״ל לשם הקבוצה הראשונה בהודעה הפרטית (PM), למידע נוסף: התאמת תבנית הנושא להודעות דוא״ל תקניות"
allow_anonymous_posting: "לאפשר למשתמשים לעבור למצב אלמוני"
- anonymous_posting_min_trust_level: "רמת האמון המזערית הנדרשת כדי לאפשר פרסום אלמוני"
+ anonymous_posting_min_trust_level: "דרגת האמון המזערית הנדרשת כדי לאפשר פרסום אלמוני"
anonymous_account_duration_minutes: "כדי להגן על האלמוניות יש ליצור חשבון אלמוני חדש כל N דקות עבור כל משתמש. למשל: אם ההגדרה היא 600, בחלוף 600 דקות מהפוסט האחרון וגם אם המשתמש עבר למצב אלמוני, ייווצר חשבון אלמוני חדש."
hide_user_profiles_from_public: "להשבית כרטיסי משתמשים, פרופילי משתמשים וספריית משתמשים עבור משתמשים אלמוניים."
allow_users_to_hide_profile: "לאפשר למשתמשים להחביא את הפרופיל והנוכחות שלהם"
@@ -2004,6 +2003,7 @@ he:
global_notice: "הצגת מודעה גלובלית דחופה בגדר חירום לכל המבקרים, יש להחליף בתוכן ריק כדי להסתיר אותה (מותר HTML)."
disable_system_edit_notifications: "ביטול התראות עריכה על ידי משתמש המערכת כאשר 'download_remote_images_to_local' פעיל."
disable_category_edit_notifications: "השבתת התראות על עריכת קטגוריות בנושאים."
+ disable_tags_edit_notifications: "השבתת התראות על עריכת תגיות בנושאים."
notification_consolidation_threshold: "מספר ההתראות שסומנו בלייק או בקשות שהתקבלו לפני שההתראות קובצו להתראה אחת. יש להגדיר ל־0 כדי להשבית."
likes_notification_consolidation_window_mins: "משך הזמן בשניות בו התראות מקובצות להתראה אחת לאחר שהגיעו לסף הזה. ניתן להגדיר את הסף דרך `SiteSetting.notification_consolidation_threshold` (סף קיבוץ התראות)."
automatically_unpin_topics: "הסרת נעיצה אוטומטית של נושאים כאשר המשתמשים מגיעים לתחתית."
@@ -2054,8 +2054,8 @@ he:
emoji_autocomplete_min_chars: "מספר התווים המזערי שנדרש להקפצת חלונית השלמה אוטומטית של אמוג׳י"
enable_inline_emoji_translation: "הפעלת תרגום לאמוג׳י כחלק מהשורה (ללא רווחים או סימני פיסוק לפני)."
approve_post_count: "מספר הפוסטים ממשתמשים חדשים או בסיסיים שחייבים לאשר אותם"
- approve_unless_trust_level: "פוסטים של משתמשים מתחת לרמת אמון זו חייבים לעבור אישור"
- approve_new_topics_unless_trust_level: "נושאים חדשים עבור משתמשים מתחת לרמת אמון זו חייבים להיות מאושרים"
+ approve_unless_trust_level: "פוסטים של משתמשים מתחת לדרגת אמון זו חייבים לעבור אישור"
+ approve_new_topics_unless_trust_level: "נושאים חדשים של משתמשים מתחת לדרגת אמון זו חייבים לעבור אישור"
approve_unless_staged: "יש לאשר נושאים ופוסטים חדשים עבור משתמשים מבוימים"
notify_about_queued_posts_after: "אם יש פוסטים שממתינים לסקירה מעבר לכמות כזו של שעות, יש לשלוח התראה לכל המפקחים. יש להגדיר ל־0 כדי לנטרל את ההתראות האלה."
auto_close_messages_post_count: "מספר פוסטים מקסימלי בהודעה לפני שהיא נסגרת אוטומטית (0 לניטרול)"
@@ -2111,12 +2111,12 @@ he:
revoke_api_keys_days: "מספר הימים בטרם שלילה אוטומטית של מפתח API שלא היה בשימוש (0 - לעולם לא)."
allow_user_api_keys: "לאפשר למשתמשים ליצור מפתחות API"
allow_user_api_key_scopes: "רשימת אזורים מותרים למפתחות API של משתמשים"
- min_trust_level_for_user_api_key: "רמת אמון נדרשת לייצור של מפתחות API של משתמש"
+ min_trust_level_for_user_api_key: "דרגת האמון הנדרשת ליצירת מפתחות API למשתמש"
allowed_user_api_auth_redirects: "כתובת מורשית להפניית אימות למפתחות API של משתמש. בסימן התו־כל * ניתן להשתמש כדי ללכוד חלק ממנה (למשל: www.example.com/*)."
allowed_user_api_push_urls: "URLים מורשים לדחיפת שרת ל API של משתמשים."
expire_user_api_keys_days: "מספר הימים בטרם פקיעת תוקף מפתח ה־API של המשתמש אוטומטית (0 - לעולם לא)."
tagging_enabled: "לאפשר תגיות על נושאים?"
- min_trust_to_create_tag: "רמת האמון המינימלית שנדרשת כדי ליצור תג."
+ min_trust_to_create_tag: "דרגת האמון המזערית שנדרשת כדי ליצור תגית."
max_tags_per_topic: "כמות התגיות המרבית שניתן להקצות לנושא."
max_tag_length: "אורך התג המקסימלי (מספר תווים)."
max_tag_search_results: "בעת חיפוש אחר תגיות, כמה תוצאות תופענה לכל היותר."
@@ -2125,7 +2125,7 @@ he:
tags_listed_by_group: "הצגת תגיות לפי קבוצת תגיות בעמוד התגיות."
tag_style: "סגנון ויזואלי לתג עיטורים."
allow_staff_to_tag_pms: "לאפשר לחברי הסגל לתייג כל הודעה אישית"
- min_trust_level_to_tag_topics: "רמת אמון מינימלית שדרושה כדי לתייג נושאים"
+ min_trust_level_to_tag_topics: "דרגת האמון המזערית שדרושה כדי לתייג נושאים"
suppress_overlapping_tags_in_list: "אם תגיות תואמות מילים בכותרות נושאים באופן מדויק, לא להציג את התגית"
remove_muted_tags_from_latest: "לא להציג נושאים שמתויגים רק בתגיות מושתקות ברשימת הנושאים האחרונים."
force_lowercase_tags: "לאלץ את כל התגיות החדשות להיות באותיות קטנות בלבד."
@@ -2348,7 +2348,7 @@ he:
security_key_no_matching_credential_error: "לא ניתן למצוא פרטי גישה במפתח האבטחה שסופק."
security_key_support_missing_error: "המכשיר או הדפדפן הנוכחי שלך לא תומך בשימוש במפתחות אבטחה, נא להשתמש בשיטה אחרת."
security_key_invalid: "אירעה שגיאה באימות מפתח האבטחה."
- not_approved: "חשבונך טרם אושר. יישלח אליך דואר אלקטרוני כשהוא יהיה מוכן להתחברות."
+ not_approved: "חשבונך טרם אושר. תישלח אליך הודעה בדוא״ל כשיהיה מוכן לכניסה."
incorrect_username_email_or_password: "שם משתמש, דואר אלקטרוני או סיסמה לא נכונים"
incorrect_password: "ססמה שגויה"
wait_approval: "תודה על שנרשמת. אנחנו ניידע אותך כשהחשבון שלך יאושר."
@@ -2358,7 +2358,7 @@ he:
not_allowed_from_ip_address: "אין לך אפשרות להיכנס בתור %{username} מכתובת IP זו."
admin_not_allowed_from_ip_address: "אין לך אפשרות להיכנס כהנהלת המערכת מכתובת IP זו."
reset_not_allowed_from_ip_address: "אי אפשר לבקש איפוס ססמה מכתובת ה־IP הזאת."
- suspended: "אינך יכול להתחבר עד %{date}."
+ suspended: "אין לך אפשרות להיכנס עד %{date}."
suspended_with_reason: "חשבון הושעה עד %{date}: %{reason}"
suspended_with_reason_forever: "חשבון מושעה: %{reason}"
errors: "%{errors}"
@@ -2369,7 +2369,7 @@ he:
csrf_detected: "זמן ההמתנה לאישור הסתיים או שהחלפת דפדפנים. נא לנסות שוב."
request_error: "אירעה שגיאה בעת התחלת האישור. נא לנסות שוב."
invalid_iat: "לא ניתן לאמת את אסימון האישור עקב הפרשים בשעון השרת. נא לנסות שוב."
- omniauth_error_unknown: "משהו השתבש במהלך עיבוד ההתחברות שלך, אנא נסו שנית."
+ omniauth_error_unknown: "משהו השתבש במהלך עיבוד הכניסה שלך, נא לנסות שוב."
omniauth_confirm_title: "כניסה עם %{provider}"
omniauth_confirm_button: "המשך"
authenticator_error_no_valid_email: "אף אחת מהכתובת שמשויכות אל %{account} אינה מורשית. ייתכן שיהיה עליך להגדיר את החשבון שלך עם כתובת דוא״ל אחרת."
@@ -2872,8 +2872,8 @@ he:
subject_template: "ייצוא הנתונים נכשל"
text_body_template: "אנו מתנצלים אך ייצוא הנתונים שלך נכשל. נא לעיין ביומנים או [ליצור קשר עם חבר סגל](%{base_url}/about)."
email_reject_insufficient_trust_level:
- title: "מייל נדחה, רמת אמון לא מספיקה"
- subject_template: "[%{email_prefix}] בעיית מייל -- רמת אמון לא מספיקה"
+ title: "דחיית דוא״ל עקב דרגת אמון בלתי מספקת"
+ subject_template: "[%{email_prefix}] בעיית דוא״ל -- דרגת אמון לא מספיקה"
text_body_template: |
ההודעה שניסית לשלוח בדוא״ל אל %{destination} (עם הכותרת %{former_title}) לא נשלחה, עמך הסליחה.
@@ -3533,15 +3533,15 @@ he:
title: "קביעת סיסמה"
subject_template: "[%{email_prefix}] קביעת סיסמה"
text_body_template: |
- מישהו ביקש להוסיף סיסמה לחשבון שלכם ב-[%{site_name}](%{base_url}). לחילופין, אתם יכולים להתחבר באמצעות כל שירות מקוון נתמך (גוגל, פייסבוק, וכד׳) שמקושר עם כתובת מייל זו.
+ מישהו ביקש להוסיף סיסמה לחשבון שלך ב־[%{site_name}](%{base_url}). לחלופין, אפשר להיכנס דרך כל שירות מקוון נתמך (Google, פייסבוק, וכד׳) שמקושר עם כתובת דוא״ל זו.
- אם לא אתם ביקשתם זאת, אתם יכולים פשוט להתעלם ממייל זה.
+ אם לא יזמת בקשה שכזאת, ניתן פשוט להתעלם מהודעה זו.
- לחצו על הקישור הבא כדי לבחור סיסמה:
+ לחיצה על הקישור הבא תאפשר לך לבחור סיסמה:
%{base_url}/u/password-reset/%{email_token}
admin_login:
title: "כניסת הנהלה"
- subject_template: "[%{email_prefix}] התחברות"
+ subject_template: "[%{email_prefix}] כניסה"
text_body_template: |
התקבלה בקשה להיכנס לחשבון שלך אצל [%{site_name}](%{base_url}).
@@ -4096,17 +4096,17 @@ he:
name: בסיסיים
description: הוענקו כל תכונות הקהילה החיוניות
long_description: |
- עיטור זה מוענק עם הגיעך לרמת אמון 1. תודה לך על הבעת העניין בקהילה תוך קריאת מגוון נושאים כדי להבין את מהות הקהילה שלנו לעומק. המגבלות שחלות על משתמשים חדשים אינן חלות עליך עוד, קיבלת יכולות קהילה חיוניות כגון הודעות אישיות, סימון בדגל, עריכת ויקי והיכולת לפרסם מגוון תמונות וקישורים.
+ עיטור זה מוענק עם הגיעך לדרגת אמון 1. תודה לך על הבעת העניין בקהילה תוך קריאת מגוון נושאים כדי להבין את מהות הקהילה שלנו לעומק. המגבלות שחלות על משתמשים חדשים אינן חלות עליך עוד, קיבלת יכולות קהילה חיוניות כגון הודעות אישיות, סימון בדגל, עריכת ויקי והיכולת לפרסם מגוון תמונות וקישורים.
member:
name: חברים
description: הוענקו הזמנות, שיחות קבוצתיות, עוד לייקים
long_description: |
- עיטור זה מוענק עם הגיעך לרמת אמון 2. תודה על השתתפותך בקהילה במשך מספר שבועות שמראים מעורבות של ממש בקהילה. מהיום יתאפשר לך לשלוח הזמנות דרך עמוד המשתמש שלך או דרך נושאים פרטניים, ליצור הודעות קבוצתיות אישיות ולקבל יותר לייקים כל יום.
+ עיטור זה מוענק עם הגיעך לדרגת אמון 2. תודה על השתתפותך בקהילה במשך מספר שבועות שמראה מעורבות של ממש בקהילה. מהיום יתאפשר לך לשלוח הזמנות דרך עמוד המשתמש שלך או דרך נושאים פרטניים, ליצור הודעות קבוצתיות אישיות ושיהיו לך יותר לייקים כל יום.
regular:
name: רגילים
description: הוענקו העברה בין קטגוריות, שינוי שם, קישורים עם מעקב, ויקי, עוד לייקים
long_description: |
- עיטור זה מוענק עם הגיעך לרמת אמון 3. תודה על נטילת חלק בקהילה דרך קבע לאורך תקופה של מספר חודשים. נכון להיום מידת המעורבות שלך הן בקריאה מרובה והן בתרומה לקהילה הן אלו שמחזקות את הקהילה שלנו והופכות אותה לנהדרת. מעתה יתאפשר לך להעביר בין קטגוריות ולשנות שמות של נושאים, להשתמש בדגלי זבל משפיעים יותר, לגשת לאזור הטרקלין הפרטי ויתאפשר לך לקבל הרבה יותר לייקים כל יום.
+ עיטור זה מוענק עם הגיעך לדרגת אמון 3. תודה על נטילת חלק בקהילה דרך קבע לאורך תקופה של מספר חודשים. נכון להיום מידת המעורבות שלך הן בקריאה מרובה והן בתרומה לקהילה הן אלו שמחזקות את הקהילה שלנו והופכות אותה לנהדרת. מעתה יתאפשר לך להעביר בין קטגוריות ולשנות שמות של נושאים, להשתמש בדגלי זבל משפיעים יותר, לגשת לאזור הטרקלין הפרטי ויהיו לך הרבה יותר לייקים כל יום.
leader:
name: מובילים
description: הוענקו עריכה, הצמדה, סגירה, העברה לארכיון, פיצול ומיזוג, עוד לייקים באופן גלובלי
@@ -4402,10 +4402,10 @@ he:
label: "שם הקהילה שלכם"
placeholder: "המקום של ג׳יין"
site_description:
- label: "נא לתאר את הקהילה שלך במשפט קצר אחד"
+ label: "נא לתאר את הקהילה שלך במשפט קצר אחד (ישמש תוצאות חיפוש ורשתות חברתיות)"
placeholder: "מקום לג׳יין וחבריה לשוחח על דברים מגניבים"
short_site_description:
- label: "נא לתאר את הקהילה שלך במספר מילים"
+ label: "נא לתאר את הקהילה שלך במספר מילים (משמש לכתובת דף הבית)"
placeholder: "הקהילה הכי טובה בעולם"
introduction:
title: "מבוא"
@@ -4582,6 +4582,7 @@ he:
contains_media: "פוסט זה כולל מדיה מוטמעת. מידע נוסף תחת %{link}."
queued_by_staff: "אחד מחברי הסגל חושב שהפוסט הזה דורש סקירה. עד אז הוא יישאר מוסתר."
links:
+ watched_word: רשימת מילים במעקב
category: הגדרות קטגוריה
actions:
agree:
diff --git a/config/locales/server.hy.yml b/config/locales/server.hy.yml
index 43b2274dcc..d3c1d0cca8 100644
--- a/config/locales/server.hy.yml
+++ b/config/locales/server.hy.yml
@@ -1115,7 +1115,6 @@ hy:
summary_score_threshold: "'Ամփոփել Այս Թեման'-ի մեջ ներառման համար անհրաժեշտ գրառման նվազագույն միավորը"
summary_percent_filter: "Երբ օգտատերը սեղմում է 'Ամփոփել Այս Թեման' , ցուցադրել գրառումների թոփ %-ը"
summary_max_results: "'Ամփոփել Այս Թեման' -ի կողմից վերադարձված առավելագույն գրառումները"
- enable_personal_messages: "Թույլատրել վստահության 1-ին մակարդակի (կարգավորվում է հաղորդագրություններ ուղարկելու համար նվազագույն վստահության մակարդակի միջոցով) օգտատերերին ստեղծել և պատասխանել հաղորդագրություններին: Նկատի ունեցեք, որ անձնակազմը միշտ կարող է ուղարկել հաղորդագրություն, կապ չունի թե ինչպիսի:"
enable_system_message_replies: "Թույլատրում է օգտատերերին պատասխանել համակարգային հաղորդագրությունների, անգամ եթե անձնական հաղորդագրություններն անջատված են"
enable_long_polling: "Ծանուցման համար օգտագործվող նամակների ավտոբուսը (Message bus) կարող է օգտագործել long polling:"
long_polling_base_url: "long polling-ի համար օգտագործվող հիմնական URL (երբ CDN-ը տալիս է դինամիկ բովանդակություն, համոզվեք, որ սա սահմանված է origin pull) , օրինակ՝ http://origin.site.com"
@@ -1351,7 +1350,6 @@ hy:
topic_view_duration_hours: "Հաշվարկել թեմայի նոր դիտում՝ ըստ յուրաքանչյուր IP-ի/Օգտատիրոջ յուրաքանչյուր N ժամը մեկ"
user_profile_view_duration_hours: "Հաշվարկել օգտատիրոջ պրոֆիլի նոր դիտում՝ ըստ յուրաքանչյուր IP-ի/Օգտատիրոջ յուրաքանչյուր N ժամը մեկ"
levenshtein_distance_spammer_emails: "Սպամմերի էլ. նամակների համապատասխանեցման ժամանակ սիմվոլների տարբերության քանակը, որը դեռևս թույլ կտա անորոշ համընկնում:"
- max_new_accounts_per_registration_ip: "Եթե արդեն իսկ կա վստահության 0 մակարդակ ունեցող (n) հաշիվ այս IP -ից (և ոչ մեկը անձնակազմի անդամ չէ կամ չունի ՎՄ2 կամ ավելի բարձր), դադարել ընդունել նոր մուտքեր այդ IP-ից:"
min_ban_entries_for_roll_up: "Խմբավորել կոճակը սեղմելիս կստեղծվի նոր սուբնեթի արգելքի մուտք, եթե կա առնվազն (N) մուտք:"
max_age_unmatched_emails: "Ջնջել չհամընկած ցուցադրված էլ. մուտքերը (N) օր հետո:"
max_age_unmatched_ips: "Ջնջել չհամընկնող ցուադրված IP մուտքերը (N) օր անց:"
@@ -3008,10 +3006,8 @@ hy:
label: "Ձեր համայնքի անվանումը"
placeholder: "Jane’s Hangout"
site_description:
- label: "Նկարագրեք Ձեր համայնքը մեկ կարճ նախադասությանբ"
placeholder: "Ջեյնի և իր ընկերների համար ընտիր թեմաներ քննարկելու վայր"
short_site_description:
- label: "Նկարագրեք Ձեր համայնքը մի քանի բառով"
placeholder: "Բոլոր ժամանակների ամենալավ համայնքը"
introduction:
title: "Ներածություն"
diff --git a/config/locales/server.id.yml b/config/locales/server.id.yml
index 63564ad6f2..2ad5654032 100644
--- a/config/locales/server.id.yml
+++ b/config/locales/server.id.yml
@@ -32,6 +32,10 @@ id:
- November
- Desember
<<: *datetime_formats
+ time:
+ am: "am"
+ pm: "pm"
+ <<: *datetime_formats
title: "Discourse"
topics: "Topik"
posts: "post"
@@ -704,8 +708,6 @@ id:
reject:
title: "Tolak"
fallback_username: "pengguna"
- time:
- <<: *datetime_formats
activemodel:
errors:
<<: *errors
diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml
index 100f5ed4a9..fbcf394308 100644
--- a/config/locales/server.it.yml
+++ b/config/locales/server.it.yml
@@ -250,7 +250,6 @@ it:
topic_invite:
failed_to_invite: "L'utente non può essere invitato in questo argomento senza essere membro di uno dei seguenti gruppi: %{group_names}."
user_exists: "Spiacenti, l'utente è stato già invitato. Puoi invitare un utente ad uno stesso argomento solo una volta."
- muted_invitee: "Spiacenti, quell'utente ti ha silenziato."
muted_topic: "Spiacenti, quell'utente ha silenziato questo argomento."
receiver_does_not_allow_pm: "Spiacenti, quell'utente non ti permette di inviargli messaggi privati."
sender_does_not_allow_pm: "Spiacenti, non permetti a quell'utente di inviarti messaggi privati."
@@ -1784,7 +1783,7 @@ it:
topic_view_duration_hours: "Conteggia una nuova visita dell'argomento una volta per IP/Utente ogni N ore"
user_profile_view_duration_hours: "Conteggia una nuova visita del profilo utente una volta per IP/Utente ogni N ore"
levenshtein_distance_spammer_emails: "Quanti caratteri di differenza faranno comunque scattare una corrispondenza approssimativa nell'analisi delle email di spam. "
- max_new_accounts_per_registration_ip: "Se vi sono già (n) account con livello di attendibilità 0 da questo IP (e nessuno è membro dello staff o a livello TL2 o superiore), non accettare nuove iscrizioni utente da questo IP."
+ max_new_accounts_per_registration_ip: "Se vi sono già (n) account con livello di attendibilità 0 da questo IP (e nessuno è membro dello staff o a livello 2 o superiore), non accettare nuove iscrizioni da questo IP. Impostare a 0 per disabilitare il limite."
min_ban_entries_for_roll_up: "Quando si clicca il pulsante Roll up, verrà creato un nuovo elenco di IP interdetti se sono presenti almeno (N) elementi."
max_age_unmatched_emails: "Elimina le voci di email scansionate senza corrispondenza dopo (N) giorni."
max_age_unmatched_ips: "Elimina le voci di IP scansionati senza corrispondenza dopo (N) giorni."
@@ -1921,6 +1920,7 @@ it:
global_notice: "Mostra un banner di avviso globale URGENTE, EMERGENZA, non eliminabile a tutti i visitatori, impostare l'opzione vuota per nasconderlo (HTML consentito)."
disable_system_edit_notifications: "Disabilita le notifiche di modifica dall'utente system quando 'download_remote_images_to_local' è attivo."
disable_category_edit_notifications: "Disabilita le notifiche di modifica delle categorie negli argomenti."
+ disable_tags_edit_notifications: "Disabilita le notifiche di modifica delle etichette sugli argomenti."
notification_consolidation_threshold: "Il numero di notifiche per richieste di adesione o di \"Mi piace\" ricevuti prima che le notifiche vengano raccolte in una sola. Impostare il valore a 0 per disabilitare l'opzione."
likes_notification_consolidation_window_mins: "Durata in minuti del periodo in cui le notifiche dei \"mi piace\" sono raccolte in un'unica notifica una volta raggiunta la soglia definita. La soglia può essere configurata tramite l'impostazione `SiteSetting.notification_consolidation_threshold`."
automatically_unpin_topics: "Spunta automaticamente gli argomenti quando l'utente arriva in fondo."
@@ -3912,10 +3912,8 @@ it:
label: "Il nome della tua comunità"
placeholder: "Il Ritrovo di Jane"
site_description:
- label: "Descrivi la tua comunità in una breve frase"
placeholder: "Un posto per Jane e per i suoi amici dove discutere di cose forti"
short_site_description:
- label: "Descrivi la tua comunità in poche parole"
placeholder: "La miglior comunità di sempre"
introduction:
title: "Introduzione"
diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml
index e05d05cb3e..b83e0908fb 100644
--- a/config/locales/server.ja.yml
+++ b/config/locales/server.ja.yml
@@ -240,7 +240,6 @@ ja:
topic_invite:
failed_to_invite: "次のいずれかのグループのグループメンバーシップがない場合、ユーザーをこのトピックに招待できません: %{group_names}。"
user_exists: "そのユーザーはすでに招待されています。ユーザーを一度しかトピックに招待できません。"
- muted_invitee: "そのユーザーはあなたをミュートしています。"
muted_topic: "そのユーザーはこのトピックをミュートしています。"
receiver_does_not_allow_pm: "そのユーザーは、あなたがプライベートメッセージを送信することを許可していません。"
sender_does_not_allow_pm: "そのユーザーがプライベートメッセージを送信することを許可していません。"
@@ -1396,7 +1395,6 @@ ja:
summary_likes_required: "'このトピックを要約' を有効にするために最低限必要なトピック内の「いいね!」数。この設定を変更すると、1 週間以内で遡って適用されます。"
summary_percent_filter: "「このトピックを要約」をクリックしたとき表示される上位投稿の割合%"
summary_max_results: "'このトピックを要約' が返す最大投稿数"
- enable_personal_messages: "信頼レベル 1 ユーザーによるメッセージとメッセージへの返信の作成を許可する (メッセージを送信する最低信頼レベルで構成可能)。スタッフは設定に関係なく必ずメッセージを作成できます。"
enable_system_message_replies: "個人メッセージが無効である場合でも、ユーザーによるシステムメッセージへの返信を許可する"
enable_long_polling: "通知用のメッセージバスによるロングポーリングの利用を許可する"
enable_chunked_encoding: "サーバーによるチャンク形式エンコーディング応答を有効にする。この機能はほとんどのセットアップで動作しますが、一部のプロキシではバッファリングが生じ、応答が遅延する可能性があります。"
@@ -1726,7 +1724,6 @@ ja:
topic_view_duration_hours: "N 時間ごとに IP/ユーザーあたりの新規トピックビューを 1 回カウントする"
user_profile_view_duration_hours: "N 時間ごとに IP/ユーザーあたりの新規ユーザープロフィールビューを 1 回カウントする"
levenshtein_distance_spammer_emails: "迷惑メールのアドレスを照合する場合、あいまい一致を許可する文字数の差。"
- max_new_accounts_per_registration_ip: "この IP でアクセスする信頼レベル 0 のアカウントが (n) 個存在する場合 (さらにこれらがスタッフメンバーや TL2 以上のメンバーでない場合)、この IP から新たに登録できないようにします。"
min_ban_entries_for_roll_up: "ロールアップボタンをクリックする際に少なくとも (N) 個のエントリーがある場合、新しいサブネット禁止エントリーを作成します。"
max_age_unmatched_emails: "(N) 日間一致しなかったスクリーン対象メールアドレスを削除します。"
max_age_unmatched_ips: "(N) 日間一致しなかったスクリーン対象 IP アドレスを削除します。"
@@ -4177,10 +4174,8 @@ ja:
label: "コミュニティの名前"
placeholder: "純子のたまり場"
site_description:
- label: "コミュニティについて簡単な一文で説明してください"
placeholder: "純子と純子の仲間がワイワイする場所"
short_site_description:
- label: "コミュニティについて短い言葉で説明してください"
placeholder: "史上最高のコミュニティ"
introduction:
title: "紹介"
diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml
index 07fc37d38b..ecda50d200 100644
--- a/config/locales/server.ko.yml
+++ b/config/locales/server.ko.yml
@@ -239,7 +239,6 @@ ko:
topic_invite:
failed_to_invite: "%{group_names} 그룹 중 하나에 그룹 구성원이 없으면이 주제에 사용자를 초대 할 수 없습니다."
user_exists: "해당 사용자는 이미 초대를 받았습니다. 토픽 초대는 딱 한번만 할 수 있습니다."
- muted_invitee: "죄송합니다. 해당 사용자는 사용자님을 관심없음으로 설정했습니다."
muted_topic: "죄송합니다. 해당 사용자는 이 글을 관심없음으로 설정했습니다."
receiver_does_not_allow_pm: "죄송합니다. 해당 사용자는 비공개 메시지를 보낼 수 있도록 허용하지 않습니다."
sender_does_not_allow_pm: "죄송합니다. 해당 사용자는 비공개 메시지를 보낼 수 있도록 허용하지 않습니다."
@@ -1405,7 +1404,7 @@ ko:
summary_percent_filter: "요약본 보기를 클릭시, 글 중에 몇 %의 상위 글을 보여줄 것인가?"
summary_max_results: "'이 주제 요약'에서 반환 한 최대 게시물"
summary_timeline_button: "타임라인에 '요약' 버튼 표시"
- enable_personal_messages: "신뢰도 1 사용자(메시지 전송을 위한 최소 신뢰도로 설정가능)가 메시지를 작성하고 답장을 쓸 수 있도록 허용합니다. 운영진은 언제나 메시지를 보낼 수 있음을 참고하세요."
+ enable_personal_messages: "회원레벨1 사용자가 메시지를 작성하고 메시지에 답장할 수 있도록 허용합니다. 관리자는 언제든지 메시지를 보낼 수 있습니다."
enable_system_message_replies: "개인 메시지가 비활성화 된 경우에도 사용자가 시스템 메시지에 회신 할 수 있습니다"
enable_long_polling: "Message bus used for notification can use long polling"
enable_chunked_encoding: "서버에서 청크 분할 인코딩 응답을 활성화합니다. 이 기능은 대부분의 설정에서 작동하지만 일부 프록시는 버퍼링되어 응답이 지연될 수 있습니다."
@@ -1732,7 +1731,6 @@ ko:
topic_view_duration_hours: "N 시간마다 IP/User 별로 새 토픽 조회수를 셉니다."
user_profile_view_duration_hours: "N 시간마다 IP/User 별로 새 프로필 조회수를 셉니다."
levenshtein_distance_spammer_emails: "스패머 메일을 체크할 때, 허용할 다른 글자 개수(fuzzy match)"
- max_new_accounts_per_registration_ip: "(n) 회원등급의 0개의 계정(스태프도 tl2 이상도 아닌 계정만)이 이 IP에 있으면, 새로운 회원가입 방지."
min_ban_entries_for_roll_up: "Roll up 버튼을 눌렀을 때, 적어도 (N)개의 엔트리가 있다면 새 subnet ban 엔트리를 만듭니다."
max_age_unmatched_emails: "(N)일 뒤에 안 맞는 막힌 이메일 접근을 지웁니다."
max_age_unmatched_ips: "(N)일 뒤에 안 맞는 막힌 IP 접근을 지웁니다."
@@ -1865,6 +1863,7 @@ ko:
permalink_normalizations: "퍼마링크를 매칭하기 전에 다음 정규표현식을 적용. 예: /(topic.*)\\?.*/\\1 를 적용하면 토픽 루트에서 퀴리 스트링을 뽑아냅니다. 캡쳐된 값에 접근하려면 정규표현식+스트링 형식에 \\1 등을 사용하면 됩니다."
global_notice: "모든 방문자에게 긴급, 긴급, 무시할 수없는 글로벌 배너 통지를 표시하고 숨기려면 공백으로 변경하십시오 (HTML 허용)."
disable_system_edit_notifications: "'download_remote_images_to_local'가 활성화되있으면 시스템 사용자에 의한 수정 알림을 비활성화합니다."
+ disable_category_edit_notifications: "글의 카테고리 편집 알림을 비활성화합니다."
notification_consolidation_threshold: "알림이 단일 알림으로 통합되기 전에 수신 된 좋아요 또는 회원 요청 알림 수 비활성화하려면 0으로 설정하십시오."
likes_notification_consolidation_window_mins: "임계 값에 도달하면 선호 알림이 단일 알림으로 통합되는 시간 (분)입니다. 임계 값은`SiteSetting.notification_consolidation_threshold`를 통해 구성 할 수 있습니다."
automatically_unpin_topics: "사용자가 하단에 도달하면 토픽 고정을 자동 해제."
@@ -2413,6 +2412,17 @@ ko:
new_version_mailer:
title: "새 버전 메일러"
subject_template: "[%{email_prefix}] 새로운 담화 버전, 업데이트 가능"
+ text_body_template: |
+ [Discourse](https://www.discourse.org)의 새로운 버전이 출시되었습니다!
+
+ 사용자의 현재 버전: %{installed_version}
+ 새 버전: **%{new_version}**
+
+ - 간편한 **[원클릭 브라우저 업그레이드](%{base_url}/admin/upgrade)**
+
+ - [릴리스 노트]( https://meta.discourse.org/tag/release-notes) 또는 [GitHub 변경 로그](https://github.com/discourse/discourse/commits/main) 보기
+
+ - [meta.discourse.org](https:// meta.discourse.org)를 방문해 뉴스, 토론 및 Discourse 지원 확인
new_version_mailer_with_notes:
title: "노트가있는 새로운 버전 메일러"
subject_template: "[%{email_prefix}] 업데이트 가능"
@@ -4096,10 +4106,8 @@ ko:
label: "커뮤니티 이름"
placeholder: "빠오 소굴"
site_description:
- label: "당신의 커뮤니티를 짧은 문장으로 축약하여 설명해보세요"
placeholder: "빠오가 친구와 함께 재밌는 일들에 대하여 이야기나누는 곳"
short_site_description:
- label: "몇 마디로 커뮤니티를 설명하십시오"
placeholder: "최고의 커뮤니티"
introduction:
title: "소개"
@@ -4261,9 +4269,11 @@ ko:
post_count: "모든 사용자의 처음 몇 개의 게시물은 관리자의 승인을 받아야 합니다. 다음을 참조하십시오. %{link}"
auto_silence_regex: "%{link}의 설정과 일치하는 새 사용자입니다."
watched_word: "이 게시물에는 감시 단어가 포함되어 있습니다. 다음 링크를 참조하십시오. %{link}"
+ category: "이 카테고리의 게시물은 관리자의 수동 승인이 필요합니다. 다음을 참조하십시오. %{link}"
must_approve_users: "모든 신규 사용자는 관리자의 승인을 받아야 합니다. 다음을 참조하십시오. %{link}"
invite_only: "모든 신규 사용자를 초대해야 합니다. 다음 링크를 참조하십시오. %{link}"
email_auth_res_enqueue: "이 이메일은 DMARC 확인에 실패했으며, 발신자가 아닌 것 같습니다. 자세한 내용은 원시 이메일 헤더를 확인하십시오."
+ suspect_user: "이 새로운 사용자는 글이나 댓글을 읽지 않고 프로필 정보를 입력했으며, 이는 스팸 발송자일 가능성이 큽니다. 다음을 참조하십시오. %{link}"
contains_media: "이 게시물에는 미디어가 포함되어 있습니다. 다음을 참조하십시오. %{link}"
queued_by_staff: "관리자가 게시물의 검토가 필요하다고 판단했습니다. 그때까지는 숨겨져 있습니다."
links:
diff --git a/config/locales/server.lt.yml b/config/locales/server.lt.yml
index 0dd0f37e55..fd3bad17c8 100644
--- a/config/locales/server.lt.yml
+++ b/config/locales/server.lt.yml
@@ -177,7 +177,6 @@ lt:
topic_invite:
failed_to_invite: "Vartotojas negali būti pakviestas į šią temą be grupės narystės vienoje iš šių grupių: %{group_names}."
user_exists: "Atsiprašome, šis vartotojas jau buvo pakviestas. Galite pakviesti vartotoją į temą tik vieną kartą."
- muted_invitee: "Atsiprašome, vartotojas jus nutildė."
muted_topic: "Atsiprašome, kad vartotojas nutildė šią temą."
receiver_does_not_allow_pm: "Atsiprašome, vartotojas neleidžia jums siųsti asmeninių pranešimų."
sender_does_not_allow_pm: "Atsiprašome, jūs neleidžiate šiam vartotojui siųsti jums asmeninių pranešimų."
@@ -1350,6 +1349,13 @@ lt:
not_allowed: "neleidžiama iš šio el. pašto teikėjo. Prašome naudoti kitą el. pašto adresą."
blocked: "negalimas."
does_not_exist: "N/A"
+ ip_address:
+ blocked: "Naujos registracijos neleidžiamos iš jūsų IP adreso."
+ max_new_accounts_per_registration_ip: "Naujos registracijos neleidžiamos iš jūsų IP adreso (pasiekta maksimali riba). Susisiekite su administracija."
+ website:
+ domain_not_allowed: "Svetainė netinkama. Leidžiami domenai yra: %{domains}"
+ destroy_reasons:
+ inactive_user: "Neaktyvus vartotojas"
invite_forum_mailer:
text_body_template: |
%{inviter_name} pakvietė jus prisijungti
@@ -2064,6 +2070,8 @@ lt:
Šis ženklelis suteikiamas, kai jūsų tema sulaukia 10 teigiamų įvertinimų. Pradėjote įdomų pokalbį, kuris patiko visuomenei.
good_topic:
name: Gera tema
+ long_description: |
+ Šis ženklelis suteikiamas, kai jūsų tema sulaukia 25 teigiamų įvertinimų. Pradėjote įdomų pokalbį, kuris patiko visuomenei.
great_topic:
name: Puiki tema
long_description: |
@@ -2234,10 +2242,8 @@ lt:
title:
label: "Jūsų bendruomenės pavadinimas"
site_description:
- label: "Apibūdinkite savo bendruomenę vienu trumpu sakiniu"
placeholder: "Džeinei ir jos draugams skirta vieta aptarti įdomius dalykus"
short_site_description:
- label: "Apibūdinkite savo bendruomenę keliais žodžiais"
placeholder: "Pati geriausia bendruomenė"
introduction:
title: "Prisistatymas"
diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml
index fca5e8e9c3..775e9e1480 100644
--- a/config/locales/server.nl.yml
+++ b/config/locales/server.nl.yml
@@ -227,7 +227,6 @@ nl:
topic_invite:
failed_to_invite: "De gebruiker kan niet voor dit topic worden uitgenodigd zonder een groepslidmaatschap in een van de volgende groepen: %{group_names}."
user_exists: "Sorry, die gebruiker is al uitgenodigd. U kunt een gebruiker maar één keer voor een topic uitnodigen."
- muted_invitee: "Sorry, die gebruiker heeft u gedempt."
backup:
operation_already_running: "Er wordt al een bewerking uitgevoerd. Er kan nu geen nieuwe taak worden gestart."
backup_file_should_be_tar_gz: "Het back-upbestand dient een .tar.gz-archief te zijn."
@@ -1349,7 +1348,6 @@ nl:
summary_likes_required: "Het minimale aantal likes in een topic voordat 'Dit topic samenvatten' wordt ingeschakeld. Wijzigingen in deze instelling worden binnen een week met terugwerkende kracht toegepast."
summary_percent_filter: "Wanneer een gebruiker op 'Dit topic samenvatten' klikt, de top % van berichten tonen"
summary_max_results: "Maximale aantal weergegeven berichten door 'Dit topic samenvatten'"
- enable_personal_messages: "Gebruikers met vertrouwensniveau 1 (instelbaar via het minimale niveau om berichten te verzenden) toestaan om berichten aan te maken en op berichten te antwoorden. Houd er rekening mee dat stafleden altijd berichten kunnen verzenden."
enable_system_message_replies: "Toestaan dat gebruikers op systeemberichten kunnen antwoorden, zelfs als persoonlijke berichten zijn uitgeschakeld"
enable_long_polling: "Gebruikte 'message bus' voor melding kan 'long polling' gebruiken"
enable_chunked_encoding: "Gesegmenteerde coderingsantwoorden van de server inschakelen. Deze functie werkt in de meeste configuraties, hoewel sommige proxy's kunnen bufferen, waardoor reacties worden vertraagd"
@@ -1624,7 +1622,6 @@ nl:
topic_view_duration_hours: "Elke N uur één keer per IP/Gebruiker een nieuw-topicweergave tellen."
user_profile_view_duration_hours: "Elke N uur één keer per IP/Gebruiker een nieuw-gebruikersprofielweergave tellen."
levenshtein_distance_spammer_emails: "Bij het vergelijken van spam-e-mails, het aantal verschillende tekens waarbij nog steeds een wazige overeenkomst kan bestaan."
- max_new_accounts_per_registration_ip: "Als er al (n) accounts met vertrouwensniveau 0 zijn van een bepaald IP-adres (en geen daarvan is staflid of heeft een vertrouwensniveau 2 of hoger), het accepteren van nieuwe registraties vanaf dat IP-adres stoppen."
min_ban_entries_for_roll_up: "Bij het klikken op de knop Samenvoegen, een nieuwe subnet-banvermelding maken als er minstens (N) vermeldingen zijn."
max_age_unmatched_emails: "Niet-overeenkomende gecontroleerde e-mailadresvermeldingen na (N) dagen verwijderen."
max_age_unmatched_ips: "Niet-overeenkomende gecontroleerde IP-adresvermeldingen na (N) dagen verwijderen."
diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml
index d0f367cbe2..16be762e05 100644
--- a/config/locales/server.pl_PL.yml
+++ b/config/locales/server.pl_PL.yml
@@ -268,7 +268,6 @@ pl_PL:
topic_invite:
failed_to_invite: "Nie można zaprosić użytkownika do tego tematu, ponieważ nie jest członkiem choć jednej z następujących grup: %{group_names}."
user_exists: "Przepraszamy, ten użytkownik został już zaproszony. Możesz zaprosić użytkownika do tematu tylko raz."
- muted_invitee: "Przepraszamy, ten użytkownik Cię wyciszył."
muted_topic: "Przepraszamy, ten użytkownik wyciszył ten temat."
receiver_does_not_allow_pm: "Przepraszamy, ten użytkownik nie pozwala na wysyłanie mu prywatnych wiadomości."
sender_does_not_allow_pm: "Przepraszamy, nie pozwalasz temu użytkownikowi na wysyłanie Ci prywatnych wiadomości."
@@ -1564,7 +1563,6 @@ pl_PL:
summary_percent_filter: "Gdy użytkownik kliknie na 'Podsumowaniu tematu', pokaż % najlepszych wpisów"
summary_max_results: "Maksymalna liczba wpisów zwróconych przez „Podsumuj ten temat”"
summary_timeline_button: "Pokaż przycisk „Podsumuj” na osi czasu"
- enable_personal_messages: "Zezwalaj użytkownikom o poziomie zaufania 1 (możliwe do zmiany przez min trust level to send messages) na tworzenie wiadomości i odpowiadanie na nie. Zwróć uwagę, że administracja zawsze może wysyłać wiadomości bez względu na wszystko."
enable_system_message_replies: "Pozwala użytkownikom odpowiadać na wiadomości systemowe, nawet jeśli wiadomości osobiste są wyłączone"
enable_long_polling: "Message bus used for notification can use long polling"
enable_chunked_encoding: "Włącz odpowiedzi fragmentaryczne serwera. Ta funkcja działa w większości konfiguracji, jednak niektóre serwery proxy mogą buforować odpowiedzi, powodując opóźnienia."
@@ -1895,7 +1893,6 @@ pl_PL:
topic_view_duration_hours: "Licz wyświetlanie nowego tematu na IP/Użytkownika co N godzin"
user_profile_view_duration_hours: "Licz nowe wyświetlenia profilu użytkownika na IP/Użytkownika co N godzin"
levenshtein_distance_spammer_emails: "Przy dopasowywaniu emaili spamowych, różnica liczby znaków, która w dalszym ciągu pozwoli na przybliżone dopasowanie."
- max_new_accounts_per_registration_ip: "Jeśli istnieję już (n) kont o poziomie zaufania 0 z tego adresu IP (i żaden nie jest z obsługi lub na TL2 lub wyżej), zatrzymaj akceptowanie nowych użytkowników z tego IP."
min_ban_entries_for_roll_up: "Podczas naciskania na przycisk Przewiń do góry, utwórz nowy wpis blokady podsieci jeśli jest co najmniej (N) wpisów."
max_age_unmatched_emails: "Usuń niedopasowany ekranowany wpis email po (N) dniach."
max_age_unmatched_ips: "Usuń niedopasowany ekranowany wpis IP po (N) dniach."
@@ -4522,10 +4519,8 @@ pl_PL:
label: "Twoja pełna nazwa"
placeholder: "Hangout Jane"
site_description:
- label: "Opisz swoja społeczność w jednym krótkim zdaniu."
placeholder: "Miejsce dla Jane i jej przyjaciół na dyskusje o fajnych sprawach."
short_site_description:
- label: "Opisz swoją społeczność w paru słowach"
placeholder: "Najlepsza społeczność kiedykolwiek"
introduction:
title: "Wprowadzenie"
diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml
index 6bec0244a7..40b422278b 100644
--- a/config/locales/server.pt.yml
+++ b/config/locales/server.pt.yml
@@ -237,7 +237,6 @@ pt:
topic_invite:
failed_to_invite: "O usuário não pode ser convidado para este tópico sem uma associação de grupo em um dos seguintes grupos: %{group_names}."
user_exists: "Pedimos desculpa, esse utilizador já foi convidado. Pode convidar um utilizador para um tópico apenas uma vez."
- muted_invitee: "Desculpe, esse usuário silenciou-te."
muted_topic: "Desculpe, esse usuário silenciou este tópico."
receiver_does_not_allow_pm: "Desculpe, esse usuário não permite que você lhe envie mensagens privadas."
sender_does_not_allow_pm: "Desculpe, você não permite que esse usuário lhe envie mensagens privadas."
@@ -1009,7 +1008,6 @@ pt:
force_https: "Forçar o site a usar apenas HTTPS. ALERTA: NÃO active esta opção enquanto não verificar que o HTTPS está completamente configurado e funcional absolutamente em todo o lado! Verificou a sua CDN, todos os logins por rede social, e quaisquer logos ou outras dependências externas para garantir que também são compatíveis com HTTPS?"
summary_score_threshold: "Pontuação mínima necessária para que uma mensagem seja incluída em 'Resumir Este Tópico'"
summary_percent_filter: "Quando um utilizador clica em 'Resumir Este Tópico', mostrar as melhores % de mensagens"
- enable_personal_messages: "Permitir que utilizadores de nível de confiança 1 (configurável através do nível de confiança mínimo para enviar mensagens) criem mensagens e respostas a mensagens. Note que a equipa de apoio pode mandar mensagens de qualquer maneira."
enable_long_polling: "O sistema de mensagens usado para notificações pode fazer solicitações longas"
long_polling_base_url: "URL base utilizado para sondar o servidor (quando um CDN serve conteúdo dinâmico, certifique-se de definir isto para a \"pull\" original) por exemplo: http://origem.site.com"
long_polling_interval: "O tempo que um servidor deverá aguardar antes de responder aos clientes quando não existirem dados para serem enviados (apenas utilizadores autenticados)"
@@ -1171,7 +1169,6 @@ pt:
topic_view_duration_hours: "Contar uma nova visualização do tópico uma vez por IP/Utilizador a cada N horas"
user_profile_view_duration_hours: "Contar visualização de novo perfil de utilizador uma vez por IP/utilizador a cada N horas"
levenshtein_distance_spammer_emails: "Ao fazer a correspondência de e-mails de spam, o número de caracteres de diferença que ainda permitirá uma correspondência difusa."
- max_new_accounts_per_registration_ip: "Se já há (n) contas com nível de confiança 0 a partir deste IP (e nenhum é um membro do pessoal ou em nível de confiança 2 ou superior), parar de aceitar novos registos a partir desse IP."
min_ban_entries_for_roll_up: "Ao clicar no botão Agrupar, irá criar uma nova entrada de sub-rede banida se houver pelo menos (N) entradas."
max_age_unmatched_emails: "Eliminar entradas de email não encontradas após (N) dias."
max_age_unmatched_ips: "Eliminar entradas IP não encontradas após (N) dias."
@@ -2200,7 +2197,6 @@ pt:
label: "O nome da sua comunidade"
placeholder: "Reduto da Joana"
site_description:
- label: "Descreva a sua comunidade numa frase curta"
placeholder: "Um lugar para a Jona e os seus amigos falarem de coisas giras"
introduction:
title: "Introdução"
diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml
index c1f943e5f5..79dd862f02 100644
--- a/config/locales/server.pt_BR.yml
+++ b/config/locales/server.pt_BR.yml
@@ -249,7 +249,6 @@ pt_BR:
topic_invite:
failed_to_invite: "O(a) usuário(a) não pode ser convidado(a) para este tópico sem uma associação de grupo em um dos seguintes grupos: %{group_names}."
user_exists: "Desculpe, este(a) usuário(a) já foi convidado(a). Você pode convidar um(a) usuário(a) para um tópico apenas uma vez."
- muted_invitee: "Desculpe, você foi silenciado(a) por este(a) usuário(a)."
muted_topic: "Desculpe, este(a) usuário(a) silenciou este tópico."
receiver_does_not_allow_pm: "Desculpe, esse usuário não permite que você envie mensagens privadas."
sender_does_not_allow_pm: "Desculpe, você não permite que o(a) usuário(a) envie mensagens privadas."
@@ -1450,7 +1449,6 @@ pt_BR:
summary_likes_required: "Quantidade mínima de curtidas em um tópico antes de ativar \"Resumir este tópico\". As alterações desta configuração serão aplicadas de forma retroativa dentro de uma semana."
summary_percent_filter: "Quando um(a) usuário(a) clicar em \"Resuma este tópico\", exiba as % melhores postagens"
summary_max_results: "Quantidade máxima de postagens retornadas por \"Resumir este tópico\""
- enable_personal_messages: "Permita que usuários(as) do nível de confiança 1 (configurável por meio do nível mínimo de confiança para enviar mensagens) criem e respondam a mensagens. Nove que a equipe sempre pode enviar mensagens."
enable_system_message_replies: "Permite que os(as) usuários(as) respondam às mensagens do sistema, mesmo se as mensagens pessoais estiverem desativadas"
enable_long_polling: "O sistema de mensagens das notificações pode fazer sondagens longas."
enable_chunked_encoding: "Ative respostas de codificação em bloco no servidor. Esse recurso funciona na maioria das configurações, mas alguns proxies podem ser armazenados em buffer, atrasando as respostas"
@@ -1780,7 +1778,6 @@ pt_BR:
topic_view_duration_hours: "Conte uma nova visualização de tópico uma vez por IP/usuário(a) a cada N horas"
user_profile_view_duration_hours: "Conte uma nova visualização de perfil de usuário(a) uma vez por IP/usuário(a) a cada N horas"
levenshtein_distance_spammer_emails: "Ao fazer correspondência de e-mails de remetentes de spam, o tamanho da diferença de caracteres que ainda causará uma correspondência aproximada."
- max_new_accounts_per_registration_ip: "Se já houver (n) contas com o nível de confiança 0 deste IP (e nenhum for membro da equipe ou tiver NC2 ou mais alto), pare de aceitar novas assinaturas desse IP."
min_ban_entries_for_roll_up: "Ao clicar no botão Combinar, uma entrada de banimento de subrede será criada se houver ao menos (N) entradas."
max_age_unmatched_emails: "Excluir entradas de e-mails filtrados sem correspondência após (N) dias."
max_age_unmatched_ips: "Excluir entradas de IPs filtrados sem correspondência após (N) dias."
@@ -4297,10 +4294,8 @@ pt_BR:
label: "Nome de sua comunidade"
placeholder: "Hangout de Jane"
site_description:
- label: "Descreva sua comunidade com uma frase curta."
placeholder: "Um lugar para Jane e seus amigos discutirem coisas legais."
short_site_description:
- label: "Descreva sua comunidade em poucas palavras"
placeholder: "Melhor comunidade de todos os tempos"
introduction:
title: "Introdução"
diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml
index 3fb10f374d..00f8522651 100644
--- a/config/locales/server.ro.yml
+++ b/config/locales/server.ro.yml
@@ -834,7 +834,6 @@ ro:
force_https: "Forțează site-ul să folosească exclusiv HTTPS. ATENȚIE: NU activa această opțiune până nu ai verificat dacă HTTPS este configurat în întregime și funcționează absolut peste tot. Ai verificat, de asemenea, dacă și CND-ul, toate autentificările cu cont pe rețele de socializaer și toate logo-urile și dependențele externe sunt compatibile cu HTTPS?"
summary_score_threshold: "Scorul minim necesar pentru ca o postare să fie inclusă în 'Rezumă acest subiect'"
summary_percent_filter: "Când un utilizator face click pe 'Rezumatul acestui subiect', arată primele % de postări"
- enable_personal_messages: "Acordă nivelul de încredere 1 (configurabil via nivel minim de încredere pentru trimiterea de mesaje) utilizatorilor, pentru a le permite să creeze și să răspundă la mesaje. Atenție: echipa poate oricând să trimită mesaje, indiferent de setări."
enable_long_polling: "Bus-ul de mesaje folosit pentru notificări poate utiliza long polling."
long_polling_base_url: "URL de bază folosit pentru long polling (atunci când un CDN servește conținut dinamic, asigură-te că setezi asta pe origin pull) ex: http://origin.site.com"
long_polling_interval: "Durata cât serverul va trebui să aștepte înainte de a răspunde clienților atunci când nu există date de trimis (exclusiv utilizatori autentificați)"
@@ -995,7 +994,6 @@ ro:
topic_view_duration_hours: "Contorizează încă o vizualizare a unui subiect nou o singură dată per IP/Utilizator la fiecare N ore."
user_profile_view_duration_hours: "Contorizează câte vizualizare de profil utilizator o singură dată per IP/Utilizator la fiecare N ore"
levenshtein_distance_spammer_emails: "Când se face detectarea spam pe bază de potrivire cu un set de criterii, care este diferența de numere de caractere care încă mai permite o potrivire aproximativă (fuzzy match)."
- max_new_accounts_per_registration_ip: "Dacă există deja (n) conturi cu nivelul de încredere 0 de la acest IP (și nici unul nu este un membru al echipei sau la NÎ2 sau mai mare), blochează acceptarea de noi înregistrări de la acest IP."
min_ban_entries_for_roll_up: "Când apeși pe butonul Consolidare, dacă există cel puțin (N) înregistrări, se va crea o nouă înregistrare de subrețea blocată"
max_age_unmatched_emails: "șterge înregistrările emailurilor verificate, care nu corespund, după (N) zile."
max_age_unmatched_ips: "șterge adresele de IP verificate, care nu corespund, după (N) zile."
@@ -1949,7 +1947,6 @@ ro:
label: "Numele comunității tale"
placeholder: "Bârlogul Mariei"
site_description:
- label: "Descrie-ți comunitatea într-o singură frază scurtă"
placeholder: "Un loc în care Maria și prietenii stau la taifas despre chestii mișto"
introduction:
title: "Introducere"
diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml
index 4b29b68443..06d1d4417a 100644
--- a/config/locales/server.ru.yml
+++ b/config/locales/server.ru.yml
@@ -268,7 +268,6 @@ ru:
topic_invite:
failed_to_invite: "Пользователь не может быть приглашён в эту тему без членства в одной из следующих групп: %{group_names}."
user_exists: "К сожалению, этот пользователь уже был приглашён. Вы можете пригласить пользователя в тему только один раз."
- muted_invitee: "Извините, но этот пользователь отключил возможность получать от вас уведомления."
muted_topic: "Извините, но этот пользователь отключил возможность получать уведомления для этой темы."
receiver_does_not_allow_pm: "Извините, но этот пользователь не позволяет вам отправлять ему личные сообщения."
sender_does_not_allow_pm: "Извините, но вы не позволяете этому пользователю отправлять вам личные сообщения."
@@ -1592,7 +1591,7 @@ ru:
notify_mods_when_user_silenced: "Отправлять сообщение всем модераторам, если пользователь автоматически заблокирован."
flag_sockpuppets: "Если новый пользователь отвечает на тему с того же IP-адреса, что и пользователь, создавший тему, пометить обе его публикации как потенциальный спам."
traditional_markdown_linebreaks: "Использовать стандартный способ переноса строки в Markdown: строка должна заканчиваться двумя пробелами."
- enable_markdown_typographer: "Использовать правила типографики для улучшения читабельности текста: заменять прямые кавычки «фигурными кавычками»; (c), (tm) - соответствующими символами и т. д."
+ enable_markdown_typographer: "Использовать правила типографики для улучшения читаемости текста: заменять прямые кавычки «фигурными кавычками»; (c), (tm) - соответствующими символами и т. д."
enable_markdown_linkify: "Автоматически отображать текст, который выглядит как ссылка, в виде ссылки: www.example.com и https://example.com будут автоматически отображены в виде ссылок"
markdown_linkify_tlds: "Список доменов верхнего уровня, которые автоматически обрабатываются как ссылки"
markdown_typographer_quotation_marks: "Список пар замены двойных и одинарных кавычек"
@@ -1852,9 +1851,9 @@ ru:
email_posts_context: "Количество предыдущих ответов, которое необходимо включать в почтовые уведомления в качестве контекста."
flush_timings_secs: "Частота, с которой оправляются метки времени на сервер, в секундах"
title_max_word_length: "Максимально допустимая длина слов в заголовке темы."
- title_min_entropy: "Минимальная энтропия, требуемая для названия темы. Энтропия - количество уникальных символов, причём некоторые русские буквы могут считаться за 2 символа, а не за 1, как английские)."
+ title_min_entropy: "Минимальная энтропия, требуемая для названия темы. (Энтропия - количество уникальных символов, причём некоторые русские буквы могут считаться за 2 символа, а не за 1, как английские)."
body_min_entropy: "Минимальная энтропия, требуемая для текста новой темы. Энтропия - количество уникальных символов, причём некоторые русские буквы могут считаться за 2 символа, а не за 1, как английские)."
- allow_uppercase_posts: "Разрешать создавать названия тем или сообщения заглавными буквами."
+ allow_uppercase_posts: "Разрешать создавать названия тем или сообщений заглавными буквами."
max_consecutive_replies: "Максимальное количество сообщений, которые пользователь может создать ПОДРЯД в теме, после чего у него не будет возможности добавить ещё один ответ."
enable_filtered_replies_view: 'Кнопка ответов отображает только текущее сообщение и ответы на него, свернув все остальные сообщения.'
title_fancy_entities: "В заголовках тем преобразовывать обычные символы ASCII и пунктуацию SmartyPants в объекты HTML"
@@ -1896,7 +1895,7 @@ ru:
topic_view_duration_hours: "Считать все просмотры темы как один просмотр, если просмотры происходят с одного IP-адреса в течение указанного здесь количества часов."
user_profile_view_duration_hours: "Считать все просмотры профиля пользователя как один просмотр, если просмотры происходят с одного IP-адреса в течение указанного здесь количества часов."
levenshtein_distance_spammer_emails: "Количество символов, на которое могут различаться сообщения, если проводится нечёткое сравнение текста при проверке писем на спам."
- max_new_accounts_per_registration_ip: "Если обнаружено указанное здесь количество аккаунтов с уровнем доверия 0, использующих общий IP-адрес (и ни один аккаунт не принадлежит сотрудникам или пользователям с уровнем доверия 2 и выше), прекратить регистрацию новых аккаунтов с этого IP-адреса."
+ max_new_accounts_per_registration_ip: "Если обнаружено указанное здесь количество аккаунтов с уровнем доверия 0, использующих общий IP-адрес (и ни один аккаунт не принадлежит сотрудникам или пользователям с уровнем доверия 2 и выше), прекратить регистрацию новых аккаунтов с этого IP-адреса. Для отключения параметра установите значение в 0."
min_ban_entries_for_roll_up: "Создавать новую запись запрета подсети, если в списке адресов есть указанное здесь количество записей."
max_age_unmatched_emails: "Удалять отфильтрованные письма после указанного здесь количества дней."
max_age_unmatched_ips: "Удалять отфильтрованные IP-адреса после указанного здесь количества дней."
@@ -2033,6 +2032,7 @@ ru:
global_notice: "Показывать глобальное постоянно отображаемое объявление со СРОЧНЫМИ / АВАРИЙНЫМИ сообщениями всем посетителям. Для скрытия объявления - удалите его содержание (разрешено использование HTML)."
disable_system_edit_notifications: "Отключить уведомления системы, если включена настройка 'download_remote_images_to_local'."
disable_category_edit_notifications: "Не уведомлять при перемещении тем в другой раздел."
+ disable_tags_edit_notifications: "Не уведомлять при изменении тегов темы."
notification_consolidation_threshold: "Максимальное количество уведомлений о полученных симпатиях или запросах на вступление в группу, после которого уведомления будут объединяться в одно. Для отключения параметра установите это значение в 0."
likes_notification_consolidation_window_mins: "Количество минут, по прошествии которых уведомления о полученных симпатиях будут объединяться в одно уведомление, если достигнуто пороговое значение, которое настраивается в параметре `site_setting.notification_consolidation_threshold`."
automatically_unpin_topics: "Автоматически откреплять полностью прочтённые темы."
@@ -4431,10 +4431,10 @@ ru:
label: "Название сообщества"
placeholder: "Женина тусовка"
site_description:
- label: "Опишите ваше сообщество одним коротким предложением"
+ label: "Опишите ваше сообщество в одном коротком предложении (используется в результатах поиска и социальных сетях)"
placeholder: "Место, где Женя и её друзья обсуждают интересные новости"
short_site_description:
- label: "Опишите свое сообщество в нескольких словах"
+ label: "Опишите ваше сообщество несколькими словами (используется для заголовка домашней страницы)"
placeholder: "Лучшее сообщество, когда-либо существовавшее в мире"
introduction:
title: "Введение"
diff --git a/config/locales/server.sk.yml b/config/locales/server.sk.yml
index de8af27724..c22b8537fc 100644
--- a/config/locales/server.sk.yml
+++ b/config/locales/server.sk.yml
@@ -936,7 +936,6 @@ sk:
topic_view_duration_hours: "Započítaj nové pozretie témy raz za IP/používateľa každých N hodín."
user_profile_view_duration_hours: "Započítaj nové pozretie používateľského profilu raz za IP/používateľa každých N hodín."
levenshtein_distance_spammer_emails: "Počet rozdielnych znakov, ktoré stále umožnia fuzzy zhodu, keď sa hľadájú emaily od spammerov."
- max_new_accounts_per_registration_ip: "Neakceptovať ďalšie prihlásenia z IP adresy v prípade, ak už existuje (n) účtov s úrovňou dôvery 0 z danej IP adresy (a žiadny nie je zamestnancec alebo s úrovňou dôvery 2 a vyššou)."
min_ban_entries_for_roll_up: "Ak existuje minimálne (N) položiek a kliknete na Zrolovať, vytvorí sa nový obmedzujúci záznam podsiete."
max_age_unmatched_emails: "Zmazať nevyskytujúce sa kontrolované emailové adresy po (N) dňoch."
max_age_unmatched_ips: "Zmazať nevyskytujúce sa kontrolované IP adresy po (N) dňoch."
diff --git a/config/locales/server.sl.yml b/config/locales/server.sl.yml
index 64f49c0429..dfa3528ae9 100644
--- a/config/locales/server.sl.yml
+++ b/config/locales/server.sl.yml
@@ -2019,10 +2019,8 @@ sl:
label: "Ime skupnosti"
placeholder: "Mikijev klub"
site_description:
- label: "Opiši vašo skupnost v enem kratkem stavku"
placeholder: "Prostor kjer Miki debatira s svojimi prijatelji o zanimivih zadevah "
short_site_description:
- label: "Opišite vašo skupnost v nekaj besedah"
placeholder: "Najboljši klub"
privacy:
title: "Dostopnost"
diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml
index ce81f88692..527a127e2b 100644
--- a/config/locales/server.sq.yml
+++ b/config/locales/server.sq.yml
@@ -747,7 +747,6 @@ sq:
privacy_policy_url: "If you have a Privacy Policy document hosted elsewhere that you want to use, provide the full URL here."
allowed_spam_host_domains: "A list of domains excluded from spam host testing. New users will never be restricted from creating posts with links to these domains."
levenshtein_distance_spammer_emails: "When matching spammer emails, number of characters difference that will still allow a fuzzy match."
- max_new_accounts_per_registration_ip: "If there are already (n) trust level 0 accounts from this IP (and none is a staff member or at TL2 or higher), stop accepting new signups from that IP."
min_ban_entries_for_roll_up: "When clicking the Roll up button, will create a new subnet ban entry if there are at least (N) entries."
max_age_unmatched_emails: "Delete unmatched screened email entries after (N) days."
max_age_unmatched_ips: "Delete unmatched screened IP entries after (N) days."
diff --git a/config/locales/server.sr.yml b/config/locales/server.sr.yml
index 23276cda97..6ac81e9905 100644
--- a/config/locales/server.sr.yml
+++ b/config/locales/server.sr.yml
@@ -487,7 +487,6 @@ sr:
label: "Ime vaše zajednice"
placeholder: "Milenino mesto za bleju"
site_description:
- label: "Opišite vašu zajednicu u jednoj kratkoj rečenici"
placeholder: "Mesto gde Milena i njene prijateljice ćaskaju o kul stvarima"
introduction:
title: "Uvod"
diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml
index d1a420f734..3821f558c2 100644
--- a/config/locales/server.sv.yml
+++ b/config/locales/server.sv.yml
@@ -250,7 +250,6 @@ sv:
topic_invite:
failed_to_invite: "Användaren kan inte bjudas in till detta ämne utan gruppmedlemskap i någon av de följande grupperna: %{group_names}."
user_exists: "Tyvärr har den användaren redan bjudits in. Du kan endast bjuda in en användare till ett ämne en gång."
- muted_invitee: "Tyvärr tystade den användaren dig."
muted_topic: "Tyvärr tystade den användaren detta ämne."
receiver_does_not_allow_pm: "Tyvärr tillåter inte den användaren att du skickar privata meddelanden till dem."
sender_does_not_allow_pm: "Tyvärr tillåter du inte den användaren att skicka privata meddelanden till dig."
@@ -1439,7 +1438,7 @@ sv:
summary_percent_filter: "Visa högsta % av inläggen när en användare bockar i 'Sammanfatta det här ämnet'"
summary_max_results: "Maximalt antal inlägg som returneras av 'Sammanfatta detta ämne'"
summary_timeline_button: "Visa en \"Sammanfatta\"-knapp på tidslinjen"
- enable_personal_messages: "Tillåt användare med förtroendenivå 1 (konfigurera via minsta förtroendenivå för att skicka meddelanden) att skapa meddelanden och svara på meddelanden. Notera att personalen alltid kan skicka meddelanden oavsett."
+ enable_personal_messages: "Tillåt användare med förtroendenivå 1 (konfigurera genom minsta förtroendenivå för att skicka meddelanden) att skapa meddelanden och svara på meddelanden. Notera att personalen alltid kan skicka meddelanden oavsett."
enable_system_message_replies: "Tillåter användare att svara på systemmeddelanden, även om personliga meddelanden har inaktiverats"
enable_long_polling: "Meddelande-buss som används för avisering kan använda long polling"
enable_chunked_encoding: "Aktivera packade kodningssvar (chunked) från servern. Den här funktionen fungerar på de flesta inställningar, men vissa proxyservrar kan buffra, vilket gör att svaren fördröjs"
@@ -1770,7 +1769,7 @@ sv:
topic_view_duration_hours: "Räkna en ny ämnesgranskning en gång per IP/Användare per N timmar"
user_profile_view_duration_hours: "Räkna en ny användarprofilgranskning en gång per IP/Användare per N timmar"
levenshtein_distance_spammer_emails: "Skillnad i antal tecken vid matchning av e-postadresser som används för skräppostutskick som fortfarande tillåter en suddig matchning."
- max_new_accounts_per_registration_ip: "Sluta acceptera nya registreringar från en IP-adress om det redan finns (n) användare med förtroendenivå 0 från den här IP-adressen (och ingen av dem är en medlem i personalen eller en användare med förtroendenivå 2 eller högre)."
+ max_new_accounts_per_registration_ip: "Sluta acceptera nya registreringar från en IP-adress om det redan finns (n) användare med förtroendenivå 0 från den här IP-adressen (och ingen av dem är en medlem i personalen eller en användare med förtroendenivå 2 eller högre). Ange värde 0 för att inaktivera begränsningen."
min_ban_entries_for_roll_up: "När upprullningsknappen klickas skapas en ny delnätspost om det finns minst (N) poster."
max_age_unmatched_emails: "Ta bort omatchade undersökta e-postinlägg efter (N) dagar."
max_age_unmatched_ips: "Ta bort omatchade undersökta IP-poster efter (N) dagar."
@@ -1907,6 +1906,7 @@ sv:
global_notice: "Visa ett BRÅDSKANDE, icke-stängningsbart globalt banderollmeddelande för alla besökare, ändra till blankt för att dölja det (HTML tillåts)."
disable_system_edit_notifications: "Inaktivera redigering av aviseringar av systemanvändaren när 'download_remote_images_to_local' har aktiverats."
disable_category_edit_notifications: "Inaktivera kategoriredigeringsaviseringar för ämnen."
+ disable_tags_edit_notifications: "Inaktivera notifieringar om att taggar har redigerats för ämnen."
notification_consolidation_threshold: "Antal meddelanden om gillad eller medlemskapsbegäran innan meddelandena konsolideras till en enda. Ställ in till 0 för att inaktivera."
likes_notification_consolidation_window_mins: "Varaktighet i minuter efter vilken gillad-aviseringar konsolideras till en enda avisering då tröskeln har uppnåtts. Tröskeln kan konfigureras via `SiteSetting.notification_consolidation_threshold`."
automatically_unpin_topics: "Ta automatiskt ned ämnen när användaren når botten."
@@ -2467,7 +2467,7 @@ sv:
new_version_mailer:
title: "E-post om ny version"
subject_template: "[%{email_prefix}] Ny version av Discourse, uppdatering tillgänglig"
- text_body_template: "Hurra, det finns en ny version av [Discourse](http://www.discourse.org) tillgänglig!\n\nDin version: %{installed_version}\nNy version: **%{new_version}**\n\n- Uppgradera genom att använda den enkla **[uppgraderingsknappen](%{base_url}/admin/upgrade)** \n\n- Se vad som är nytt i [uppdateringsnoteringarna](https://meta.discourse.org/tag/release-notes) eller läs i [GitHubs råa ändringslogg](https://github.com/discourse/discourse/commits/main)\n\n- Besök [meta.discourse.org](http://meta.discourse.org) för att läsa de senaste nyheterna, diskutera eller få hjälp angående Discourse\n"
+ text_body_template: "Hurra, det finns en ny version av [Discourse](http://www.discourse.org) tillgänglig!\n\nDin version: %{installed_version}\nNy version: **%{new_version}**\n\n- Uppgradera genom att använda den enkla **[uppgraderingsknappen](%{base_url}/admin/upgrade)** \n\n- Se vad som är nytt i [uppdateringsnoteringarna](https://meta.discourse.org/tag/release-notes) eller läs i [GitHubs obearbetade ändringslogg](https://github.com/discourse/discourse/commits/main)\n\n- Besök [meta.discourse.org](http://meta.discourse.org) för att läsa de senaste nyheterna, diskutera eller få hjälp angående Discourse\n"
new_version_mailer_with_notes:
title: "E-post om ny version med anteckningar"
subject_template: "[%{email_prefix}] Uppdatering tillgänglig"
@@ -2479,7 +2479,7 @@ sv:
- Uppgradera genom att använda den enkla **[uppgraderingsknappen](%{base_url}/admin/upgrade)**
- - Se vad som är nytt i [uppdateringsnoteringarna](https://meta.discourse.org/tag/release-notes) eller läs i [GitHubs råa ändringslogg](https://github.com/discourse/discourse/commits/main)
+ - Se vad som är nytt i [uppdateringsnoteringarna](https://meta.discourse.org/tag/release-notes) eller läs i [GitHubs obearbetade ändringslogg](https://github.com/discourse/discourse/commits/main)
- Besök [meta.discourse.org](http://meta.discourse.org) för att läsa de senaste nyheterna, diskutera eller få hjälp angående Discourse
@@ -3991,10 +3991,10 @@ sv:
label: "Ditt forums namn"
placeholder: "Här hänger Jane!"
site_description:
- label: "Beskriv ditt forum med en kort mening"
+ label: "Beskriv din gemenskap i en kort mening (används i sökresultat och sociala medier)"
placeholder: "Ett ställe där Jane och hennes vänner kan diskutera coola grejor"
short_site_description:
- label: "Beskriv ditt forum med ett par ord"
+ label: "Beskriv din gemenskap med några få ord (används för hemsidans titel)"
placeholder: "Bästa forumet någonsin"
introduction:
title: "Introduktion"
diff --git a/config/locales/server.sw.yml b/config/locales/server.sw.yml
index e356e2d8a3..bd24784c51 100644
--- a/config/locales/server.sw.yml
+++ b/config/locales/server.sw.yml
@@ -789,7 +789,6 @@ sw:
allowed_inline_onebox_domains: "Orodha za anwani za mtandao ambazo zitawekwa kwenye boxi kama zikiunganishwa bila kichwa cha habari"
summary_score_threshold: "Alama ya chini ambayo mada inahitaji kabla ya kuwekwa ndani ya 'Fupisha Hii Mada' "
summary_percent_filter: "Mtumiaji akibonyeza 'Fupisha Hii Mada', onyesha % machapisho ya juu"
- enable_personal_messages: "Ruhusu watumiaji wenye kiwango cha 1 cha uaminifu (inasanidiwa kwa kupitia kiwango cha chini cha uaminifu kutuma ujumbe) kutuma ujumbe wa barua pepe. Wasaidizi wataweza kutuma ujumbe mda kila wakati."
enable_system_message_replies: "Waruhusu watumiaji wajibu ujumbe wa mfumo,hata kama ujumbe binafsi umezuiliwa."
notify_mods_when_user_silenced: "Kama mtumiaji akinyamazishwa, tuma ujumbe kwa wasimamizi wote."
markdown_linkify_tlds: "Orodha ya vikoa vya hali ya juu ambavyo otomatikali ni viungo."
@@ -889,7 +888,6 @@ sw:
allowed_spam_host_domains: "Orodha ya vikoa vilivyotengwa kutoka kwenye majaribio ya komputa mwenyeji ya barua taka. Watumiaji wapya hawatazuiliwa kutengeneza machapisho yenye viungo kwenda kwenye vikoa hivi."
topic_view_duration_hours: "Hesabu utembezi wa mada mpya mara moja kuendana na anwani ya mtandao/Mtumiaji kila baada ya masaa N"
user_profile_view_duration_hours: "Hesabu utembezi wa mada mpya mara moja kuendana na anwani ya mtandao/Mtumiaji kila baada ya masaa N"
- max_new_accounts_per_registration_ip: "Kama kuna akaunti 0 zenye kiwango (n) cha uaminifu kutoka kwenye anwani hii ya mtandao (na hakuna hata mmoja ambaye ni msaidizi or kwenye kiwango cha 2 cha uaminifu au zaidi), kataa usajili kutoka kwenye anwani hiyo."
num_hours_to_close_topic: "Mda wa masaa kusitisha mada ili kuingilia."
auto_silence_fast_typers_max_trust_level: "Kiwango cha juu cha uaminifu kunyamazisha wanaochapa haraka sana otomatikali"
reply_by_email_enabled: "Ruhusu majibu ya mada kupitia barua pepe."
@@ -2027,7 +2025,6 @@ sw:
label: "Jina la Jumuia"
placeholder: "Sehemu ya Jeni ya Kuburudika"
site_description:
- label: "Elezea jamii yako ndani ya sentensi moja"
placeholder: "Sehemu ya Jeni na marafiki zake kujadili vitu vizuri"
introduction:
title: "Utambulisho"
diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml
index 534e63f25c..24753ec717 100644
--- a/config/locales/server.tr_TR.yml
+++ b/config/locales/server.tr_TR.yml
@@ -250,7 +250,6 @@ tr_TR:
topic_invite:
failed_to_invite: "Kullanıcı, aşağıdaki gruplardan birinde grup üyeliği olmadan bu konuya davet edilemez: %{group_names}."
user_exists: "Üzgünüz, bu kullanıcı zaten davet edildi. Konuya yalnızca bir kullanıcı davet edebilirsiniz."
- muted_invitee: "Üzgünüz, o kullanıcı sizi sessize aldı."
muted_topic: "Üzgünüz, o kullanıcı bu konuyu sessize aldı."
receiver_does_not_allow_pm: "Üzgünüz, bu kullanıcı onlara özel mesaj göndermenize izin vermiyor."
sender_does_not_allow_pm: "Üzgünüz, o kullanıcının size özel mesaj göndermesine izin vermiyorsunuz."
@@ -266,6 +265,7 @@ tr_TR:
not_found: "İstenilen URL ya da kaynak bulunamadı."
invalid_access: "İstenilen kaynağı görüntüleyebilmeniz için izniniz yok."
authenticator_not_found: "Kimlik doğrulama yöntemi mevcut değil veya devre dışı bırakıldı."
+ authenticator_no_connect: "Bu kimlik doğrulama sağlayıcısı varolan bir forum hesabına bağlanmaya izin vermez."
invalid_api_credentials: "İstenilen kaynağı görüntülemenize izin verilmiyor. API kullanıcı adı veya parolası geçersiz."
provider_not_enabled: "İstenilen kaynağı görüntülemenize izin verilmiyor. Kimlik doğrulama kontrolü etkin değil."
provider_not_found: "İstenilen kaynağı görüntülemenize izin verilmiyor. Kimlik doğrulama sağlayıcısı mevcut değil."
@@ -367,7 +367,7 @@ tr_TR:
read_full_topic: "Konunun tamamını okuyun"
private_message_abbrev: "İlt"
rss_description:
- latest: "En son konular"
+ latest: "En Son Konular"
top: "En iyi konular"
top_all: "Tüm zamanlarda en iyi konular"
top_yearly: "Yıllık en iyi konular"
@@ -477,6 +477,7 @@ tr_TR:
Herkesin benzersiz bir profil resmi olduğunda, tartışmaları takip etmek ve konuşmalarda ilginç insanlar bulmak daha kolay!
sequential_replies: "### Aynı anda birkaç gönderiye yanıt vermeyi denediniz \nBunun yerine yerine, lütfen önceki gönderilerden alıntılar veya @name referansları içeren tek bir yanıt veriniz.\nMetni vurgulayıp görünen alıntı düğmesini seçerek önceki yanıtınızı düzenleyebilirsiniz. Böylece diğer kullanıcıların, yanıtlarınızı okuması daha kolay olacaktır.\n"
dominating_topic: Birden fazla gönderdiniz %{percent}Burada cevapların% else orada herkesin duymak isteriz edilir?
+ get_a_room: '@%{reply_username} %{count} kez yanıt verdiniz, bunun yerine ona kişisel bir mesaj gönderebileceğinizi biliyor muydunuz?'
too_many_replies: |
### Bu konu için cevap limitinizi doldurdunuz
@@ -654,6 +655,9 @@ tr_TR:
has_likes:
one: "%{count} Beğeni"
other: "%{count} Beğeni"
+ cannot_permanently_delete:
+ many_posts: "Başka gönderiler olduğu için bu konuyu kalıcı olarak silemezsiniz."
+ wait_or_different_admin: "Bu gönderiyi kalıcı olarak silmeden önce %{time_left} beklemelisiniz veya farklı bir yönetici bunu yapmalıdır."
rate_limiter:
slow_down: "Bu işlemi çok fazla yaptınız, lütfen daha sonra tekrar deneyin."
too_many_requests: "Bu işlemi çok defa yaptınız. Lütfen tekrar denemeden önce %{time_left} bekleyin."
@@ -1330,8 +1334,12 @@ tr_TR:
queue_size_warning: "Kuyruğa eklenmiş işlerin sayısı fazla: %{queue_size}. Bu Sidekiq işlem(ler)indeki bir sorunu işaret ediyor olabilir, ya da daha fazla Sidekiq işçisi eklemeniz gerekiyor olabilir."
memory_warning: "Sunucunuz toplam 1GB'tan az bellek ile çalışıyor. En az 1GB bellek tavsiye edilmektedir."
google_oauth2_config_warning: 'Sunucu, Google OAuth2 (enable_google_oauth2_logins) ile kaydolmaya ve oturum açmaya izin verecek şekilde yapılandırılmıştır, ancak istemci kimliği ve istemci gizli değerleri ayarlanmamıştır. Site Ayarları ve ayarları güncelleyin. Daha fazlasını öğrenmek için bu kılavuza bakın.'
+ facebook_config_warning: 'Sunucu Facebook (enable_facebook_logins) ile üyelik oluşturulmasına ve giriş yapılmasına izin veriyor, fakat uygulama ID''si ve gizli uygulama değerleri henüz ayarlanmamış. Site Ayarlarına gidin ve ayarları güncelleyin. Daha fazla bilgi için bu kılavuza bakın.'
+ twitter_config_warning: 'Sunucu Twitter (enable_twitter_logins) ile üyelik oluşturulması ve giriş yapılmasına izin veriyor, fakat anahtar ve gizli değerler henüz ayarlanmamış. Site Ayarlarınagidin ve ayarları güncelleyin. Daha fazla bilgi için bu kılavuza bakın.'
+ github_config_warning: 'Sunucu GitHub (enable_github_logins) ile üyelik oluşturulması ve giriş yapılmasına izin veriyor, fakat iclient ID ve client secret değerleri henüz ayarlanmamış. Site Ayarlarına gidin ve ayarları güncelleyin. Daha fazla bilgi için bu kılavuza bakın.'
s3_config_warning: 'Sunucu s3''e dosya yüklenebilmesi için yapılandırılmış, fakat şunlardan en az biri henüz ayarlanmamış: s3_access_key_id, s3_secret_access_key or s3_upload_bucket. Site Ayarlarına gidin ve ayarları güncelleyin. Daha fazla bilgi için "S3''e resim yüklemeleri nasıl ayarlanır?" konulu gönderiye bakın.'
s3_backup_config_warning: 'Sunucu s3''e yedeklerin yüklenebilmesi için yapılandırılmış, fakat şunlardan en az biri henüz ayarlanmamış: s3_access_key_id, s3_secret_access_key or s3_upload_bucket. Site Ayarlarına gidin ve ayarları güncelleyin. Daha fazla bilgi için "S3''e resim yüklemeleri nasıl ayarlanır?" konulu gönderiye bakın.'
+ s3_cdn_warning: 'Sunucu, dosyaları S3''e yükleyecek şekilde yapılandırılmış ancak yapılandırılmış bir S3 CDN''si yok. Bu, pahalı S3 maliyetlerine ve daha yavaş site performansına yol açabilir. Daha fazla bilgi için bkz. "Yüklemeler için Nesne Depolamayı Kullanma".'
image_magick_warning: 'Sunucu büyük resimlerin küçük boylarının oluşturulması için yapılandırılmış, fakat ImageMagick henüz kurulmamış. Favori paket yöneticinizi kullanarak ImageMagick kurun veya son sürümünü indirin.'
failing_emails_warning: 'Başarısızlıkla sonuçlanmış %{num_failed_jobs} e-posta işlemi bulunuyor. app.yml dosyanızı kontrol edin ve e-posta sunucu ayarlarınızın doğru olduğundan emin olun. Sidekiq''deki başarısız işlemlere göz atın.'
subfolder_ends_in_slash: "Alt dizin kurulumunuz hatalı, DISCOURSE_RELATIVE_URL_ROOT sonunda yan çizgi bulunmalı."
@@ -1345,6 +1353,7 @@ tr_TR:
force_https_warning: "Web siteniz SSL kullanıyor. Ancak site ayarlarınızda ` force_https` henüz etkin değil."
out_of_date_themes: "Aşağıdaki temalar için güncellemeler mevcuttur:"
unreachable_themes: "Aşağıdaki temalar için güncellemeleri kontrol edemedik:"
+ watched_word_regexp_error: "İzlenen %{action} kelimeler için normal ifadeye geçersiz, Lütfen İzlenen Kelime ayarlarınızı kontrol edin veya 'izlenen kelimeler normal ifadeler' site ayarını devre dışı bırakın."
site_settings:
disabled: "devredışı"
display_local_time_in_user_card: "Kullanıcı kartı açıkken yerel saati kullanıcının saat dilimine göre görüntüle."
@@ -1352,6 +1361,7 @@ tr_TR:
delete_old_hidden_posts: "30 günden fazla süreyle gizli kalan gizlenmiş gönderileri otomatik olarak sil."
default_locale: "Bu Söylem örneğinin varsayılan dili. Özelleştir / Metinile sistem tarafından oluşturulan kategorilerin ve konuların metnini değiştirebilirsiniz."
allow_user_locale: "Kullanıcıların arayüz için kendi istedikleri dili seçmesine izin ver"
+ set_locale_from_accept_language_header: "anonim kullanıcılar için web tarayıcılarının dil başlıklarından arayüz dilini ayarlayın"
support_mixed_text_direction: "Soldan sağa ve sağdan sola karışık metin yönlerini destekleyin."
min_post_length: "Gönderide olması gereken en az karakter sayısı"
min_first_post_length: "İlk gönderi için (konu içi) izin verilen en az karakter sayısı"
@@ -1359,6 +1369,7 @@ tr_TR:
max_post_length: "Gönderide izin verilen en fazla karakter sayısı"
topic_featured_link_enabled: "Konuları olan bir bağlantı yayınlamayı etkinleştir."
show_topic_featured_link_in_digest: "Özetlenmiş e-postada konu özellikli bağlantıyı gösterin."
+ min_topic_views_for_delete_confirm: "Bir konunun silindiğinde bir onay açılır penceresinin görünmesi için sahip olması gereken minimum görüntüleme sayısı"
min_topic_title_length: "Konuda olması gereken en az karakter sayısı"
max_topic_title_length: "Konu başlığında izin verilen en fazla karakter sayısı"
min_personal_message_title_length: "İleti başlıkları için izin verilen en az karakter sayısı"
@@ -1375,11 +1386,14 @@ tr_TR:
category_search_priority_high_weight: "Yüksek kategori öncelikli arama için sıralamada uygulanan ağırlık."
allow_uncategorized_topics: "Konuların kategori seçmeden oluşturulmasına izin ver. DİKKAT: Bu özelliği kapamadan önce kategorisiz tüm konuları kategorize etmeniz lazım."
allow_duplicate_topic_titles: "Aynı başlık ile birden çok konu açılmasına izin ver."
+ allow_duplicate_topic_titles_category: "Farklı kategorilerde aynı başlıkla konu açılmasına izin ver. allow_duplicate_topic_titles devre dışı bırakılmalıdır."
unique_posts_mins: "Kullanıcının aynı içerikle yeni bir gönderi oluşturmadan önce geçmesi gereken dakika"
educate_until_posts: "Kullanıcılar, ilk (n) gönderilerini yazmaya başladıklarında, pop-up yeni kullanıcı eğitim paneli metin düzenleyecisinin üstünde çıksın."
title: "Sayfa başlığı etiketinde kullanılacak, bu sitenin ismi."
site_description: "Meta açıklama etiketinde kullanılacak, bu sitenin bir cümlelik açıklaması."
short_site_description: "Ana sayfadaki başlık etiketinde kullanılan kısa açıklama."
+ contact_email: "Bu site için yöneticini e-posta adresi. /about iletişim formu içerisinde, sadece acil durumlar ve kritik bildirimler için kullanılacak."
+ contact_url: "Bu site için iletişim URL'sidir. Acil konular için / about iletişim formunda kullanılır."
crawl_images: "Doğru genişlik ve yükseklik boyutlarını girmek için uzak URL'lerdeki resimlerin birer kopyasını alınsın."
download_remote_images_to_local: "Uzaktaki resimler yerel resimlere çevirmek için indirilsin; bu ayar resim bağlantılarının kırılmasını önleyecektir"
download_remote_images_threshold: "Uzaktaki resimlerin yerele indirilmesi için gereken en az disk alanı (yüzdesel)"
@@ -1406,6 +1420,7 @@ tr_TR:
post_onebox_maxlength: "Kutulanmış bir Discourse gönderisinin en fazla karakter uzunluğu"
blocked_onebox_domains: "Asla yayınlanmayacak alanların listesi."
allowed_inline_onebox_domains: "Başlıksız bağlanırsa minyatür formda yayınlanacak alanların listesi"
+ enable_inline_onebox_on_all_domains: "İnline_onebox_domain_whitelist site ayarını yok say ve tüm alan adlarında inline onebox'a izin ver."
force_custom_user_agent_hosts: "Tüm isteklerde özel onebox kullanıcı aracısını kullanacak ana makineler. (Özellikle kullanıcı aracısıyla erişimi sınırlayan ana bilgisayarlar için kullanışlıdır)."
max_oneboxes_per_post: "Gönderide olabilecek en fazla kutulama sayısı"
facebook_app_access_token: "Facebook uygulama kimliğinizden ve gizli dizenizden bir token oluşturuldu. Instagram onebox'larını oluşturmak için kullanılır."
@@ -1418,6 +1433,7 @@ tr_TR:
mobile_logo_dark: "'Mobil logo' ayarı için koyu şema alternatifi."
large_icon: "Diğer meta veri simgeleri için temel olarak kullanılan resim. İdeal olarak 512 x 512 den büyük olmalıdır. Boş bırakılırsa logo_small kullanılacaktır."
manifest_icon: "Android de logo/splash görüntüsü olarak kullanılan resim. Otomatik şekilde 512 × 512 olarak yeniden boyutlandırılır. Boş bırakılırsa, large_icon kullanılır."
+ manifest_screenshots: "Uygulama yükleme (PWA) sayfasında örnek özelliklerinizi ve işlevselliğinizi gösteren ekran görüntüleri. Tüm resimler yerel yüklemeler olmalı ve aynı boyutlarda olmalıdır."
favicon: "Siteniz için bir simge, bkz. Https://en.wikipedia.org/wiki/Favicon . Bir CDN üzerinde düzgün çalışmak için bir png olması gerekir. 32x32 olarak yeniden boyutlandırılacak. Boş bırakılırsa, large_icon kullanılır."
apple_touch_icon: "Apple dokunmatik cihazlar için kullanılan simge. Otomatik olarak 180x180 boyutuna getirilecek. Boş bırakılırsa, large_icon kullanılır."
opengraph_image: "Sayfada başka uygun görüntü yoksa varsayılan açık grafik görüntüsü. Boş bırakılırsa, large_icon kullanılır"
@@ -1426,14 +1442,19 @@ tr_TR:
email_custom_headers: "Sınırlandırılmış özel e-posta başlıkları listesi"
email_subject: "Standart e-postalar için özelleştirilebilir konu biçimi. Bkz. https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801"
detailed_404: "Kullanıcılara belirli bir konuya neden erişemedikleri hakkında daha fazla ayrıntı sağlar. Not: Bu daha az güvenlidir çünkü kullanıcılar bir URL'nin geçerli bir konuya bağlantı verip vermediğini bilecektir."
+ enforce_second_factor: "Kullanıcıları ikinci faktör kimlik doğrulamasını etkinleştirmeye zorlar. Tüm kullanıcılara uygulamak için \"tümü\"; seçeneğini seçin. Yalnızca yetkili kullanıcılara uygulamak için \"yetkili\" seçin."
force_https: "Sitenizi HTTPS kullanmaya zorlayın. DİKKAT: bu seçeneği HTTPS'nin her yerde doğru bir şekilde çalıştığından emin olmadan SEÇMEYİN. Sitenizde bulunan paylaşım siteleri bağlantılarını, CDN' adresinizi, dışsal bağlantısı olan görsellerinizi de kontrol ettiniz mi?"
+ same_site_cookies: "Aynı site çerezlerini kullanın, desteklenen tüm tarayıcılardaki Siteler Arası İstek Sahteciliği'ni (Lax veya Strict) ortadan kaldırırlar. Uyarı: Katı kurallı oturum, yalnızca giriş yapmaya ve TOA kullanmaya zorlayan sitelerde çalışır."
summary_score_threshold: "Bir gönderinin 'Bu Konuyu Özetle' içinde yer alması için gereken en az skor."
+ summary_posts_required: "'Bu Konuyu Özetle' etkinleştirilmeden önce bir konudaki minimum gönderi sayısı. Bu ayarda yapılan değişiklikler bir hafta içinde geriye dönük olarak uygulanacaktır."
+ summary_likes_required: "'Bu Konuyu Özetle' etkinleştirilmeden önce bir konudaki minimum gönderi sayısı. Bu ayarda yapılan değişiklikler bir hafta içinde geriye dönük olarak uygulanacaktır."
summary_percent_filter: "Kullanıcı 'Bu Konuyu Özetle'ye tıkladığında, gönderinin ilk % kısmını göster"
summary_max_results: "'Bu Konuyu Özetle' den dönen en fazla gönderi sayısı"
summary_timeline_button: "Zaman çizelgesinde 'Özetle' düğmesi göster"
- enable_personal_messages: "Güven seviyesi 1'e(özel ileti göndermek için en az seviye ayarıyla belirlenebilir) sahip kullanıcıların ileti oluşturup cevaplamasına izin ver. Görevliler her durumda ileti gönderebilir."
+ enable_personal_messages: "Güven düzeyi 1 (mesaj göndermek için minimum güven seviyesi ile yapılandırılabilir) kullanıcıların mesaj oluşturmasına ve mesajları yanıtlamasına izin verin. Personelin ne olursa olsun her zaman mesaj gönderebileceğini unutmayın."
enable_system_message_replies: "Kişisel mesajlar devre dışı bırakılsa bile kullanıcıların sistem mesajlarını yanıtlamasına izin verir"
enable_long_polling: "Bildiri için kullanılan ileti yolu uzun sorgular yapabilir"
+ enable_chunked_encoding: "Sunucu tarafından yığınlanmış kodlama yanıtlarını etkinleştirin. Bu özellik çoğu kurulumda çalışır, ancak bazı proxy'ler arabelleğe alabilir ve yanıtların gecikmesine neden olabilir"
long_polling_base_url: "Uzun sorgular için kullanılan baz URL (CDN dinamik içerik sunuyorsa, bunu origin olarak ayarladığına emin ol) ör: http://origin.site.com"
long_polling_interval: "Gönderilecek bilgi olmadığı zaman sunucunun kullanıcılara geri dönmeden önce beklemesi gereken zaman (sadece giriş yapmış kullanıcın için)"
polling_interval: "Uzun sorgular yapılmadığı zaman, kaç mili saniyede bir giriş yapmış kullanıcılar poll yapmalı"
@@ -1727,7 +1748,6 @@ tr_TR:
topic_view_duration_hours: "Her N saatte IP/Kullanıcı başına bir kez yeni konu görüntülemesi say"
user_profile_view_duration_hours: "Her N saatte IP/Kullanıcı başına bir kez yeni profil görüntülemesi say"
levenshtein_distance_spammer_emails: "İstenmeyen e-postaları eşleştirilirken, bulanık eşleşme için tahammül edilecek karakter sayısı farklılığı."
- max_new_accounts_per_registration_ip: "Eğer bu IP'den güven seviyesi 0 olan halihazırda (n) hesap varsa (hiçbiri görevli, GS2 ya da daha yüksek seviyede biri değilse), bu IP'den yeni kayıt kabul etme."
min_ban_entries_for_roll_up: "Topla düğmesine tıklandığında, (N) adetten fazla giriş varsa yeni bir subnet engelleme girişi yaratılacak."
max_age_unmatched_emails: "Taranmış e-posta kayıtlarından karşılığı olmayanları (N) gün sonunda sil."
max_age_unmatched_ips: "Taranmış IP girişlerinden karşılığı olmayanları (N) gün sonunda sil."
@@ -2835,7 +2855,7 @@ tr_TR:
Tedbir olarak hesabınız sessize alındı. Bir görevli hesabınızı kontrol edene kadar konulara cevap yazamayacak veya yeni konu açamayacaksınız. Verdiğimiz rahatsızlıktan dolayı özür dileriz.
- Ek bilgi için, başvurunuz [topluluk yönergelerine](%{base_url}/guidelines).
+ Ek bilgi için [topluluk yönergelerine](%{base_url}/guidelines) bakınız.
too_many_tl3_flags:
title: "Çok Fazla GS3 Raporu"
subject_template: "Yeni hesap askıda"
@@ -2846,7 +2866,7 @@ tr_TR:
Tedbir olarak hesabınız sessize alındı. Bir görevli hesabınızı kontrol edene kadar konulara cevap yazamayacak veya yeni konu açamayacaksınız. Verdiğimiz rahatsızlıktan dolayı özür dileriz.
- Ek bilgi için, başvurunuz [topluluk yönergelerine](%{base_url}/guidelines).
+ Ek bilgi için [topluluk yönergelerine](%{base_url}/guidelines) bakınız.
silenced_by_staff:
title: "Görevli tarafından sessize alındı."
subject_template: "Hesap geçiçi olarak askıda"
@@ -2857,7 +2877,7 @@ tr_TR:
Lütfen gezmeye devam edin fakat bir [görevli](%{base_url}/about) son gönderilerinizi kontrol edene kadar konulara cevap yazamayacak veya konu açamayacaksınız. Verdiğimiz rahatsızlıktan dolayı özür dileriz.
- Ek bilgi için, başvurunuz [topluluk yönergelerine](%{base_url}/guidelines).
+ Ek bilgi için [topluluk yönergelerine](%{base_url}/guidelines) bakınız.
user_automatically_silenced:
title: "Kullanıcı otomatik olarak sessize alındı"
subject_template: "Topluluk raporlarından dolayı yeni kullanıcı %{username} sessize alındı"
@@ -3793,10 +3813,8 @@ tr_TR:
label: "Topluluğunuzun ismi"
placeholder: "Ali'nin Yeri"
site_description:
- label: "Topluluğunuzu kısa bir cümle ile tanımlayın"
placeholder: "Ali ve arkadaşlarının ilginç şeyleri tartışabilecekleri bir yer"
short_site_description:
- label: "Topluluğunuzu birkaç kelimeyle açıklayın"
placeholder: "Şimdiye kadarki en iyi topluluk"
introduction:
title: "Tanıtım"
diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml
index 96ea10014c..11e2620856 100644
--- a/config/locales/server.uk.yml
+++ b/config/locales/server.uk.yml
@@ -268,7 +268,6 @@ uk:
topic_invite:
failed_to_invite: "Користувача не можна запросити в цю тему без членства в одній із наступних груп: %{group_names}."
user_exists: "На жаль, цього користувача вже запрошено. Ви можете запросити користувача до теми лише один раз."
- muted_invitee: "На жаль, цей користувач вас ігнорує."
muted_topic: "На жаль, цей користувач ігнорує цю тему."
receiver_does_not_allow_pm: "На жаль, цей користувач не дозволяє вам надсилати йому приватні повідомлення."
sender_does_not_allow_pm: "На жаль, ви не дозволяєте цьому користувачеві надсилати вам приватні повідомлення."
@@ -536,6 +535,7 @@ uk:
Для більшості людей набагато простіше читати теми, у яких довгі відповіді і їх мало, ніж коли багато коротеньких відповідей.
dominating_topic: Ви розмістили тут більше %{percent}% відповідей, можливо є ще хтось, кого варто було би почути?
+ get_a_room: Ви відповіли користувачу @%{reply_username} %{count} разів, ви знали, що можете надіслати їм особисте повідомлення?
too_many_replies: "### Ви досягли межі відповідей в цю тему. \nНа жаль, нові користувачі тимчасово обмежені %{newuser_max_replies_per_topic} відповідей в цій темі. \nЗамість того, щоб додавати ще одну відповідь, будь ласка, подумайте про редагування попередніх відповідей або відвідайте інші теми.\n"
reviving_old_topic: |
### Відновити обговорення в цій темі?
@@ -712,12 +712,27 @@ uk:
few: "%{count} Симпатії"
many: "%{count} Симпатій"
other: "%{count} Симпатій"
+ cannot_permanently_delete:
+ many_posts: "Ви не можете остаточно видалити цю тему, тому що вона має інші повідомлення."
+ wait_or_different_admin: "Ви повинні зачекати %{time_left} , перш ніж остаточно видалити цей пост, або інший адміністратор може зробити це."
rate_limiter:
slow_down: "Ви виконали цю дію занадто багато разів, спробуйте ще раз пізніше."
too_many_requests: "Ви виконуєте цю дію занадто часто. Зачекайте будь-ласка %{time_left} перш ніж спробувати знову."
by_type:
+ first_day_replies_per_day: "Ми цінуємо ваш ентузіазм, так тримати! Зважаючи на безпеку нашої спільноти, ви досягли максимальної кількості відповідей, які за перший день може створювати новий користувач. Зачекайте, будь ласка, %{time_left} , і ви зможете створювати більше відповідей."
+ first_day_topics_per_day: "Ми цінуємо ваш ентузіазм! Тим не менш, для безпеки нашої спільноти ви досягли максимальної кількості тем, які новий користувач може створити за перший день. Будь ласка, зачекайте %{time_left} , і ви зможете створювати нові теми."
+ create_topic: "Ви створюєте теми занадто швидко. Будь ласка зачекайте %{time_left}, потім спробуйте ще раз"
+ create_post: "Ви відповідаєте занадто швидко. Будь ласка, зачекайте %{time_left} перед наступною спробою."
+ delete_post: "Ви видаляєте повідомлення занадто швидко. Будь ласка зачекайте %{time_left} перед наступною спробою."
+ public_group_membership: "Ви приєднуєтеся / залишаєте групи занадто часто. Будь ласка зачекайте %{time_left} перед наступною спробою."
+ topics_per_day: "Ви досягли максимальної кількості нових тем, дозволених за день. Ви можете створити нові теми через %{time_left}."
+ pms_per_day: "Ви досягли максимальної кількості нових тем, дозволених за день. Ви можете створити нові теми через %{time_left}."
+ create_like: "Ого! Ви ділилися великою любов'ю! Ви досягли максимуму лайків за сьогодні, але, коли ви отримаєте вищий рівень довіри, то зможете додавати більше лайків. Ви зможете знову вподобати допис через %{time_left}."
+ create_bookmark: "Ви досягли максимальної кількості щоденних закладок. Ви можете створити більше закладок через %{time_left}."
+ edit_post: "Ви досягли максимальної кількості щоденних правок. Ви зможете знову вносити зміни через %{time_left}."
live_post_counts: "Ви занадто швидко переглядаєте пости. Будь ласка зачекайте %{time_left} перед наступною спробою."
unsubscribe_via_email: "Ви досягли максимальної кількості відписок через email на сьогодні. Будь ласка зачекайте %{time_left} перед наступною спробою."
+ topic_invitations_per_day: "Ви досягли максимальної кількості запрошень в тему за день. Ви знову зможете надсилати запрошення через %{time_left}."
hours:
one: "%{count} година"
few: "%{count} години"
@@ -978,6 +993,18 @@ uk:
others: "Закладок немає."
no_drafts:
self: "У вас немає чернеток; почніть складати відповідь в будь-якій темі, і вона буде автоматично збережена як нова чернетка."
+ email_settings:
+ pop3_authentication_error: "Виникла проблема з обліковими даними POP3, перевірте ім'я користувача та пароль і повторіть спробу."
+ imap_authentication_error: "Виникла проблема з обліковими даними IMAP, перевірте ім'я користувача та пароль і повторіть спробу."
+ imap_no_response_error: "Сталася помилка під час з’єднання з сервером IMAP. %{message}"
+ smtp_authentication_error: "Виникла проблема з обліковими даними SMTP, перевірте ім'я користувача та пароль і повторіть спробу."
+ authentication_error_gmail_app_password: 'Потрібен спеціальний пароль. Дізнайтеся більше у цій статті довідки Google'
+ smtp_server_busy_error: "Сервер SMTP зараз зайнятий, повторіть спробу пізніше."
+ smtp_unhandled_error: "Сталася необроблена помилка під час підключення до сервера SMTP. %{message}"
+ imap_unhandled_error: "Під час з’єднання з сервером IMAP сталася невідома помилка. %{message}"
+ connection_error: "Виникла проблема з'єднання з сервером, перевірте ім'я сервера і порт і спробуйте знову."
+ timeout_error: "Час очікування підключення до сервера вичерпано, перевірте ім'я сервера і порт і спробуйте знову."
+ unhandled_error: "Необроблена помилка під час перевірки параметрів електронної пошти. %{message}"
webauthn:
validation:
invalid_type_error: "Наданий тип webauthn був недійсним. Дійсні типи є webauthn.get та webauthn.create."
@@ -1026,6 +1053,7 @@ uk:
remove: "Ця тема тепер не є оголошенням і більше не буде доступна широкому загалу вгорі на всіх сторінках."
unsubscribed:
title: "Налаштування електронних листів змінені!"
+ description: "Параметри електронної пошти для %{email} було оновлено. Щоб змінити параметри електронної пошти відвідайте налаштування користувача."
topic_description: "Щоб повторно підписатися на %{link}, використовуйте кнопку сповіщень внизу або праворуч від теми."
private_topic_description: "Щоб повторно підписатися, використовуйте елемент сповіщення внизу або праворуч від теми."
uploads:
@@ -1432,6 +1460,7 @@ uk:
force_https_warning: "Ваш веб-сайт використовує SSL. Але `force_https` ще не ввімкнено в налаштуваннях вашого сайту."
out_of_date_themes: "Оновлення доступні для таких тем:"
unreachable_themes: "Нам не вдалося перевірити наявність оновлень за наступними темами:"
+ watched_word_regexp_error: "Неприпустимий регулярний вираз для '%{action}' контрольованих слів. Будь ласка, перевірте ваші Налаштування контрольованих слів, або вимкніть параметр 'контрольовані слова представлені регулярними виразами'."
site_settings:
disabled: "вимкнуто"
display_local_time_in_user_card: "Відображення локального часу на основі часового поясу користувача під час відкриття картки користувача."
@@ -1464,11 +1493,13 @@ uk:
category_search_priority_high_weight: "Вага, застосована до ранжирування для високого пріоритету пошуку категорії."
allow_uncategorized_topics: "Дозволити створення тем без категорії. УВАГА: Якщо є які-небудь теми без категорії, ви повинні перекласифікувати їх, перш ніж відключити."
allow_duplicate_topic_titles: "Дозволити теми з однаковими, дублюючими назвами."
+ allow_duplicate_topic_titles_category: "Дозволити теми з ідентичними, повторюваними заголовками, якщо категорія інша. Параметр allow_duplicate_topic_titles повинен бути вимкненим."
unique_posts_mins: "Кількість хвилин, перш ніж користувач зможе знову написати допис з тим самим вмістом"
educate_until_posts: "Кількість перших повідомлень нових користувачів, для яких потрібно показувати нескладну підказку з порадами для новачків."
title: "Назва цього сайту. Буде додано в HTML-тег title."
site_description: "Опишіть сайт одним реченням, для використання опису в мета-тег description."
short_site_description: "Короткий опис, що використовується в тезі заголовка на головній сторінці"
+ contact_email: "Адреса електронної пошти ключового контакту, відповідального за цей сайт. Використовується для критичних повідомлень, а також відображається на /about для невідкладних питань."
contact_url: "Контактний URL цього сайту. Вказано на сторінці /about для термінових питань."
crawl_images: "Отримувати зображення з віддалених адрес, щоб встановити правильні розміри ширини та висоти."
download_remote_images_to_local: "Завантажувати картинки, вставлені в повідомлення посиланнями на інші сайти, і зберігати їх локально, щоб запобігти їх зміни або втрату."
@@ -1520,13 +1551,14 @@ uk:
detailed_404: "Надає користувачам більше інформації про те, чому він не може отримати доступ до певної теми. Примітка: Це не дуже безпечно, оскільки користувачі дізнаються, чи URL-адреса посилається на дійсну тему."
enforce_second_factor: "Примусова активація двофакторної аутентифікації. Виберіть \"Всі\" для примусового увімкнення всім користувачам. Виберіть \"Персонал\" для використання тільки для співробітників користувачів."
force_https: "Примусово використовувати лише HTTPS для вашого сайту. УВАГА: можуть зникнути зображення до моменту, коли HTTPS повністю налаштовано і працюватиме всюди! Ви перевірили всі свої CDN, соціальні логіни і будь-які зовнішні логотипи і залежності, щоб переконатися, що вони всі сумісні з HTTPS, також?"
+ same_site_cookies: "Використовувати файли cookie, зі знешкодженням при перехресному запиті CSRF для підтримуючих це браузерів (Lax або Strict). Попередження: Strict будуть працювати тільки на сайтах, які використовують SSO."
summary_score_threshold: "Мінімальна оцінка повідомлення, необхідна для його включення в зведення по темі"
summary_posts_required: "Мінімальна кількість дописів у темі перед увімкненням \"Підсумок теми\". Зміни цього налаштування застосовуватимуться до тем минулого тижня."
summary_likes_required: "Мінімальна кількість вподобань у темі перед увімкненням \"Підсумок теми\". Зміни цього налаштування застосовуватимуться до тем минулого тижня."
summary_percent_filter: "При натисканні на кнопку \"Зведення по темі\", показувати кращі % дописів"
summary_max_results: "Максимальна кількість повідомлень, що показуються в 'Зведенні по темі'"
summary_timeline_button: "Відображати кнопку «Підсумок» на шкалі часу"
- enable_personal_messages: "Дозволити користувачам рівня довіри 1 (який можна налаштувати через мінімальний рівень довіри для надсилання повідомлень) створювати повідомлення та відповідати на повідомлення. Зверніть увагу, що співробітники завжди можуть надсилати повідомлення, незважаючи ні на що."
+ enable_personal_messages: "Дозволити користувачам рівня довіри 1 (який можна налаштувати через мінімальний рівень довіри для надсилання повідомлень) створювати повідомлення та відповідати на повідомлення. Зверніть увагу, що співробітники завжди можуть надсилати повідомлення, незалежно від того."
enable_system_message_replies: "Дозволяє користувачам відповідати на системні повідомлення, навіть якщо приватні повідомлення відключені"
enable_long_polling: "Використовувати механізм long polling для повідомлень про події"
enable_chunked_encoding: "Увімкнути дозвіл на фрагментацію повідомлень (chunked encoding) на сервері. Ця функція працює на більшості випадків, однак деякі проксі-сервери можуть буферизувати контент, що спричиняє затримку відповідей"
@@ -1544,6 +1576,9 @@ uk:
tl2_additional_likes_per_day_multiplier: "Збільшити ліміт лайків на день для tl2 (member) до"
tl3_additional_likes_per_day_multiplier: "Збільшити ліміт лайків на день для tl3 (member) до"
tl4_additional_likes_per_day_multiplier: "Збільшити ліміт лайків на день для tl4 (leader) до"
+ tl2_additional_edits_per_day_multiplier: "Збільшити ліміт редагувань на день для tl2 (учасника) шляхом множення на це число"
+ tl3_additional_edits_per_day_multiplier: "Збільшити ліміт редагувань на день для tl3 (активний користувач) шляхом множення на це число"
+ tl4_additional_edits_per_day_multiplier: "Збільшити ліміт редагувань на день для tl4 (лідера) шляхом множення на це число"
num_users_to_silence_new_user: "Якщо повідомлення нового користувача отримують більше прапорів спаму ніж num_spam_flags_to_silence_new_user від багатьох різних користувачів, приховайте всі його публікації та запобігти подальшим публікаціям. 0 відключити."
num_tl3_flags_to_silence_new_user: "Якщо повідомлення нового користувача отримують більше прапорів від користувачів з 3 рівнем довіри ніж num_tl3_users_to_silence_new_user, приховати усі його повідомлення та запобігти подальшим публікаціям. 0 відключити"
num_tl3_users_to_silence_new_user: "Якщо повідомлення нового користувача отримують прапорів бульше num_tl3_flags_to_silence_new_user від багатьох різних користувачів з 3 рівнем довіри, приховати всі його дописи та запобігти подальшим публікаціям. 0 відключити."
@@ -1558,6 +1593,8 @@ uk:
must_approve_users: "Персонал повинен схвалити всі нові облікові записи користувачів, перш ніж їм буде надано доступ до сайту"
invite_code: "Користувач повинен ввести цей код для дозволу реєстрації облікового запису, ігнорується, коли пусто (нечутлива до регістру)"
approve_suspect_users: "Додавати підозрілих користувачів до черги огляду. Підозрілі користувачі можуть входити до профілю, але не можуть читати повідомлення."
+ review_every_post: "Всі дописи повинні бути переглянуті. ПОПЕРЕДЖЕННЯ! НЕ РЕКОМЕНДУЄТЬСЯ ДЛЯ ПЕРЕВАНТАЖЕНИХ САЙТІВ."
+ pending_users_reminder_delay_minutes: "Повідомляти модераторів, якщо нові користувачі чекають схвалення довше ніж стільки хвилин. Встановіть -1 для вимкнення сповіщення."
persistent_sessions: "Користувачі залишатимуться авторизовані при закритті веб-браузера"
maximum_session_age: "Користувач залишиться в системі протягом n годин з моменту останнього відвідування"
ga_version: "Версія Google Universal Analytics для використання: v3 (analytics.js), v4 (gtag)"
@@ -1581,6 +1618,7 @@ uk:
content_security_policy: "Увімкнути політику безпеки вмісту"
content_security_policy_report_only: "Увімкнути тільки звіт про політику безпеки вмісту"
content_security_policy_collect_reports: "Увімкнути збір звітів про порушення CSP на /csp_reports"
+ content_security_policy_frame_ancestors: "Обмежити за допомогою CSP, хто може вбудувати цей сайт в iframes. Керування дозволеними хостами на Вбудовування"
content_security_policy_script_src: "Додаткові джерела сценаріїв з дозволом. Поточний хост і CDN включені за замовчуванням. Див. Mitigate XSS Attacks with Content Security Policy."
invalidate_inactive_admin_email_after_days: "Облікові записи адміністраторів, які не відвідували сайт протягом цієї кількості днів, потрібно буде повторно підтвердити свою електронну адресу пошти перед входом у систему. Встановіть 0, щоб відключити."
top_menu: "Визначає, які елементи відображаються в навігації на головній сторінці та в якому порядку. Наприклад latest|new|unread|categories|top|read|posted|bookmarks"
@@ -1629,8 +1667,21 @@ uk:
min_admin_password_length: "Мінімальна довжина пароля для адміністратора."
password_unique_characters: "Мінімальна довжина пароля для адміністратора."
block_common_passwords: "Не дозволяти використовувати паролі зі списку 10 000 самих часто використовуваних паролів."
+ auth_skip_create_confirm: Під час реєстрації через зовнішню автентифікацію пропускати спливаюче вікно створення облікового запису. Найкраще використовувати разом із sso_overrides_email, sso_overrides_username та sso_overrides_name.
auth_immediately: "Автоматично перенаправляти на зовнішню систему входу без взаємодії з користувачем. Це набуває чинності лише тоді, коли login_required має значення true, і існує лише один зовнішній метод автентифікації"
+ enable_discourse_connect: "Увімкнути вхід через DiscourseConnect (раніше 'Discourse SSO') (ПОПЕРЕДЖЕННЯ: EMAIL КОРИСТУВАЧІВ *ПОВИННІ* ПЕРЕВІРЯТИСЯ ЗОВНІШНІМ САЙТОМ!)"
+ verbose_discourse_connect_logging: "Записувати всі дані діагностики, пов'язані з DiscourseConnect у файл /logs"
+ enable_discourse_connect_provider: "Використовувати DiscourseConnect (раніше Discourse SSO) протокол єдиного входу /session/sso_provider. Потребує встановлення параметра sso_provider_secrets"
+ discourse_connect_url: "Адреса кінцевої точки DiscourseConnect (має містити http:// або https://)"
+ discourse_connect_secret: "Секретний набір символів, який використовується DiscourseConnect для перевірки автентичності зашифрованого входу, переконайтеся, що це 10 або більше символів"
+ discourse_connect_provider_secrets: "Список секретних пар доменів, що використовують DiscourseConnect. Переконайтеся, що DiscourseConnect секрет має 10 символів або більше. Символ підстановки * може бути використаний для відповідності будь-якому домену або лише його частині (наприклад, *.example.com)."
discourse_connect_overrides_bio: "Перезаписує інформацію в профіль користувача і не дозволяє користувачеві його змінювати"
+ discourse_connect_overrides_groups: "Синхронізуйте всі групи вручну з групами, вказаними в атрибуті груп (ПОПЕРЕДЖЕННЯ: якщо ви не вказали групи, то весь список груп користувача буде очищено)"
+ auth_overrides_email: "Переписує локальну адресу електронної пошти, поштою зовнішнього сайту при кожному вході та забороняє її зміні. Застосовується для всіх провайдерів автентифікації. (ПОПЕРЕДЖЕННЯ: через це можуть статися розбіжності)"
+ auth_overrides_username: "Замінює локальне ім’я користувача на зовнішнє ім’я користувача при використанні SSO при кожному вході та забороняє зміни. Застосовується до всіх постачальників аутентифікації. (ПОПЕРЕДЖЕННЯ: розбіжності можуть виникати через різницю в довжині імені користувача/вимогах)"
+ auth_overrides_name: "Переписує повне ім'я на повне ім'я зовнішнього сайту під час кожного входу та запобігає локальним змінам. Застосовується до всіх постачальників автентифікації."
+ discourse_connect_overrides_avatar: "Переписує аватар користувача значенням з DiscourseConnect. Якщо цей параметр увімкнено, користувачі не зможуть завантажувати аватари на Disсourse."
+ discourse_connect_overrides_location: "Змінює місце знаходження користувача зовнішнім його розташуванням при використанні SSO та запобігає локальним змінам."
enable_local_logins_via_email: "Дозволити користувачам запитувати посилання для входу в один клік та надсилати їм електронною поштою цього посилання."
allow_new_registrations: "Дозволити реєстрацію нових користувачів. Вимкніть, щоб заборонити відвідувачам створювати нові облікові записи."
enable_signup_cta: "Покажіть повідомлення анонімним користувачам, які повернулися, з пропозицією зареєструвати обліковий запис."
@@ -1807,7 +1858,6 @@ uk:
topic_view_duration_hours: "Кількість нових переглядів теми один раз на IP/Користувача кожні N годин"
user_profile_view_duration_hours: "Кількість нових переглядів профілю користувача один раз на IP/Користувача кожні N годин"
levenshtein_distance_spammer_emails: "При перевірці листа на спам, яка кількість символів має бути різною"
- max_new_accounts_per_registration_ip: "Якщо з цієї IP-адреси вже є (n) облікових записів рівня довіри 0 (і жоден не є співробітником або рівня TL2 або вище), припиняти приймати нові реєстрації з цієї IP."
min_ban_entries_for_roll_up: "При натисканні кнопки \"Згорнути\" буде створено новий запис блокування підмережі, якщо є принаймні (N) записів."
max_age_unmatched_emails: "Видаляти невідповідні записи електронної пошти через (N) днів."
max_age_unmatched_ips: "Видаляти невідповідні IP-записи через (N) днів."
@@ -1864,7 +1914,9 @@ uk:
log_mail_processing_failures: "Журнал усіх помилок обробки електронної пошти в /logs"
email_in_min_trust: "Мінімальний рівень довіри, який потрібен користувачу для дозволу створювати нові теми через email. "
email_in_spam_header: "Заголовок електронної пошти для виявлення спаму."
+ imap_batch_import_email: "Мінімальна кількість нових листів, які запускають режим імпорту (вимикає поштові сповіщення)."
email_prefix: "[Label], який використовується в темі електронних листів. За замовчуванням встановлено значення 'title', якщо воно явно не встановлено."
+ email_site_title: "Назва сайту використовується як відправник електронних листів з сайту. Типове значення «title», якщо не встановлено. Якщо заголовок містить символи, які заборонено в рядках відправника електронної пошти, то буде використано цей параметр."
minimum_topics_similar: "Скільки тем має існувати, перш ніж подібні теми будуть показані під час створення нових тем."
relative_date_duration: "Кількість днів після публікації, коли дати публікації будуть відображені як відносні (7d), а не абсолютні (20 лютого)."
delete_user_max_post_age: "Не дозволяти видаляти користувачів, чия перша публікація старша (x) днів."
@@ -3114,10 +3166,10 @@ uk:
label: "Назва спільноти"
placeholder: "Женіна тусовка"
site_description:
- label: "Опишіть Ваше співтовариство одним коротким реченням"
+ label: "Опишіть свою спільноту в одному короткому реченні (використовується в результатах пошуку та соціальних мережах)"
placeholder: "Місце для Джейн та її друзів для обговорення класних речей"
short_site_description:
- label: "Опишіть свою спільноту в декількох словах"
+ label: "Опишіть свою спільноту кількома словами (використовується для заголовка домашньої сторінки)"
placeholder: "Краще співтовариство"
introduction:
title: "Вступ"
diff --git a/config/locales/server.ur.yml b/config/locales/server.ur.yml
index 35b9c3164d..a759a64690 100644
--- a/config/locales/server.ur.yml
+++ b/config/locales/server.ur.yml
@@ -1208,7 +1208,6 @@ ur:
summary_score_threshold: "'اِس ٹاپک کا خلاصہ کریں' میں شامل ہونے کیلئے ایک پوسٹ کا کم از کم سکور"
summary_percent_filter: "جب صارف 'اِس ٹاپک کا خلاصہ کریں' پر کلک کرتا ہے، تو سب سے اوپر % پوسٹس دکھائیں"
summary_max_results: "'اِس ٹاپک کا خلاصہ کریں' کی طرف سے لوٹائی جانے والی زیادہ سے زیادہ پوسٹس"
- enable_personal_messages: "ٹرسٹ لَیول 1 (پیغامات بھیجنے کیلئے کم از کم ٹرسٹ لَیول کے ذریعہ ترتیب دے سکتے ہیں) والے صارفین کو پیغامات بنانے اور پیغامات کا جواب دینے کی اجازت دیں۔ نوٹ کریں کہ جوبھی ہو، اسٹاف ہمیشہ پیغامات بھیج سکتا ہے۔"
enable_system_message_replies: "صارفین کو سِسٹم پیغامات کا جواب دینے کی اجازت دیں، یہاں تک کہ اگر ذاتی پیغامات بھی غیر فعال ہوں"
enable_long_polling: "نوٹیفکیشن کیلئے مَیسج بس استعمال ہو رہا ہے، لانگ پولِنگ کا استعمال کیا جا سکتا ہے"
long_polling_base_url: "لانگ پولِنگ کیلئے استعمال ہونے والا بَیس URL (جب ایک CDN متحرک مواد فراہم کر رہا ہو، تو اِس کو اوریِجِن پُل پر سَیٹ کرنا یقینی بنائیں) مثال: http://origin.site.com"
@@ -1459,7 +1458,6 @@ ur:
topic_view_duration_hours: "ہر ن گھنٹوں پر فی IP/صارف ایک نیا ٹاپک وِیو شمار کریں"
user_profile_view_duration_hours: "ہر ن گھنٹوں پر فی IP/صارف ایک نیا صارف پروفائل وِیو شمار کریں"
levenshtein_distance_spammer_emails: "سپَیمر ای میل کو مَیچ کرتے وقت، فرق حروف کی تعداد جس پر بھی ایک فزِّی میچ ہو سکے گا۔"
- max_new_accounts_per_registration_ip: "اگر اِس IP کی طرف سے پہلے سے ہی (ن) ٹرسٹ لَیول 0 اکاؤنٹس موجود ہیں (اور کوئی بھی سٹاف کا رکن یا ٹ.ل.2 یا اُس سے زیادہ نہیں ہے)، اُس IP سے نئے سائن اَپ کو قبول کرنا روک دیں۔"
min_ban_entries_for_roll_up: "رول اَپ بٹن پر کلک کرتے وقت اگر کم از کم (ن) اندراج موجود ہوں، تو ایک نیا ذیلی نیٹ بَین اندراج تخلیق ہو جائے گا۔"
max_age_unmatched_emails: "غیر مَیچ شدہ سکرین کردہ ای میل اندراجات (ن) دنوں کے بعد حذف کر دیں۔"
max_age_unmatched_ips: "غیر مَیچ شدہ سکرین کردہ IP اندراجات (ن) دنوں کے بعد حذف کر دیں۔"
@@ -3284,10 +3282,8 @@ ur:
label: "آپ کی کمیونٹی کا نام"
placeholder: "جمیلہ کے ٹھہرنے کی جگہ"
site_description:
- label: "اپنی کمیونٹی کو ایک مختصر جملہ میں بیان کریں"
placeholder: "جمیلہ اور اس کی دوستوں کیلئے دلچسپ چیزوں پر بحث کرنے کی ایک جگہ"
short_site_description:
- label: "اپنی کمیونٹی کو چند الفاظ میں بیان کریں"
placeholder: "ہمیشہ کی سب سے بہترین کمیونٹی"
introduction:
title: "تعارف"
diff --git a/config/locales/server.vi.yml b/config/locales/server.vi.yml
index 2c944c1c1f..6aa84b65ef 100644
--- a/config/locales/server.vi.yml
+++ b/config/locales/server.vi.yml
@@ -1067,7 +1067,6 @@ vi:
topic_view_duration_hours: "Đếm lượt xem chủ đề mới một lần cho mỗi IP/User trong N giờ"
user_profile_view_duration_hours: "Đếm lượt xem hồ sơ mới một lần cho mỗi IP/User trong N giờ"
levenshtein_distance_spammer_emails: "Khi phù hợp với thư rác, số ký tự khác biệt vẫn sẽ cho phép khả năng chính xác mờ."
- max_new_accounts_per_registration_ip: "Nếu đã có (n) tài khoản với độ tin cậy mức 0 từ IP này (không phải là BQT, mức TL2 hoặc cao hơn), ngừng chấp nhận đăng ký mới từ IP này."
min_ban_entries_for_roll_up: "Khi click vào nút cuộn lên, sẽ tạo ra một entry subnet cấm mới nếu có ít nhất (N) entry."
max_age_unmatched_emails: "Xóa các email hiển thị không khớp sau (N) ngày."
max_age_unmatched_ips: "Xóa các IP hiển thị không khớp sau (N) ngày."
diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml
index bbd129cb98..2f26dae6b2 100644
--- a/config/locales/server.zh_CN.yml
+++ b/config/locales/server.zh_CN.yml
@@ -241,7 +241,6 @@ zh_CN:
topic_invite:
failed_to_invite: "如果用户不是以下任一群组的成员,则无法被邀请加入此话题:%{group_names}。"
user_exists: "抱歉,该用户已被邀请。您一次只可以邀请一个用户加入话题。"
- muted_invitee: "抱歉,该用户已将您设为免打扰。"
muted_topic: "抱歉,该用户已将此话题设为免打扰。"
receiver_does_not_allow_pm: "抱歉,该用户不允许您向他们发送私信。"
sender_does_not_allow_pm: "抱歉,您不允许该用户向您发送私信。"
@@ -1403,7 +1402,6 @@ zh_CN:
summary_percent_filter: "当用户点击“总结此话题”时,显示前百分之几的帖子"
summary_max_results: "“总结此话题”返回的最大帖子数量"
summary_timeline_button: "在时间线中显示“总结”按钮"
- enable_personal_messages: "允许信任级别 1(可通过最小信任级别配置来发送消息)用户创建消息和回复消息。注意:管理人员不受限制。"
enable_system_message_replies: "即使禁用了个人消息,也允许用户回复系统消息"
enable_long_polling: "用于通知的消息总线可以使用长轮询"
enable_chunked_encoding: "启用服务器的分块编码响应。此功能适用于大多数设置,但某些代理可能会缓冲,导致响应延迟"
@@ -1734,7 +1732,6 @@ zh_CN:
topic_view_duration_hours: "每个 IP/用户每 N 小时计算一次新话题浏览"
user_profile_view_duration_hours: "每个 IP/用户每 N 小时计算一次新用户个人资料浏览"
levenshtein_distance_spammer_emails: "在匹配垃圾信息发布者电子邮件时,仍然允许模糊匹配的字符数差异。"
- max_new_accounts_per_registration_ip: "如果已经有 (n) 个来自此 IP 的信任级别 0 帐户(并且没有一个是管理人员或者是 TL2 或更高级别),则停止接受来自该 IP 的新注册。"
min_ban_entries_for_roll_up: "点击“汇总”按钮时,如果至少有 (N) 个条目,将创建一个子网禁止条目"
max_age_unmatched_emails: "在 (N) 天后删除不匹配的已屏蔽电子邮件条目。"
max_age_unmatched_ips: "在 (N) 天后删除不匹配的已屏蔽 IP 条目。"
@@ -4272,10 +4269,8 @@ zh_CN:
label: "您社区的名称"
placeholder: "朱桦的环聊"
site_description:
- label: "用一句话描述您的社区"
placeholder: "朱桦和她的朋友讨论新奇事物的地方"
short_site_description:
- label: "用几个词描述您的社区"
placeholder: "有史以来最好的社区"
introduction:
title: "介绍"
diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml
index f903a5e3eb..0d05adf6d8 100644
--- a/config/locales/server.zh_TW.yml
+++ b/config/locales/server.zh_TW.yml
@@ -1142,7 +1142,6 @@ zh_TW:
summary_score_threshold: "將一個貼文包含在“概括主題”中所需的最少分數"
summary_percent_filter: "當使用者點擊 \"此話題的摘要\",顯示前面多少 % 的貼文"
summary_max_results: "“概括主題”將顯示最大貼文數量"
- enable_personal_messages: "允許信任等級1(可以另外選擇發送消息的信任等級)的使用者建立訊息和回覆訊息。注意:管理人員不受限制。"
enable_system_message_replies: "即使個人訊息功能未開啟,允許使用者回覆系統傳送的訊息。"
enable_long_polling: "啟用消息匯流排使通知功能可以使用長輪詢(long polling)"
long_polling_base_url: "長輪詢的基本 URL(當用 CDN 分發動態內容,請設置此至原始拉取地址)例如:http://origin.site.com"
@@ -1383,7 +1382,6 @@ zh_TW:
topic_view_duration_hours: "按照每 IP/使用者每 N 小時來記錄一次新的主題訪問"
user_profile_view_duration_hours: "按照每 IP/使用者每 N 小時來記錄使用者資料訪問數"
levenshtein_distance_spammer_emails: "當比對廣告Email時,數字與文字將仍使用模糊比對"
- max_new_accounts_per_registration_ip: "如果已經有了從這個 IP 創建的 (n) 個信任等級0的賬戶(並且沒有一個是職員或者是信任等級2以上的使用者),不再允許來自該 IP 地址的註冊請求。"
min_ban_entries_for_roll_up: "當點擊折疊按鈕且不少於 (N) 條記錄時,將會創建一個子網禁止記錄。"
max_age_unmatched_emails: "在 (N) 天後刪除不匹配的電郵地址。"
max_age_unmatched_ips: "在 (N) 天後刪除不匹配的 IP 記錄。"
@@ -3050,10 +3048,8 @@ zh_TW:
label: "你社群的名字"
placeholder: "小明的論壇"
site_description:
- label: "用一句簡短的話描述你的社群"
placeholder: "小明和他的朋友討論酷東西的論壇"
short_site_description:
- label: "用幾句話描述你的社群"
placeholder: "最佳社群"
introduction:
title: "介紹"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 107c4b91d4..39835d5a88 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -267,11 +267,11 @@ basic:
client: true
default: true
hidden: true
- enable_experimental_image_uploader:
+ enable_experimental_composer_uploader:
client: true
default: false
hidden: true
- enable_experimental_composer_uploader:
+ enable_experimental_backup_uploader:
client: true
default: false
hidden: true
@@ -453,12 +453,17 @@ login:
client: true
auth_immediately:
default: true
+ auth_overrides_email:
+ default: false
+ validator: "SsoOverridesEmailValidator"
+ client: true
+ auth_overrides_username: false
+ auth_overrides_name: false
enable_discourse_connect:
client: true
default: false
validator: "EnableSsoValidator"
discourse_connect_allows_all_return_paths: false
- enable_discourse_connect_provider: false
verbose_discourse_connect_logging: false
verbose_upload_logging:
hidden: true
@@ -475,22 +480,8 @@ login:
discourse_connect_secret:
default: ""
secret: true
- discourse_connect_provider_secrets:
- default: ""
- type: list
- list_type: secret
- secret: true
- placeholder:
- key: "sso_provider.key_placeholder"
- value: "sso_provider.value_placeholder"
discourse_connect_overrides_groups: false
discourse_connect_overrides_bio: false
- auth_overrides_email:
- default: false
- validator: "SsoOverridesEmailValidator"
- client: true
- auth_overrides_username: false
- auth_overrides_name: false
discourse_connect_overrides_avatar:
default: false
client: true
@@ -502,6 +493,15 @@ login:
discourse_connect_csrf_protection:
default: true
hidden: true
+ enable_discourse_connect_provider: false
+ discourse_connect_provider_secrets:
+ default: ""
+ type: list
+ list_type: secret
+ secret: true
+ placeholder:
+ key: "sso_provider.key_placeholder"
+ value: "sso_provider.value_placeholder"
blocked_email_domains:
default: "mailinator.com"
type: list
@@ -1637,7 +1637,7 @@ security:
allow_any: false
choices: "['*'] + Onebox::Engine.all_iframe_origins"
allowed_iframes:
- default: "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/"
+ default: "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com"
type: list
list_type: simple
client: true
@@ -1680,6 +1680,7 @@ security:
hidden: true
can_permanently_delete:
default: false
+ client: true
hidden: true
onebox:
@@ -2214,6 +2215,9 @@ uncategorized:
disable_category_edit_notifications:
default: false
+ disable_tags_edit_notifications:
+ default: false
+
notification_consolidation_threshold:
default: 3
min: 0
diff --git a/docs/INSTALL-cloud.md b/docs/INSTALL-cloud.md
index 792e068b49..d2504711f6 100644
--- a/docs/INSTALL-cloud.md
+++ b/docs/INSTALL-cloud.md
@@ -1,12 +1,26 @@
-**Set up Discourse in the cloud in under 30 minutes** with zero knowledge of Rails or Linux shell. One example is [DigitalOcean][do], but these steps will work on any **Docker-compatible** cloud provider or local server.
+**Set up Discourse in the cloud in under 30 minutes** with zero knowledge of Rails or Linux shell. One example
+is [DigitalOcean][do], but these steps will work on any **Docker-compatible** cloud provider or local server. This
+walkthrough will go through these in detail:
-> 🔔 Don't have 30 minutes to set this up? For a flat one-time fee of $150, the community can install Discourse in the cloud for you. [Click here to purchase a self-supported community install](https://www.literatecomputing.com/product/discourse-install/).
+1. [Create new cloud server](#1-create-new-cloud-server)
+2. [Access new cloud server](#2-access-your-cloud-server)
+3. [Install Discourse](#3-install-discourse)
+4. [Setting up email](#4-setting-up-email)
+5. [Customize domain name](#5-customize-domain-name)
+6. [Edit Discourse configuration](#6-edit-discourse-configuration)
+7. [Start Discourse](#7-start-discourse)
+8. [Register new account and become admin](#8-register-new-account-and-become-admin)
+9. [Post-install maintenance](#9-post-install-maintenance)
+10. [(Optional) Add more Discourse features](#10-optional-add-more-discourse-features)
-### Create New Cloud Server
+> 🔔 Don't have 30 minutes to set this up? For a flat one-time fee of $150, the community can install Discourse in the cloud for you. [Click here to purchase a self-supported community install](https://www.literatecomputing.com/product/discourse-install/).
+
+### 1. Create New Cloud Server
Create your new cloud server, for example [on DigitalOcean][do]:
-- The default of **the current supported LTS release of Ubuntu Server** works fine. At minimum, a 64-bit Linux OS with a modern kernel version is required.
+- The default of **the current supported LTS release of Ubuntu Server** works fine. At minimum, a 64-bit Linux OS with a
+ modern kernel version is required.
- The default of **1 GB** RAM works fine for small Discourse communities. We recommend 2 GB RAM for larger communities.
@@ -16,7 +30,7 @@ Create your new cloud server, for example [on DigitalOcean][do]:
Create your new Droplet. You may receive an email with the root password, however, [you should set up SSH keys](https://www.google.com/search?q=digitalocean+ssh+keys), as they are more secure.
-### Access Your Cloud Server
+### 2. Access Your Cloud Server
Connect to your server via its IP address using SSH, or [Putty][put] on Windows:
@@ -24,8 +38,7 @@ Connect to your server via its IP address using SSH, or [Putty][put] on Windows:
Either use the root password from the email DigitalOcean sent you when the server was set up, or have a valid SSH key configured on your local machine.
-
-### Install Discourse
+### 3. Install Discourse
Clone the [Official Discourse Docker Image][dd] into `/var/discourse`.
@@ -35,7 +48,7 @@ Clone the [Official Discourse Docker Image][dd] into `/var/discourse`.
You will need to be root through the rest of the setup and bootstrap process.
-### Email
+### 4. Setting Up Email
> ⚠️ **Email is CRITICAL for account creation and notifications in Discourse.** If you do not properly configure email before bootstrapping YOU WILL HAVE A BROKEN SITE!
@@ -49,7 +62,7 @@ You will need to be root through the rest of the setup and bootstrap process.
- If you're having trouble getting emails to work, follow our [Email Troubleshooting Guide](https://meta.discourse.org/t/troubleshooting-email-on-a-new-discourse-install/16326)
-### Domain Name
+### 5. Customize Domain Name
> 🔔 Discourse will not work from an IP address, you must own a domain name such as `example.com` to proceed.
@@ -59,7 +72,7 @@ You will need to be root through the rest of the setup and bootstrap process.
- Your DNS controls should be accessible from the place where you purchased your domain name. Create a DNS [`A` record](https://support.dnsimple.com/articles/a-record/) for the `discourse.example.com` hostname in your DNS control panel, pointing to the IP address of your cloud instance where you are installing Discourse.
-### Edit Discourse Configuration
+### 6. Edit Discourse Configuration
Launch the setup tool at
@@ -82,13 +95,13 @@ Let's Encrypt account setup is to give you a free HTTPS certificate for your sit
This will generate an `app.yml` configuration file on your behalf, and then kicks off bootstrap. Bootstrapping takes between **2-8 minutes** to set up your Discourse. If you need to change these settings after bootstrapping, you can run `./discourse-setup` again (it will re-use your previous values from the file) or edit `/containers/app.yml` manually with `nano` and then `./launcher rebuild app`, otherwise your changes will not take effect.
-### Start Discourse
+### 7. Start Discourse
Once bootstrapping is complete, your Discourse should be accessible in your web browser via the domain name `discourse.example.com` you entered earlier.
-### Register New Account and Become Admin
+### 8. Register New Account and Become Admin
Register a new admin account using one of the email addresses you entered before bootstrapping.
@@ -106,7 +119,7 @@ After completing the setup wizard, you should see Staff topics and **READ ME FIR
-### Post-Install Maintenance
+### 9. Post-Install Maintenance
- We strongly suggest you turn on automatic security updates for your OS. In Ubuntu use the `dpkg-reconfigure -plow unattended-upgrades` command. In CentOS/RHEL, use the [`yum-cron`](https://www.redhat.com/sysadmin/using-yum-cron) package.
- If you are using a password and not a SSH key, be sure to enforce a strong root password. In Ubuntu use the `apt install libpam-cracklib` package. We also recommend `fail2ban` which blocks any IP addresses for 10 minutes that attempt more than 3 password retries.
@@ -146,7 +159,7 @@ Options:
--docker-args Extra arguments to pass when running docker
```
-### Add More Discourse Features
+### 10. (Optional) Add More Discourse Features
Do you want...
diff --git a/lefthook.yml b/lefthook.yml
index b5e3a77397..534e33e87b 100644
--- a/lefthook.yml
+++ b/lefthook.yml
@@ -5,14 +5,10 @@ pre-commit:
glob: "*.rb"
run: bundle exec rubocop --parallel {staged_files}
prettier:
- glob: "*.{js,es6}"
+ glob: "*.js"
include: "app/assets/javascripts|test/javascripts"
run: yarn pprettier --list-different {staged_files}
- eslint-es6:
- glob: "*.es6"
- include: "app/assets/javascripts|test/javascripts"
- run: yarn eslint --ext .es6 -f compact {staged_files}
- eslint-js:
+ eslint:
glob: "*.js"
include: "app/assets/javascripts|test/javascripts"
run: yarn eslint -f compact {staged_files}
@@ -52,23 +48,17 @@ lints:
rubocop:
run: bundle exec rubocop --parallel
prettier:
- glob: "*.{js,es6}"
+ glob: "*.js"
include: "app/assets/javascripts|test/javascripts"
run: yarn pprettier --list-different {all_files}
- eslint-assets-es6:
- run: yarn eslint --ext .es6 app/assets/javascripts
eslint-assets-js:
run: yarn eslint app/assets/javascripts
- eslint-test-es6:
- run: yarn eslint --ext .es6 test/javascripts
eslint-test-js:
run: yarn eslint test/javascripts
eslint-plugins-assets:
- run: yarn eslint --global I18n --ext .es6 plugins/**/assets/javascripts
+ run: yarn eslint plugins/**/assets/javascripts
eslint-plugins-test:
- run: yarn eslint --global I18n --ext .es6 plugins/**/test/javascripts
- eslint-assets-tests:
- run: yarn eslint app/assets/javascripts test/javascripts
+ run: yarn eslint plugins/**/test/javascripts
i18n-lint:
glob: "**/{client,server}.en.yml"
run: bundle exec ruby script/i18n_lint.rb {all_files}
diff --git a/lib/admin_user_index_query.rb b/lib/admin_user_index_query.rb
index 8780aced0c..5303077182 100644
--- a/lib/admin_user_index_query.rb
+++ b/lib/admin_user_index_query.rb
@@ -37,7 +37,7 @@ class AdminUserIndexQuery
end
def custom_direction
- Discourse.deprecate(":ascending is deprecated please use :asc instead", output_in_test: true) if params[:ascending]
+ Discourse.deprecate(":ascending is deprecated please use :asc instead", output_in_test: true, drop_from: '2.9.0') if params[:ascending]
asc = params[:asc] || params[:ascending]
asc.present? && asc ? "ASC" : "DESC"
end
diff --git a/lib/auth/auth_provider.rb b/lib/auth/auth_provider.rb
index 50bb76c7e8..09f0f5f39a 100644
--- a/lib/auth/auth_provider.rb
+++ b/lib/auth/auth_provider.rb
@@ -16,24 +16,24 @@ class Auth::AuthProvider
attr_accessor(*auth_attributes)
def enabled_setting=(val)
- Discourse.deprecate("(#{authenticator.name}) enabled_setting is deprecated. Please define authenticator.enabled? instead")
+ Discourse.deprecate("(#{authenticator.name}) enabled_setting is deprecated. Please define authenticator.enabled? instead", drop_from: '2.9.0')
@enabled_setting = val
end
def background_color=(val)
- Discourse.deprecate("(#{authenticator.name}) background_color is no longer functional. Please use CSS instead")
+ Discourse.deprecate("(#{authenticator.name}) background_color is no longer functional. Please use CSS instead", drop_from: '2.9.0')
end
def full_screen_login=(val)
- Discourse.deprecate("(#{authenticator.name}) full_screen_login is now forced. The full_screen_login parameter can be removed from the auth_provider.")
+ Discourse.deprecate("(#{authenticator.name}) full_screen_login is now forced. The full_screen_login parameter can be removed from the auth_provider.", drop_from: '2.9.0')
end
def full_screen_login_setting=(val)
- Discourse.deprecate("(#{authenticator.name}) full_screen_login is now forced. The full_screen_login_setting parameter can be removed from the auth_provider.")
+ Discourse.deprecate("(#{authenticator.name}) full_screen_login is now forced. The full_screen_login_setting parameter can be removed from the auth_provider.", drop_from: '2.9.0')
end
def message=(val)
- Discourse.deprecate("(#{authenticator.name}) message is no longer used because all logins are full screen. It should be removed from the auth_provider")
+ Discourse.deprecate("(#{authenticator.name}) message is no longer used because all logins are full screen. It should be removed from the auth_provider", drop_from: '2.9.0')
end
def name
diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb
index 8c16392b3a..a35d40adda 100644
--- a/lib/auth/default_current_user_provider.rb
+++ b/lib/auth/default_current_user_provider.rb
@@ -137,8 +137,9 @@ class Auth::DefaultCurrentUserProvider
# user api key handling
if user_api_key
- limiter_min = RateLimiter.new(nil, "user_api_min_#{user_api_key}", GlobalSetting.max_user_api_reqs_per_minute, 60)
- limiter_day = RateLimiter.new(nil, "user_api_day_#{user_api_key}", GlobalSetting.max_user_api_reqs_per_day, 86400)
+ hashed_user_api_key = ApiKey.hash_key(user_api_key)
+ limiter_min = RateLimiter.new(nil, "user_api_min_#{hashed_user_api_key}", GlobalSetting.max_user_api_reqs_per_minute, 60)
+ limiter_day = RateLimiter.new(nil, "user_api_day_#{hashed_user_api_key}", GlobalSetting.max_user_api_reqs_per_day, 86400)
unless limiter_day.can_perform?
limiter_day.performed!
@@ -382,7 +383,7 @@ class Auth::DefaultCurrentUserProvider
limit = GlobalSetting.max_admin_api_reqs_per_minute.to_i
if GlobalSetting.respond_to?(:max_admin_api_reqs_per_key_per_minute)
- Discourse.deprecate("DISCOURSE_MAX_ADMIN_API_REQS_PER_KEY_PER_MINUTE is deprecated. Please use DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE")
+ Discourse.deprecate("DISCOURSE_MAX_ADMIN_API_REQS_PER_KEY_PER_MINUTE is deprecated. Please use DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE", drop_from: '2.9.0')
limit = [ GlobalSetting.max_admin_api_reqs_per_key_per_minute.to_i, limit].max
end
diff --git a/lib/auth/result.rb b/lib/auth/result.rb
index 08a12203d2..5f68606d2a 100644
--- a/lib/auth/result.rb
+++ b/lib/auth/result.rb
@@ -77,8 +77,8 @@ class Auth::Result
def apply_user_attributes!
change_made = false
- if SiteSetting.auth_overrides_username? && username.present? && username != user.username
- user.username = UserNameSuggester.suggest(username_suggester_attributes, user.username)
+ if SiteSetting.auth_overrides_username? && username.present? && UserNameSuggester.fix_username(username) != user.username
+ user.username = UserNameSuggester.suggest(username)
change_made = true
end
diff --git a/lib/backup_restore/s3_backup_store.rb b/lib/backup_restore/s3_backup_store.rb
index f991c05829..40326f7019 100644
--- a/lib/backup_restore/s3_backup_store.rb
+++ b/lib/backup_restore/s3_backup_store.rb
@@ -2,12 +2,18 @@
module BackupRestore
class S3BackupStore < BackupStore
- UPLOAD_URL_EXPIRES_AFTER_SECONDS ||= 21_600 # 6 hours
+ UPLOAD_URL_EXPIRES_AFTER_SECONDS ||= 6.hours.to_i
+
+ delegate :abort_multipart, :presign_multipart_part, :list_multipart_parts,
+ :complete_multipart, to: :s3_helper
def initialize(opts = {})
@s3_options = S3Helper.s3_options(SiteSetting)
@s3_options.merge!(opts[:s3_options]) if opts[:s3_options]
- @s3_helper = S3Helper.new(s3_bucket_name_with_prefix, '', @s3_options.clone)
+ end
+
+ def s3_helper
+ @s3_helper ||= S3Helper.new(s3_bucket_name_with_prefix, '', @s3_options.clone)
end
def remote?
@@ -15,12 +21,12 @@ module BackupRestore
end
def file(filename, include_download_source: false)
- obj = @s3_helper.object(filename)
+ obj = s3_helper.object(filename)
create_file_from_object(obj, include_download_source) if obj.exists?
end
def delete_file(filename)
- obj = @s3_helper.object(filename)
+ obj = s3_helper.object(filename)
if obj.exists?
obj.delete
@@ -29,11 +35,11 @@ module BackupRestore
end
def download_file(filename, destination_path, failure_message = nil)
- @s3_helper.download_file(filename, destination_path, failure_message)
+ s3_helper.download_file(filename, destination_path, failure_message)
end
def upload_file(filename, source_path, content_type)
- obj = @s3_helper.object(filename)
+ obj = s3_helper.object(filename)
raise BackupFileExists.new if obj.exists?
obj.upload_file(source_path, content_type: content_type)
@@ -41,30 +47,71 @@ module BackupRestore
end
def generate_upload_url(filename)
- obj = @s3_helper.object(filename)
+ obj = s3_helper.object(filename)
raise BackupFileExists.new if obj.exists?
- ensure_cors!
+ # TODO (martin) We can remove this at a later date when we move this
+ # ensure CORS for backups and direct uploads to a post-site-setting
+ # change event, so the rake task doesn't have to be run manually.
+ @s3_helper.ensure_cors!([S3CorsRulesets::BACKUP_DIRECT_UPLOAD])
+
presigned_url(obj, :put, UPLOAD_URL_EXPIRES_AFTER_SECONDS)
rescue Aws::Errors::ServiceError => e
Rails.logger.warn("Failed to generate upload URL for S3: #{e.message.presence || e.class.name}")
raise StorageError.new(e.message.presence || e.class.name)
end
- def vacate_legacy_prefix
- legacy_s3_helper = S3Helper.new(s3_bucket_name_with_legacy_prefix, '', @s3_options.clone)
- bucket, prefix = s3_bucket_name_with_prefix.split('/', 2)
- legacy_keys = legacy_s3_helper.list
- .reject { |o| o.key.starts_with? prefix }
- .map { |o| o.key }
- legacy_keys.each do |legacy_key|
- @s3_helper.s3_client.copy_object({
- copy_source: File.join(bucket, legacy_key),
- bucket: bucket,
- key: File.join(prefix, legacy_key.split('/').last)
- })
- legacy_s3_helper.delete_object(legacy_key)
+ def signed_url_for_temporary_upload(file_name, expires_in: S3Helper::UPLOAD_URL_EXPIRES_AFTER_SECONDS, metadata: {})
+ obj = object_from_path(file_name)
+ raise BackupFileExists.new if obj.exists?
+ key = temporary_upload_path(file_name)
+ s3_helper.presigned_url(
+ key,
+ method: :put_object,
+ expires_in: expires_in,
+ opts: {
+ metadata: metadata,
+ acl: "private"
+ }
+ )
+ end
+
+ def temporary_upload_path(file_name)
+ FileStore::BaseStore.temporary_upload_path(file_name, folder_prefix: temporary_folder_prefix)
+ end
+
+ def temporary_folder_prefix
+ folder_prefix = s3_helper.s3_bucket_folder_path.nil? ? "" : s3_helper.s3_bucket_folder_path
+
+ if Rails.env.test?
+ folder_prefix = File.join(folder_prefix, "test_#{ENV['TEST_ENV_NUMBER'].presence || '0'}")
end
+
+ folder_prefix
+ end
+
+ def create_multipart(file_name, content_type, metadata: {})
+ obj = object_from_path(file_name)
+ raise BackupFileExists.new if obj.exists?
+ key = temporary_upload_path(file_name)
+ s3_helper.create_multipart(key, content_type, metadata: metadata)
+ end
+
+ def move_existing_stored_upload(
+ existing_external_upload_key:,
+ original_filename: nil,
+ content_type: nil
+ )
+ s3_helper.copy(
+ existing_external_upload_key,
+ File.join(s3_helper.s3_bucket_folder_path, original_filename),
+ options: { acl: "private", apply_metadata_to_destination: true }
+ )
+ s3_helper.delete_object(existing_external_upload_key)
+ end
+
+ def object_from_path(path)
+ s3_helper.object(path)
end
private
@@ -72,7 +119,7 @@ module BackupRestore
def unsorted_files
objects = []
- @s3_helper.list.each do |obj|
+ s3_helper.list.each do |obj|
if obj.key.match?(file_regex)
objects << create_file_from_object(obj)
end
@@ -98,17 +145,6 @@ module BackupRestore
obj.presigned_url(method, expires_in: expires_in_seconds)
end
- def ensure_cors!
- rule = {
- allowed_headers: ["*"],
- allowed_methods: ["PUT"],
- allowed_origins: [Discourse.base_url_no_prefix],
- max_age_seconds: 3000
- }
-
- @s3_helper.ensure_cors!([rule])
- end
-
def cleanup_allowed?
!SiteSetting.s3_disable_cleanup
end
@@ -117,17 +153,9 @@ module BackupRestore
File.join(SiteSetting.s3_backup_bucket, RailsMultisite::ConnectionManagement.current_db)
end
- def s3_bucket_name_with_legacy_prefix
- if Rails.configuration.multisite
- File.join(SiteSetting.s3_backup_bucket, "backups", RailsMultisite::ConnectionManagement.current_db)
- else
- SiteSetting.s3_backup_bucket
- end
- end
-
def file_regex
@file_regex ||= begin
- path = @s3_helper.s3_bucket_folder_path || ""
+ path = s3_helper.s3_bucket_folder_path || ""
if path.present?
path = "#{path}/" unless path.end_with?("/")
diff --git a/lib/cache.rb b/lib/cache.rb
index 7d69361915..ca814c9e2b 100644
--- a/lib/cache.rb
+++ b/lib/cache.rb
@@ -86,7 +86,7 @@ class Cache
if raw
begin
- Marshal.load(raw)
+ Marshal.load(raw) # rubocop:disable Security/MarshalLoad
rescue => e
log_first_exception(e)
end
@@ -113,7 +113,7 @@ class Cache
def read_entry(key)
if data = redis.get(key)
- Marshal.load(data)
+ Marshal.load(data) # rubocop:disable Security/MarshalLoad
end
rescue => e
# corrupt cache, this can happen if Marshal version
diff --git a/lib/discourse_plugin_registry.rb b/lib/discourse_plugin_registry.rb
index 8106809144..53a05aa4b4 100644
--- a/lib/discourse_plugin_registry.rb
+++ b/lib/discourse_plugin_registry.rb
@@ -91,6 +91,8 @@ class DiscoursePluginRegistry
define_filtered_register :presence_channel_prefixes
+ define_filtered_register :push_notification_filters
+
def self.register_auth_provider(auth_provider)
self.auth_providers << auth_provider
end
diff --git a/lib/discourse_updates.rb b/lib/discourse_updates.rb
index fbc81aac9f..c7450feb5a 100644
--- a/lib/discourse_updates.rb
+++ b/lib/discourse_updates.rb
@@ -61,18 +61,34 @@ module DiscourseUpdates
Discourse.redis.get last_installed_version_key
end
+ def last_installed_version=(arg)
+ Discourse.redis.set(last_installed_version_key, arg)
+ end
+
def latest_version
Discourse.redis.get latest_version_key
end
+ def latest_version=(arg)
+ Discourse.redis.set(latest_version_key, arg)
+ end
+
def missing_versions_count
Discourse.redis.get(missing_versions_count_key).try(:to_i)
end
+ def missing_versions_count=(arg)
+ Discourse.redis.set(missing_versions_count_key, arg)
+ end
+
def critical_updates_available?
(Discourse.redis.get(critical_updates_available_key) || false) == 'true'
end
+ def critical_updates_available=(arg)
+ Discourse.redis.set(critical_updates_available_key, arg)
+ end
+
def updated_at
t = Discourse.redis.get(updated_at_key)
t ? Time.zone.parse(t) : nil
@@ -82,12 +98,6 @@ module DiscourseUpdates
Discourse.redis.set updated_at_key, time_with_zone.as_json
end
- ['last_installed_version', 'latest_version', 'missing_versions_count', 'critical_updates_available'].each do |name|
- eval "define_method :#{name}= do |arg|
- Discourse.redis.set #{name}_key, arg
- end"
- end
-
def missing_versions=(versions)
# delete previous list from redis
prev_keys = Discourse.redis.lrange(missing_versions_list_key, 0, 4)
diff --git a/lib/file_store/base_store.rb b/lib/file_store/base_store.rb
index f3d948f45a..5d21449bcd 100644
--- a/lib/file_store/base_store.rb
+++ b/lib/file_store/base_store.rb
@@ -41,7 +41,7 @@ module FileStore
File.join(path, "test_#{ENV['TEST_ENV_NUMBER'].presence || '0'}")
end
- def temporary_upload_path(file_name, folder_prefix: "")
+ def self.temporary_upload_path(file_name, folder_prefix: "")
# We don't want to use the original file name as it can contain special
# characters, which can interfere with external providers operations and
# introduce other unexpected behaviour.
@@ -49,7 +49,6 @@ module FileStore
File.join(
TEMPORARY_UPLOAD_PREFIX,
folder_prefix,
- upload_path,
SecureRandom.hex,
file_name_random
)
diff --git a/lib/file_store/local_store.rb b/lib/file_store/local_store.rb
index e945ac1c32..e71e3a6ba7 100644
--- a/lib/file_store/local_store.rb
+++ b/lib/file_store/local_store.rb
@@ -39,6 +39,10 @@ module FileStore
File.join(Discourse.base_path, upload_path)
end
+ def temporary_upload_path(filename)
+ FileStore::BaseStore.temporary_upload_path(filename, folder_prefix: relative_base_url)
+ end
+
def external?
false
end
diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb
index 2e63681821..fb06605112 100644
--- a/lib/file_store/s3_store.rb
+++ b/lib/file_store/s3_store.rb
@@ -11,6 +11,9 @@ module FileStore
class S3Store < BaseStore
TOMBSTONE_PREFIX ||= "tombstone/"
+ delegate :abort_multipart, :presign_multipart_part, :list_multipart_parts,
+ :complete_multipart, to: :s3_helper
+
def initialize(s3_helper = nil)
@s3_helper = s3_helper
end
@@ -35,7 +38,11 @@ module FileStore
url
end
- def move_existing_stored_upload(existing_external_upload_key, upload, content_type = nil)
+ def move_existing_stored_upload(
+ existing_external_upload_key:,
+ upload: nil,
+ content_type: nil
+ )
upload.url = nil
path = get_path_for_upload(upload)
url, upload.etag = store_file(
@@ -210,10 +217,6 @@ module FileStore
upload.url
end
- def path_from_url(url)
- URI.parse(url).path.delete_prefix("/")
- end
-
def cdn_url(url)
return url if SiteSetting.Upload.s3_cdn_url.blank?
schema = url[/^(https?:)?\/\//, 1]
@@ -228,7 +231,7 @@ module FileStore
def signed_url_for_temporary_upload(file_name, expires_in: S3Helper::UPLOAD_URL_EXPIRES_AFTER_SECONDS, metadata: {})
key = temporary_upload_path(file_name)
- presigned_url(
+ s3_helper.presigned_url(
key,
method: :put_object,
expires_in: expires_in,
@@ -240,7 +243,10 @@ module FileStore
end
def temporary_upload_path(file_name)
- s3_bucket_folder_path.nil? ? super(file_name) : super(file_name, folder_prefix: s3_bucket_folder_path)
+ folder_prefix = s3_bucket_folder_path.nil? ? upload_path : File.join(s3_bucket_folder_path, upload_path)
+ FileStore::BaseStore.temporary_upload_path(
+ file_name, folder_prefix: folder_prefix
+ )
end
def object_from_path(path)
@@ -315,76 +321,13 @@ module FileStore
FileUtils.mv(old_upload_path, public_upload_path) if old_upload_path
end
- def abort_multipart(key:, upload_id:)
- s3_helper.s3_client.abort_multipart_upload(
- bucket: s3_bucket_name,
- key: key,
- upload_id: upload_id
- )
- end
-
def create_multipart(file_name, content_type, metadata: {})
key = temporary_upload_path(file_name)
- response = s3_helper.s3_client.create_multipart_upload(
- acl: "private",
- bucket: s3_bucket_name,
- key: key,
- content_type: content_type,
- metadata: metadata
- )
- { upload_id: response.upload_id, key: key }
- end
-
- def presign_multipart_part(upload_id:, key:, part_number:)
- presigned_url(
- key,
- method: :upload_part,
- expires_in: S3Helper::UPLOAD_URL_EXPIRES_AFTER_SECONDS,
- opts: {
- part_number: part_number,
- upload_id: upload_id
- }
- )
- end
-
- def list_multipart_parts(upload_id:, key:)
- s3_helper.s3_client.list_parts(
- bucket: s3_bucket_name,
- key: key,
- upload_id: upload_id
- )
- end
-
- def complete_multipart(upload_id:, key:, parts:)
- s3_helper.s3_client.complete_multipart_upload(
- bucket: s3_bucket_name,
- key: key,
- upload_id: upload_id,
- multipart_upload: {
- parts: parts
- }
- )
+ s3_helper.create_multipart(key, content_type, metadata: metadata)
end
private
- def presigned_url(
- key,
- method:,
- expires_in: S3Helper::UPLOAD_URL_EXPIRES_AFTER_SECONDS,
- opts: {}
- )
- signer = Aws::S3::Presigner.new(client: s3_helper.s3_client)
- signer.presigned_url(
- method,
- {
- bucket: s3_bucket_name,
- key: key,
- expires_in: expires_in,
- }.merge(opts)
- )
- end
-
def presigned_get_url(
url,
force_download: false,
diff --git a/lib/final_destination.rb b/lib/final_destination.rb
index e3805479c8..09f4d0c58e 100644
--- a/lib/final_destination.rb
+++ b/lib/final_destination.rb
@@ -225,7 +225,7 @@ class FinalDestination
end
if @follow_canonical
- next_url = uri(fetch_canonical_url(response.body))
+ next_url = fetch_canonical_url(response.body)
if next_url.to_s.present? && next_url != @uri
@follow_canonical = false
@@ -239,7 +239,7 @@ class FinalDestination
@content_type = response.headers['Content-Type'] if response.headers.has_key?('Content-Type')
@status = :resolved
return @uri
- when 400, 405, 406, 409, 500, 501
+ when 103, 400, 405, 406, 409, 500, 501
response_status, small_headers = small_get(request_headers)
if response_status == 200
@@ -481,10 +481,17 @@ class FinalDestination
def fetch_canonical_url(body)
return if body.blank?
- canonical_link = Nokogiri::HTML5(body).at("link[rel='canonical']")
- return if canonical_link.nil?
+ canonical_element = Nokogiri::HTML5(body).at("link[rel='canonical']")
+ return if canonical_element.nil?
+ canonical_uri = uri(canonical_element['href'])
+ return if canonical_uri.blank?
- canonical_link['href']
+ return canonical_uri if canonical_uri.host.present?
+ parts = [@uri.host, canonical_uri.to_s]
+ complete_url = canonical_uri.to_s.starts_with?('/') ? parts.join('') : parts.join('/')
+ complete_url = "#{@uri.scheme}://#{complete_url}" if @uri.scheme
+
+ uri(complete_url)
end
end
diff --git a/lib/flag_query.rb b/lib/flag_query.rb
index 5c6c0bce9b..beb69d7897 100644
--- a/lib/flag_query.rb
+++ b/lib/flag_query.rb
@@ -1,9 +1,6 @@
# frozen_string_literal: true
-require 'ostruct'
-
module FlagQuery
-
def self.plugin_post_custom_fields
@plugin_post_custom_fields ||= {}
end
@@ -12,212 +9,4 @@ module FlagQuery
def self.register_plugin_post_custom_field(field, plugin)
plugin_post_custom_fields[field] = plugin
end
-
- def self.flagged_posts_report(current_user, opts = nil)
- Discourse.deprecate("FlagQuery is deprecated, use the Reviewable API instead.", since: "2.3.0beta5", drop_from: "2.4")
-
- opts ||= {}
- offset = opts[:offset] || 0
- per_page = opts[:per_page] || 25
-
- reviewables = ReviewableFlaggedPost.default_visible.viewable_by(current_user, order: 'created_at DESC')
- reviewables = reviewables.where(topic_id: opts[:topic_id]) if opts[:topic_id]
- reviewables = reviewables.where(target_created_by_id: opts[:user_id]) if opts[:user_id]
- reviewables = reviewables.limit(per_page).offset(offset)
-
- if opts[:filter] == 'old'
- reviewables = reviewables.where("status <> ?", Reviewable.statuses[:pending])
- else
- reviewables = reviewables.pending
- end
-
- total_rows = reviewables.count
-
- post_ids = reviewables.map(&:target_id).uniq
-
- posts = DB.query(<<~SQL, post_ids: post_ids)
- SELECT p.id,
- p.cooked as excerpt,
- p.raw,
- p.user_id,
- p.topic_id,
- p.post_number,
- p.reply_count,
- p.hidden,
- p.deleted_at,
- p.user_deleted,
- NULL as post_action_ids,
- (SELECT created_at FROM post_revisions WHERE post_id = p.id AND user_id = p.user_id ORDER BY created_at DESC LIMIT 1) AS last_revised_at,
- (SELECT COUNT(*) FROM post_actions WHERE (disagreed_at IS NOT NULL OR agreed_at IS NOT NULL OR deferred_at IS NOT NULL) AND post_id = p.id)::int AS previous_flags_count
- FROM posts p
- WHERE p.id in (:post_ids)
- SQL
-
- post_lookup = {}
- user_ids = Set.new
- topic_ids = Set.new
-
- posts.each do |p|
- user_ids << p.user_id
- topic_ids << p.topic_id
- p.excerpt = Post.excerpt(p.excerpt)
- post_lookup[p.id] = p
- end
-
- all_post_actions = []
- reviewables.each do |r|
- post = post_lookup[r.target_id]
- post.post_action_ids ||= []
-
- r.reviewable_scores.order('created_at desc').each do |rs|
- action = {
- id: rs.id,
- post_id: post.id,
- user_id: rs.user_id,
- post_action_type_id: rs.reviewable_score_type,
- created_at: rs.created_at,
- disposed_by_id: rs.reviewed_by_id,
- disposed_at: rs.reviewed_at,
- disposition: ReviewableScore.statuses[rs.status],
- targets_topic: r.payload['targets_topic'],
- staff_took_action: rs.took_action?
- }
- action[:name_key] = PostActionType.types.key(rs.reviewable_score_type)
-
- if rs.meta_topic.present?
- meta_posts = rs.meta_topic.ordered_posts
-
- conversation = {}
- if response = meta_posts[0]
- action[:related_post_id] = response.id
-
- conversation[:response] = {
- excerpt: excerpt(response.cooked),
- user_id: response.user_id
- }
- user_ids << response.user_id
- if reply = meta_posts[1]
- conversation[:reply] = {
- excerpt: excerpt(reply.cooked),
- user_id: reply.user_id
- }
- user_ids << reply.user_id
- conversation[:has_more] = rs.meta_topic.posts_count > 2
- end
- end
-
- action.merge!(permalink: rs.meta_topic.relative_url, conversation: conversation)
- end
-
- post.post_action_ids << action[:id]
- all_post_actions << action
- user_ids << action[:user_id]
- user_ids << rs.reviewed_by_id if rs.reviewed_by_id
- end
- end
-
- post_custom_field_names = []
- plugin_post_custom_fields.each do |field, plugin|
- post_custom_field_names << field if plugin.enabled?
- end
-
- post_custom_fields = Post.custom_fields_for_ids(post_ids, post_custom_field_names)
-
- # maintain order
- posts = post_ids.map { |id| post_lookup[id] }
-
- # TODO: add serializer so we can skip this
- posts.map! do |post|
- result = post.to_h
- if cfs = post_custom_fields[post.id]
- result[:custom_fields] = cfs
- end
- result
- end
-
- guardian = Guardian.new(current_user)
- users = User.includes(:user_stat).where(id: user_ids.to_a).to_a
- User.preload_custom_fields(users, User.allowed_user_custom_fields(guardian))
-
- [
- posts,
- Topic.with_deleted.where(id: topic_ids.to_a).to_a,
- users,
- all_post_actions,
- total_rows
- ]
- end
-
- def self.flagged_post_actions(opts = nil)
- Discourse.deprecate("FlagQuery is deprecated, please use the Reviewable API instead.", since: "2.3.0beta5", drop_from: "2.4")
-
- opts ||= {}
-
- scores = ReviewableScore.includes(:reviewable).where('reviewables.type' => 'ReviewableFlaggedPost')
- scores = scores.where('reviewables.topic_id' => opts[:topic_id]) if opts[:topic_id]
- scores = scores.where('reviewables.target_created_by_id' => opts[:user_id]) if opts[:user_id]
-
- if opts[:filter] == 'without_custom'
- return scores.where(reviewable_score_type: PostActionType.flag_types_without_custom.values)
- end
-
- if opts[:filter] == "old"
- scores = scores.where('reviewables.status <> ?', Reviewable.statuses[:pending])
- else
- scores = scores.where('reviewables.status' => Reviewable.statuses[:pending])
- end
-
- scores
- end
-
- def self.flagged_topics
- Discourse.deprecate("FlagQuery has been deprecated. Please use the Reviewable API instead.", since: "2.3.0beta5", drop_from: "2.4")
-
- params = {
- pending: Reviewable.statuses[:pending],
- min_score: Reviewable.min_score_for_priority
- }
-
- results = DB.query(<<~SQL, params)
- SELECT rs.reviewable_score_type,
- p.id AS post_id,
- r.topic_id,
- rs.created_at,
- p.user_id
- FROM reviewables AS r
- INNER JOIN reviewable_scores AS rs ON rs.reviewable_id = r.id
- INNER JOIN posts AS p ON p.id = r.target_id
- WHERE r.type = 'ReviewableFlaggedPost'
- AND r.status = :pending
- AND r.score >= :min_score
- ORDER BY rs.created_at DESC
- SQL
-
- ft_by_id = {}
- user_ids = Set.new
-
- results.each do |r|
- ft = ft_by_id[r.topic_id] ||= OpenStruct.new(
- topic_id: r.topic_id,
- flag_counts: {},
- user_ids: Set.new,
- last_flag_at: r.created_at,
- )
-
- ft.flag_counts[r.reviewable_score_type] ||= 0
- ft.flag_counts[r.reviewable_score_type] += 1
-
- ft.user_ids << r.user_id
- user_ids << r.user_id
- end
-
- all_topics = Topic.where(id: ft_by_id.keys).to_a
- all_topics.each { |t| ft_by_id[t.id].topic = t }
-
- Topic.preload_custom_fields(all_topics, TopicList.preloaded_custom_fields)
- {
- flagged_topics: ft_by_id.values,
- users: User.where(id: user_ids)
- }
- end
end
diff --git a/lib/freedom_patches/active_record_base.rb b/lib/freedom_patches/active_record_base.rb
index 0a7a54e763..f4909ae1f8 100644
--- a/lib/freedom_patches/active_record_base.rb
+++ b/lib/freedom_patches/active_record_base.rb
@@ -26,7 +26,7 @@ class ActiveRecord::Base
# Execute SQL manually
def self.exec_sql(*args)
- Discourse.deprecate("exec_sql should not be used anymore, please use DB.exec or DB.query instead!")
+ Discourse.deprecate("exec_sql should not be used anymore, please use DB.exec or DB.query instead!", drop_from: '2.9.0')
conn = ActiveRecord::Base.connection
sql = ActiveRecord::Base.public_send(:sanitize_sql_array, args)
diff --git a/lib/guardian.rb b/lib/guardian.rb
index 693222846f..a4af71e2a5 100644
--- a/lib/guardian.rb
+++ b/lib/guardian.rb
@@ -397,7 +397,7 @@ class Guardian
end
def can_bulk_invite_to_forum?(user)
- user.admin? && !SiteSetting.enable_discourse_connect
+ user.admin?
end
def can_resend_all_invites?(user)
@@ -494,7 +494,7 @@ class Guardian
def allow_themes?(theme_ids, include_preview: false)
return true if theme_ids.blank?
- if allowed_theme_ids = GlobalSetting.allowed_theme_ids
+ if allowed_theme_ids = Theme.allowed_remote_theme_ids
if (theme_ids - allowed_theme_ids).present?
return false
end
diff --git a/lib/guardian/topic_guardian.rb b/lib/guardian/topic_guardian.rb
index 1d33bfdb24..ef619bd112 100644
--- a/lib/guardian/topic_guardian.rb
+++ b/lib/guardian/topic_guardian.rb
@@ -156,7 +156,7 @@ module TopicGuardian
def can_permanently_delete_topic?(topic)
return false if !SiteSetting.can_permanently_delete
return false if !topic
- return false if topic.posts_count > 1
+ return false if topic.posts_count > 0
return false if !is_admin? || !can_see_topic?(topic)
return false if !topic.deleted_at
return false if topic.deleted_by_id == @user.id && topic.deleted_at >= Post::PERMANENT_DELETE_TIMER.ago
diff --git a/lib/i18n/locale_file_checker.rb b/lib/i18n/locale_file_checker.rb
index e818823fd8..757e9b969a 100644
--- a/lib/i18n/locale_file_checker.rb
+++ b/lib/i18n/locale_file_checker.rb
@@ -165,7 +165,7 @@ class LocaleFileChecker
def plural_keys
@plural_keys ||= begin
- eval(File.read("#{Rails.root}/#{PLURALS_FILE}")).map do |locale, value|
+ eval(File.read("#{Rails.root}/#{PLURALS_FILE}")).map do |locale, value| # rubocop:disable Security/Eval
[locale.to_s, value[:i18n][:plural][:keys].map(&:to_s)]
end.to_h
end
diff --git a/lib/imap/sync.rb b/lib/imap/sync.rb
index 5f034270dc..040910cbc8 100644
--- a/lib/imap/sync.rb
+++ b/lib/imap/sync.rb
@@ -141,7 +141,7 @@ module Imap
message_id: Email.message_id_clean(email['ENVELOPE'].message_id),
imap_uid: nil,
imap_uid_validity: nil
- ).where("to_addresses LIKE '%#{@group.email_username}%'").first
+ ).where("to_addresses LIKE ?", "%#{@group.email_username}%").first
if incoming_email
incoming_email.update(
diff --git a/lib/import_export/base_exporter.rb b/lib/import_export/base_exporter.rb
index 509266652c..5a7f4a6195 100644
--- a/lib/import_export/base_exporter.rb
+++ b/lib/import_export/base_exporter.rb
@@ -14,7 +14,7 @@ module ImportExport
:public_admission, :membership_request_template, :messageable_level, :mentionable_level,
:members_visibility_level, :publish_read_state]
- USER_ATTRS = [:id, :email, :username, :name, :created_at, :trust_level, :active, :last_emailed_at]
+ USER_ATTRS = [:id, :email, :username, :name, :created_at, :trust_level, :active, :last_emailed_at, :custom_fields]
TOPIC_ATTRS = [:id, :title, :created_at, :views, :category_id, :closed, :archived, :archetype]
diff --git a/lib/middleware/anonymous_cache.rb b/lib/middleware/anonymous_cache.rb
index a62fb01836..cb06d8793f 100644
--- a/lib/middleware/anonymous_cache.rb
+++ b/lib/middleware/anonymous_cache.rb
@@ -29,7 +29,7 @@ module Middleware
method << "|#{k}=#\{h.#{v}}"
end
method << "\"\nend"
- eval(method)
+ eval(method) # rubocop:disable Security/Eval
@@compiled = true
end
@@ -315,7 +315,7 @@ module Middleware
if PAYLOAD_INVALID_REQUEST_METHODS.include?(env[Rack::REQUEST_METHOD]) &&
env[Rack::RACK_INPUT].size > 0
- return [413, {}, []]
+ return [413, { "Cache-Control" => "private, max-age=0, must-revalidate" }, []]
end
helper = Helper.new(env)
diff --git a/lib/middleware/discourse_public_exceptions.rb b/lib/middleware/discourse_public_exceptions.rb
index 8b9cbc827d..fcc2b5ed26 100644
--- a/lib/middleware/discourse_public_exceptions.rb
+++ b/lib/middleware/discourse_public_exceptions.rb
@@ -35,7 +35,7 @@ module Middleware
begin
request.format
rescue Mime::Type::InvalidMimeType
- return [400, {}, ["Invalid MIME type"]]
+ return [400, { "Cache-Control" => "private, max-age=0, must-revalidate" }, ["Invalid MIME type"]]
end
if ApplicationController.rescue_with_handler(exception, object: fake_controller)
diff --git a/lib/new_post_result.rb b/lib/new_post_result.rb
index 25d61f497a..4fa50b2486 100644
--- a/lib/new_post_result.rb
+++ b/lib/new_post_result.rb
@@ -36,7 +36,8 @@ class NewPostResult
def queued_post
Discourse.deprecate(
"NewPostManager#queued_post is deprecated. Please use #reviewable instead.",
- output_in_test: true
+ output_in_test: true,
+ drop_from: '2.9.0',
)
reviewable
diff --git a/lib/onebox/engine/github_actions_onebox.rb b/lib/onebox/engine/github_actions_onebox.rb
index cdb7e16378..182fff49d5 100644
--- a/lib/onebox/engine/github_actions_onebox.rb
+++ b/lib/onebox/engine/github_actions_onebox.rb
@@ -71,7 +71,7 @@ module Onebox
raw["head_commit"]["message"].lines.first
elsif type == :pr_run
pr_url = "https://api.github.com/repos/#{match[:org]}/#{match[:repo]}/pulls/#{match[:pr_id]}"
- ::MultiJson.load(URI.open(pr_url, read_timeout: timeout))["title"]
+ ::MultiJson.load(URI.parse(pr_url).open(read_timeout: timeout))["title"]
end
{
diff --git a/lib/onebox/engine/instagram_onebox.rb b/lib/onebox/engine/instagram_onebox.rb
index 21a8ae6c6f..7cc96ad3d6 100644
--- a/lib/onebox/engine/instagram_onebox.rb
+++ b/lib/onebox/engine/instagram_onebox.rb
@@ -9,22 +9,41 @@ module Onebox
matches_regexp(/^https?:\/\/(?:www\.)?(?:instagram\.com|instagr\.am)\/?(?:.*)\/(?:p|tv)\/[a-zA-Z\d_-]+/)
always_https
+ requires_iframe_origins "https://www.instagram.com"
def clean_url
url.scan(/^https?:\/\/(?:www\.)?(?:instagram\.com|instagr\.am)\/?(?:.*)\/(?:p|tv)\/[a-zA-Z\d_-]+/).flatten.first
end
def data
- oembed = get_oembed
- raise "No oEmbed data found. Ensure 'facebook_app_access_token' is valid" if oembed.data.empty?
+ @data ||= begin
+ oembed = get_oembed
+ raise "No oEmbed data found. Ensure 'facebook_app_access_token' is valid" if oembed.data.empty?
- {
- link: clean_url.gsub("/#{oembed.author_name}/", "/"),
- title: "@#{oembed.author_name}",
- image: oembed.thumbnail_url,
- description: Onebox::Helpers.truncate(oembed.title, 250),
- }
+ {
+ link: clean_url.gsub("/#{oembed.author_name}/", "/") + '/embed',
+ title: "@#{oembed.author_name}",
+ image: oembed.thumbnail_url,
+ image_width: oembed.data[:thumbnail_width],
+ image_height: oembed.data[:thumbnail_height],
+ description: Onebox::Helpers.truncate(oembed.title, 250),
+ }
+ end
+ end
+ def placeholder_html
+ ::Onebox::Helpers.image_placeholder_html
+ end
+
+ def to_html
+ <<-HTML
+
+ HTML
end
protected
diff --git a/lib/onebox/engine/json.rb b/lib/onebox/engine/json.rb
index 261dc0309c..204b09c720 100644
--- a/lib/onebox/engine/json.rb
+++ b/lib/onebox/engine/json.rb
@@ -6,7 +6,7 @@ module Onebox
private
def raw
- @raw ||= ::MultiJson.load(URI.open(url, read_timeout: timeout))
+ @raw ||= ::MultiJson.load(URI.parse(url).open(read_timeout: timeout))
end
end
end
diff --git a/lib/onebox/engine/pubmed_onebox.rb b/lib/onebox/engine/pubmed_onebox.rb
index 1cf8a0ac9b..366d5d5029 100644
--- a/lib/onebox/engine/pubmed_onebox.rb
+++ b/lib/onebox/engine/pubmed_onebox.rb
@@ -12,7 +12,7 @@ module Onebox
def xml
return @xml if defined?(@xml)
- doc = Nokogiri::XML(URI.open(URI.join(@url, "?report=xml&format=text")))
+ doc = Nokogiri::XML(URI.join(@url, "?report=xml&format=text").open)
pre = doc.xpath("//pre")
@xml = Nokogiri::XML("
ve ** bu özel ileti yi yer im lerine ekle
**. Bunu yaparsanız, gelecekte bir :gift: kazanabilirsiniz!
+ Daha fazla bilgi edinmek istiyorsanız aşağıdan
ikonuna tıklayın ve **bu özel iletiyi yer imlerine
ekleyin**. Bunu yaparsanız, gelecekte bir :gift: kazanabilirsiniz!
reply: |-
Mükemmel! Artık istediğiniz zaman doğrudan [profilinizdeki yer imleri sekmesinden](%{bookmark_url}) özel sohbetimize geri dönüş yolunu kolayca bulabilirsiniz. Sağ üstteki ↗ profil resminizi seçmeniz yeterli
not_found: |-
diff --git a/plugins/discourse-narrative-bot/plugin.rb b/plugins/discourse-narrative-bot/plugin.rb
index 5bda9a65ea..2748dc9bc5 100644
--- a/plugins/discourse-narrative-bot/plugin.rb
+++ b/plugins/discourse-narrative-bot/plugin.rb
@@ -5,6 +5,7 @@
# version: 1.0
# authors: Nick Sahler, Alan Tan
# url: https://github.com/discourse/discourse/tree/main/plugins/discourse-narrative-bot
+# transpile_js: true
enabled_site_setting :discourse_narrative_bot_enabled
hide_plugin if self.respond_to?(:hide_plugin)
diff --git a/plugins/discourse-presence/README.md b/plugins/discourse-presence/README.md
index 4e41c6c62e..64be78e1ca 100644
--- a/plugins/discourse-presence/README.md
+++ b/plugins/discourse-presence/README.md
@@ -1,14 +1,2 @@
# Discourse Presence plugin
This plugin shows which users are currently writing a reply at the same time as you.
-
-## Installation
-
-Follow the directions at [Install a Plugin](https://meta.discourse.org/t/install-a-plugin/19157) using https://github.com/discourse/discourse-presence.git as the repository URL.
-
-## Authors
-
-André Pereira, David Taylor
-
-## License
-
-GNU GPL v2
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js
new file mode 100644
index 0000000000..4430cf35ae
--- /dev/null
+++ b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js
@@ -0,0 +1,113 @@
+import discourseComputed, {
+ observes,
+ on,
+} from "discourse-common/utils/decorators";
+import { equal, gt, readOnly, union } from "@ember/object/computed";
+import Component from "@ember/component";
+import { inject as service } from "@ember/service";
+
+export default Component.extend({
+ presence: service(),
+ composerPresenceManager: service(),
+
+ @discourseComputed(
+ "model.replyingToTopic",
+ "model.editingPost",
+ "model.whisper",
+ "model.composerOpened"
+ )
+ state(replyingToTopic, editingPost, whisper, composerOpen) {
+ if (!composerOpen) {
+ return;
+ } else if (editingPost) {
+ return "edit";
+ } else if (whisper) {
+ return "whisper";
+ } else if (replyingToTopic) {
+ return "reply";
+ }
+ },
+
+ isReply: equal("state", "reply"),
+ isEdit: equal("state", "edit"),
+ isWhisper: equal("state", "whisper"),
+
+ @discourseComputed("model.topic.id", "isReply", "isWhisper")
+ replyChannelName(topicId, isReply, isWhisper) {
+ if (topicId && (isReply || isWhisper)) {
+ return `/discourse-presence/reply/${topicId}`;
+ }
+ },
+
+ @discourseComputed("model.topic.id", "isReply", "isWhisper")
+ whisperChannelName(topicId, isReply, isWhisper) {
+ if (topicId && this.currentUser.staff && (isReply || isWhisper)) {
+ return `/discourse-presence/whisper/${topicId}`;
+ }
+ },
+
+ @discourseComputed("isEdit", "model.post.id")
+ editChannelName(isEdit, postId) {
+ if (isEdit) {
+ return `/discourse-presence/edit/${postId}`;
+ }
+ },
+
+ _setupChannel(channelKey, name) {
+ if (this[channelKey]?.name !== name) {
+ this[channelKey]?.unsubscribe();
+ if (name) {
+ this.set(channelKey, this.presence.getChannel(name));
+ this[channelKey].subscribe();
+ } else if (this[channelKey]) {
+ this.set(channelKey, null);
+ }
+ }
+ },
+
+ @observes("replyChannelName", "whisperChannelName", "editChannelName")
+ _setupChannels() {
+ this._setupChannel("replyChannel", this.replyChannelName);
+ this._setupChannel("whisperChannel", this.whisperChannelName);
+ this._setupChannel("editChannel", this.editChannelName);
+ },
+
+ _cleanupChannels() {
+ this._setupChannel("replyChannel", null);
+ this._setupChannel("whisperChannel", null);
+ this._setupChannel("editChannel", null);
+ },
+
+ replyingUsers: union("replyChannel.users", "whisperChannel.users"),
+ editingUsers: readOnly("editChannel.users"),
+
+ @discourseComputed("isReply", "replyingUsers.[]", "editingUsers.[]")
+ presenceUsers(isReply, replyingUsers, editingUsers) {
+ const users = isReply ? replyingUsers : editingUsers;
+ return users
+ ?.filter((u) => u.id !== this.currentUser.id)
+ ?.slice(0, this.siteSettings.presence_max_users_shown);
+ },
+
+ shouldDisplay: gt("presenceUsers.length", 0),
+
+ @on("didInsertElement")
+ subscribe() {
+ this._setupChannels();
+ },
+
+ @observes("model.reply", "state", "model.post.id", "model.topic.id")
+ _contentChanged() {
+ if (this.model.reply === "") {
+ return;
+ }
+ const entity = this.state === "edit" ? this.model?.post : this.model?.topic;
+ this.composerPresenceManager.notifyState(this.state, entity?.id);
+ },
+
+ @on("willDestroyElement")
+ closeComposer() {
+ this._cleanupChannels();
+ this.composerPresenceManager.leave();
+ },
+});
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6
deleted file mode 100644
index 19a82d0309..0000000000
--- a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6
+++ /dev/null
@@ -1,117 +0,0 @@
-import {
- CLOSED,
- COMPOSER_TYPE,
- EDITING,
- KEEP_ALIVE_DURATION_SECONDS,
- REPLYING,
-} from "discourse/plugins/discourse-presence/discourse/lib/presence";
-import { cancel, throttle } from "@ember/runloop";
-import discourseComputed, {
- observes,
- on,
-} from "discourse-common/utils/decorators";
-import { gt, readOnly } from "@ember/object/computed";
-import Component from "@ember/component";
-import { inject as service } from "@ember/service";
-
-export default Component.extend({
- // Passed in variables
- presenceManager: service(),
-
- @discourseComputed("model.topic.id")
- users(topicId) {
- return this.presenceManager.users(topicId);
- },
-
- @discourseComputed("model.topic.id")
- editingUsers(topicId) {
- return this.presenceManager.editingUsers(topicId);
- },
-
- isReply: readOnly("model.replyingToTopic"),
- isEdit: readOnly("model.editingPost"),
-
- @on("didInsertElement")
- subscribe() {
- this.presenceManager.subscribe(this.get("model.topic.id"), COMPOSER_TYPE);
- },
-
- @discourseComputed(
- "model.post.id",
- "editingUsers.@each.last_seen",
- "users.@each.last_seen",
- "isReply",
- "isEdit"
- )
- presenceUsers(postId, editingUsers, users, isReply, isEdit) {
- if (isEdit) {
- return editingUsers.filterBy("post_id", postId);
- } else if (isReply) {
- return users;
- }
- return [];
- },
-
- shouldDisplay: gt("presenceUsers.length", 0),
-
- @observes("model.reply", "model.title")
- typing() {
- throttle(this, this._typing, KEEP_ALIVE_DURATION_SECONDS * 1000);
- },
-
- _typing() {
- if ((!this.isReply && !this.isEdit) || !this.get("model.composerOpened")) {
- return;
- }
-
- let data = {
- topicId: this.get("model.topic.id"),
- state: this.isEdit ? EDITING : REPLYING,
- whisper: this.get("model.whisper"),
- postId: this.get("model.post.id"),
- presenceStaffOnly: this.get("model._presenceStaffOnly"),
- };
-
- this._prevPublishData = data;
-
- this._throttle = this.presenceManager.publish(
- data.topicId,
- data.state,
- data.whisper,
- data.postId,
- data.presenceStaffOnly
- );
- },
-
- @observes("model.whisper")
- cancelThrottle() {
- this._cancelThrottle();
- },
-
- @observes("model.action", "model.topic.id")
- composerState() {
- if (this._prevPublishData) {
- this.presenceManager.publish(
- this._prevPublishData.topicId,
- CLOSED,
- this._prevPublishData.whisper,
- this._prevPublishData.postId
- );
- this._prevPublishData = null;
- }
- },
-
- @on("willDestroyElement")
- closeComposer() {
- this._cancelThrottle();
- this._prevPublishData = null;
- this.presenceManager.cleanUpPresence(COMPOSER_TYPE);
- },
-
- _cancelThrottle() {
- if (this._throttle) {
- cancel(this._throttle);
- this._throttle = null;
- }
- },
-});
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.js b/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.js
new file mode 100644
index 0000000000..42e504cee7
--- /dev/null
+++ b/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.js
@@ -0,0 +1,63 @@
+import discourseComputed, { on } from "discourse-common/utils/decorators";
+import Component from "@ember/component";
+import { gt, union } from "@ember/object/computed";
+import { inject as service } from "@ember/service";
+
+export default Component.extend({
+ topic: null,
+ presence: service(),
+ replyChannel: null,
+ whisperChannel: null,
+
+ @discourseComputed("replyChannel.users.[]")
+ replyUsers(users) {
+ return users?.filter((u) => u.id !== this.currentUser.id);
+ },
+
+ @discourseComputed("whisperChannel.users.[]")
+ whisperUsers(users) {
+ return users?.filter((u) => u.id !== this.currentUser.id);
+ },
+
+ users: union("replyUsers", "whisperUsers"),
+
+ @discourseComputed("topic.id")
+ replyChannelName(id) {
+ return `/discourse-presence/reply/${id}`;
+ },
+
+ @discourseComputed("topic.id")
+ whisperChannelName(id) {
+ return `/discourse-presence/whisper/${id}`;
+ },
+
+ shouldDisplay: gt("users.length", 0),
+
+ didReceiveAttrs() {
+ this._super(...arguments);
+
+ if (this.replyChannel?.name !== this.replyChannelName) {
+ this.replyChannel?.unsubscribe();
+ this.set("replyChannel", this.presence.getChannel(this.replyChannelName));
+ this.replyChannel.subscribe();
+ }
+
+ if (
+ this.currentUser.staff &&
+ this.whisperChannel?.name !== this.whisperChannelName
+ ) {
+ this.whisperChannel?.unsubscribe();
+ this.set(
+ "whisperChannel",
+ this.presence.getChannel(this.whisperChannelName)
+ );
+ this.whisperChannel.subscribe();
+ }
+ },
+
+ @on("willDestroyElement")
+ _destroyed() {
+ this.replyChannel?.unsubscribe();
+ this.whisperChannel?.unsubscribe();
+ },
+});
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.js.es6
deleted file mode 100644
index f38ac5c582..0000000000
--- a/plugins/discourse-presence/assets/javascripts/discourse/components/topic-presence-display.js.es6
+++ /dev/null
@@ -1,37 +0,0 @@
-import discourseComputed, { on } from "discourse-common/utils/decorators";
-import Component from "@ember/component";
-import { TOPIC_TYPE } from "discourse/plugins/discourse-presence/discourse/lib/presence";
-import { gt } from "@ember/object/computed";
-import { inject as service } from "@ember/service";
-
-export default Component.extend({
- topic: null,
- topicId: null,
- presenceManager: service(),
-
- @discourseComputed("topic.id")
- users(topicId) {
- return this.presenceManager.users(topicId);
- },
-
- shouldDisplay: gt("users.length", 0),
-
- didReceiveAttrs() {
- this._super(...arguments);
- if (this.topicId) {
- this.presenceManager.unsubscribe(this.topicId, TOPIC_TYPE);
- }
- this.set("topicId", this.get("topic.id"));
- },
-
- @on("didInsertElement")
- subscribe() {
- this.set("topicId", this.get("topic.id"));
- this.presenceManager.subscribe(this.get("topic.id"), TOPIC_TYPE);
- },
-
- @on("willDestroyElement")
- _destroyed() {
- this.presenceManager.unsubscribe(this.get("topic.id"), TOPIC_TYPE);
- },
-});
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/lib/presence.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/lib/presence.js.es6
deleted file mode 100644
index 7db5048f67..0000000000
--- a/plugins/discourse-presence/assets/javascripts/discourse/lib/presence.js.es6
+++ /dev/null
@@ -1,229 +0,0 @@
-import { cancel, later } from "@ember/runloop";
-import EmberObject from "@ember/object";
-import { ajax } from "discourse/lib/ajax";
-import discourseComputed from "discourse-common/utils/decorators";
-
-// The durations chosen here determines the accuracy of the presence feature and
-// is tied closely with the server side implementation. Decreasing the duration
-// to increase the accuracy will come at the expense of having to more network
-// calls to publish the client's state.
-//
-// Logic walk through of our heuristic implementation:
-// - When client A is typing, a message is published every KEEP_ALIVE_DURATION_SECONDS.
-// - Client B receives the message and stores each user in an array and marks
-// the user with a client-side timestamp of when the user was seen.
-// - If client A continues to type, client B will continue to receive messages to
-// update the client-side timestamp of when client A was last seen.
-// - If client A disconnects or becomes inactive, the state of client A will be
-// cleaned up on client B by a scheduler that runs every TIMER_INTERVAL_MILLISECONDS
-export const KEEP_ALIVE_DURATION_SECONDS = 10;
-const BUFFER_DURATION_SECONDS = KEEP_ALIVE_DURATION_SECONDS + 2;
-
-const MESSAGE_BUS_LAST_ID = 0;
-const TIMER_INTERVAL_MILLISECONDS = 2000;
-
-export const REPLYING = "replying";
-export const EDITING = "editing";
-export const CLOSED = "closed";
-
-export const TOPIC_TYPE = "topic";
-export const COMPOSER_TYPE = "composer";
-
-const Presence = EmberObject.extend({
- users: null,
- editingUsers: null,
- subscribers: null,
- topicId: null,
- currentUser: null,
- messageBus: null,
- siteSettings: null,
-
- init() {
- this._super(...arguments);
-
- this.setProperties({
- users: [],
- editingUsers: [],
- subscribers: new Set(),
- });
- },
-
- subscribe(type) {
- if (this.subscribers.size === 0) {
- this.messageBus.subscribe(
- this.channel,
- (message) => {
- const { user, state } = message;
- if (this.get("currentUser.id") === user.id) {
- return;
- }
-
- switch (state) {
- case REPLYING:
- this._appendUser(this.users, user);
- break;
- case EDITING:
- this._appendUser(this.editingUsers, user, {
- post_id: parseInt(message.post_id, 10),
- });
- break;
- case CLOSED:
- this._removeUser(user);
- break;
- }
- },
- MESSAGE_BUS_LAST_ID
- );
- }
-
- this.subscribers.add(type);
- },
-
- unsubscribe(type) {
- this.subscribers.delete(type);
- const noSubscribers = this.subscribers.size === 0;
-
- if (noSubscribers) {
- this.messageBus.unsubscribe(this.channel);
- this._stopTimer();
-
- this.setProperties({
- users: [],
- editingUsers: [],
- });
- }
-
- return noSubscribers;
- },
-
- @discourseComputed("topicId")
- channel(topicId) {
- return `/presence-plugin/${topicId}`;
- },
-
- publish(state, whisper, postId, staffOnly) {
- // NOTE: `user_option` is the correct place to get this value from, but
- // it may not have been set yet. It will always have been set directly
- // on the currentUser, via the preloaded_json payload.
- // TODO: Remove this when preloaded_json is refactored.
- let hiddenProfile = this.get(
- "currentUser.user_option.hide_profile_and_presence"
- );
- if (hiddenProfile === undefined) {
- hiddenProfile = this.get("currentUser.hide_profile_and_presence");
- }
-
- if (hiddenProfile && this.get("siteSettings.allow_users_to_hide_profile")) {
- return;
- }
-
- const data = {
- state,
- topic_id: this.topicId,
- };
-
- if (whisper) {
- data.is_whisper = true;
- }
-
- if (postId && state === EDITING) {
- data.post_id = postId;
- }
-
- if (staffOnly) {
- data.staff_only = true;
- }
-
- return ajax("/presence-plugin/publish", {
- type: "POST",
- data,
- });
- },
-
- _removeUser(user) {
- [this.users, this.editingUsers].forEach((users) => {
- const existingUser = users.findBy("id", user.id);
- if (existingUser) {
- users.removeObject(existingUser);
- }
- });
- },
-
- _cleanUpUsers() {
- [this.users, this.editingUsers].forEach((users) => {
- const staleUsers = [];
-
- users.forEach((user) => {
- if (user.last_seen <= Date.now() - BUFFER_DURATION_SECONDS * 1000) {
- staleUsers.push(user);
- }
- });
-
- users.removeObjects(staleUsers);
- });
-
- return this.users.length === 0 && this.editingUsers.length === 0;
- },
-
- _appendUser(users, user, attrs) {
- let existingUser;
- let usersLength = 0;
-
- users.forEach((u) => {
- if (u.id === user.id) {
- existingUser = u;
- }
-
- if (attrs && attrs.post_id) {
- if (u.post_id === attrs.post_id) {
- usersLength++;
- }
- } else {
- usersLength++;
- }
- });
-
- const props = attrs || {};
- props.last_seen = Date.now();
-
- if (existingUser) {
- existingUser.setProperties(props);
- } else {
- const limit = this.get("siteSettings.presence_max_users_shown");
-
- if (usersLength < limit) {
- users.pushObject(EmberObject.create(Object.assign(user, props)));
- }
- }
-
- this._startTimer(() => {
- this._cleanUpUsers();
- });
- },
-
- _scheduleTimer(callback) {
- return later(
- this,
- () => {
- const stop = callback();
-
- if (!stop) {
- this.set("_timer", this._scheduleTimer(callback));
- }
- },
- TIMER_INTERVAL_MILLISECONDS
- );
- },
-
- _stopTimer() {
- cancel(this._timer);
- },
-
- _startTimer(callback) {
- if (!this._timer) {
- this.set("_timer", this._scheduleTimer(callback));
- }
- },
-});
-
-export default Presence;
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/services/composer-presence-manager.js b/plugins/discourse-presence/assets/javascripts/discourse/services/composer-presence-manager.js
new file mode 100644
index 0000000000..e302a3a585
--- /dev/null
+++ b/plugins/discourse-presence/assets/javascripts/discourse/services/composer-presence-manager.js
@@ -0,0 +1,64 @@
+import Service, { inject as service } from "@ember/service";
+import { cancel, debounce } from "@ember/runloop";
+import { isTesting } from "discourse-common/config/environment";
+
+const PRESENCE_CHANNEL_PREFIX = "/discourse-presence";
+const KEEP_ALIVE_DURATION_SECONDS = 10;
+
+export default class ComposerPresenceManager extends Service {
+ @service presence;
+
+ notifyState(intent, id) {
+ if (
+ this.siteSettings.allow_users_to_hide_profile &&
+ this.currentUser.hide_profile_and_presence
+ ) {
+ return;
+ }
+
+ if (intent === undefined) {
+ return this.leave();
+ }
+
+ if (!["reply", "whisper", "edit"].includes(intent)) {
+ throw `Unknown intent ${intent}`;
+ }
+
+ const state = `${intent}/${id}`;
+
+ if (this._state !== state) {
+ this._enter(intent, id);
+ this._state = state;
+ }
+
+ if (!isTesting()) {
+ this._autoLeaveTimer = debounce(
+ this,
+ this.leave,
+ KEEP_ALIVE_DURATION_SECONDS * 1000
+ );
+ }
+ }
+
+ leave() {
+ this._presentChannel?.leave();
+ this._presentChannel = null;
+ this._state = null;
+ if (this._autoLeaveTimer) {
+ cancel(this._autoLeaveTimer);
+ this._autoLeaveTimer = null;
+ }
+ }
+
+ _enter(intent, id) {
+ this.leave();
+
+ let channelName = `${PRESENCE_CHANNEL_PREFIX}/${intent}/${id}`;
+ this._presentChannel = this.presence.getChannel(channelName);
+ this._presentChannel.enter();
+ }
+
+ willDestroy() {
+ this.leave();
+ }
+}
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/services/presence-manager.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/services/presence-manager.js.es6
deleted file mode 100644
index ae24b63073..0000000000
--- a/plugins/discourse-presence/assets/javascripts/discourse/services/presence-manager.js.es6
+++ /dev/null
@@ -1,82 +0,0 @@
-import Presence, {
- CLOSED,
-} from "discourse/plugins/discourse-presence/discourse/lib/presence";
-import Service from "@ember/service";
-
-const PresenceManager = Service.extend({
- presences: null,
-
- init() {
- this._super(...arguments);
-
- this.setProperties({
- presences: {},
- });
- },
-
- subscribe(topicId, type) {
- if (!topicId) {
- return;
- }
- this._getPresence(topicId).subscribe(type);
- },
-
- unsubscribe(topicId, type) {
- if (!topicId) {
- return;
- }
- const presence = this._getPresence(topicId);
-
- if (presence.unsubscribe(type)) {
- delete this.presences[topicId];
- }
- },
-
- users(topicId) {
- if (!topicId) {
- return [];
- }
- return this._getPresence(topicId).users;
- },
-
- editingUsers(topicId) {
- if (!topicId) {
- return [];
- }
- return this._getPresence(topicId).editingUsers;
- },
-
- publish(topicId, state, whisper, postId, staffOnly) {
- if (!topicId) {
- return;
- }
- return this._getPresence(topicId).publish(
- state,
- whisper,
- postId,
- staffOnly
- );
- },
-
- cleanUpPresence(type) {
- Object.keys(this.presences).forEach((key) => {
- this.publish(key, CLOSED);
- this.unsubscribe(key, type);
- });
- },
-
- _getPresence(topicId) {
- if (!this.presences[topicId]) {
- this.presences[topicId] = Presence.create({
- messageBus: this.messageBus,
- siteSettings: this.siteSettings,
- currentUser: this.currentUser,
- topicId,
- });
- }
-
- return this.presences[topicId];
- },
-});
-
-export default PresenceManager;
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/composer-fields/presence.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/composer-fields/presence.js.es6
deleted file mode 100644
index 75ca86b4a4..0000000000
--- a/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/composer-fields/presence.js.es6
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- shouldRender(_, component) {
- return component.siteSettings.presence_enabled;
- },
-};
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/topic-above-footer-buttons/presence.hbs b/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/topic-above-footer-buttons/presence.hbs
index c8514c7edc..5b76786960 100644
--- a/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/topic-above-footer-buttons/presence.hbs
+++ b/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/topic-above-footer-buttons/presence.hbs
@@ -1 +1,2 @@
+{{!-- Note: the topic-above-footer-buttons outlet is only rendered for logged-in users --}}
{{topic-presence-display topic=model}}
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/topic-above-footer-buttons/presence.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/topic-above-footer-buttons/presence.js.es6
deleted file mode 100644
index 75ca86b4a4..0000000000
--- a/plugins/discourse-presence/assets/javascripts/discourse/templates/connectors/topic-above-footer-buttons/presence.js.es6
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- shouldRender(_, component) {
- return component.siteSettings.presence_enabled;
- },
-};
diff --git a/plugins/discourse-presence/config/locales/server.id.yml b/plugins/discourse-presence/config/locales/server.id.yml
index 596e36b2e1..6b2f60edcd 100644
--- a/plugins/discourse-presence/config/locales/server.id.yml
+++ b/plugins/discourse-presence/config/locales/server.id.yml
@@ -5,3 +5,6 @@
# https://translate.discourse.org/
id:
+ site_settings:
+ presence_enabled: "Tampilkan pengguna yang sedang membalas topik saat ini, atau mengedit posting saat ini?"
+ presence_max_users_shown: "Jumlah maksimum pengguna yang ditampilkan."
diff --git a/plugins/discourse-presence/plugin.rb b/plugins/discourse-presence/plugin.rb
index d20f4a2b1d..b1e356d747 100644
--- a/plugins/discourse-presence/plugin.rb
+++ b/plugins/discourse-presence/plugin.rb
@@ -1,178 +1,72 @@
# frozen_string_literal: true
# name: discourse-presence
-# about: Show which users are writing a reply to a topic
+# about: Show which users are replying to a topic, or editing a post
# version: 2.0
# authors: André Pereira, David Taylor, tgxworld
# url: https://github.com/discourse/discourse/tree/main/plugins/discourse-presence
+# transpile_js: true
enabled_site_setting :presence_enabled
hide_plugin if self.respond_to?(:hide_plugin)
register_asset 'stylesheets/presence.scss'
-PLUGIN_NAME ||= -"discourse-presence"
-
after_initialize do
- MessageBus.register_client_message_filter('/presence-plugin/') do |message|
- published_at = message.data["published_at"]
+ register_presence_channel_prefix("discourse-presence") do |channel_name|
+ if topic_id = channel_name[/\/discourse-presence\/reply\/(\d+)/, 1]
+ topic = Topic.find(topic_id)
+ config = PresenceChannel::Config.new
- if published_at
- (Time.zone.now.to_i - published_at) <= ::Presence::MAX_BACKLOG_AGE_SECONDS
- else
- false
- end
- end
-
- module ::Presence
- MAX_BACKLOG_AGE_SECONDS = 10
-
- class Engine < ::Rails::Engine
- engine_name PLUGIN_NAME
- isolate_namespace Presence
- end
- end
-
- require_dependency "application_controller"
-
- class Presence::PresencesController < ::ApplicationController
- requires_plugin PLUGIN_NAME
- before_action :ensure_logged_in
- before_action :ensure_presence_enabled
-
- EDITING_STATE = 'editing'
- REPLYING_STATE = 'replying'
- CLOSED_STATE = 'closed'
-
- def handle_message
- [:state, :topic_id].each do |key|
- raise ActionController::ParameterMissing.new(key) unless params.key?(key)
- end
-
- topic_id = permitted_params[:topic_id]
- topic = Topic.find_by(id: topic_id)
-
- raise Discourse::InvalidParameters.new(:topic_id) unless topic
- guardian.ensure_can_see!(topic)
-
- post = nil
-
- if (permitted_params[:post_id])
- if (permitted_params[:state] != EDITING_STATE)
- raise Discourse::InvalidParameters.new(:state)
- end
-
- post = Post.find_by(id: permitted_params[:post_id])
- raise Discourse::InvalidParameters.new(:topic_id) unless post
-
- guardian.ensure_can_edit!(post)
- end
-
- opts = {
- max_backlog_age: Presence::MAX_BACKLOG_AGE_SECONDS
- }
-
- if permitted_params[:staff_only]
- opts[:group_ids] = [Group::AUTO_GROUPS[:staff]]
+ if topic.private_message?
+ config.allowed_user_ids = topic.allowed_users.pluck(:id)
+ config.allowed_group_ids = topic.allowed_groups.pluck(:group_id) + [::Group::AUTO_GROUPS[:staff]]
+ elsif secure_group_ids = topic.secure_group_ids
+ config.allowed_group_ids = secure_group_ids + [::Group::AUTO_GROUPS[:admins]]
else
- case permitted_params[:state]
- when EDITING_STATE
- opts[:group_ids] = [Group::AUTO_GROUPS[:staff]]
-
- if !post.locked? && !permitted_params[:is_whisper]
- opts[:user_ids] = [post.user_id]
-
- if topic.private_message?
- if post.wiki
- opts[:user_ids] = opts[:user_ids].concat(
- topic.allowed_users.where(
- "trust_level >= ? AND NOT admin OR moderator",
- SiteSetting.min_trust_to_edit_wiki_post
- ).pluck(:id)
- )
-
- opts[:user_ids].uniq!
-
- # Ignore trust level and just publish to all allowed groups since
- # trying to figure out which users in the allowed groups have
- # the necessary trust levels can lead to a large array of user ids
- # if the groups are big.
- opts[:group_ids] = opts[:group_ids].concat(
- topic.allowed_groups.pluck(:id)
- )
- end
- else
- if post.wiki
- opts[:group_ids] << Group::AUTO_GROUPS[:"trust_level_#{SiteSetting.min_trust_to_edit_wiki_post}"]
- elsif SiteSetting.trusted_users_can_edit_others?
- opts[:group_ids] << Group::AUTO_GROUPS[:trust_level_4]
- end
- end
- end
- when REPLYING_STATE
- if permitted_params[:is_whisper]
- opts[:group_ids] = [Group::AUTO_GROUPS[:staff]]
- elsif topic.private_message?
- opts[:user_ids] = topic.allowed_users.pluck(:id)
-
- opts[:group_ids] = [Group::AUTO_GROUPS[:staff]].concat(
- topic.allowed_groups.pluck(:id)
- )
- else
- opts[:group_ids] = topic.secure_group_ids
- end
- when CLOSED_STATE
- if topic.private_message?
- opts[:user_ids] = topic.allowed_users.pluck(:id)
-
- opts[:group_ids] = [Group::AUTO_GROUPS[:staff]].concat(
- topic.allowed_groups.pluck(:id)
- )
- else
- opts[:group_ids] = topic.secure_group_ids
- end
- end
+ # config.public=true would make data available to anon, so use the tl0 group instead
+ config.allowed_group_ids = [ ::Group::AUTO_GROUPS[:trust_level_0] ]
end
- payload = {
- user: BasicUserSerializer.new(current_user, root: false).as_json,
- state: permitted_params[:state],
- is_whisper: permitted_params[:is_whisper].present?,
- published_at: Time.zone.now.to_i
- }
+ config
+ elsif topic_id = channel_name[/\/discourse-presence\/whisper\/(\d+)/, 1]
+ Topic.find(topic_id) # Just ensure it exists
+ PresenceChannel::Config.new(allowed_group_ids: [::Group::AUTO_GROUPS[:staff]])
+ elsif post_id = channel_name[/\/discourse-presence\/edit\/(\d+)/, 1]
+ post = Post.find(post_id)
+ topic = Topic.find(post.topic_id)
- if (post_id = permitted_params[:post_id]).present?
- payload[:post_id] = post_id
+ config = PresenceChannel::Config.new
+ config.allowed_group_ids = [ ::Group::AUTO_GROUPS[:staff] ]
+
+ # Locked and whisper posts are staff only
+ next config if post.locked? || post.whisper?
+
+ config.allowed_user_ids = [ post.user_id ]
+
+ if topic.private_message? && post.wiki
+ # Ignore trust level and just publish to all allowed groups since
+ # trying to figure out which users in the allowed groups have
+ # the necessary trust levels can lead to a large array of user ids
+ # if the groups are big.
+ config.allowed_user_ids += topic.allowed_users.pluck(:id)
+ config.allowed_group_ids += topic.allowed_groups.pluck(:id)
+ elsif post.wiki
+ config.allowed_group_ids << Group::AUTO_GROUPS[:"trust_level_#{SiteSetting.min_trust_to_edit_wiki_post}"]
end
- MessageBus.publish("/presence-plugin/#{topic_id}", payload, opts)
-
- render json: success_json
- end
-
- private
-
- def ensure_presence_enabled
- if !SiteSetting.presence_enabled ||
- (SiteSetting.allow_users_to_hide_profile &&
- current_user.user_option.hide_profile_and_presence?)
-
- raise Discourse::NotFound
+ if !topic.private_message? && SiteSetting.trusted_users_can_edit_others?
+ config.allowed_group_ids << Group::AUTO_GROUPS[:trust_level_4]
end
+
+ if SiteSetting.enable_category_group_moderation? && group_id = topic.category&.reviewable_by_group_id
+ config.allowed_group_ids << group_id
+ end
+
+ config
end
-
- def permitted_params
- params.permit(:state, :topic_id, :post_id, :is_whisper, :staff_only)
- end
+ rescue ActiveRecord::RecordNotFound
+ nil
end
-
- Presence::Engine.routes.draw do
- post '/publish' => 'presences#handle_message'
- end
-
- Discourse::Application.routes.append do
- mount ::Presence::Engine, at: '/presence-plugin'
- end
-
end
diff --git a/plugins/discourse-presence/spec/integration/presence_spec.rb b/plugins/discourse-presence/spec/integration/presence_spec.rb
new file mode 100644
index 0000000000..a59c43b837
--- /dev/null
+++ b/plugins/discourse-presence/spec/integration/presence_spec.rb
@@ -0,0 +1,193 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe "discourse-presence" do
+ describe 'PresenceChannel configuration' do
+ fab!(:user) { Fabricate(:user) }
+ fab!(:user2) { Fabricate(:user) }
+ fab!(:admin) { Fabricate(:admin) }
+
+ fab!(:group) do
+ group = Fabricate(:group)
+ group.add(user)
+ group
+ end
+
+ fab!(:category) { Fabricate(:private_category, group: group) }
+ fab!(:private_topic) { Fabricate(:topic, category: category) }
+ fab!(:public_topic) { Fabricate(:topic, first_post: Fabricate(:post)) }
+
+ fab!(:private_message) do
+ Fabricate(:private_message_topic,
+ allowed_groups: [group]
+ )
+ end
+
+ before { PresenceChannel.clear_all! }
+
+ it 'handles invalid topic IDs' do
+ expect do
+ PresenceChannel.new('/discourse-presence/reply/-999').config
+ end.to raise_error(PresenceChannel::NotFound)
+
+ expect do
+ PresenceChannel.new('/discourse-presence/reply/blah').config
+ end.to raise_error(PresenceChannel::NotFound)
+ end
+
+ it 'handles deleted topics' do
+ public_topic.trash!
+
+ expect do
+ PresenceChannel.new("/discourse-presence/reply/#{public_topic.id}").config
+ end.to raise_error(PresenceChannel::NotFound)
+
+ expect do
+ PresenceChannel.new("/discourse-presence/whisper/#{public_topic.id}").config
+ end.to raise_error(PresenceChannel::NotFound)
+
+ expect do
+ PresenceChannel.new("/discourse-presence/edit/#{public_topic.first_post.id}").config
+ end.to raise_error(PresenceChannel::NotFound)
+ end
+
+ it 'handles secure category permissions for reply' do
+ c = PresenceChannel.new("/discourse-presence/reply/#{private_topic.id}")
+ expect(c.can_view?(user_id: user.id)).to eq(true)
+ expect(c.can_enter?(user_id: user.id)).to eq(true)
+
+ group.remove(user)
+
+ c = PresenceChannel.new("/discourse-presence/reply/#{private_topic.id}", use_cache: false)
+ expect(c.can_view?(user_id: user.id)).to eq(false)
+ expect(c.can_enter?(user_id: user.id)).to eq(false)
+ end
+
+ it 'handles secure category permissions for edit' do
+ p = Fabricate(:post, topic: private_topic, user: private_topic.user)
+ c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
+ expect(c.can_view?(user_id: user.id)).to eq(false)
+ expect(c.can_view?(user_id: private_topic.user.id)).to eq(true)
+ end
+
+ it 'handles category moderators for edit' do
+ SiteSetting.trusted_users_can_edit_others = false
+ p = Fabricate(:post, topic: private_topic, user: private_topic.user)
+
+ c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
+ expect(c.config.allowed_group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
+
+ SiteSetting.enable_category_group_moderation = true
+ category.update(reviewable_by_group_id: group.id)
+
+ c = PresenceChannel.new("/discourse-presence/edit/#{p.id}", use_cache: false)
+ expect(c.config.allowed_group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff], group.id)
+ end
+
+ it 'handles permissions for a public topic' do
+ c = PresenceChannel.new("/discourse-presence/reply/#{public_topic.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(::Group::AUTO_GROUPS[:trust_level_0])
+ end
+
+ it 'handles permissions for secure category topics' do
+ c = PresenceChannel.new("/discourse-presence/reply/#{private_topic.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(group.id, Group::AUTO_GROUPS[:admins])
+ expect(c.config.allowed_user_ids).to eq(nil)
+ end
+
+ it 'handles permissions for private messsages' do
+ c = PresenceChannel.new("/discourse-presence/reply/#{private_message.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(group.id, Group::AUTO_GROUPS[:staff])
+ expect(c.config.allowed_user_ids).to contain_exactly(
+ *private_message.topic_allowed_users.pluck(:user_id)
+ )
+ end
+
+ it "handles permissions for whispers" do
+ c = PresenceChannel.new("/discourse-presence/whisper/#{public_topic.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
+ expect(c.config.allowed_user_ids).to eq(nil)
+ end
+
+ it 'only allows staff when editing whispers' do
+ p = Fabricate(:whisper, topic: public_topic, user: admin)
+ c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
+ expect(c.config.allowed_user_ids).to eq(nil)
+ end
+
+ it 'only allows staff when editing a locked post' do
+ p = Fabricate(:post, topic: public_topic, user: admin, locked_by_id: Discourse.system_user.id)
+ c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
+ expect(c.config.allowed_user_ids).to eq(nil)
+ end
+
+ it "allows author, staff, TL4 when editing a public post" do
+ p = Fabricate(:post, topic: public_topic, user: user)
+ c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(
+ Group::AUTO_GROUPS[:trust_level_4],
+ Group::AUTO_GROUPS[:staff]
+ )
+ expect(c.config.allowed_user_ids).to contain_exactly(user.id)
+ end
+
+ it "allows only author and staff when editing a public post with tl4 editing disabled" do
+ SiteSetting.trusted_users_can_edit_others = false
+
+ p = Fabricate(:post, topic: public_topic, user: user)
+ c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(
+ Group::AUTO_GROUPS[:staff]
+ )
+ expect(c.config.allowed_user_ids).to contain_exactly(user.id)
+ end
+
+ it "follows the wiki edit trust level site setting" do
+ p = Fabricate(:post, topic: public_topic, user: user, wiki: true)
+ SiteSetting.min_trust_to_edit_wiki_post = TrustLevel.levels[:basic]
+ SiteSetting.trusted_users_can_edit_others = false
+
+ c = PresenceChannel.new("/discourse-presence/edit/#{p.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(
+ Group::AUTO_GROUPS[:staff],
+ Group::AUTO_GROUPS[:trust_level_1]
+ )
+ expect(c.config.allowed_user_ids).to contain_exactly(user.id)
+ end
+
+ it "allows author and staff when editing a private message" do
+ post = Fabricate(:post, topic: private_message, user: user)
+
+ c = PresenceChannel.new("/discourse-presence/edit/#{post.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(
+ Group::AUTO_GROUPS[:staff]
+ )
+ expect(c.config.allowed_user_ids).to contain_exactly(user.id)
+ end
+
+ it "includes all message participants for PM wiki" do
+ post = Fabricate(:post, topic: private_message, user: user, wiki: true)
+
+ c = PresenceChannel.new("/discourse-presence/edit/#{post.id}")
+ expect(c.config.public).to eq(false)
+ expect(c.config.allowed_group_ids).to contain_exactly(
+ Group::AUTO_GROUPS[:staff],
+ *private_message.allowed_groups.pluck(:id)
+ )
+ expect(c.config.allowed_user_ids).to contain_exactly(user.id, *private_message.allowed_users.pluck(:id))
+ end
+ end
+end
diff --git a/plugins/discourse-presence/spec/requests/presence_controller_spec.rb b/plugins/discourse-presence/spec/requests/presence_controller_spec.rb
deleted file mode 100644
index adededeeb3..0000000000
--- a/plugins/discourse-presence/spec/requests/presence_controller_spec.rb
+++ /dev/null
@@ -1,472 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe ::Presence::PresencesController do
- describe '#handle_message' do
- context 'when not logged in' do
- it 'should raise the right error' do
- post '/presence-plugin/publish.json'
-
- expect(response.status).to eq(403)
- end
- end
-
- context 'when logged in' do
- fab!(:user) { Fabricate(:user) }
- fab!(:user2) { Fabricate(:user) }
- fab!(:admin) { Fabricate(:admin) }
-
- fab!(:group) do
- group = Fabricate(:group)
- group.add(user)
- group
- end
-
- fab!(:category) { Fabricate(:private_category, group: group) }
- fab!(:private_topic) { Fabricate(:topic, category: category) }
- fab!(:public_topic) { Fabricate(:topic, first_post: Fabricate(:post)) }
-
- fab!(:private_message) do
- Fabricate(:private_message_topic,
- allowed_groups: [group]
- )
- end
-
- before do
- sign_in(user)
- end
-
- it 'returns the right response when user disables the presence feature' do
- user.user_option.update_column(:hide_profile_and_presence, true)
-
- post '/presence-plugin/publish.json'
-
- expect(response.status).to eq(404)
- end
-
- it 'returns the right response when user disables the presence feature and allow_users_to_hide_profile is disabled' do
- user.user_option.update_column(:hide_profile_and_presence, true)
- SiteSetting.allow_users_to_hide_profile = false
-
- post '/presence-plugin/publish.json', params: { topic_id: public_topic.id, state: 'replying' }
-
- expect(response.status).to eq(200)
- end
-
- it 'returns the right response when the presence site settings is disabled' do
- SiteSetting.presence_enabled = false
-
- post '/presence-plugin/publish.json'
-
- expect(response.status).to eq(404)
- end
-
- it 'returns the right response if required params are missing' do
- post '/presence-plugin/publish.json'
-
- expect(response.status).to eq(400)
- end
-
- it 'returns the right response if topic_id is invalid' do
- post '/presence-plugin/publish.json', params: { topic_id: -999, state: 'replying' }
-
- expect(response.status).to eq(400)
- end
-
- it 'returns the right response when user does not have access to the topic' do
- group.remove(user)
-
- post '/presence-plugin/publish.json', params: { topic_id: private_topic.id, state: 'replying' }
-
- expect(response.status).to eq(403)
- end
-
- it 'returns the right response when an invalid state is provided with a post_id' do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- post_id: public_topic.first_post.id,
- state: 'some state'
- }
-
- expect(response.status).to eq(400)
- end
-
- it 'returns the right response when user can not edit a post' do
- Fabricate(:post, topic: private_topic, user: private_topic.user)
-
- post '/presence-plugin/publish.json', params: {
- topic_id: private_topic.id,
- post_id: private_topic.first_post.id,
- state: 'editing'
- }
-
- expect(response.status).to eq(403)
- end
-
- it 'returns the right response when an invalid post_id is given' do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- post_id: -9,
- state: 'editing'
- }
-
- expect(response.status).to eq(400)
- end
-
- it 'publishes the right message for a public topic' do
- freeze_time
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: { topic_id: public_topic.id, state: 'replying' }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.channel).to eq("/presence-plugin/#{public_topic.id}")
- expect(message.data.dig(:user, :id)).to eq(user.id)
- expect(message.data[:published_at]).to eq(Time.zone.now.to_i)
- expect(message.group_ids).to eq(nil)
- expect(message.user_ids).to eq(nil)
- end
-
- it 'publishes the right message for a restricted topic' do
- freeze_time
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: private_topic.id,
- state: 'replying'
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.channel).to eq("/presence-plugin/#{private_topic.id}")
- expect(message.data.dig(:user, :id)).to eq(user.id)
- expect(message.data[:published_at]).to eq(Time.zone.now.to_i)
- expect(message.group_ids).to contain_exactly(group.id)
- expect(message.user_ids).to eq(nil)
- end
-
- it 'publishes the right message for a private message' do
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: private_message.id,
- state: 'replying'
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(
- group.id,
- Group::AUTO_GROUPS[:staff]
- )
-
- expect(message.user_ids).to contain_exactly(
- *private_message.topic_allowed_users.pluck(:user_id)
- )
- end
-
- it 'publishes the message to staff group when user is whispering' do
- SiteSetting.enable_whispers = true
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- state: 'replying',
- is_whisper: true
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
- expect(message.user_ids).to eq(nil)
- end
-
- it 'publishes the message to staff group when staff_only param override is present' do
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- state: 'replying',
- staff_only: true
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
- expect(message.user_ids).to eq(nil)
- end
-
- it 'publishes the message to staff group when a staff is editing a whisper' do
- SiteSetting.enable_whispers = true
- sign_in(admin)
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- post_id: public_topic.first_post.id,
- state: 'editing',
- is_whisper: true
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
- expect(message.user_ids).to eq(nil)
- end
-
- it 'publishes the message to staff group when a staff is editing a locked post' do
- SiteSetting.enable_whispers = true
- sign_in(admin)
- locked_post = Fabricate(:post, topic: public_topic, locked_by_id: admin.id)
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- post_id: locked_post.id,
- state: 'editing',
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
- expect(message.user_ids).to eq(nil)
- end
-
- it 'publishes the message to author, staff group and TL4 group when editing a public post' do
- post = Fabricate(:post, topic: public_topic, user: user)
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- post_id: post.id,
- state: 'editing',
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(
- Group::AUTO_GROUPS[:trust_level_4],
- Group::AUTO_GROUPS[:staff]
- )
-
- expect(message.user_ids).to contain_exactly(user.id)
- end
-
- it 'publishes the message to author and staff group when editing a public post ' \
- 'if SiteSettings.trusted_users_can_edit_others is set to false' do
-
- post = Fabricate(:post, topic: public_topic, user: user)
- SiteSetting.trusted_users_can_edit_others = false
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- post_id: post.id,
- state: 'editing',
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
- expect(message.user_ids).to contain_exactly(user.id)
- end
-
- it 'publishes the message to SiteSetting.min_trust_to_edit_wiki_post group ' \
- 'and staff group when editing a wiki in a public topic' do
-
- post = Fabricate(:post, topic: public_topic, user: user, wiki: true)
- SiteSetting.min_trust_to_edit_wiki_post = TrustLevel.levels[:basic]
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- post_id: post.id,
- state: 'editing',
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(
- Group::AUTO_GROUPS[:trust_level_1],
- Group::AUTO_GROUPS[:staff]
- )
-
- expect(message.user_ids).to contain_exactly(user.id)
- end
-
- it 'publishes the message to author and staff group when editing a private message' do
- post = Fabricate(:post, topic: private_message, user: user)
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: private_message.id,
- post_id: post.id,
- state: 'editing',
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(
- Group::AUTO_GROUPS[:staff],
- )
-
- expect(message.user_ids).to contain_exactly(user.id)
- end
-
- it 'publishes the message to users with trust levels of SiteSetting.min_trust_to_edit_wiki_post ' \
- 'and staff group when editing a wiki in a private message' do
-
- post = Fabricate(:post,
- topic: private_message,
- user: private_message.user,
- wiki: true
- )
-
- user2.update!(trust_level: TrustLevel.levels[:newuser])
- group.add(user2)
-
- SiteSetting.min_trust_to_edit_wiki_post = TrustLevel.levels[:basic]
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: private_message.id,
- post_id: post.id,
- state: 'editing',
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(
- Group::AUTO_GROUPS[:staff],
- group.id
- )
-
- expect(message.user_ids).to contain_exactly(
- *private_message.allowed_users.pluck(:id)
- )
- end
-
- it 'publishes the right message when closing composer in public topic' do
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: public_topic.id,
- state: described_class::CLOSED_STATE,
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to eq(nil)
- expect(message.user_ids).to eq(nil)
- end
-
- it 'publishes the right message when closing composer in private topic' do
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: private_topic.id,
- state: described_class::CLOSED_STATE,
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(group.id)
- expect(message.user_ids).to eq(nil)
- end
-
- it 'publishes the right message when closing composer in private message' do
- post = Fabricate(:post, topic: private_message, user: user)
-
- messages = MessageBus.track_publish do
- post '/presence-plugin/publish.json', params: {
- topic_id: private_message.id,
- state: described_class::CLOSED_STATE,
- }
-
- expect(response.status).to eq(200)
- end
-
- expect(messages.length).to eq(1)
-
- message = messages.first
-
- expect(message.group_ids).to contain_exactly(
- Group::AUTO_GROUPS[:staff],
- group.id
- )
-
- expect(message.user_ids).to contain_exactly(
- *private_message.allowed_users.pluck(:id)
- )
- end
- end
- end
-end
diff --git a/plugins/discourse-presence/test/javascripts/acceptance/discourse-presence-test.js b/plugins/discourse-presence/test/javascripts/acceptance/discourse-presence-test.js
new file mode 100644
index 0000000000..3500f13a21
--- /dev/null
+++ b/plugins/discourse-presence/test/javascripts/acceptance/discourse-presence-test.js
@@ -0,0 +1,232 @@
+import {
+ acceptance,
+ count,
+ exists,
+ queryAll,
+} from "discourse/tests/helpers/qunit-helpers";
+import { click, currentURL, fillIn, visit } from "@ember/test-helpers";
+import { test } from "qunit";
+import {
+ joinChannel,
+ leaveChannel,
+ presentUserIds,
+} from "discourse/tests/helpers/presence-pretender";
+import User from "discourse/models/user";
+import selectKit from "discourse/tests/helpers/select-kit-helper";
+
+acceptance("Discourse Presence Plugin", function (needs) {
+ needs.user();
+ needs.settings({ enable_whispers: true });
+
+ test("Doesn't break topic creation", async function (assert) {
+ await visit("/");
+ await click("#create-topic");
+ await fillIn("#reply-title", "Internationalization Localization");
+ await fillIn(
+ ".d-editor-input",
+ "this is the *content* of a new topic post"
+ );
+ await click("#reply-control button.create");
+
+ assert.strictEqual(
+ currentURL(),
+ "/t/internationalization-localization/280",
+ "it transitions to the newly created topic URL"
+ );
+ });
+
+ test("Publishes own reply presence", async function (assert) {
+ await visit("/t/internationalization-localization/280");
+
+ await click("#topic-footer-buttons .btn.create");
+ assert.ok(exists(".d-editor-input"), "the composer input is visible");
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/reply/280"),
+ [],
+ "does not publish presence for open composer"
+ );
+
+ await fillIn(".d-editor-input", "this is the content of my reply");
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/reply/280"),
+ [User.current().id],
+ "publishes presence when typing"
+ );
+
+ await click("#reply-control button.create");
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/reply/280"),
+ [],
+ "leaves channel when composer closes"
+ );
+ });
+
+ test("Uses whisper channel for whispers", async function (assert) {
+ await visit("/t/internationalization-localization/280");
+
+ await click("#topic-footer-buttons .btn.create");
+ assert.ok(exists(".d-editor-input"), "the composer input is visible");
+
+ await fillIn(".d-editor-input", "this is the content of my reply");
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/reply/280"),
+ [User.current().id],
+ "publishes reply presence when typing"
+ );
+
+ const menu = selectKit(".toolbar-popup-menu-options");
+ await menu.expand();
+ await menu.selectRowByValue("toggleWhisper");
+
+ assert.strictEqual(
+ count(".composer-actions svg.d-icon-far-eye-slash"),
+ 1,
+ "it sets the post type to whisper"
+ );
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/reply/280"),
+ [],
+ "removes reply presence"
+ );
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/whisper/280"),
+ [User.current().id],
+ "adds whisper presence"
+ );
+
+ await click("#reply-control button.create");
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/whisper/280"),
+ [],
+ "leaves whisper channel when composer closes"
+ );
+ });
+
+ test("Uses the edit channel for editing", async function (assert) {
+ await visit("/t/internationalization-localization/280");
+
+ await click(".topic-post:nth-of-type(1) button.show-more-actions");
+ await click(".topic-post:nth-of-type(1) button.edit");
+
+ assert.strictEqual(
+ queryAll(".d-editor-input").val(),
+ queryAll(".topic-post:nth-of-type(1) .cooked > p").text(),
+ "composer has contents of post to be edited"
+ );
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/edit/398"),
+ [],
+ "is not present when composer first opened"
+ );
+
+ await fillIn(".d-editor-input", "some edited content");
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/edit/398"),
+ [User.current().id],
+ "becomes present in the edit channel"
+ );
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/reply/280"),
+ [],
+ "is not made present in the reply channel"
+ );
+
+ assert.deepEqual(
+ presentUserIds("/discourse-presence/whisper/280"),
+ [],
+ "is not made present in the whisper channel"
+ );
+ });
+
+ test("Displays replying and whispering presence at bottom of topic", async function (assert) {
+ await visit("/t/internationalization-localization/280");
+
+ const avatarSelector =
+ ".topic-above-footer-buttons-outlet.presence .presence-avatars .avatar";
+ assert.ok(
+ exists(".topic-above-footer-buttons-outlet.presence"),
+ "includes the presence component"
+ );
+ assert.strictEqual(count(avatarSelector), 0, "no avatars displayed");
+
+ await joinChannel("/discourse-presence/reply/280", {
+ id: 123,
+ avatar_template: "/a/b/c.jpg",
+ username: "myusername",
+ });
+
+ assert.strictEqual(count(avatarSelector), 1, "avatar displayed");
+
+ await joinChannel("/discourse-presence/whisper/280", {
+ id: 124,
+ avatar_template: "/a/b/c.jpg",
+ username: "myusername2",
+ });
+
+ assert.strictEqual(count(avatarSelector), 2, "whisper avatar displayed");
+
+ await leaveChannel("/discourse-presence/reply/280", {
+ id: 123,
+ });
+
+ assert.strictEqual(count(avatarSelector), 1, "reply avatar removed");
+
+ await leaveChannel("/discourse-presence/whisper/280", {
+ id: 124,
+ });
+
+ assert.strictEqual(count(avatarSelector), 0, "whisper avatar removed");
+ });
+
+ test("Displays replying and whispering presence in composer", async function (assert) {
+ await visit("/t/internationalization-localization/280");
+ await click("#topic-footer-buttons .btn.create");
+ assert.ok(exists(".d-editor-input"), "the composer input is visible");
+
+ const avatarSelector =
+ ".composer-fields-outlet.presence .presence-avatars .avatar";
+ assert.ok(
+ exists(".composer-fields-outlet.presence"),
+ "includes the presence component"
+ );
+ assert.strictEqual(count(avatarSelector), 0, "no avatars displayed");
+
+ await joinChannel("/discourse-presence/reply/280", {
+ id: 123,
+ avatar_template: "/a/b/c.jpg",
+ username: "myusername",
+ });
+
+ assert.strictEqual(count(avatarSelector), 1, "avatar displayed");
+
+ await joinChannel("/discourse-presence/whisper/280", {
+ id: 124,
+ avatar_template: "/a/b/c.jpg",
+ username: "myusername2",
+ });
+
+ assert.strictEqual(count(avatarSelector), 2, "whisper avatar displayed");
+
+ await leaveChannel("/discourse-presence/reply/280", {
+ id: 123,
+ });
+
+ assert.strictEqual(count(avatarSelector), 1, "reply avatar removed");
+
+ await leaveChannel("/discourse-presence/whisper/280", {
+ id: 124,
+ });
+
+ assert.strictEqual(count(avatarSelector), 0, "whisper avatar removed");
+ });
+});
diff --git a/plugins/lazy-yt/assets/javascripts/lib/lazyYT.js b/plugins/lazy-yt/assets/javascripts/lib/lazyYT.js
index b6f3ddf669..643bfc7ade 100644
--- a/plugins/lazy-yt/assets/javascripts/lib/lazyYT.js
+++ b/plugins/lazy-yt/assets/javascripts/lib/lazyYT.js
@@ -162,10 +162,10 @@ export default function initLazyYt($) {
$.fn.lazyYT = function (newSettings) {
let defaultSettings = {
default_ratio: "16:9",
- callback: null, // ToDO execute callback if given
+ callback: null, // TODO: execute callback if given
container_class: "lazyYT-container",
};
- let settings = $.extend(defaultSettings, newSettings);
+ let settings = Object.assign(defaultSettings, newSettings);
return this.each(function () {
let $el = $(this).addClass(settings.container_class);
diff --git a/plugins/poll/assets/javascripts/components/poll-breakdown-chart.js.es6 b/plugins/poll/assets/javascripts/components/poll-breakdown-chart.js
similarity index 100%
rename from plugins/poll/assets/javascripts/components/poll-breakdown-chart.js.es6
rename to plugins/poll/assets/javascripts/components/poll-breakdown-chart.js
diff --git a/plugins/poll/assets/javascripts/components/poll-breakdown-option.js.es6 b/plugins/poll/assets/javascripts/components/poll-breakdown-option.js
similarity index 100%
rename from plugins/poll/assets/javascripts/components/poll-breakdown-option.js.es6
rename to plugins/poll/assets/javascripts/components/poll-breakdown-option.js
diff --git a/plugins/poll/assets/javascripts/controllers/poll-breakdown.js.es6 b/plugins/poll/assets/javascripts/controllers/poll-breakdown.js
similarity index 98%
rename from plugins/poll/assets/javascripts/controllers/poll-breakdown.js.es6
rename to plugins/poll/assets/javascripts/controllers/poll-breakdown.js
index 794efa0de3..77d1c1e7a6 100644
--- a/plugins/poll/assets/javascripts/controllers/poll-breakdown.js.es6
+++ b/plugins/poll/assets/javascripts/controllers/poll-breakdown.js
@@ -8,6 +8,7 @@ import discourseComputed from "discourse-common/utils/decorators";
import { htmlSafe } from "@ember/template";
import loadScript from "discourse/lib/load-script";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import bootbox from "bootbox";
export default Controller.extend(ModalFunctionality, {
model: null,
diff --git a/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6 b/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js
similarity index 100%
rename from plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6
rename to plugins/poll/assets/javascripts/controllers/poll-ui-builder.js
diff --git a/plugins/poll/assets/javascripts/initializers/add-poll-ui-builder.js.es6 b/plugins/poll/assets/javascripts/initializers/add-poll-ui-builder.js
similarity index 100%
rename from plugins/poll/assets/javascripts/initializers/add-poll-ui-builder.js.es6
rename to plugins/poll/assets/javascripts/initializers/add-poll-ui-builder.js
diff --git a/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 b/plugins/poll/assets/javascripts/initializers/extend-for-poll.js
similarity index 100%
rename from plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6
rename to plugins/poll/assets/javascripts/initializers/extend-for-poll.js
diff --git a/plugins/poll/assets/javascripts/lib/chart-colors.js.es6 b/plugins/poll/assets/javascripts/lib/chart-colors.js
similarity index 100%
rename from plugins/poll/assets/javascripts/lib/chart-colors.js.es6
rename to plugins/poll/assets/javascripts/lib/chart-colors.js
diff --git a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6 b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js
similarity index 99%
rename from plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
rename to plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js
index 061ccceaf9..543a8e67d9 100644
--- a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
+++ b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js
@@ -107,14 +107,14 @@ function getTitle(tokens, startToken) {
const rule = {
tag: "poll",
- before: function (state, tagInfo, raw) {
+ before(state, tagInfo, raw) {
let token = state.push("text", "", 0);
token.content = raw;
token.bbcode_attrs = tagInfo.attrs;
token.bbcode_type = "poll_open";
},
- after: function (state, openToken, raw) {
+ after(state, openToken, raw) {
const titleTokens = getTitle(state.tokens, openToken);
let items = getListItems(state.tokens, openToken);
diff --git a/plugins/poll/assets/javascripts/lib/even-round.js.es6 b/plugins/poll/assets/javascripts/lib/even-round.js
similarity index 100%
rename from plugins/poll/assets/javascripts/lib/even-round.js.es6
rename to plugins/poll/assets/javascripts/lib/even-round.js
diff --git a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6 b/plugins/poll/assets/javascripts/widgets/discourse-poll.js
similarity index 99%
rename from plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
rename to plugins/poll/assets/javascripts/widgets/discourse-poll.js
index 6b476dea10..3b1dfa5bd3 100644
--- a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
+++ b/plugins/poll/assets/javascripts/widgets/discourse-poll.js
@@ -13,6 +13,7 @@ import { popupAjaxError } from "discourse/lib/ajax-error";
import { relativeAge } from "discourse/lib/formatter";
import round from "discourse/lib/round";
import showModal from "discourse/lib/show-modal";
+import bootbox from "bootbox";
const FETCH_VOTERS_COUNT = 25;
@@ -567,7 +568,7 @@ function pieChartConfig(data, labels, opts = {}) {
plugins: {
legend: {
labels: {
- generateLabels: function () {
+ generateLabels() {
return labels.map((text, index) => {
return {
fillStyle: getColors(data.length)[index],
@@ -791,7 +792,7 @@ export default createWidget("discourse-poll", {
(attrs.post.get("topic.archived") && !staffOnly) ||
(this.isClosed() && !staffOnly);
- const newAttrs = jQuery.extend({}, attrs, {
+ const newAttrs = Object.assign({}, attrs, {
canCastVotes: this.canCastVotes(),
hasVoted: this.hasVoted(),
isAutomaticallyClosed: this.isAutomaticallyClosed(),
diff --git a/plugins/poll/assets/stylesheets/desktop/poll-ui-builder.scss b/plugins/poll/assets/stylesheets/desktop/poll-ui-builder.scss
index caff3a9eec..1bfac2e179 100644
--- a/plugins/poll/assets/stylesheets/desktop/poll-ui-builder.scss
+++ b/plugins/poll/assets/stylesheets/desktop/poll-ui-builder.scss
@@ -1,6 +1,7 @@
.poll-ui-builder-modal {
.modal-inner-container {
- width: 600px;
+ width: 40em; // scale with user font-size
+ max-width: 100vw; // prevent overflow if user font-size is enourmous
}
.modal-body {
diff --git a/plugins/poll/config/locales/client.uk.yml b/plugins/poll/config/locales/client.uk.yml
index 4f308dde33..20d55f2baa 100644
--- a/plugins/poll/config/locales/client.uk.yml
+++ b/plugins/poll/config/locales/client.uk.yml
@@ -53,6 +53,9 @@ uk:
show-results:
title: "Показати результати опитування"
label: "Показати результати"
+ remove-vote:
+ title: "Видалити свій голос"
+ label: "Вилучити голос"
hide-results:
title: "Назад до своїх голосів"
label: "Показати голосування"
diff --git a/plugins/poll/config/locales/server.id.yml b/plugins/poll/config/locales/server.id.yml
index ea2c870ba5..f67c0ef460 100644
--- a/plugins/poll/config/locales/server.id.yml
+++ b/plugins/poll/config/locales/server.id.yml
@@ -29,3 +29,5 @@ id:
only_staff_or_op_can_toggle_status: "Hanya anggota staf atau penerbit asli yang dapat merubah status polling."
email:
link_to_poll: "Klik untuk menampilkan polling."
+ user_field:
+ no_data: "Tidak ada Data"
diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb
index de3c8a18ec..078d40c0ec 100644
--- a/plugins/poll/plugin.rb
+++ b/plugins/poll/plugin.rb
@@ -5,6 +5,7 @@
# version: 1.0
# authors: Vikhyat Korrapati (vikhyat), Régis Hanol (zogstrip)
# url: https://github.com/discourse/discourse/tree/main/plugins/poll
+# transpile_js: true
register_asset "stylesheets/common/poll.scss"
register_asset "stylesheets/desktop/poll.scss", :desktop
diff --git a/plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js
similarity index 95%
rename from plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js.es6
rename to plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js
index b7320a1e64..750a053078 100644
--- a/plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js
@@ -6,7 +6,7 @@ import {
} from "discourse/tests/helpers/qunit-helpers";
import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
import { test } from "qunit";
-import { visit } from "@ember/test-helpers";
+import { click, visit } from "@ember/test-helpers";
acceptance("Poll breakdown", function (needs) {
needs.user();
@@ -76,7 +76,7 @@ acceptance("Poll breakdown", function (needs) {
assert.ok(exists(".poll-breakdown-total-votes"), "displays the vote count");
- assert.equal(
+ assert.strictEqual(
count(".poll-breakdown-chart-container"),
2,
"renders a chart for each of the groups in group_results response"
@@ -92,7 +92,7 @@ acceptance("Poll breakdown", function (needs) {
await visit("/t/-/topic_with_pie_chart_poll");
await click(".poll-show-breakdown");
- assert.equal(
+ assert.strictEqual(
query(".poll-breakdown-option-count").textContent.trim(),
"40.0%",
"displays the correct vote percentage"
@@ -100,7 +100,7 @@ acceptance("Poll breakdown", function (needs) {
await click(".modal-tabs .count");
- assert.equal(
+ assert.strictEqual(
query(".poll-breakdown-option-count").textContent.trim(),
"2",
"displays the correct vote count"
@@ -108,7 +108,7 @@ acceptance("Poll breakdown", function (needs) {
await click(".modal-tabs .percentage");
- assert.equal(
+ assert.strictEqual(
query(".poll-breakdown-option-count").textContent.trim(),
"40.0%",
"displays the percentage again"
diff --git a/plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js
similarity index 100%
rename from plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js.es6
rename to plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js
diff --git a/plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js
similarity index 100%
rename from plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js.es6
rename to plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js
diff --git a/plugins/poll/test/javascripts/acceptance/poll-in-reply-history-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-in-reply-history-test.js
similarity index 99%
rename from plugins/poll/test/javascripts/acceptance/poll-in-reply-history-test.js.es6
rename to plugins/poll/test/javascripts/acceptance/poll-in-reply-history-test.js
index 7399ee3aac..7e8e011e35 100644
--- a/plugins/poll/test/javascripts/acceptance/poll-in-reply-history-test.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/poll-in-reply-history-test.js
@@ -1,7 +1,7 @@
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
import { test } from "qunit";
-import { visit } from "@ember/test-helpers";
+import { click, visit } from "@ember/test-helpers";
acceptance("Poll in a post reply history", function (needs) {
needs.user();
diff --git a/plugins/poll/test/javascripts/acceptance/poll-pie-chart-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-pie-chart-test.js
similarity index 90%
rename from plugins/poll/test/javascripts/acceptance/poll-pie-chart-test.js.es6
rename to plugins/poll/test/javascripts/acceptance/poll-pie-chart-test.js
index 4d720b2009..c36042d49e 100644
--- a/plugins/poll/test/javascripts/acceptance/poll-pie-chart-test.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/poll-pie-chart-test.js
@@ -18,25 +18,25 @@ acceptance("Rendering polls with pie charts", function (needs) {
const poll = query(".poll");
- assert.equal(
+ assert.strictEqual(
query(".info-number", poll).innerHTML,
"2",
"it should display the right number of voters"
);
- assert.equal(
+ assert.strictEqual(
queryAll(".info-number", poll)[1].innerHTML,
"5",
"it should display the right number of votes"
);
- assert.equal(
+ assert.strictEqual(
poll.classList.contains("pie"),
true,
"pie class is present on poll div"
);
- assert.equal(
+ assert.strictEqual(
queryAll(".poll-results-chart", poll).length,
1,
"Renders the chart div instead of bar container"
diff --git a/plugins/poll/test/javascripts/acceptance/poll-quote-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-quote-test.js
similarity index 98%
rename from plugins/poll/test/javascripts/acceptance/poll-quote-test.js.es6
rename to plugins/poll/test/javascripts/acceptance/poll-quote-test.js
index d15664bd55..491aa00ecd 100644
--- a/plugins/poll/test/javascripts/acceptance/poll-quote-test.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/poll-quote-test.js
@@ -1,7 +1,7 @@
import { acceptance, count } from "discourse/tests/helpers/qunit-helpers";
import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
import { test } from "qunit";
-import { visit } from "@ember/test-helpers";
+import { click, visit } from "@ember/test-helpers";
acceptance("Poll quote", function (needs) {
needs.user();
@@ -432,7 +432,7 @@ acceptance("Poll quote", function (needs) {
test("renders and extends", async function (assert) {
await visit("/t/-/topic_with_two_quoted_polls");
await click(".quote-controls");
- assert.equal(count(".poll"), 2, "polls are rendered");
- assert.equal(count(".poll-buttons"), 2, "polls are extended");
+ assert.strictEqual(count(".poll"), 2, "polls are rendered");
+ assert.strictEqual(count(".poll-buttons"), 2, "polls are extended");
});
});
diff --git a/plugins/poll/test/javascripts/acceptance/poll-results-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-results-test.js
similarity index 97%
rename from plugins/poll/test/javascripts/acceptance/poll-results-test.js.es6
rename to plugins/poll/test/javascripts/acceptance/poll-results-test.js
index ef547c8460..5a7e52da2a 100644
--- a/plugins/poll/test/javascripts/acceptance/poll-results-test.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/poll-results-test.js
@@ -6,7 +6,7 @@ import {
} from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
-import { visit } from "@ember/test-helpers";
+import { click, visit } from "@ember/test-helpers";
acceptance("Poll results", function (needs) {
needs.user();
@@ -567,12 +567,12 @@ acceptance("Poll results", function (needs) {
test("can load more voters", async function (assert) {
await visit("/t/-/load-more-poll-voters");
- assert.equal(
- find(".poll-container .results li:nth-child(1) .poll-voters li").length,
+ assert.strictEqual(
+ count(".poll-container .results li:nth-child(1) .poll-voters li"),
1
);
- assert.equal(
- find(".poll-container .results li:nth-child(2) .poll-voters li").length,
+ assert.strictEqual(
+ count(".poll-container .results li:nth-child(2) .poll-voters li"),
0
);
@@ -626,24 +626,24 @@ acceptance("Poll results", function (needs) {
});
await visit("/t/-/load-more-poll-voters");
- assert.equal(
- find(".poll-container .results li:nth-child(1) .poll-voters li").length,
+ assert.strictEqual(
+ count(".poll-container .results li:nth-child(1) .poll-voters li"),
1
);
- assert.equal(
- find(".poll-container .results li:nth-child(2) .poll-voters li").length,
+ assert.strictEqual(
+ count(".poll-container .results li:nth-child(2) .poll-voters li"),
1
);
await click(".poll-voters-toggle-expand a");
await visit("/t/-/load-more-poll-voters");
- assert.equal(
- find(".poll-container .results li:nth-child(1) .poll-voters li").length,
+ assert.strictEqual(
+ count(".poll-container .results li:nth-child(1) .poll-voters li"),
2
);
- assert.equal(
- find(".poll-container .results li:nth-child(2) .poll-voters li").length,
+ assert.strictEqual(
+ count(".poll-container .results li:nth-child(2) .poll-voters li"),
0
);
});
@@ -652,13 +652,13 @@ acceptance("Poll results", function (needs) {
await visit("/t/-/load-more-poll-voters");
await click(".toggle-results");
- assert.equal(count(".poll-container .d-icon-circle"), 1);
- assert.equal(count(".poll-container .d-icon-far-circle"), 1);
+ assert.strictEqual(count(".poll-container .d-icon-circle"), 1);
+ assert.strictEqual(count(".poll-container .d-icon-far-circle"), 1);
await click(".remove-vote");
- assert.equal(count(".poll-container .d-icon-circle"), 0);
- assert.equal(count(".poll-container .d-icon-far-circle"), 2);
+ assert.strictEqual(count(".poll-container .d-icon-circle"), 0);
+ assert.strictEqual(count(".poll-container .d-icon-far-circle"), 2);
});
});
diff --git a/plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-desktop.js.es6 b/plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-desktop.js
similarity index 86%
rename from plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-desktop.js.es6
rename to plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-desktop.js
index 12147b270e..36363f9acf 100644
--- a/plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-desktop.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-desktop.js
@@ -1,7 +1,7 @@
import { acceptance, queryAll } from "discourse/tests/helpers/qunit-helpers";
import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
import { test } from "qunit";
-import { visit } from "@ember/test-helpers";
+import { click, visit } from "@ember/test-helpers";
acceptance("Rendering polls with bar charts - desktop", function (needs) {
needs.user();
@@ -46,15 +46,15 @@ acceptance("Rendering polls with bar charts - desktop", function (needs) {
const polls = queryAll(".poll");
- assert.equal(polls.length, 2, "it should render the polls correctly");
+ assert.strictEqual(polls.length, 2, "it should render the polls correctly");
- assert.equal(
+ assert.strictEqual(
queryAll(".info-number", polls[0]).text(),
"2",
"it should display the right number of votes"
);
- assert.equal(
+ assert.strictEqual(
queryAll(".info-number", polls[1]).text(),
"3",
"it should display the right number of votes"
@@ -65,11 +65,11 @@ acceptance("Rendering polls with bar charts - desktop", function (needs) {
await visit("/t/-/14");
const polls = queryAll(".poll");
- assert.equal(polls.length, 1, "it should render the poll correctly");
+ assert.strictEqual(polls.length, 1, "it should render the poll correctly");
await click("button.toggle-results");
- assert.equal(
+ assert.strictEqual(
queryAll(".poll-voters:nth-of-type(1) li").length,
25,
"it should display the right number of voters"
@@ -77,7 +77,7 @@ acceptance("Rendering polls with bar charts - desktop", function (needs) {
await click(".poll-voters-toggle-expand:nth-of-type(1) a");
- assert.equal(
+ assert.strictEqual(
queryAll(".poll-voters:nth-of-type(1) li").length,
26,
"it should display the right number of voters"
@@ -88,11 +88,11 @@ acceptance("Rendering polls with bar charts - desktop", function (needs) {
await visit("/t/-/13");
const polls = queryAll(".poll");
- assert.equal(polls.length, 1, "it should render the poll correctly");
+ assert.strictEqual(polls.length, 1, "it should render the poll correctly");
await click("button.toggle-results");
- assert.equal(
+ assert.strictEqual(
queryAll(".poll-voters:nth-of-type(1) li").length,
25,
"it should display the right number of voters"
@@ -105,7 +105,7 @@ acceptance("Rendering polls with bar charts - desktop", function (needs) {
await click(".poll-voters-toggle-expand:nth-of-type(1) a");
- assert.equal(
+ assert.strictEqual(
queryAll(".poll-voters:nth-of-type(1) li").length,
30,
"it should display the right number of voters"
diff --git a/plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-mobile.js.es6 b/plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-mobile.js
similarity index 88%
rename from plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-mobile.js.es6
rename to plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-mobile.js
index 654241d26c..44c74c2685 100644
--- a/plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-mobile.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/polls-bar-chart-test-mobile.js
@@ -1,7 +1,7 @@
import { acceptance, queryAll } from "discourse/tests/helpers/qunit-helpers";
import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
import { test } from "qunit";
-import { visit } from "@ember/test-helpers";
+import { click, visit } from "@ember/test-helpers";
acceptance("Rendering polls with bar charts - mobile", function (needs) {
needs.user();
@@ -27,11 +27,11 @@ acceptance("Rendering polls with bar charts - mobile", function (needs) {
await visit("/t/-/13");
const polls = queryAll(".poll");
- assert.equal(polls.length, 1, "it should render the poll correctly");
+ assert.strictEqual(polls.length, 1, "it should render the poll correctly");
await click("button.toggle-results");
- assert.equal(
+ assert.strictEqual(
queryAll(".poll-voters:nth-of-type(1) li").length,
25,
"it should display the right number of voters"
@@ -44,7 +44,7 @@ acceptance("Rendering polls with bar charts - mobile", function (needs) {
await click(".poll-voters-toggle-expand:nth-of-type(1) a");
- assert.equal(
+ assert.strictEqual(
queryAll(".poll-voters:nth-of-type(1) li").length,
35,
"it should display the right number of voters"
diff --git a/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6 b/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js
similarity index 81%
rename from plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6
rename to plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js
index 6f4a420f5c..2993cb42b9 100644
--- a/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6
+++ b/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js
@@ -23,14 +23,14 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
pollOptions: [{ value: "a" }],
});
- assert.equal(controller.isMultiple, true, "it should be true");
+ assert.strictEqual(controller.isMultiple, true, "it should be true");
controller.setProperties({
pollType: "random",
pollOptions: [{ value: "b" }],
});
- assert.equal(controller.isMultiple, false, "it should be false");
+ assert.strictEqual(controller.isMultiple, false, "it should be false");
});
test("isNumber", function (assert) {
@@ -38,11 +38,11 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
controller.set("pollType", REGULAR_POLL_TYPE);
- assert.equal(controller.isNumber, false, "it should be false");
+ assert.strictEqual(controller.isNumber, false, "it should be false");
controller.set("pollType", NUMBER_POLL_TYPE);
- assert.equal(controller.isNumber, true, "it should be true");
+ assert.strictEqual(controller.isNumber, true, "it should be true");
});
test("pollOptionsCount", function (assert) {
@@ -50,47 +50,47 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
controller.set("pollOptions", [{ value: "1" }, { value: "2" }]);
- assert.equal(controller.pollOptionsCount, 2, "it should equal 2");
+ assert.strictEqual(controller.pollOptionsCount, 2, "it should equal 2");
controller.set("pollOptions", []);
- assert.equal(controller.pollOptionsCount, 0, "it should equal 0");
+ assert.strictEqual(controller.pollOptionsCount, 0, "it should equal 0");
});
test("disableInsert", function (assert) {
const controller = setupController(this);
controller.siteSettings.poll_maximum_options = 20;
- assert.equal(controller.disableInsert, true, "it should be true");
+ assert.strictEqual(controller.disableInsert, true, "it should be true");
controller.set("pollOptions", [{ value: "a" }, { value: "b" }]);
- assert.equal(controller.disableInsert, false, "it should be false");
+ assert.strictEqual(controller.disableInsert, false, "it should be false");
controller.set("pollType", NUMBER_POLL_TYPE);
- assert.equal(controller.disableInsert, false, "it should be false");
+ assert.strictEqual(controller.disableInsert, false, "it should be false");
controller.setProperties({
pollType: REGULAR_POLL_TYPE,
pollOptions: [{ value: "a" }, { value: "b" }, { value: "c" }],
});
- assert.equal(controller.disableInsert, false, "it should be false");
+ assert.strictEqual(controller.disableInsert, false, "it should be false");
controller.setProperties({
pollType: REGULAR_POLL_TYPE,
pollOptions: [],
});
- assert.equal(controller.disableInsert, true, "it should be true");
+ assert.strictEqual(controller.disableInsert, true, "it should be true");
controller.setProperties({
pollType: REGULAR_POLL_TYPE,
pollOptions: [{ value: "w" }],
});
- assert.equal(controller.disableInsert, false, "it should be false");
+ assert.strictEqual(controller.disableInsert, false, "it should be false");
});
test("number pollOutput", async function (assert) {
@@ -103,7 +103,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
});
await settled();
- assert.equal(
+ assert.strictEqual(
controller.pollOutput,
"[poll type=number results=always min=1 max=20 step=1]\n[/poll]\n",
"it should return the right output"
@@ -111,7 +111,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
controller.set("pollStep", 2);
await settled();
- assert.equal(
+ assert.strictEqual(
controller.pollOutput,
"[poll type=number results=always min=1 max=20 step=2]\n[/poll]\n",
"it should return the right output"
@@ -119,7 +119,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
controller.set("publicPoll", true);
- assert.equal(
+ assert.strictEqual(
controller.pollOutput,
"[poll type=number results=always min=1 max=20 step=2 public=true]\n[/poll]\n",
"it should return the right output"
@@ -127,7 +127,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
controller.set("pollStep", 0);
- assert.equal(
+ assert.strictEqual(
controller.pollOutput,
"[poll type=number results=always min=1 max=20 step=1 public=true]\n[/poll]\n",
"it should return the right output"
@@ -143,7 +143,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
pollType: REGULAR_POLL_TYPE,
});
- assert.equal(
+ assert.strictEqual(
controller.pollOutput,
"[poll type=regular results=always chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
@@ -151,7 +151,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
controller.set("publicPoll", "true");
- assert.equal(
+ assert.strictEqual(
controller.pollOutput,
"[poll type=regular results=always public=true chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
@@ -159,7 +159,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
controller.set("pollGroups", "test");
- assert.equal(
+ assert.strictEqual(
controller.get("pollOutput"),
"[poll type=regular results=always public=true chartType=bar groups=test]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
@@ -176,7 +176,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
pollOptions: [{ value: "1" }, { value: "2" }],
});
- assert.equal(
+ assert.strictEqual(
controller.pollOutput,
"[poll type=multiple results=always min=1 max=2 chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
@@ -184,7 +184,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
controller.set("publicPoll", "true");
- assert.equal(
+ assert.strictEqual(
controller.pollOutput,
"[poll type=multiple results=always min=1 max=2 public=true chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
@@ -204,7 +204,7 @@ discourseModule("Unit | Controller | poll-ui-builder", function () {
test("poll result is always by default", function (assert) {
const controller = setupController(this);
- assert.equal(controller.pollResult, "always");
+ assert.strictEqual(controller.pollResult, "always");
});
test("staff_only option is present for staff", async function (assert) {
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
similarity index 100%
rename from plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6
rename to plugins/poll/test/javascripts/helpers/display-poll-builder-button.js
diff --git a/plugins/poll/test/javascripts/widgets/discourse-poll-option-test.js.es6 b/plugins/poll/test/javascripts/widgets/discourse-poll-option-test.js
similarity index 100%
rename from plugins/poll/test/javascripts/widgets/discourse-poll-option-test.js.es6
rename to plugins/poll/test/javascripts/widgets/discourse-poll-option-test.js
diff --git a/plugins/poll/test/javascripts/widgets/discourse-poll-standard-results-test.js.es6 b/plugins/poll/test/javascripts/widgets/discourse-poll-standard-results-test.js
similarity index 74%
rename from plugins/poll/test/javascripts/widgets/discourse-poll-standard-results-test.js.es6
rename to plugins/poll/test/javascripts/widgets/discourse-poll-standard-results-test.js
index b7d219b88c..59dff66e23 100644
--- a/plugins/poll/test/javascripts/widgets/discourse-poll-standard-results-test.js.es6
+++ b/plugins/poll/test/javascripts/widgets/discourse-poll-standard-results-test.js
@@ -31,8 +31,8 @@ discourseModule(
},
test(assert) {
- assert.equal(queryAll(".option .percentage")[0].innerText, "56%");
- assert.equal(queryAll(".option .percentage")[1].innerText, "44%");
+ assert.strictEqual(queryAll(".option .percentage")[0].innerText, "56%");
+ assert.strictEqual(queryAll(".option .percentage")[1].innerText, "44%");
},
});
@@ -50,8 +50,8 @@ discourseModule(
},
test(assert) {
- assert.equal(queryAll(".option .percentage")[0].innerText, "56%");
- assert.equal(queryAll(".option .percentage")[1].innerText, "44%");
+ assert.strictEqual(queryAll(".option .percentage")[0].innerText, "56%");
+ assert.strictEqual(queryAll(".option .percentage")[1].innerText, "44%");
},
});
@@ -78,17 +78,17 @@ discourseModule(
test(assert) {
let percentages = queryAll(".option .percentage");
- assert.equal(percentages[0].innerText, "41%");
- assert.equal(percentages[1].innerText, "33%");
- assert.equal(percentages[2].innerText, "16%");
- assert.equal(percentages[3].innerText, "8%");
+ assert.strictEqual(percentages[0].innerText, "41%");
+ assert.strictEqual(percentages[1].innerText, "33%");
+ assert.strictEqual(percentages[2].innerText, "16%");
+ assert.strictEqual(percentages[3].innerText, "8%");
- assert.equal(
+ assert.strictEqual(
queryAll(".option")[3].querySelectorAll("span")[1].innerText,
"a"
);
- assert.equal(percentages[4].innerText, "8%");
- assert.equal(
+ assert.strictEqual(percentages[4].innerText, "8%");
+ assert.strictEqual(
queryAll(".option")[4].querySelectorAll("span")[1].innerText,
"b"
);
diff --git a/plugins/poll/test/javascripts/widgets/discourse-poll-test.js.es6 b/plugins/poll/test/javascripts/widgets/discourse-poll-test.js
similarity index 93%
rename from plugins/poll/test/javascripts/widgets/discourse-poll-test.js.es6
rename to plugins/poll/test/javascripts/widgets/discourse-poll-test.js
index 90da62b6b2..431c50de33 100644
--- a/plugins/poll/test/javascripts/widgets/discourse-poll-test.js.es6
+++ b/plugins/poll/test/javascripts/widgets/discourse-poll-test.js
@@ -11,6 +11,7 @@ import EmberObject from "@ember/object";
import I18n from "I18n";
import pretender from "discourse/tests/helpers/create-pretender";
import hbs from "htmlbars-inline-precompile";
+import { click } from "@ember/test-helpers";
let requests = 0;
@@ -123,12 +124,12 @@ discourseModule(
await click(
"li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']"
);
- assert.equal(requests, 1);
- assert.equal(count(".chosen"), 1);
- assert.equal(queryAll(".chosen").text(), "100%yes");
+ assert.strictEqual(requests, 1);
+ assert.strictEqual(count(".chosen"), 1);
+ assert.strictEqual(queryAll(".chosen").text(), "100%yes");
await click(".toggle-results");
- assert.equal(
+ assert.strictEqual(
queryAll("li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']")
.length,
1
@@ -171,11 +172,11 @@ discourseModule(
await click(
"li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']"
);
- assert.equal(
+ assert.strictEqual(
queryAll(".poll-container .alert").text(),
I18n.t("poll.results.groups.title", { groups: "foo" })
);
- assert.equal(requests, 0);
+ assert.strictEqual(requests, 0);
assert.ok(!exists(".chosen"));
},
});
diff --git a/plugins/styleguide/assets/javascripts/discourse/components/color-example.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/color-example.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/components/color-example.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/components/color-example.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-example.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-example.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/components/styleguide-example.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/components/styleguide-example.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-icons.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-icons.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/components/styleguide-icons.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/components/styleguide-icons.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-link.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-link.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/components/styleguide-link.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/components/styleguide-link.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-markdown.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-markdown.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/components/styleguide-markdown.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/components/styleguide-markdown.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-section.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-section.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/components/styleguide-section.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/components/styleguide-section.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide-show.js.es6 b/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide-show.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/controllers/styleguide-show.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/controllers/styleguide-show.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide.js.es6 b/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/controllers/styleguide.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/controllers/styleguide.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/helpers/section-title.js.es6 b/plugins/styleguide/assets/javascripts/discourse/helpers/section-title.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/helpers/section-title.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/helpers/section-title.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js.es6 b/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js
similarity index 99%
rename from plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js
index 0e01c5fc44..df2f90450e 100644
--- a/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js.es6
+++ b/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js
@@ -89,7 +89,7 @@ export function createData(store) {
topicId++;
return store.createRecord(
"topic",
- $.extend(
+ Object.assign(
{
id: topicId,
title: `Example Topic Title ${topicId}`,
diff --git a/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js.es6 b/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/routes/styleguide-show.js.es6 b/plugins/styleguide/assets/javascripts/discourse/routes/styleguide-show.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/routes/styleguide-show.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/routes/styleguide-show.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/routes/styleguide.js.es6 b/plugins/styleguide/assets/javascripts/discourse/routes/styleguide.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/routes/styleguide.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/routes/styleguide.js
diff --git a/plugins/styleguide/assets/javascripts/discourse/styleguide-route-map.js.es6 b/plugins/styleguide/assets/javascripts/discourse/styleguide-route-map.js
similarity index 100%
rename from plugins/styleguide/assets/javascripts/discourse/styleguide-route-map.js.es6
rename to plugins/styleguide/assets/javascripts/discourse/styleguide-route-map.js
diff --git a/plugins/styleguide/config/locales/client.id.yml b/plugins/styleguide/config/locales/client.id.yml
index 5190ea6536..436ae7ff79 100644
--- a/plugins/styleguide/config/locales/client.id.yml
+++ b/plugins/styleguide/config/locales/client.id.yml
@@ -7,7 +7,26 @@
id:
js:
styleguide:
+ welcome: "Untuk memulai, pilih bagian dari menu di sebelah kiri."
+ categories:
+ atoms: Atom
+ molecules: Molekul
+ organisms: Organisme
sections:
+ typography:
+ title: "Tipografi"
+ example: "Selamat datang di Discourse"
+ paragraph: "Lorem ipsum dolor duduk amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut tenaga kerja et dolore magna aliqua. Ut Enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea komodo consequat. Duis aute irure dolor di reprehenderit di voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt anim id est laborum."
+ date_time_inputs:
+ title: "Masukan Tanggal/Waktu"
+ font_scale:
+ title: "Sistem Font"
+ colors:
+ title: "Warna"
+ icons:
+ title: "Ikon"
+ buttons:
+ title: "Tombol"
categories:
title: "Kategori"
navigation:
diff --git a/plugins/styleguide/plugin.rb b/plugins/styleguide/plugin.rb
index 4c3244b1f6..f90169de17 100644
--- a/plugins/styleguide/plugin.rb
+++ b/plugins/styleguide/plugin.rb
@@ -4,6 +4,7 @@
# about: Preview how Widgets are Styled in Discourse
# version: 0.2
# author: Robin Ward
+# transpile_js: true
register_asset "stylesheets/styleguide.scss"
enabled_site_setting :styleguide_enabled
diff --git a/public/javascripts/media-optimization-worker.js b/public/javascripts/media-optimization-worker.js
index e57e362723..b3fd8f0127 100644
--- a/public/javascripts/media-optimization-worker.js
+++ b/public/javascripts/media-optimization-worker.js
@@ -2,10 +2,10 @@ function resizeWithAspect(
input_width,
input_height,
target_width,
- target_height,
+ target_height
) {
if (!target_width && !target_height) {
- throw Error('Need to specify at least width or height when resizing');
+ throw Error("Need to specify at least width or height when resizing");
}
if (target_width && target_height) {
@@ -33,9 +33,6 @@ function logIfDebug(message) {
}
async function optimize(imageData, fileName, width, height, settings) {
-
- await loadLibs(settings);
-
const mozJpegDefaultOptions = {
quality: settings.encode_quality,
baseline: false,
@@ -63,7 +60,11 @@ async function optimize(imageData, fileName, width, height, settings) {
// resize
if (width > settings.resize_threshold) {
try {
- const target_dimensions = resizeWithAspect(width, height, settings.resize_target);
+ const target_dimensions = resizeWithAspect(
+ width,
+ height,
+ settings.resize_target
+ );
const resizeResult = self.codecs.resize(
new Uint8ClampedArray(imageData),
width, //in
@@ -75,12 +76,12 @@ async function optimize(imageData, fileName, width, height, settings) {
settings.resize_linear_rgb
);
if (resizeResult[3] !== 255) {
- throw "Image corrupted during resize. Falling back to the original for encode"
+ throw "Image corrupted during resize. Falling back to the original for encode";
}
maybeResized = new ImageData(
resizeResult,
target_dimensions.width,
- target_dimensions.height,
+ target_dimensions.height
).data;
width = target_dimensions.width;
height = target_dimensions.height;
@@ -102,12 +103,12 @@ async function optimize(imageData, fileName, width, height, settings) {
mozJpegDefaultOptions
);
- const finalSize = result.byteLength
+ const finalSize = result.byteLength;
logIfDebug(`Worker post reencode file: ${finalSize}`);
logIfDebug(`Reduction: ${(initialSize / finalSize).toFixed(1)}x speedup`);
if (finalSize < 20000) {
- throw "Final size suspciously small, discarding optimizations"
+ throw "Final size suspciously small, discarding optimizations";
}
let transferrable = Uint8Array.from(result).buffer; // decoded was allocated inside WASM so it **cannot** be transfered to another context, need to copy by value
@@ -132,7 +133,7 @@ onmessage = async function (e) {
type: "file",
file: optimized,
fileName: e.data.fileName,
- fileId: e.data.fileId
+ fileId: e.data.fileId,
},
[optimized]
);
@@ -142,31 +143,27 @@ onmessage = async function (e) {
type: "error",
file: e.data.file,
fileName: e.data.fileName,
- fileId: e.data.fileId
+ fileId: e.data.fileId,
});
}
break;
+ case "install":
+ await loadLibs(e.data.settings);
+ postMessage({ type: "installed" });
+ break;
default:
logIfDebug(`Sorry, we are out of ${e}.`);
}
};
-async function loadLibs(settings){
-
+async function loadLibs(settings) {
if (self.codecs) return;
- if (!self.loadedMozJpeg) {
- importScripts(settings.mozjpeg_script);
- self.loadedMozJpeg = true;
- }
-
- if (!self.loadedResizeScript) {
- importScripts(settings.resize_script);
- self.loadedResizeScript = true;
- }
+ importScripts(settings.mozjpeg_script);
+ importScripts(settings.resize_script);
let encoderModuleOverrides = {
- locateFile: function(path, prefix) {
+ locateFile: function (path, prefix) {
// if it's a mem init file, use a custom dir
if (path.endsWith(".wasm")) return settings.mozjpeg_wasm;
// otherwise, use the default, the prefix (JS file's dir) + the path
@@ -181,5 +178,5 @@ async function loadLibs(settings){
const { resize } = wasm_bindgen;
await wasm_bindgen(settings.resize_wasm);
- self.codecs = {mozjpeg_enc: mozjpeg_enc_module, resize: resize};
+ self.codecs = { mozjpeg_enc: mozjpeg_enc_module, resize: resize };
}
diff --git a/script/bench.rb b/script/bench.rb
index ce88f408b4..d1ab78d791 100644
--- a/script/bench.rb
+++ b/script/bench.rb
@@ -297,7 +297,7 @@ begin
run("RAILS_ENV=profile bundle exec rake assets:clean")
def get_mem(pid)
- YAML.load `ruby script/memstats.rb #{pid} --yaml`
+ YAML.safe_load `ruby script/memstats.rb #{pid} --yaml`
end
mem = get_mem(pid)
diff --git a/script/benchmarks/cache/bench.rb b/script/benchmarks/cache/bench.rb
index ff6092e859..fad1b73607 100644
--- a/script/benchmarks/cache/bench.rb
+++ b/script/benchmarks/cache/bench.rb
@@ -46,7 +46,7 @@ Benchmark.ips do |x|
x.report("redis get string marshal") do |times|
while times > 0
- Marshal.load(Discourse.redis.get("test_keym"))
+ Marshal.load(Discourse.redis.get("test_keym")) # rubocop:disable Security/MarshalLoad
times -= 1
end
end
diff --git a/script/bulk_import/vanilla.rb b/script/bulk_import/vanilla.rb
index 92ddf3fd17..cb85b853f9 100644
--- a/script/bulk_import/vanilla.rb
+++ b/script/bulk_import/vanilla.rb
@@ -5,6 +5,8 @@ require "mysql2"
require "rake"
require "htmlentities"
+# NOTE: this importer expects a MySQL DB to directly connect to
+
class BulkImport::Vanilla < BulkImport::Base
VANILLA_DB = "dbname"
diff --git a/script/discourse b/script/discourse
index 1d4e4ff702..bca7197c30 100755
--- a/script/discourse
+++ b/script/discourse
@@ -4,6 +4,10 @@
require "thor"
class DiscourseCLI < Thor
+ def self.exit_on_failure?
+ true
+ end
+
desc "remap [--global,--regex] FROM TO", "Remap a string sequence across all tables"
long_desc <<-LONGDESC
Replace a string sequence FROM with TO across all tables.
diff --git a/script/import_scripts/jforum.rb b/script/import_scripts/jforum.rb
index e5d56718b6..1c1136f208 100644
--- a/script/import_scripts/jforum.rb
+++ b/script/import_scripts/jforum.rb
@@ -10,7 +10,7 @@ class ImportScripts::JForum < ImportScripts::Base
def initialize
super
- @settings = YAML.load(File.read(ARGV.first), symbolize_names: true)
+ @settings = YAML.safe_load(File.read(ARGV.first), symbolize_names: true)
@database_client = Mysql2::Client.new(
host: @settings[:database][:host],
diff --git a/script/import_scripts/mbox/support/indexer.rb b/script/import_scripts/mbox/support/indexer.rb
index 47ce3ed133..c8164534d7 100644
--- a/script/import_scripts/mbox/support/indexer.rb
+++ b/script/import_scripts/mbox/support/indexer.rb
@@ -48,7 +48,7 @@ module ImportScripts::Mbox
if File.exist?(metadata_file)
# workaround for YML files that contain classname in file header
yaml = File.read(metadata_file).sub(/^--- !.*$/, '---')
- metadata = YAML.load(yaml)
+ metadata = YAML.safe_load(yaml)
else
metadata = {}
end
diff --git a/script/import_scripts/nodebb/nodebb.rb b/script/import_scripts/nodebb/nodebb.rb
index 45fe69c7c8..5fed857301 100644
--- a/script/import_scripts/nodebb/nodebb.rb
+++ b/script/import_scripts/nodebb/nodebb.rb
@@ -180,7 +180,7 @@ class ImportScripts::NodeBB < ImportScripts::Base
if is_external
# download external image
begin
- string_io = open(picture, read_timeout: 5)
+ string_io = uri.open(read_timeout: 5)
rescue Net::ReadTimeout
puts "timeout downloading avatar for user #{imported_user.id}"
return nil
@@ -246,7 +246,7 @@ class ImportScripts::NodeBB < ImportScripts::Base
if is_external
begin
- string_io = open(picture, read_timeout: 5)
+ string_io = uri.open(read_timeout: 5)
rescue Net::ReadTimeout
return nil
end
diff --git a/script/import_scripts/phorum.rb b/script/import_scripts/phorum.rb
index 3dc4e80b04..dc2639933e 100644
--- a/script/import_scripts/phorum.rb
+++ b/script/import_scripts/phorum.rb
@@ -25,6 +25,7 @@ class ImportScripts::Phorum < ImportScripts::Base
import_users
import_categories
import_posts
+ import_attachments
end
def import_users
@@ -34,7 +35,7 @@ class ImportScripts::Phorum < ImportScripts::Base
batches(BATCH_SIZE) do |offset|
results = mysql_query(
- "SELECT user_id id, username, email, real_name name, date_added created_at,
+ "SELECT user_id id, username, TRIM(email) AS email, username name, date_added created_at,
date_last_active last_seen_at, admin
FROM #{TABLE_PREFIX}users
WHERE #{TABLE_PREFIX}users.active = 1
@@ -209,12 +210,79 @@ class ImportScripts::Phorum < ImportScripts::Base
s.gsub!(/\[hr\]/i, "
")
+ # remove trailing
+ s = s.chomp("
")
+
s
end
def mysql_query(sql)
@client.query(sql, cache_rows: false)
end
+
+ def import_attachments
+ puts '', 'importing attachments...'
+
+ uploads = mysql_query <<-SQL
+ SELECT message_id, filename, FROM_BASE64(file_data) AS file_data, file_id
+ FROM #{TABLE_PREFIX}files
+ where message_id > 0
+ order by file_id
+ SQL
+
+ current_count = 0
+ total_count = uploads.count
+
+ uploads.each do |upload|
+
+ # puts "*** processing file #{upload['file_id']}"
+
+ post_id = post_id_from_imported_post_id(upload['message_id'])
+
+ if post_id.nil?
+ puts "Post #{upload['message_id']} for attachment #{upload['file_id']} not found"
+ next
+ end
+
+ post = Post.find(post_id)
+
+ real_filename = upload['filename']
+ real_filename.prepend SecureRandom.hex if real_filename[0] == '.'
+
+ tmpfile = 'attach_' + upload['file_id'].to_s
+ filename = File.join('/tmp/', tmpfile)
+ File.open(filename, 'wb') { |f|
+ f.write(upload['file_data'])
+ }
+
+ upl_obj = create_upload(post.user.id, filename, real_filename)
+
+ # puts "discourse post #{post['id']} and upload #{upl_obj['id']}"
+
+ if upl_obj&.persisted?
+ html = html_for_upload(upl_obj, real_filename)
+ if !post.raw[html]
+ post.raw += "\n\n#{html}\n\n"
+ post.save!
+ if PostUpload.where(post: post, upload: upl_obj).exists?
+ puts "skipping creating uploaded for previously uploaded file #{upload['file_id']}"
+ else
+ PostUpload.create!(post: post, upload: upl_obj)
+ end
+ # PostUpload.create!(post: post, upload: upl_obj) unless PostUpload.where(post: post, upload: upl_obj).exists?
+ else
+ puts "Skipping attachment #{upload['file_id']}"
+ end
+ else
+ puts "Failed to upload attachment #{upload['file_id']}"
+ exit
+ end
+
+ current_count += 1
+ print_status(current_count, total_count)
+ end
+ end
+
end
ImportScripts::Phorum.new.perform
diff --git a/script/import_scripts/smf2.rb b/script/import_scripts/smf2.rb
index 74b63189cd..9885f1edc3 100644
--- a/script/import_scripts/smf2.rb
+++ b/script/import_scripts/smf2.rb
@@ -558,7 +558,7 @@ class ImportScripts::Smf2 < ImportScripts::Base
def read_smf_settings
settings = File.join(self.smfroot, 'Settings.php')
- IO.readlines(settings).each do |line|
+ File.readlines(settings).each do |line|
next unless m = /\$([a-z_]+)\s*=\s*['"](.+?)['"]\s*;\s*((#|\/\/).*)?$/.match(line)
case m[1]
when 'db_server' then self.host ||= m[2]
diff --git a/script/import_scripts/vanilla.rb b/script/import_scripts/vanilla.rb
index 08f8ada44b..1d67cd045a 100644
--- a/script/import_scripts/vanilla.rb
+++ b/script/import_scripts/vanilla.rb
@@ -3,6 +3,9 @@
require "csv"
require File.expand_path(File.dirname(__FILE__) + "/base.rb")
+# NOTE: this importer expects a text file obtained through Vanilla Porter
+# user documentation: https://meta.discourse.org/t/how-to-migrate-import-from-vanilla-to-discourse/27273
+
class ImportScripts::Vanilla < ImportScripts::Base
def initialize
@@ -199,7 +202,9 @@ class ImportScripts::Vanilla < ImportScripts::Base
user_emails_in_conversation = @users.select { |u| user_ids_in_conversation.include?(u[:user_id]) }
.map { |u| u[:email] }
# retrieve their usernames from the database
- target_usernames = User.where("email IN (?)", user_emails_in_conversation).pluck(:username).to_a
+ target_usernames = User.joins(:user_emails)
+ .where(user_emails: { email: user_emails_in_conversation })
+ .pluck(:username)
next if target_usernames.blank?
@@ -207,7 +212,6 @@ class ImportScripts::Vanilla < ImportScripts::Base
first_message = @conversation_messages.select { |cm| cm[:message_id] == conversation[:first_message_id] }.first
{
- archetype: Archetype.private_message,
id: "conversation#" + conversation[:conversation_id],
user_id: user.id,
title: "Private message from #{user.username}",
diff --git a/script/import_scripts/zendesk_api.rb b/script/import_scripts/zendesk_api.rb
index 4b5a2b5680..9237a76442 100644
--- a/script/import_scripts/zendesk_api.rb
+++ b/script/import_scripts/zendesk_api.rb
@@ -333,7 +333,7 @@ class ImportScripts::ZendeskApi < ImportScripts::Base
attempts = 0
begin
- open("#{$1}") do |image|
+ URI.parse(image_url).open do |image|
# IMAGE_DOWNLOAD_PATH is whatever image, it will be replaced with the downloaded image
File.open(IMAGE_DOWNLOAD_PATH, "wb") do |file|
file.write(image.read)
diff --git a/script/memstats.rb b/script/memstats.rb
index f387a88337..998d8d525f 100755
--- a/script/memstats.rb
+++ b/script/memstats.rb
@@ -131,7 +131,7 @@ def format_number(n)
end
def get_commandline(pid)
- commandline = IO.read("/proc/#{pid}/cmdline").split("\0")
+ commandline = File.read("/proc/#{pid}/cmdline").split("\0")
if commandline.first =~ /java$/ then
loop { break if commandline.shift == "-jar" }
return "[java] #{commandline.shift}"
diff --git a/spec/components/auth/default_current_user_provider_spec.rb b/spec/components/auth/default_current_user_provider_spec.rb
index b8da06fc58..502e289f86 100644
--- a/spec/components/auth/default_current_user_provider_spec.rb
+++ b/spec/components/auth/default_current_user_provider_spec.rb
@@ -621,8 +621,8 @@ describe Auth::DefaultCurrentUserProvider do
end
it "rate limits api usage" do
- limiter1 = RateLimiter.new(nil, "user_api_day_#{api_key.key}", 10, 60)
- limiter2 = RateLimiter.new(nil, "user_api_min_#{api_key.key}", 10, 60)
+ limiter1 = RateLimiter.new(nil, "user_api_day_#{ApiKey.hash_key(api_key.key)}", 10, 60)
+ limiter2 = RateLimiter.new(nil, "user_api_min_#{ApiKey.hash_key(api_key.key)}", 10, 60)
limiter1.clear!
limiter2.clear!
diff --git a/spec/components/discourse_spec.rb b/spec/components/discourse_spec.rb
index 701cc39950..aa2542e04b 100644
--- a/spec/components/discourse_spec.rb
+++ b/spec/components/discourse_spec.rb
@@ -104,6 +104,7 @@ describe Discourse do
after do
Discourse.plugins.delete plugin1
Discourse.plugins.delete plugin2
+ DiscoursePluginRegistry.reset!
end
before do
diff --git a/spec/components/discourse_updates_spec.rb b/spec/components/discourse_updates_spec.rb
index 6bb8fd2740..dbe653f085 100644
--- a/spec/components/discourse_updates_spec.rb
+++ b/spec/components/discourse_updates_spec.rb
@@ -3,23 +3,18 @@
require 'rails_helper'
describe DiscourseUpdates do
-
def stub_data(latest, missing, critical, updated_at)
- DiscourseUpdates.stubs(:latest_version).returns(latest)
- DiscourseUpdates.stubs(:missing_versions_count).returns(missing)
- DiscourseUpdates.stubs(:critical_updates_available?).returns(critical)
- DiscourseUpdates.stubs(:updated_at).returns(updated_at)
- end
-
- before do
- Jobs::VersionCheck.any_instance.stubs(:execute).returns(true)
+ DiscourseUpdates.latest_version = latest
+ DiscourseUpdates.missing_versions_count = missing
+ DiscourseUpdates.critical_updates_available = critical
+ DiscourseUpdates.updated_at = updated_at
end
subject { DiscourseUpdates.check_version }
context 'version check was done at the current installed version' do
before do
- DiscourseUpdates.stubs(:last_installed_version).returns(Discourse::VERSION::STRING)
+ DiscourseUpdates.last_installed_version = Discourse::VERSION::STRING
end
context 'a good version check request happened recently' do
@@ -36,7 +31,7 @@ describe DiscourseUpdates do
end
it 'returns the timestamp of the last version check' do
- expect(subject.updated_at).to eq_time(time)
+ expect(subject.updated_at).to be_within_one_second_of(time)
end
end
@@ -52,7 +47,7 @@ describe DiscourseUpdates do
end
it 'returns the timestamp of the last version check' do
- expect(subject.updated_at).to eq_time(time)
+ expect(subject.updated_at).to be_within_one_second_of(time)
end
end
end
@@ -115,7 +110,7 @@ describe DiscourseUpdates do
context 'version check was done at a different installed version' do
before do
- DiscourseUpdates.stubs(:last_installed_version).returns('0.9.1')
+ DiscourseUpdates.last_installed_version = '0.9.1'
end
shared_examples "when last_installed_version is old" do
@@ -211,7 +206,7 @@ describe DiscourseUpdates do
]
Discourse.redis.set('new_features', MultiJson.dump(features_with_versions))
- DiscourseUpdates.stubs(:last_installed_version).returns("2.7.0.beta2")
+ DiscourseUpdates.last_installed_version = "2.7.0.beta2"
result = DiscourseUpdates.new_features
expect(result.length).to eq(3)
diff --git a/spec/components/file_store/s3_store_spec.rb b/spec/components/file_store/s3_store_spec.rb
index 472e61cd83..82563ef97b 100644
--- a/spec/components/file_store/s3_store_spec.rb
+++ b/spec/components/file_store/s3_store_spec.rb
@@ -163,7 +163,11 @@ describe FileStore::S3Store do
s3_helper.expects(:copy).with(external_upload_stub.key, kind_of(String), options: upload_opts).returns(["path", "etag"])
s3_helper.expects(:delete_object).with(external_upload_stub.key)
upload = Fabricate(:upload, extension: "png", sha1: upload_sha1, original_filename: original_filename)
- store.move_existing_stored_upload(external_upload_stub.key, upload, "image/png")
+ store.move_existing_stored_upload(
+ existing_external_upload_key: external_upload_stub.key,
+ upload: upload,
+ content_type: "image/png"
+ )
end
context "when the file is a PDF" do
@@ -175,7 +179,11 @@ describe FileStore::S3Store do
disp_opts = { content_disposition: "attachment; filename=\"#{original_filename}\"; filename*=UTF-8''#{original_filename}", content_type: "application/pdf" }
s3_helper.expects(:copy).with(external_upload_stub.key, kind_of(String), options: upload_opts.merge(disp_opts)).returns(["path", "etag"])
upload = Fabricate(:upload, extension: "png", sha1: upload_sha1, original_filename: original_filename)
- store.move_existing_stored_upload(external_upload_stub.key, upload, "application/pdf")
+ store.move_existing_stored_upload(
+ existing_external_upload_key: external_upload_stub.key,
+ upload: upload,
+ content_type: "application/pdf"
+ )
end
end
end
diff --git a/spec/components/final_destination_spec.rb b/spec/components/final_destination_spec.rb
index 8b781324bd..e9548ead0b 100644
--- a/spec/components/final_destination_spec.rb
+++ b/spec/components/final_destination_spec.rb
@@ -194,6 +194,31 @@ describe FinalDestination do
expect(final.status).to eq(:resolved)
end
+ it 'resolves the canonical link when the URL is relative' do
+ host = "https://codinghorror.com"
+
+ canonical_follow("#{host}/blog", "/blog/canonical")
+ stub_request(:head, "#{host}/blog/canonical").to_return(doc_response)
+
+ final = FinalDestination.new("#{host}/blog", opts.merge(follow_canonical: true))
+
+ expect(final.resolve.to_s).to eq("#{host}/blog/canonical")
+ expect(final.redirected?).to eq(false)
+ expect(final.status).to eq(:resolved)
+ end
+
+ it 'resolves the canonical link when the URL is relative and does not start with the / symbol' do
+ host = "https://codinghorror.com"
+ canonical_follow("#{host}/blog", "blog/canonical")
+ stub_request(:head, "#{host}/blog/canonical").to_return(doc_response)
+
+ final = FinalDestination.new("#{host}/blog", opts.merge(follow_canonical: true))
+
+ expect(final.resolve.to_s).to eq("#{host}/blog/canonical")
+ expect(final.redirected?).to eq(false)
+ expect(final.status).to eq(:resolved)
+ end
+
it "does not follow the canonical link if it's the same as the current URL" do
canonical_follow("https://eviltrout.com", "https://eviltrout.com")
diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb
index 0185b32217..3ff0420bb2 100644
--- a/spec/components/guardian_spec.rb
+++ b/spec/components/guardian_spec.rb
@@ -3196,14 +3196,9 @@ describe Guardian do
context "allowlist mode" do
before do
- GlobalSetting.reset_allowed_theme_ids!
global_setting :allowed_theme_repos, " https://magic.com/repo.git, https://x.com/git"
end
- after do
- GlobalSetting.reset_allowed_theme_ids!
- end
-
it "should respect theme allowlisting" do
r = RemoteTheme.create!(remote_url: "https://magic.com/repo.git")
theme.update!(remote_theme_id: r.id)
diff --git a/spec/components/imap/sync_spec.rb b/spec/components/imap/sync_spec.rb
index 04ee4eb3cd..3c494904bb 100644
--- a/spec/components/imap/sync_spec.rb
+++ b/spec/components/imap/sync_spec.rb
@@ -2,7 +2,6 @@
require 'rails_helper'
require 'imap/sync'
-require_relative 'imap_helper'
describe Imap::Sync do
diff --git a/spec/components/middleware/anonymous_cache_spec.rb b/spec/components/middleware/anonymous_cache_spec.rb
index e0b23c8a54..9cf0e05866 100644
--- a/spec/components/middleware/anonymous_cache_spec.rb
+++ b/spec/components/middleware/anonymous_cache_spec.rb
@@ -240,11 +240,12 @@ describe Middleware::AnonymousCache do
context 'invalid request payload' do
it 'returns 413 for GET request with payload' do
- status, _, _ = middleware.call(env.tap do |environment|
+ status, headers, _ = middleware.call(env.tap do |environment|
environment[Rack::RACK_INPUT].write("test")
end)
expect(status).to eq(413)
+ expect(headers["Cache-Control"]).to eq("private, max-age=0, must-revalidate")
end
end
diff --git a/spec/components/oneboxer_spec.rb b/spec/components/oneboxer_spec.rb
index 527f0545d2..21afb3abc9 100644
--- a/spec/components/oneboxer_spec.rb
+++ b/spec/components/oneboxer_spec.rb
@@ -308,7 +308,7 @@ describe Oneboxer do
end
end
- context 'facebook_app_access_token' do
+ context 'instagram' do
it 'providing a token should attempt to use new endpoint' do
url = "https://www.instagram.com/p/CHLkBERAiLa"
access_token = 'abc123'
@@ -318,7 +318,7 @@ describe Oneboxer do
stub_request(:head, url)
stub_request(:get, "https://graph.facebook.com/v9.0/instagram_oembed?url=#{url}&access_token=#{access_token}").to_return(body: response("instagram_new"))
- expect(Oneboxer.preview(url, invalidate_oneboxes: true)).not_to include('instagram-description')
+ expect(Oneboxer.preview(url, invalidate_oneboxes: true)).to include('placeholder-icon image')
end
it 'unconfigured token should attempt to use old endpoint' do
@@ -326,7 +326,15 @@ describe Oneboxer do
stub_request(:head, url)
stub_request(:get, "https://api.instagram.com/oembed/?url=#{url}").to_return(body: response("instagram_old"))
- expect(Oneboxer.preview(url, invalidate_oneboxes: true)).to include('instagram-description')
+ expect(Oneboxer.preview(url, invalidate_oneboxes: true)).to include('placeholder-icon image')
+ end
+
+ it 'renders result using an iframe' do
+ url = "https://www.instagram.com/p/CHLkBERAiLa"
+ stub_request(:head, url)
+ stub_request(:get, "https://api.instagram.com/oembed/?url=#{url}").to_return(body: response("instagram_old"))
+
+ expect(Oneboxer.onebox(url, invalidate_oneboxes: true)).to include('iframe')
end
end
diff --git a/spec/components/pbkdf2_spec.rb b/spec/components/pbkdf2_spec.rb
index 0de2f5e635..bbc07c4be0 100644
--- a/spec/components/pbkdf2_spec.rb
+++ b/spec/components/pbkdf2_spec.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require 'rails_helper'
require 'pbkdf2'
describe Pbkdf2 do
diff --git a/spec/components/post_destroyer_spec.rb b/spec/components/post_destroyer_spec.rb
index a1e3b989f3..530df2385c 100644
--- a/spec/components/post_destroyer_spec.rb
+++ b/spec/components/post_destroyer_spec.rb
@@ -473,6 +473,18 @@ describe PostDestroyer do
expect_job_enqueued(job: :sync_topic_user_bookmarked, args: { topic_id: post2.topic_id })
end
+ it "skips post revise validations when post is marked for deletion by the author" do
+ SiteSetting.min_first_post_length = 100
+ post = create_post(raw: "this is a long post what passes the min_first_post_length validation " * 3)
+ PostDestroyer.new(post.user, post).destroy
+ post.reload
+ expect(post.errors).to be_blank
+ expect(post.revisions.count).to eq(1)
+ expect(post.raw).to eq(I18n.t("js.topic.deleted_by_author_simple"))
+ expect(post.user_deleted).to eq(true)
+ expect(post.topic.closed).to eq(true)
+ end
+
context "as a moderator" do
it "deletes the post" do
author = post.user
diff --git a/spec/components/post_revisor_spec.rb b/spec/components/post_revisor_spec.rb
index 207f217783..d0c318a2c1 100644
--- a/spec/components/post_revisor_spec.rb
+++ b/spec/components/post_revisor_spec.rb
@@ -148,6 +148,22 @@ describe PostRevisor do
subject { PostRevisor.new(post) }
+ it 'destroys last revision if edit is undone' do
+ old_raw = post.raw
+
+ subject.revise!(admin, raw: 'new post body', tags: ['new-tag'])
+ expect(post.topic.reload.tags.map(&:name)).to contain_exactly('new-tag')
+ expect(post.post_revisions.reload.size).to eq(1)
+
+ subject.revise!(admin, raw: old_raw, tags: [])
+ expect(post.topic.reload.tags.map(&:name)).to be_empty
+ expect(post.post_revisions.reload.size).to eq(0)
+
+ subject.revise!(admin, raw: 'next post body', tags: ['new-tag'])
+ expect(post.topic.reload.tags.map(&:name)).to contain_exactly('new-tag')
+ expect(post.post_revisions.reload.size).to eq(1)
+ end
+
describe 'with the same body' do
it "doesn't change version" do
expect {
@@ -703,6 +719,17 @@ describe PostRevisor do
expect(post.revisions.first.modifications["archetype"][1]).to eq(new_archetype)
end
+ it "revises and tracks changes of topic tags" do
+ subject.revise!(admin, tags: ['new-tag'])
+ expect(post.post_revisions.last.modifications).to eq('tags' => [[], ['new-tag']])
+
+ subject.revise!(admin, tags: ['new-tag', 'new-tag-2'])
+ expect(post.post_revisions.last.modifications).to eq('tags' => [[], ['new-tag', 'new-tag-2']])
+
+ subject.revise!(admin, tags: ['new-tag-3'])
+ expect(post.post_revisions.last.modifications).to eq('tags' => [[], ['new-tag-3']])
+ end
+
context "#publish_changes" do
let!(:post) { Fabricate(:post, topic: topic) }
diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb
index 7acb5e0f3b..d4b7267291 100644
--- a/spec/components/pretty_text_spec.rb
+++ b/spec/components/pretty_text_spec.rb
@@ -1479,6 +1479,22 @@ HTML
HTML
end
+ it "does not replace hashtags and mentions when watched words are regular expressions" do
+ SiteSetting.watched_words_regular_expressions = true
+
+ Fabricate(:user, username: "test")
+ category = Fabricate(:category, slug: "test")
+ Fabricate(:watched_word, action: WatchedWord.actions[:replace], word: "es", replacement: "discourse")
+
+ expect(PrettyText.cook("@test #test test")).to match_html(<<~HTML)
+