diff --git a/.eslintignore b/.eslintignore index f0d962bb0f..516be7613d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,8 +10,8 @@ lib/highlight_js/ plugins/**/lib/javascripts/locale public/ vendor/ -test/javascripts/test_helper.js -test/javascripts/fixtures -test/javascripts/helpers/assertions.js +app/assets/javascripts/discourse/tests/test_helper.js +app/assets/javascripts/discourse/tests/fixtures +app/assets/javascripts/discourse/tests/helpers/assertions.js node_modules/ dist/ diff --git a/.eslintrc b/.eslintrc index 8f0efccbc7..4719bf3553 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,5 +2,12 @@ "extends": "eslint-config-discourse", "rules": { "discourse-ember/global-ember": 2 + }, + "globals": { + "moduleFor": "off", + "moduleForComponent": "off", + "testStart": "off", + "testDone": "off", + "sinon": "off" } } diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 1b68361eec..7102158a40 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -43,3 +43,9 @@ bf88410126f73aab47b7e694e3c5b46453cec1b6 # REFACTOR: Support bundling our `admin` section as an ember addon ce3fe2f4c4ddf166949ee3cec3d9ecbf9108ab52 + +# REFACTOR: Move qunit tests to a different directory structure +bc97c79a35d8acd283d4d8b79aa079bce9d127c6 + +# REFACTOR: Move javascript tests inside discourse app +23f24bfb510edb25b18b6a0d5485270c88df9b24 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3fbf97497..3eb5f267b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,9 +91,10 @@ jobs: gem install bundler -v 2.1.4 --no-doc bundle config deployment 'true' bundle config without 'development' + bundle config path vendor/bundle - name: Bundler cache - uses: actions/cache@v1 + uses: actions/cache@v2 id: bundler-cache with: path: vendor/bundle @@ -109,7 +110,7 @@ jobs: run: echo "::set-output name=dir::$(yarn cache dir)" - name: Yarn cache - uses: actions/cache@v1 + uses: actions/cache@v2 id: yarn-cache with: path: ${{ steps.yarn-cache-dir.outputs.dir }} @@ -146,7 +147,7 @@ jobs: - name: ESLint (core) if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' - run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern app/assets/javascripts test/javascripts + run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern app/assets/javascripts - name: ESLint (core plugins) if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' @@ -163,7 +164,6 @@ jobs: yarn prettier --list-different \ "app/assets/stylesheets/**/*.scss" \ "app/assets/javascripts/**/*.{js,es6}" \ - "test/javascripts/**/*.{js,es6}" \ "plugins/**/assets/stylesheets/**/*.scss" \ "plugins/**/assets/javascripts/**/*.{js,es6}" @@ -175,6 +175,19 @@ jobs: "plugins/**/assets/stylesheets/**/*.scss" \ "plugins/**/assets/javascripts/**/*.{js,es6}" + - name: Ember template lint (core and core plugins) + if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' + run: | + yarn ember-template-lint \ + app/assets/javascripts \ + plugins/**/assets/javascripts + + - name: Ember template lint (all plugins) + if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'PLUGINS' + run: | + yarn ember-template-lint \ + plugins/**/assets/javascripts + - name: Core English locale if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' run: bundle exec ruby script/i18n_lint.rb "config/**/locales/{client,server}.en.yml" diff --git a/.gitignore b/.gitignore index 6322b83c8d..421c63ae6d 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ bootsnap-compile-cache/ !/plugins/discourse-nginx-performance-report !/plugins/discourse-narrative-bot !/plugins/discourse-presence +!/plugins/styleguide !/plugins/discourse-local-dates /plugins/*/auto_generated/ diff --git a/.prettierignore b/.prettierignore index b97584a5f2..83294653c9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -18,8 +18,9 @@ lib/highlight_js/ plugins/**/lib/javascripts/locale public/ vendor/ -test/javascripts/test_helper.js -test/javascripts/fixtures -test/javascripts/helpers/assertions.js +app/assets/javascripts/discourse/tests/test_helper.js +app/assets/javascripts/discourse/tests/fixtures +app/assets/javascripts/discourse/tests/helpers/assertions.js node_modules/ dist/ +**/*.rb diff --git a/Gemfile.lock b/Gemfile.lock index 18ea6e44fd..7894346162 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,7 +66,7 @@ GEM barber (0.12.2) ember-source (>= 1.0, < 3.1) execjs (>= 1.2, < 3) - better_errors (2.8.1) + better_errors (2.8.3) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) @@ -105,7 +105,7 @@ GEM jquery-rails (>= 1.0.17) railties (>= 3.1) discourse-ember-source (3.12.2.2) - discourse-fonts (0.0.3) + discourse-fonts (0.0.5) discourse_image_optim (0.26.2) exifr (~> 1.2, >= 1.2.2) fspath (~> 3.0) @@ -180,14 +180,14 @@ GEM mini_mime (>= 0.1.1) maxminddb (0.1.22) memory_profiler (0.9.14) - message_bus (3.3.2) + message_bus (3.3.4) rack (>= 1.1.3) method_source (1.0.0) mini_mime (1.0.2) mini_portile2 (2.4.0) mini_racer (0.3.1) libv8 (~> 8.4.255) - mini_scheduler (0.12.2) + mini_scheduler (0.12.3) sidekiq mini_sql (0.3) mini_suffix (0.3.0) @@ -249,7 +249,7 @@ GEM parallel (1.19.2) parallel_tests (3.3.0) parallel - parser (2.7.1.5) + parser (2.7.2.0) ast (~> 2.4.1) pg (1.2.3) progress (3.5.2) @@ -262,7 +262,7 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.6) - puma (5.0.0) + puma (5.0.2) nio4r (~> 2.0) r2 (0.2.7) rack (2.2.3) @@ -303,12 +303,12 @@ GEM redis (4.2.2) redis-namespace (1.8.0) redis (>= 3.0.4) - regexp_parser (1.8.0) + regexp_parser (1.8.2) request_store (1.5.0) rack (>= 1.4) rexml (3.2.4) rinku (2.0.6) - rotp (6.1.0) + rotp (6.2.0) rqrcode (1.1.2) chunky_png (~> 1.0) rqrcode_core (~> 0.1) @@ -317,12 +317,12 @@ GEM rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) rspec-mocks (~> 3.9.0) - rspec-core (3.9.2) + rspec-core (3.9.3) rspec-support (~> 3.9.3) rspec-expectations (3.9.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) - rspec-html-matchers (0.9.2) + rspec-html-matchers (0.9.4) nokogiri (~> 1) rspec (>= 3.0.0.a, < 4) rspec-mocks (3.9.1) @@ -342,16 +342,16 @@ GEM json-schema (~> 2.2) railties (>= 3.1, < 7.0) rtlit (0.0.5) - rubocop (0.91.1) + rubocop (0.93.1) parallel (~> 1.10) - parser (>= 2.7.1.1) + parser (>= 2.7.1.5) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.7) + regexp_parser (>= 1.8) rexml - rubocop-ast (>= 0.4.0, < 1.0) + rubocop-ast (>= 0.6.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.5.0) + rubocop-ast (0.8.0) parser (>= 2.7.1.5) rubocop-discourse (2.3.2) rubocop (>= 0.69.0) @@ -415,7 +415,7 @@ GEM kgio (~> 2.6) raindrops (~> 0.7) uniform_notifier (1.13.0) - webmock (3.9.1) + webmock (3.9.2) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-logs.js b/app/assets/javascripts/admin/addon/controllers/admin-email-logs.js index c0bb4f29f7..048ad49152 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-logs.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-logs.js @@ -1,9 +1,14 @@ import Controller from "@ember/controller"; import EmailLog from "admin/models/email-log"; +import EmberObject from "@ember/object"; export default Controller.extend({ loading: false, + init() { + this._super(...arguments); + this.set("filter", EmberObject.create()); + }, loadLogs(sourceModel, loadMore) { if ((loadMore && this.loading) || this.get("model.allLoaded")) { return; @@ -13,8 +18,14 @@ export default Controller.extend({ sourceModel = sourceModel || EmailLog; + let args = {}; + Object.keys(this.filter).forEach((k) => { + if (this.filter[k]) { + args[k] = this.filter[k]; + } + }); return sourceModel - .findAll(this.filter, loadMore ? this.get("model.length") : null) + .findAll(args, loadMore ? this.get("model.length") : null) .then((logs) => { if (this.model && loadMore && logs.length < 50) { this.model.set("allLoaded", true); diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-uploaded-image-list.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-uploaded-image-list.js index 2185b7b137..0aeffaad59 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-uploaded-image-list.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-uploaded-image-list.js @@ -7,7 +7,7 @@ export default Controller.extend(ModalFunctionality, { @observes("model.value") _setup() { const value = this.get("model.value"); - this.set("images", value && value.length ? value.split("\n") : []); + this.set("images", value && value.length ? value.split("|") : []); }, actions: { @@ -20,7 +20,7 @@ export default Controller.extend(ModalFunctionality, { }, close() { - this.save(this.images.join("\n")); + this.save(this.images.join("|")); this.send("closeModal"); }, }, diff --git a/app/assets/javascripts/admin/addon/mixins/period-computation.js b/app/assets/javascripts/admin/addon/mixins/period-computation.js index 1b6a92c61d..7b8afe796a 100644 --- a/app/assets/javascripts/admin/addon/mixins/period-computation.js +++ b/app/assets/javascripts/admin/addon/mixins/period-computation.js @@ -14,7 +14,7 @@ export default Mixin.create({ @discourseComputed("period") startDate(period) { - let fullDay = moment().locale("en").utc().subtract(1, "day"); + let fullDay = moment().locale("en").utc().endOf("day"); switch (period) { case "yearly": @@ -24,7 +24,7 @@ export default Mixin.create({ return fullDay.subtract(3, "month").startOf("day"); break; case "weekly": - return fullDay.subtract(1, "week").startOf("day"); + return fullDay.subtract(6, "days").startOf("day"); break; case "monthly": return fullDay.subtract(1, "month").startOf("day"); @@ -46,7 +46,7 @@ export default Mixin.create({ @discourseComputed() endDate() { - return moment().locale("en").utc().subtract(1, "day").endOf("day"); + return moment().locale("en").utc().endOf("day"); }, @discourseComputed() diff --git a/app/assets/javascripts/admin/addon/models/admin-user.js b/app/assets/javascripts/admin/addon/models/admin-user.js index 8d4b38baea..4a58d21273 100644 --- a/app/assets/javascripts/admin/addon/models/admin-user.js +++ b/app/assets/javascripts/admin/addon/models/admin-user.js @@ -139,8 +139,8 @@ const AdminUser = User.extend({ bootbox.hideAll(); let error; AdminUser.find(user.get("id")).then((u) => user.setProperties(u)); - if (e.responseJSON && e.responseJSON.errors) { - error = e.responseJSON.errors[0]; + if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { + error = e.jqXHR.responseJSON.errors[0]; } error = error || I18n.t("admin.user.delete_posts_failed"); bootbox.alert(error); @@ -236,8 +236,8 @@ const AdminUser = User.extend({ .then(() => window.location.reload()) .catch((e) => { let error; - if (e.responseJSON && e.responseJSON.errors) { - error = e.responseJSON.errors[0]; + if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { + error = e.jqXHR.responseJSON.errors[0]; } error = error || @@ -260,8 +260,8 @@ const AdminUser = User.extend({ .then(() => window.location.reload()) .catch((e) => { let error; - if (e.responseJSON && e.responseJSON.errors) { - error = e.responseJSON.errors[0]; + if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { + error = e.jqXHR.responseJSON.errors[0]; } error = error || diff --git a/app/assets/javascripts/admin/addon/routes/admin-email-incomings.js b/app/assets/javascripts/admin/addon/routes/admin-email-incomings.js index 09118ea430..6321c8ef01 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-email-incomings.js +++ b/app/assets/javascripts/admin/addon/routes/admin-email-incomings.js @@ -8,6 +8,6 @@ export default DiscourseRoute.extend({ setupController(controller, model) { controller.set("model", model); - controller.set("filter", { status: this.status }); + controller.set("filter.status", this.status); }, }); diff --git a/app/assets/javascripts/admin/addon/templates/customize-themes-show.hbs b/app/assets/javascripts/admin/addon/templates/customize-themes-show.hbs index 73d78b085e..7e532cfe89 100644 --- a/app/assets/javascripts/admin/addon/templates/customize-themes-show.hbs +++ b/app/assets/javascripts/admin/addon/templates/customize-themes-show.hbs @@ -55,26 +55,26 @@
{{#if model.remote_theme}} - {{#if model.remote_theme.remote_url}} - {{#if sourceIsHttp}} - {{i18n "admin.customize.theme.source_url"}}{{d-icon "link"}} - {{else}} -
{{model.remote_theme.remote_url}}
- {{/if}} - {{/if}} - {{#if model.remote_theme.about_url}} - {{i18n "admin.customize.theme.about_theme"}}{{d-icon "link"}} - {{/if}} - {{#if model.remote_theme.license_url}} - {{i18n "admin.customize.theme.license"}}{{d-icon "link"}} + {{#if model.remote_theme.remote_url}} + {{#if sourceIsHttp}} + {{i18n "admin.customize.theme.source_url"}}{{d-icon "link"}} + {{else}} +
{{model.remote_theme.remote_url}}
{{/if}} + {{/if}} + {{#if model.remote_theme.about_url}} + {{i18n "admin.customize.theme.about_theme"}}{{d-icon "link"}} + {{/if}} + {{#if model.remote_theme.license_url}} + {{i18n "admin.customize.theme.license"}}{{d-icon "link"}} + {{/if}} - {{#if model.description}} - {{model.description}} - {{/if}} + {{#if model.description}} + {{model.description}} + {{/if}} - {{#if model.remote_theme.authors}}{{i18n "admin.customize.theme.authors"}} {{model.remote_theme.authors}}{{/if}} - {{#if model.remote_theme.theme_version}}{{i18n "admin.customize.theme.version"}} {{model.remote_theme.theme_version}}{{/if}} + {{#if model.remote_theme.authors}}{{i18n "admin.customize.theme.authors"}} {{model.remote_theme.authors}}{{/if}} + {{#if model.remote_theme.theme_version}}{{i18n "admin.customize.theme.version"}} {{model.remote_theme.theme_version}}{{/if}}
{{#if model.remote_theme.is_git}} @@ -119,16 +119,15 @@ {{/if}}
{{else}} - {{i18n "admin.customize.theme.creator"}} - - {{#user-link user=model.user}} - {{format-username model.user.username}} - {{/user-link}} - + {{i18n "admin.customize.theme.creator"}} + + {{#user-link user=model.user}} + {{format-username model.user.username}} + {{/user-link}} + {{/if}}
- {{#unless model.component}}
{{inline-edit-checkbox action=(action "applyDefault") labelKey="admin.customize.theme.is_default" checked=model.default}} @@ -136,7 +135,6 @@
{{/unless}} - {{#unless model.component}} {{#d-section class="form-horizontal theme settings control-unit"}}
@@ -253,7 +251,7 @@
{{i18n "admin.customize.theme.theme_settings"}}
{{#d-section class="form-horizontal theme settings control-unit"}} {{#each settings as |setting|}} - {{theme-setting-editor setting=setting model=model class="theme-setting"}} + {{theme-setting-editor setting=setting model=model class="theme-setting control-unit"}} {{/each}} {{/d-section}}
@@ -271,7 +269,7 @@ {{/if}}
- + {{d-icon "desktop"}}{{i18n "admin.customize.theme.preview"}} {{d-icon "download"}} {{i18n "admin.export_json.button_text"}} diff --git a/app/assets/javascripts/admin/addon/templates/dashboard_general.hbs b/app/assets/javascripts/admin/addon/templates/dashboard_general.hbs index 819abd9361..ec8f59eed6 100644 --- a/app/assets/javascripts/admin/addon/templates/dashboard_general.hbs +++ b/app/assets/javascripts/admin/addon/templates/dashboard_general.hbs @@ -10,7 +10,11 @@ {{i18n "admin.dashboard.community_health"}} - {{period-chooser period=period action=(action "changePeriod") content=availablePeriods fullDay=true}} + {{period-chooser + period=period + action=(action "changePeriod") + content=availablePeriods + fullDay=false}}
diff --git a/app/assets/javascripts/admin/addon/templates/dashboard_moderation.hbs b/app/assets/javascripts/admin/addon/templates/dashboard_moderation.hbs index a6566b78e6..f2191fbeec 100644 --- a/app/assets/javascripts/admin/addon/templates/dashboard_moderation.hbs +++ b/app/assets/javascripts/admin/addon/templates/dashboard_moderation.hbs @@ -13,7 +13,7 @@ period=period action=(action "changePeriod") content=availablePeriods - fullDay=true}} + fullDay=false}}
diff --git a/test/javascripts/admin/components/group-list-setting-test.js b/app/assets/javascripts/admin/tests/admin/integration/components/group-list-setting-test.js similarity index 86% rename from test/javascripts/admin/components/group-list-setting-test.js rename to app/assets/javascripts/admin/tests/admin/integration/components/group-list-setting-test.js index 2472d66a43..4df70d2c89 100644 --- a/test/javascripts/admin/components/group-list-setting-test.js +++ b/app/assets/javascripts/admin/tests/admin/integration/components/group-list-setting-test.js @@ -1,6 +1,7 @@ +import { moduleForComponent } from "ember-qunit"; import EmberObject from "@ember/object"; -import selectKit from "helpers/select-kit-helper"; -import componentTest from "helpers/component-test"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("group-list", { integration: true }); diff --git a/test/javascripts/admin/components/themes-list-item-test.js b/app/assets/javascripts/admin/tests/admin/integration/components/themes-list-item-test.js similarity index 94% rename from test/javascripts/admin/components/themes-list-item-test.js rename to app/assets/javascripts/admin/tests/admin/integration/components/themes-list-item-test.js index d983624c6a..70bace09ad 100644 --- a/test/javascripts/admin/components/themes-list-item-test.js +++ b/app/assets/javascripts/admin/tests/admin/integration/components/themes-list-item-test.js @@ -1,5 +1,6 @@ +import { moduleForComponent } from "ember-qunit"; import I18n from "I18n"; -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; import Theme from "admin/models/theme"; moduleForComponent("themes-list-item", { integration: true }); diff --git a/test/javascripts/admin/components/themes-list-test.js b/app/assets/javascripts/admin/tests/admin/integration/components/themes-list-test.js similarity index 97% rename from test/javascripts/admin/components/themes-list-test.js rename to app/assets/javascripts/admin/tests/admin/integration/components/themes-list-test.js index 631e84b70b..7dc377972f 100644 --- a/test/javascripts/admin/components/themes-list-test.js +++ b/app/assets/javascripts/admin/tests/admin/integration/components/themes-list-test.js @@ -1,5 +1,6 @@ +import { moduleForComponent } from "ember-qunit"; import I18n from "I18n"; -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; import Theme, { THEMES, COMPONENTS } from "admin/models/theme"; moduleForComponent("themes-list", { integration: true }); diff --git a/test/javascripts/admin/controllers/admin-customize-themes-show-test.js b/app/assets/javascripts/admin/tests/admin/unit/controllers/admin-customize-themes-show-test.js similarity index 83% rename from test/javascripts/admin/controllers/admin-customize-themes-show-test.js rename to app/assets/javascripts/admin/tests/admin/unit/controllers/admin-customize-themes-show-test.js index b0e06a9734..318f55fdc0 100644 --- a/test/javascripts/admin/controllers/admin-customize-themes-show-test.js +++ b/app/assets/javascripts/admin/tests/admin/unit/controllers/admin-customize-themes-show-test.js @@ -1,3 +1,5 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; import { mapRoutes } from "discourse/mapping-router"; import Theme from "admin/models/theme"; @@ -8,7 +10,7 @@ moduleFor("controller:admin-customize-themes-show", { needs: ["controller:adminUser"], }); -QUnit.test("can display source url for remote themes", function (assert) { +test("can display source url for remote themes", function (assert) { const repoUrl = "https://github.com/discourse/discourse-brand-header.git"; const remoteTheme = Theme.create({ id: 2, @@ -29,9 +31,7 @@ QUnit.test("can display source url for remote themes", function (assert) { ); }); -QUnit.test("can display source url for remote theme branches", function ( - assert -) { +test("can display source url for remote theme branches", function (assert) { const remoteTheme = Theme.create({ id: 2, default: true, diff --git a/test/javascripts/admin/controllers/admin-customize-themes-test.js b/app/assets/javascripts/admin/tests/admin/unit/controllers/admin-customize-themes-test.js similarity index 90% rename from test/javascripts/admin/controllers/admin-customize-themes-test.js rename to app/assets/javascripts/admin/tests/admin/unit/controllers/admin-customize-themes-test.js index bd21cafb49..06eec98328 100644 --- a/test/javascripts/admin/controllers/admin-customize-themes-test.js +++ b/app/assets/javascripts/admin/tests/admin/unit/controllers/admin-customize-themes-test.js @@ -1,3 +1,5 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; import { mapRoutes } from "discourse/mapping-router"; import Theme from "admin/models/theme"; @@ -8,7 +10,7 @@ moduleFor("controller:admin-customize-themes", { needs: ["controller:adminUser"], }); -QUnit.test("can list themes correctly", function (assert) { +test("can list themes correctly", function (assert) { const defaultTheme = Theme.create({ id: 2, default: true, name: "default" }); const userTheme = Theme.create({ id: 3, diff --git a/test/javascripts/admin/controllers/admin-user-badges-test.js b/app/assets/javascripts/admin/tests/admin/unit/controllers/admin-user-badges-test.js similarity index 92% rename from test/javascripts/admin/controllers/admin-user-badges-test.js rename to app/assets/javascripts/admin/tests/admin/unit/controllers/admin-user-badges-test.js index 175d1f29af..b377f7f31d 100644 --- a/test/javascripts/admin/controllers/admin-user-badges-test.js +++ b/app/assets/javascripts/admin/tests/admin/unit/controllers/admin-user-badges-test.js @@ -1,3 +1,5 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; import Badge from "discourse/models/badge"; import { mapRoutes } from "discourse/mapping-router"; @@ -8,7 +10,7 @@ moduleFor("controller:admin-user-badges", { needs: ["controller:adminUser"], }); -QUnit.test("grantableBadges", function (assert) { +test("grantableBadges", function (assert) { const badgeFirst = Badge.create({ id: 3, name: "A Badge", diff --git a/test/javascripts/admin/models/theme-test.js b/app/assets/javascripts/admin/tests/admin/unit/models/theme-test.js similarity index 82% rename from test/javascripts/admin/models/theme-test.js rename to app/assets/javascripts/admin/tests/admin/unit/models/theme-test.js index d05cc24439..05f4dea5c1 100644 --- a/test/javascripts/admin/models/theme-test.js +++ b/app/assets/javascripts/admin/tests/admin/unit/models/theme-test.js @@ -1,8 +1,9 @@ +import { test, module } from "qunit"; import Theme from "admin/models/theme"; -QUnit.module("model:theme"); +module("model:theme"); -QUnit.test("can add an upload correctly", function (assert) { +test("can add an upload correctly", function (assert) { let theme = Theme.create(); assert.equal( diff --git a/app/assets/javascripts/discourse-loader.js b/app/assets/javascripts/discourse-loader.js index 28b0998d85..a9531575b2 100644 --- a/app/assets/javascripts/discourse-loader.js +++ b/app/assets/javascripts/discourse-loader.js @@ -7,6 +7,11 @@ var define, requirejs; "discourse-common/utils/decorators", "discourse/lib/raw-templates": "discourse-common/lib/raw-templates", "preload-store": "discourse/lib/preload-store", + "fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures", + }; + var ALIAS_PREPEND = { + fixtures: "discourse/tests/", + helpers: "discourse/tests/", }; // In future versions of ember we don't need this @@ -141,6 +146,9 @@ var define, requirejs; "@ember/object/internals": { guidFor: Ember.guidFor, }, + "@ember/test-helpers": { + setResolver: window.setResolver, + }, I18n: { // eslint-disable-next-line default: I18n, @@ -301,9 +309,13 @@ var define, requirejs; function transformForAliases(name) { var alias = ALIASES[name]; if (!alias) { - return name; + var segment = name.split("/")[0]; + var prepend = ALIAS_PREPEND[segment]; + if (!prepend) { + return name; + } + alias = prepend + name; } - deprecatedModule(name, alias); return alias; } diff --git a/app/assets/javascripts/discourse/app/components/d-button.js b/app/assets/javascripts/discourse/app/components/d-button.js index a501bbf2b5..b053b41867 100644 --- a/app/assets/javascripts/discourse/app/components/d-button.js +++ b/app/assets/javascripts/discourse/app/components/d-button.js @@ -94,6 +94,8 @@ export default Component.extend({ if (action) { if (typeof action === "string") { + // Note: This is deprecated in new Embers and needs to be removed in the future. + // There is already a warning in the console. this.sendAction("action", this.actionParam); } else if (typeof action === "object" && action.value) { action.value(this.actionParam); diff --git a/app/assets/javascripts/discourse/app/components/discourse-banner.js b/app/assets/javascripts/discourse/app/components/discourse-banner.js index e9149bb0e8..baf54cfdc2 100644 --- a/app/assets/javascripts/discourse/app/components/discourse-banner.js +++ b/app/assets/javascripts/discourse/app/components/discourse-banner.js @@ -2,6 +2,8 @@ import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; export default Component.extend({ + hide: false, + @discourseComputed("banner.html") content(bannerHtml) { const $div = $("
"); @@ -30,7 +32,7 @@ export default Component.extend({ if (this.user) { this.user.dismissBanner(this.get("banner.key")); } else { - this.set("visible", false); + this.set("hide", true); this.keyValueStore.set({ key: "dismissed_banner_key", value: this.get("banner.key"), diff --git a/app/assets/javascripts/discourse/app/components/discourse-linked-text.js b/app/assets/javascripts/discourse/app/components/discourse-linked-text.js index 0c60dd1b6d..acbb88021a 100644 --- a/app/assets/javascripts/discourse/app/components/discourse-linked-text.js +++ b/app/assets/javascripts/discourse/app/components/discourse-linked-text.js @@ -5,10 +5,10 @@ import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ tagName: "span", - @discourseComputed("text") + @discourseComputed("text", "textParams") translatedText(text) { if (text) { - return I18n.t(text); + return I18n.t(...arguments); } }, diff --git a/app/assets/javascripts/discourse/app/components/quote-button.js b/app/assets/javascripts/discourse/app/components/quote-button.js index 6665453a3a..e0b2bae75d 100644 --- a/app/assets/javascripts/discourse/app/components/quote-button.js +++ b/app/assets/javascripts/discourse/app/components/quote-button.js @@ -19,6 +19,12 @@ function getQuoteTitle(element) { if (!titleEl) { return; } + + const titleLink = titleEl.querySelector("a:not(.back)"); + if (titleLink) { + return titleLink.textContent.trim(); + } + return titleEl.textContent.trim().replace(/:$/, ""); } diff --git a/app/assets/javascripts/discourse/app/components/search-advanced-options.js b/app/assets/javascripts/discourse/app/components/search-advanced-options.js index 3c2e753abb..b4c40f833a 100644 --- a/app/assets/javascripts/discourse/app/components/search-advanced-options.js +++ b/app/assets/javascripts/discourse/app/components/search-advanced-options.js @@ -11,7 +11,8 @@ const REGEXP_CATEGORY_PREFIX = /^(category:|#)/gi; const REGEXP_TAGS_PREFIX = /^(tags?:|#(?=[a-z0-9\-]+::tag))/gi; const REGEXP_IN_PREFIX = /^(in|with):/gi; const REGEXP_STATUS_PREFIX = /^status:/gi; -const REGEXP_MIN_POST_COUNT_PREFIX = /^min_post_count:/gi; +const REGEXP_MIN_POSTS_PREFIX = /^min_posts:/gi; +const REGEXP_MAX_POSTS_PREFIX = /^max_posts:/gi; const REGEXP_MIN_VIEWS_PREFIX = /^min_views:/gi; const REGEXP_MAX_VIEWS_PREFIX = /^max_views:/gi; const REGEXP_POST_TIME_PREFIX = /^(before|after):/gi; @@ -94,7 +95,8 @@ export default Component.extend({ all_tags: false, }, status: null, - min_post_count: null, + min_posts: null, + max_posts: null, min_views: null, max_views: null, time: { @@ -162,8 +164,13 @@ export default Component.extend({ this.setSearchedTermValueForPostTime(); this.setSearchedTermValue( - "searchedTerms.min_post_count", - REGEXP_MIN_POST_COUNT_PREFIX + "searchedTerms.min_posts", + REGEXP_MIN_POSTS_PREFIX + ); + + this.setSearchedTermValue( + "searchedTerms.max_posts", + REGEXP_MAX_POSTS_PREFIX ); this.setSearchedTermValue( @@ -355,10 +362,16 @@ export default Component.extend({ @action onChangeSearchTermMinPostCount(value) { - this.set("searchedTerms.min_post_count", value.length ? value : null); + this.set("searchedTerms.min_posts", value.length ? value : null); this._updateSearchTermForMinPostCount(); }, + @action + onChangeSearchTermMaxPostCount(value) { + this.set("searchedTerms.max_posts", value.length ? value : null); + this._updateSearchTermForMaxPostCount(); + }, + @action onChangeSearchTermMinViews(value) { this.set("searchedTerms.min_views", value.length ? value : null); @@ -632,18 +645,40 @@ export default Component.extend({ }, _updateSearchTermForMinPostCount() { - const match = this.filterBlocks(REGEXP_MIN_POST_COUNT_PREFIX); - const postsCountFilter = this.get("searchedTerms.min_post_count"); + const match = this.filterBlocks(REGEXP_MIN_POSTS_PREFIX); + const postsCountFilter = this.get("searchedTerms.min_posts"); let searchTerm = this.searchTerm || ""; if (postsCountFilter) { if (match.length !== 0) { searchTerm = searchTerm.replace( match[0], - `min_post_count:${postsCountFilter}` + `min_posts:${postsCountFilter}` ); } else { - searchTerm += ` min_post_count:${postsCountFilter}`; + searchTerm += ` min_posts:${postsCountFilter}`; + } + + this._updateSearchTerm(searchTerm); + } else if (match.length !== 0) { + searchTerm = searchTerm.replace(match[0], ""); + this._updateSearchTerm(searchTerm); + } + }, + + _updateSearchTermForMaxPostCount() { + const match = this.filterBlocks(REGEXP_MAX_POSTS_PREFIX); + const postsCountFilter = this.get("searchedTerms.max_posts"); + let searchTerm = this.searchTerm || ""; + + if (postsCountFilter) { + if (match.length !== 0) { + searchTerm = searchTerm.replace( + match[0], + `max_posts:${postsCountFilter}` + ); + } else { + searchTerm += ` max_posts:${postsCountFilter}`; } this._updateSearchTerm(searchTerm); diff --git a/app/assets/javascripts/discourse/app/components/tag-groups-form.js b/app/assets/javascripts/discourse/app/components/tag-groups-form.js index c475a6c87d..de2d762100 100644 --- a/app/assets/javascripts/discourse/app/components/tag-groups-form.js +++ b/app/assets/javascripts/discourse/app/components/tag-groups-form.js @@ -4,32 +4,109 @@ import { isEmpty } from "@ember/utils"; import Component from "@ember/component"; import { bufferedProperty } from "discourse/mixins/buffered-content"; import PermissionType from "discourse/models/permission-type"; +import Group from "discourse/models/group"; import bootbox from "bootbox"; export default Component.extend(bufferedProperty("model"), { tagName: "", + allGroups: null, - @discourseComputed("buffered.isSaving", "buffered.name", "buffered.tag_names") - savingDisabled(isSaving, name, tagNames) { - return isSaving || isEmpty(name) || isEmpty(tagNames); + init() { + this._super(...arguments); + this.setGroupOptions(); + }, + + setGroupOptions() { + Group.findAll().then((groups) => { + this.set("allGroups", groups); + }); + }, + + @discourseComputed( + "buffered.isSaving", + "buffered.name", + "buffered.tag_names", + "buffered.permissions" + ) + savingDisabled(isSaving, name, tagNames, permissions) { + return ( + isSaving || + isEmpty(name) || + isEmpty(tagNames) || + (!this.everyoneSelected(permissions) && + isEmpty(this.selectedGroupNames(permissions))) + ); + }, + + @discourseComputed("buffered.permissions") + showPrivateChooser(permissions) { + if (!permissions) { + return true; + } + + return permissions.everyone !== PermissionType.READONLY; + }, + + @discourseComputed("buffered.permissions", "allGroups") + selectedGroupIds(permissions, allGroups) { + if (!permissions || !allGroups) { + return []; + } + + const selectedGroupNames = Object.keys(permissions); + let groupIds = []; + allGroups.forEach((group) => { + if (selectedGroupNames.includes(group.name)) { + groupIds.push(group.id); + } + }); + + return groupIds; + }, + + everyoneSelected(permissions) { + if (!permissions) { + return true; + } + + return permissions.everyone === PermissionType.FULL; + }, + + selectedGroupNames(permissions) { + if (!permissions) { + return []; + } + + return Object.keys(permissions).filter((name) => name !== "everyone"); }, actions: { - setPermissions(permissionName) { + setPermissionsType(permissionName) { + let updatedPermissions = Object.assign( + {}, + this.buffered.get("permissions") + ); + if (permissionName === "private") { - this.buffered.set("permissions", { - staff: PermissionType.FULL, - }); + delete updatedPermissions.everyone; } else if (permissionName === "visible") { - this.buffered.set("permissions", { - staff: PermissionType.FULL, - everyone: PermissionType.READONLY, - }); + updatedPermissions.everyone = PermissionType.READONLY; } else { - this.buffered.set("permissions", { - everyone: PermissionType.FULL, - }); + updatedPermissions.everyone = PermissionType.FULL; } + + this.buffered.set("permissions", updatedPermissions); + }, + + setPermissionsGroups(groupIds) { + let permissions = {}; + this.allGroups.forEach((group) => { + if (groupIds.includes(group.id)) { + permissions[group.name] = PermissionType.FULL; + } + }); + + this.buffered.set("permissions", permissions); }, save() { @@ -41,6 +118,14 @@ export default Component.extend(bufferedProperty("model"), { "permissions" ); + // If 'everyone' is set to full, we can remove any groups. + if ( + !attrs.permissions || + attrs.permissions.everyone === PermissionType.FULL + ) { + attrs.permissions = { everyone: PermissionType.FULL }; + } + this.model.save(attrs).then(() => { this.commitBuffer(); diff --git a/app/assets/javascripts/discourse/app/components/tag-info.js b/app/assets/javascripts/discourse/app/components/tag-info.js index 5dc585f2d5..0aef4eedb6 100644 --- a/app/assets/javascripts/discourse/app/components/tag-info.js +++ b/app/assets/javascripts/discourse/app/components/tag-info.js @@ -74,7 +74,7 @@ export default Component.extend({ }, deleteTag() { - this.sendAction("deleteAction", this.tagInfo); + this.deleteAction(this.tagInfo); }, unlinkSynonym(tag) { diff --git a/app/assets/javascripts/discourse/app/controllers/avatar-selector.js b/app/assets/javascripts/discourse/app/controllers/avatar-selector.js index 843a2bb61b..099919cbce 100644 --- a/app/assets/javascripts/discourse/app/controllers/avatar-selector.js +++ b/app/assets/javascripts/discourse/app/controllers/avatar-selector.js @@ -11,6 +11,31 @@ export default Controller.extend(ModalFunctionality, { gravatarBaseUrl: setting("gravatar_base_url"), gravatarLoginUrl: setting("gravatar_login_url"), + @discourseComputed( + "siteSettings.selectable_avatars_enabled", + "siteSettings.selectable_avatars" + ) + selectableAvatars(enabled, list) { + if (enabled) { + return list ? list.split("|") : []; + } + }, + + @discourseComputed( + "user.avatar_template", + "user.system_avatar_template", + "user.gravatar_avatar_template" + ) + selected(avatarTemplate, systemAvatarTemplate, gravatarAvatarTemplate) { + if (avatarTemplate === systemAvatarTemplate) { + return "system"; + } else if (avatarTemplate === gravatarAvatarTemplate) { + return "gravatar"; + } else { + return "custom"; + } + }, + @discourseComputed( "selected", "user.system_avatar_upload_id", @@ -55,7 +80,7 @@ export default Controller.extend(ModalFunctionality, { actions: { uploadComplete() { - this.set("selected", "uploaded"); + this.set("selected", "custom"); }, refreshGravatar() { diff --git a/app/assets/javascripts/discourse/app/controllers/create-account.js b/app/assets/javascripts/discourse/app/controllers/create-account.js index b0afe35163..d0976f6104 100644 --- a/app/assets/javascripts/discourse/app/controllers/create-account.js +++ b/app/assets/javascripts/discourse/app/controllers/create-account.js @@ -229,7 +229,7 @@ export default Controller.extend( return this._hpPromise; } - this._hpPromise = ajax(userPath("hp.json")) + this._hpPromise = ajax("/session/hp.json") .then((json) => { this._challengeDate = new Date(); // remove 30 seconds for jitter, make sure this works for at least diff --git a/app/assets/javascripts/discourse/app/controllers/discovery-sortable.js b/app/assets/javascripts/discourse/app/controllers/discovery-sortable.js index 0b84ba3304..73654634ae 100644 --- a/app/assets/javascripts/discourse/app/controllers/discovery-sortable.js +++ b/app/assets/javascripts/discourse/app/controllers/discovery-sortable.js @@ -39,10 +39,12 @@ export function changeSort(sortBy) { } } -export function resetParams() { +export function resetParams(skipParams = []) { let { controller } = this; controllerOpts.queryParams.forEach((p) => { - controller.set(p, queryParams[p].default); + if (!skipParams.includes(p)) { + controller.set(p, queryParams[p].default); + } }); } diff --git a/app/assets/javascripts/discourse/app/controllers/discovery/topics.js b/app/assets/javascripts/discourse/app/controllers/discovery/topics.js index c344dff51f..679ada3505 100644 --- a/app/assets/javascripts/discourse/app/controllers/discovery/topics.js +++ b/app/assets/javascripts/discourse/app/controllers/discovery/topics.js @@ -27,6 +27,7 @@ const controllerOpts = { router: service(), period: null, + canCreateTopicOnCategory: null, canStar: alias("currentUser.id"), showTopicPostBadges: not("discoveryTopics.new"), @@ -57,9 +58,9 @@ const controllerOpts = { return false; }, - refresh() { + refresh(options = { skipResettingParams: [] }) { const filter = this.get("model.filter"); - this.send("resetParams"); + this.send("resetParams", options.skipResettingParams); // Don't refresh if we're still loading if (this.get("discovery.loading")) { @@ -72,23 +73,36 @@ const controllerOpts = { this.set("discovery.loading", true); this.topicTrackingState.resetTracking(); + this.store.findFiltered("topicList", { filter }).then((list) => { TopicList.hideUniformCategory(list, this.category); - this.setProperties({ model: list }); - this.resetSelected(); - - if (this.topicTrackingState) { - this.topicTrackingState.sync(list, filter); + // If query params are present in the current route, we need still need to sync topic + // tracking with the topicList without any query params. Then we set the topic + // list to the list filtered with query params in the afterRefresh. + const params = this.router.currentRoute.queryParams; + if (Object.keys(params).length) { + this.store + .findFiltered("topicList", { filter, params }) + .then((listWithParams) => { + this.afterRefresh(filter, list, listWithParams); + }); + } else { + this.afterRefresh(filter, list); } - - this.send("loadingComplete"); }); }, resetNew() { - Topic.resetNew(this.category, !this.noSubcategories).then(() => - this.send("refresh") + const tracked = + (this.router.currentRoute.queryParams["f"] || + this.router.currentRoute.queryParams["filter"]) === "tracked"; + + Topic.resetNew(this.category, !this.noSubcategories, tracked).then(() => + this.send( + "refresh", + tracked ? { skipResettingParams: ["filter", "f"] } : {} + ) ); }, @@ -97,6 +111,17 @@ const controllerOpts = { }, }, + afterRefresh(filter, list, listModel = list) { + this.setProperties({ model: listModel }); + this.resetSelected(); + + if (this.topicTrackingState) { + this.topicTrackingState.sync(list, filter); + } + + this.send("loadingComplete"); + }, + isFilterPage: function (filter, filterType) { if (!filter) { return false; @@ -134,11 +159,6 @@ const controllerOpts = { weekly: equal("period", "weekly"), daily: equal("period", "daily"), - @discourseComputed("model") - canCreateTopicOnCategory(model) { - return model.can_create_topic; - }, - @discourseComputed("allLoaded", "model.topics.length") footerMessage(allLoaded, topicsLength) { if (!allLoaded) { diff --git a/app/assets/javascripts/discourse/app/controllers/group-requests.js b/app/assets/javascripts/discourse/app/controllers/group-requests.js index 70a84c4c89..a9f7e8dfd8 100644 --- a/app/assets/javascripts/discourse/app/controllers/group-requests.js +++ b/app/assets/javascripts/discourse/app/controllers/group-requests.js @@ -36,7 +36,7 @@ export default Controller.extend({ return; } - if (!refresh && model.members.length >= model.user_count) { + if (!refresh && model.requesters.length >= model.user_count) { this.set("application.showFooter", true); return; } diff --git a/app/assets/javascripts/discourse/app/controllers/insert-hyperlink.js b/app/assets/javascripts/discourse/app/controllers/insert-hyperlink.js index d3821b2aa3..d57eec38b4 100644 --- a/app/assets/javascripts/discourse/app/controllers/insert-hyperlink.js +++ b/app/assets/javascripts/discourse/app/controllers/insert-hyperlink.js @@ -4,6 +4,7 @@ import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { searchForTerm } from "discourse/lib/search"; import { bind } from "discourse-common/utils/decorators"; +import { prefixProtocol } from "discourse/lib/url"; export default Controller.extend(ModalFunctionality, { _debounced: null, @@ -144,8 +145,7 @@ export default Controller.extend(ModalFunctionality, { actions: { ok() { const origLink = this.linkUrl; - const linkUrl = - origLink.indexOf("://") === -1 ? `http://${origLink}` : origLink; + const linkUrl = prefixProtocol(origLink); const sel = this.toolbarEvent.selected; if (isEmpty(linkUrl)) { diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/email.js b/app/assets/javascripts/discourse/app/controllers/preferences/email.js index f18ed92c99..16772da45f 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/email.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/email.js @@ -15,6 +15,7 @@ export default Controller.extend({ success: false, oldEmail: null, newEmail: null, + successMessage: null, newEmailEmpty: empty("newEmail"), @@ -77,7 +78,25 @@ export default Controller.extend({ ? this.model.addEmail(this.newEmail) : this.model.changeEmail(this.newEmail) ).then( - () => this.set("success", true), + () => { + this.set("success", true); + + if (this.model.staff) { + this.set( + "successMessage", + I18n.t("user.change_email.success_staff") + ); + } else { + if (this.currentUser.admin) { + this.set( + "successMessage", + I18n.t("user.change_email.success_via_admin") + ); + } else { + this.set("successMessage", I18n.t("user.change_email.success")); + } + } + }, (e) => { this.setProperties({ error: true, saving: false }); if ( diff --git a/app/assets/javascripts/discourse/app/controllers/topic-bulk-actions.js b/app/assets/javascripts/discourse/app/controllers/topic-bulk-actions.js index 650b38fa26..8b902bf40b 100644 --- a/app/assets/javascripts/discourse/app/controllers/topic-bulk-actions.js +++ b/app/assets/javascripts/discourse/app/controllers/topic-bulk-actions.js @@ -68,6 +68,10 @@ addBulkButton("showAppendTagTopics", "append_tags", { class: "btn-default", enabledSetting: "tagging_enabled", }); +addBulkButton("removeTags", "remove_tags", { + icon: "tag", + class: "btn-danger", +}); addBulkButton("deleteTopics", "delete", { icon: "trash-alt", class: "btn-danger", @@ -201,6 +205,10 @@ export default Controller.extend(ModalFunctionality, { resetRead() { this.performAndRefresh({ type: "reset_read" }); }, + + removeTags() { + this.performAndRefresh({ type: "remove_tags" }); + }, }, }); diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js index 03f90ce7cf..c7b91cdf23 100644 --- a/app/assets/javascripts/discourse/app/controllers/topic.js +++ b/app/assets/javascripts/discourse/app/controllers/topic.js @@ -2,7 +2,7 @@ import I18n from "I18n"; import { isPresent, isEmpty } from "@ember/utils"; import { or, and, not, alias } from "@ember/object/computed"; import EmberObject from "@ember/object"; -import { next, schedule } from "@ember/runloop"; +import { next, schedule, later } from "@ember/runloop"; import Controller, { inject as controller } from "@ember/controller"; import { bufferedProperty } from "discourse/mixins/buffered-content"; import Composer from "discourse/models/composer"; @@ -30,6 +30,8 @@ import { deepMerge } from "discourse-common/lib/object"; let customPostMessageCallbacks = {}; +const RETRIES_ON_RATE_LIMIT = 4; + export function resetCustomPostMessageCallbacks() { customPostMessageCallbacks = {}; } @@ -1292,6 +1294,42 @@ export default Controller.extend(bufferedProperty("model"), { this.model.destroy(this.currentUser); }, + retryOnRateLimit(times, promise, topicId) { + const currentTopicId = this.get("model.id"); + topicId = topicId || currentTopicId; + if (topicId !== currentTopicId) { + // we navigated to another topic, so skip + return; + } + + if (this.retryRateLimited || times <= 0) { + return; + } + + promise().catch((e) => { + const xhr = e.jqXHR; + if ( + xhr && + xhr.status === 429 && + xhr.responseJSON && + xhr.responseJSON.extras && + xhr.responseJSON.extras.wait_seconds + ) { + let waitSeconds = xhr.responseJSON.extras.wait_seconds; + if (waitSeconds < 5) { + waitSeconds = 5; + } + + this.retryRateLimited = true; + + later(() => { + this.retryRateLimited = false; + this.retryOnRateLimit(times - 1, promise, topicId); + }, waitSeconds * 1000); + } + }); + }, + subscribe() { this.unsubscribe(); @@ -1363,7 +1401,22 @@ export default Controller.extend(bufferedProperty("model"), { break; } case "created": { - postStream.triggerNewPostInStream(data.id).then(() => refresh()); + this.newPostsInStream = this.newPostsInStream || []; + this.newPostsInStream.push(data.id); + + this.retryOnRateLimit(RETRIES_ON_RATE_LIMIT, () => { + const postIds = this.newPostsInStream; + this.newPostsInStream = []; + + return postStream + .triggerNewPostsInStream(postIds, { background: true }) + .then(() => refresh()) + .catch((e) => { + this.newPostsInStream = postIds.concat(this.newPostsInStream); + throw e; + }); + }); + if (this.get("currentUser.id") !== data.user_id) { this.documentTitle.incrementBackgroundContextCount(); } diff --git a/app/assets/javascripts/discourse/app/helpers/period-title.js b/app/assets/javascripts/discourse/app/helpers/period-title.js index cf5f6969f7..090f7cacd3 100644 --- a/app/assets/javascripts/discourse/app/helpers/period-title.js +++ b/app/assets/javascripts/discourse/app/helpers/period-title.js @@ -12,7 +12,7 @@ const TITLE_SUBS = { export default htmlHelper((period, options) => { const title = I18n.t("filters.top." + (TITLE_SUBS[period] || "this_week")); if (options.hash.showDateRange) { - var dateString = ""; + let dateString = ""; let finish; if (options.hash.fullDay) { @@ -41,11 +41,15 @@ export default htmlHelper((period, options) => { finish.format(I18n.t("dates.long_no_year_no_time")); break; case "weekly": + let start; + if (options.hash.fullDay) { + start = finish.clone().subtract(1, "week"); + } else { + start = finish.clone().subtract(6, "days"); + } + dateString = - finish - .clone() - .subtract(1, "week") - .format(I18n.t("dates.long_no_year_no_time")) + + start.format(I18n.t("dates.long_no_year_no_time")) + " - " + finish.format(I18n.t("dates.long_no_year_no_time")); break; diff --git a/app/assets/javascripts/discourse/app/helpers/share-url.js b/app/assets/javascripts/discourse/app/helpers/share-url.js new file mode 100644 index 0000000000..afa0f75c44 --- /dev/null +++ b/app/assets/javascripts/discourse/app/helpers/share-url.js @@ -0,0 +1,8 @@ +import { helperContext } from "discourse-common/lib/helpers"; + +export function resolveShareUrl(url, user) { + const badgesEnabled = helperContext().siteSettings.enable_badges; + const userSuffix = user && badgesEnabled ? `?u=${user.username_lower}` : ""; + + return url + userSuffix; +} diff --git a/app/assets/javascripts/discourse/app/initializers/avatar-select.js b/app/assets/javascripts/discourse/app/initializers/avatar-select.js deleted file mode 100644 index 2c3d48fc72..0000000000 --- a/app/assets/javascripts/discourse/app/initializers/avatar-select.js +++ /dev/null @@ -1,36 +0,0 @@ -import showModal from "discourse/lib/show-modal"; -import { ajax } from "discourse/lib/ajax"; - -export default { - name: "avatar-select", - - initialize(container) { - this.selectableAvatarsEnabled = container.lookup( - "site-settings:main" - ).selectable_avatars_enabled; - - container - .lookup("service:app-events") - .on("show-avatar-select", this, "_showAvatarSelect"); - }, - - _showAvatarSelect(user) { - const avatarTemplate = user.avatar_template; - let selected = "uploaded"; - - if (avatarTemplate === user.system_avatar_template) { - selected = "system"; - } else if (avatarTemplate === user.gravatar_avatar_template) { - selected = "gravatar"; - } - - const modal = showModal("avatar-selector"); - modal.setProperties({ user, selected }); - - if (this.selectableAvatarsEnabled) { - ajax("/site/selectable-avatars.json").then((avatars) => - modal.set("selectableAvatars", avatars) - ); - } - }, -}; diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index 56fa57a29e..4e2072948e 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -61,6 +61,10 @@ import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts"; import { addFeaturedLinkMetaDecorator } from "discourse/lib/render-topic-featured-link"; import { getOwner } from "discourse-common/lib/get-owner"; import { addAdvancedSearchOptions } from "discourse/components/search-advanced-options"; +import { + addSaveableUserField, + addSaveableUserOptionField, +} from "discourse/models/user"; // If you add any methods to the API ensure you bump up this number const PLUGIN_API_VERSION = "0.11.0"; @@ -1207,6 +1211,13 @@ class PluginApi { addAdvancedSearchOptions(options) { addAdvancedSearchOptions(options); } + + addSaveableUserField(fieldName) { + addSaveableUserField(fieldName); + } + addSaveableUserOptionField(fieldName) { + addSaveableUserOptionField(fieldName); + } } let _pluginv01; diff --git a/app/assets/javascripts/discourse/app/lib/to-markdown.js b/app/assets/javascripts/discourse/app/lib/to-markdown.js index 78e16d5901..2989d36e39 100644 --- a/app/assets/javascripts/discourse/app/lib/to-markdown.js +++ b/app/assets/javascripts/discourse/app/lib/to-markdown.js @@ -25,7 +25,16 @@ export class Tag { } if (this.inline) { - text = " " + text + " "; + const prev = this.element.prev; + const next = this.element.next; + + if (prev && prev.name !== "#text") { + text = " " + text; + } + + if (next && next.name !== "#text") { + text = text + " "; + } } return text; diff --git a/app/assets/javascripts/discourse/app/lib/url.js b/app/assets/javascripts/discourse/app/lib/url.js index 5acade559a..7f90a93570 100644 --- a/app/assets/javascripts/discourse/app/lib/url.js +++ b/app/assets/javascripts/discourse/app/lib/url.js @@ -34,6 +34,7 @@ const SERVER_SIDE_ONLY = [ /^\/admin\/logs\/watched_words\/action\/[^\/]+\/download$/, /^\/pub\//, /^\/invites\//, + /^\/styleguide/, ]; // The amount of height (in pixles) that we factor in when jumpEnd is called so @@ -493,4 +494,10 @@ export function setURLContainer(container) { setOwner(_urlInstance, container); } +export function prefixProtocol(url) { + return url.indexOf("://") === -1 && url.indexOf("mailto:") !== 0 + ? "https://" + url + : url; +} + export default _urlInstance; diff --git a/app/assets/javascripts/discourse/app/mixins/bulk-topic-selection.js b/app/assets/javascripts/discourse/app/mixins/bulk-topic-selection.js index e495649464..279fe79fa3 100644 --- a/app/assets/javascripts/discourse/app/mixins/bulk-topic-selection.js +++ b/app/assets/javascripts/discourse/app/mixins/bulk-topic-selection.js @@ -22,22 +22,21 @@ export default Mixin.create({ }, dismissRead(operationType, options) { - let operation; - if (operationType === "posts") { - operation = { type: "dismiss_posts" }; - } else { - operation = { - type: "change_notification_level", - notification_level_id: NotificationLevels.REGULAR, - }; - } + const operation = + operationType === "posts" + ? { type: "dismiss_posts" } + : { + type: "change_notification_level", + notification_level_id: NotificationLevels.REGULAR, + }; - let promise; - if (this.selected.length > 0) { - promise = Topic.bulkOperation(this.selected, operation); - } else { - promise = Topic.bulkOperationByFilter("unread", operation, options); - } + const tracked = + (this.router.currentRoute.queryParams["f"] || + this.router.currentRoute.queryParams["filter"]) === "tracked"; + + const promise = this.selected.length + ? Topic.bulkOperation(this.selected, operation, tracked) + : Topic.bulkOperationByFilter("unread", operation, options, tracked); promise.then((result) => { if (result && result.topic_ids) { @@ -47,7 +46,10 @@ export default Mixin.create({ } this.send("closeModal"); - this.send("refresh"); + this.send( + "refresh", + tracked ? { skipResettingParams: ["filter", "f"] } : {} + ); }); }, }, diff --git a/app/assets/javascripts/discourse/app/models/composer.js b/app/assets/javascripts/discourse/app/models/composer.js index 44ff23abe0..0d6c4367d8 100644 --- a/app/assets/javascripts/discourse/app/models/composer.js +++ b/app/assets/javascripts/discourse/app/models/composer.js @@ -113,6 +113,7 @@ const Composer = RestModel.extend({ noBump: false, draftSaving: false, draftSaved: false, + draftForceSave: false, archetypes: reads("site.archetypes"), @@ -1171,7 +1172,8 @@ const Composer = RestModel.extend({ this.draftKey, this.draftSequence, data, - this.messageBus.clientId + this.messageBus.clientId, + { forceSave: this.draftForceSave } ) .then((result) => { if (result.draft_sequence) { @@ -1186,6 +1188,7 @@ const Composer = RestModel.extend({ this.setProperties({ draftSaved: true, draftConflictUser: null, + draftForceSave: false, }); } }) @@ -1203,10 +1206,29 @@ const Composer = RestModel.extend({ const json = e.jqXHR.responseJSON; draftStatus = json.errors[0]; if (json.extras && json.extras.description) { - bootbox.alert(json.extras.description); + const buttons = []; + + // ignore and force save draft + buttons.push({ + label: I18n.t("composer.ignore"), + class: "btn", + callback: () => { + this.set("draftForceSave", true); + }, + }); + + // reload + buttons.push({ + label: I18n.t("composer.reload"), + class: "btn btn-primary", + callback: () => { + window.location.reload(); + }, + }); + + bootbox.dialog(json.extras.description, buttons); } } - this.setProperties({ draftStatus: draftStatus || I18n.t("composer.drafts_offline"), draftConflictUser: null, diff --git a/app/assets/javascripts/discourse/app/models/draft.js b/app/assets/javascripts/discourse/app/models/draft.js index cc09c97a08..25bddd8329 100644 --- a/app/assets/javascripts/discourse/app/models/draft.js +++ b/app/assets/javascripts/discourse/app/models/draft.js @@ -23,11 +23,17 @@ Draft.reopenClass({ return current; }, - save(key, sequence, data, clientId) { + save(key, sequence, data, clientId, { forceSave = false } = {}) { data = typeof data === "string" ? data : JSON.stringify(data); return ajax("/draft.json", { type: "POST", - data: { draft_key: key, sequence, data, owner: clientId }, + data: { + draft_key: key, + sequence, + data, + owner: clientId, + force_save: forceSave, + }, }); }, }); diff --git a/app/assets/javascripts/discourse/app/models/post-stream.js b/app/assets/javascripts/discourse/app/models/post-stream.js index 340b1b2c6f..d68c2cac41 100644 --- a/app/assets/javascripts/discourse/app/models/post-stream.js +++ b/app/assets/javascripts/discourse/app/models/post-stream.js @@ -11,6 +11,7 @@ import { loadTopicView } from "discourse/models/topic"; import { Promise } from "rsvp"; import User from "discourse/models/user"; import { deepMerge } from "discourse-common/lib/object"; +import deprecated from "discourse-common/lib/deprecated"; export default RestModel.extend({ _identityMap: null, @@ -599,15 +600,25 @@ export default RestModel.extend({ }); }, + /* mainly for backwards compatability with plugins, used in quick messages plugin + * TODO: remove July 2021 + * */ + triggerNewPostInStream(postId, opts) { + deprecated( + "Please use triggerNewPostsInStream, this method will be removed July 2021" + ); + return this.triggerNewPostsInStream([postId], opts); + }, + /** - Finds and adds a post to the stream by id. Typically this would happen if we receive a message + Finds and adds posts to the stream by id. Typically this would happen if we receive a message from the message bus indicating there's a new post. We'll only insert it if we currently have no filters. **/ - triggerNewPostInStream(postId) { + triggerNewPostsInStream(postIds, opts) { const resolved = Promise.resolve(); - if (!postId) { + if (!postIds || postIds.length === 0) { return resolved; } @@ -617,27 +628,46 @@ export default RestModel.extend({ } const loadedAllPosts = this.loadedAllPosts; + this._loadingPostIds = this._loadingPostIds || []; - if (this.stream.indexOf(postId) === -1) { - this.stream.addObject(postId); - if (loadedAllPosts) { - this.set("loadingLastPost", true); - return this.findPostsByIds([postId]) - .then((posts) => { - const ignoredUsers = - User.current() && User.current().get("ignored_users"); - posts.forEach((p) => { - if (ignoredUsers && ignoredUsers.includes(p.username)) { - this.stream.removeObject(postId); - return; - } - this.appendPost(p); - }); - }) - .finally(() => { - this.set("loadingLastPost", false); - }); + let missingIds = []; + + postIds.forEach((postId) => { + if (postId && this.stream.indexOf(postId) === -1) { + missingIds.push(postId); } + }); + + if (missingIds.length === 0) { + return resolved; + } + + if (loadedAllPosts) { + missingIds.forEach((postId) => { + if (this._loadingPostIds.indexOf(postId) === -1) { + this._loadingPostIds.push(postId); + } + }); + this.set("loadingLastPost", true); + return this.findPostsByIds(this._loadingPostIds, opts) + .then((posts) => { + this._loadingPostIds = null; + const ignoredUsers = + User.current() && User.current().get("ignored_users"); + posts.forEach((p) => { + if (ignoredUsers && ignoredUsers.includes(p.username)) { + this.stream.removeObject(p.id); + return; + } + this.stream.addObject(p.id); + this.appendPost(p); + }); + }) + .finally(() => { + this.set("loadingLastPost", false); + }); + } else { + missingIds.forEach((postId) => this.stream.addObject(postId)); } return resolved; @@ -789,11 +819,11 @@ export default RestModel.extend({ // Get the index in the stream of a post id. (Use this for the topic progress bar.) progressIndexOfPostId(post) { const postId = post.get("id"); - const index = this.stream.indexOf(postId); if (this.isMegaTopic) { return post.get("post_number"); } else { + const index = this.stream.indexOf(postId); return index + 1; } }, @@ -972,17 +1002,17 @@ export default RestModel.extend({ }); }, - findPostsByIds(postIds) { + findPostsByIds(postIds, opts) { const identityMap = this._identityMap; const unloaded = postIds.filter((p) => !identityMap[p]); // Load our unloaded posts by id - return this.loadIntoIdentityMap(unloaded).then(() => { + return this.loadIntoIdentityMap(unloaded, opts).then(() => { return postIds.map((p) => identityMap[p]).compact(); }); }, - loadIntoIdentityMap(postIds) { + loadIntoIdentityMap(postIds, opts) { if (isEmpty(postIds)) { return Promise.resolve([]); } @@ -993,7 +1023,15 @@ export default RestModel.extend({ const data = { post_ids: postIds, include_suggested: includeSuggested }; const store = this.store; - return ajax(url, { data }).then((result) => { + let headers = {}; + if (opts && opts.background) { + headers["Discourse-Background"] = "true"; + } + + return ajax(url, { + data, + headers, + }).then((result) => { if (result.suggested_topics) { this.set("topic.suggested_topics", result.suggested_topics); } diff --git a/app/assets/javascripts/discourse/app/models/post.js b/app/assets/javascripts/discourse/app/models/post.js index 512cf9ea8c..8da2e30627 100644 --- a/app/assets/javascripts/discourse/app/models/post.js +++ b/app/assets/javascripts/discourse/app/models/post.js @@ -17,18 +17,13 @@ import Site from "discourse/models/site"; import User from "discourse/models/user"; import showModal from "discourse/lib/show-modal"; import { fancyTitle } from "discourse/lib/topic-fancy-title"; +import { resolveShareUrl } from "discourse/helpers/share-url"; const Post = RestModel.extend({ @discourseComputed("url") shareUrl(url) { const user = User.current(); - const userSuffix = user ? `?u=${user.username_lower}` : ""; - - if (this.firstPost) { - return this.get("topic.url") + userSuffix; - } else { - return url + userSuffix; - } + return resolveShareUrl(url, user); }, new_user: equal("trust_level", 0), diff --git a/app/assets/javascripts/discourse/app/models/topic.js b/app/assets/javascripts/discourse/app/models/topic.js index 39b8b264f0..13ab5fae0b 100644 --- a/app/assets/javascripts/discourse/app/models/topic.js +++ b/app/assets/javascripts/discourse/app/models/topic.js @@ -24,6 +24,7 @@ import Site from "discourse/models/site"; import User from "discourse/models/user"; import bootbox from "bootbox"; import { deepMerge } from "discourse-common/lib/object"; +import { resolveShareUrl } from "discourse/helpers/share-url"; export function loadTopicView(topic, args) { const data = deepMerge({}, args); @@ -242,8 +243,7 @@ const Topic = RestModel.extend({ @discourseComputed("url") shareUrl(url) { const user = User.current(); - const userQueryString = user ? `?u=${user.get("username_lower")}` : ""; - return `${url}${userQueryString}`; + return resolveShareUrl(url, user); }, printUrl: fmt("url", "%@/print"), @@ -799,18 +799,21 @@ Topic.reopenClass({ return promise; }, - bulkOperation(topics, operation) { + bulkOperation(topics, operation, tracked) { + const data = { + topic_ids: topics.mapBy("id"), + operation, + tracked, + }; + return ajax("/topics/bulk", { type: "PUT", - data: { - topic_ids: topics.map((t) => t.get("id")), - operation, - }, + data, }); }, - bulkOperationByFilter(filter, operation, options) { - let data = { filter, operation }; + bulkOperationByFilter(filter, operation, options, tracked) { + const data = { filter, operation, tracked }; if (options) { if (options.categoryId) { @@ -830,10 +833,12 @@ Topic.reopenClass({ }); }, - resetNew(category, include_subcategories) { - const data = category - ? { category_id: category.id, include_subcategories } - : {}; + resetNew(category, include_subcategories, tracked = false) { + const data = { tracked }; + if (category) { + data.category_id = category.id; + data.include_subcategories = include_subcategories; + } return ajax("/topics/reset-new", { type: "PUT", data }); }, diff --git a/app/assets/javascripts/discourse/app/models/user.js b/app/assets/javascripts/discourse/app/models/user.js index 98baf09ec0..ac9ed573e6 100644 --- a/app/assets/javascripts/discourse/app/models/user.js +++ b/app/assets/javascripts/discourse/app/models/user.js @@ -41,6 +41,68 @@ export const SECOND_FACTOR_METHODS = { const isForever = (dt) => moment().diff(dt, "years") < -500; +let userFields = [ + "bio_raw", + "website", + "location", + "name", + "title", + "locale", + "custom_fields", + "user_fields", + "muted_usernames", + "ignored_usernames", + "allowed_pm_usernames", + "profile_background_upload_url", + "card_background_upload_url", + "muted_tags", + "tracked_tags", + "watched_tags", + "watching_first_post_tags", + "date_of_birth", + "primary_group_id", +]; + +export function addSaveableUserField(fieldName) { + userFields.push(fieldName); +} + +let userOptionFields = [ + "mailing_list_mode", + "mailing_list_mode_frequency", + "external_links_in_new_tab", + "email_digests", + "email_in_reply_to", + "email_messages_level", + "email_level", + "email_previous_replies", + "color_scheme_id", + "dark_scheme_id", + "dynamic_favicon", + "enable_quoting", + "enable_defer", + "automatically_unpin_topics", + "digest_after_minutes", + "new_topic_duration_minutes", + "auto_track_topics_after_msecs", + "notification_level_when_replying", + "like_notification_frequency", + "include_tl0_in_digests", + "theme_ids", + "allow_private_messages", + "enable_allowed_pm_users", + "homepage_id", + "hide_profile_and_presence", + "text_size", + "title_count_mode", + "timezone", + "skip_new_user_tips", +]; + +export function addSaveableUserOptionField(fieldName) { + userOptionFields.push(fieldName); +} + const User = RestModel.extend({ hasPMs: gt("private_messages_stats.all", 0), hasStartedPMs: gt("private_messages_stats.mine", 0), @@ -267,64 +329,10 @@ const User = RestModel.extend({ }, save(fields) { - let userFields = [ - "bio_raw", - "website", - "location", - "name", - "title", - "locale", - "custom_fields", - "user_fields", - "muted_usernames", - "ignored_usernames", - "allowed_pm_usernames", - "profile_background_upload_url", - "card_background_upload_url", - "muted_tags", - "tracked_tags", - "watched_tags", - "watching_first_post_tags", - "date_of_birth", - "primary_group_id", - ]; - const data = this.getProperties( userFields.filter((uf) => !fields || fields.indexOf(uf) !== -1) ); - let userOptionFields = [ - "mailing_list_mode", - "mailing_list_mode_frequency", - "external_links_in_new_tab", - "email_digests", - "email_in_reply_to", - "email_messages_level", - "email_level", - "email_previous_replies", - "color_scheme_id", - "dark_scheme_id", - "dynamic_favicon", - "enable_quoting", - "enable_defer", - "automatically_unpin_topics", - "digest_after_minutes", - "new_topic_duration_minutes", - "auto_track_topics_after_msecs", - "notification_level_when_replying", - "like_notification_frequency", - "include_tl0_in_digests", - "theme_ids", - "allow_private_messages", - "enable_allowed_pm_users", - "homepage_id", - "hide_profile_and_presence", - "text_size", - "title_count_mode", - "timezone", - "skip_new_user_tips", - ]; - if (fields) { userOptionFields = userOptionFields.filter( (uo) => fields.indexOf(uo) !== -1 diff --git a/app/assets/javascripts/discourse/app/routes/preferences-account.js b/app/assets/javascripts/discourse/app/routes/preferences-account.js index bfd5874edb..7e2fee5b55 100644 --- a/app/assets/javascripts/discourse/app/routes/preferences-account.js +++ b/app/assets/javascripts/discourse/app/routes/preferences-account.js @@ -1,3 +1,4 @@ +import showModal from "discourse/lib/show-modal"; import UserBadge from "discourse/models/user-badge"; import RestrictedUserRoute from "discourse/routes/restricted-user"; @@ -33,7 +34,7 @@ export default RestrictedUserRoute.extend({ actions: { showAvatarSelector(user) { - this.appEvents.trigger("show-avatar-select", user); + showModal("avatar-selector").setProperties({ user }); }, }, }); diff --git a/app/assets/javascripts/discourse/app/services/document-title.js b/app/assets/javascripts/discourse/app/services/document-title.js index 709b5e3664..944940c098 100644 --- a/app/assets/javascripts/discourse/app/services/document-title.js +++ b/app/assets/javascripts/discourse/app/services/document-title.js @@ -44,6 +44,7 @@ export default Service.extend({ this.notificationCount = 0; } this.appEvents.trigger("discourse:focus-changed", session.hasFocus); + this._renderFavicon(); this._renderTitle(); }, diff --git a/app/assets/javascripts/discourse/app/templates/components/category-read-only-banner.hbs b/app/assets/javascripts/discourse/app/templates/components/category-read-only-banner.hbs index 4c00dc8044..902d051eb0 100644 --- a/app/assets/javascripts/discourse/app/templates/components/category-read-only-banner.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/category-read-only-banner.hbs @@ -1,7 +1,7 @@ {{#if shouldShow}}
- {{category.read_only_banner}} + {{html-safe category.read_only_banner}}
{{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/components/discourse-banner.hbs b/app/assets/javascripts/discourse/app/templates/components/discourse-banner.hbs index 9b6b18c530..dc074862b6 100644 --- a/app/assets/javascripts/discourse/app/templates/components/discourse-banner.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/discourse-banner.hbs @@ -1,7 +1,7 @@ {{#if visible}}
-
+ +
-
- {{input - type="number" - value=(readonly searchedTerms.min_post_count) - class="input-small" - id="search-min-post-count" - input=(action "onChangeSearchTermMinPostCount" value="target.value") - }} +
+
+ {{input + type="number" + value=(readonly searchedTerms.min_posts) + class="input-small" + id="search-min-post-count" + input=(action "onChangeSearchTermMinPostCount" value="target.value") + placeholder=(i18n "search.advanced.post.min.placeholder") + }} +
+
+ +
+
+ {{input + type="number" + value=(readonly searchedTerms.max_posts) + class="input-small" + id="search-max-post-count" + input=(action "onChangeSearchTermMaxPostCount" value="target.value") + placeholder=(i18n "search.advanced.post.max.placeholder") + }} +
-
- -
- {{input - type="number" - value=(readonly searchedTerms.min_views) - class="input-small" - id="search-min-views" - input=(action "onChangeSearchTermMinViews" value="target.value") - }} +
+ +
+
+ {{input + type="number" + value=(readonly searchedTerms.min_views) + class="input-small" + id="search-min-views" + input=(action "onChangeSearchTermMinViews" value="target.value") + placeholder=(i18n "search.advanced.min_views.placeholder") + }} +
+
+ +
+
+ {{input + type="number" + value=(readonly searchedTerms.max_views) + class="input-small" + id="search-max-views" + input=(action "onChangeSearchTermMaxViews" value="target.value") + placeholder=(i18n "search.advanced.max_views.placeholder") + }} +
- -
- -
- {{input - type="number" - value=(readonly searchedTerms.max_views) - class="input-small" - id="search-max-views" - input=(action "onChangeSearchTermMaxViews" value="target.value") - }} -
-
-
{{plugin-outlet name="advanced-search-options-below" args=(hash searchedTerms=searchedTerms onChangeSearchedTermField=onChangeSearchedTermField) tagName=""}} diff --git a/app/assets/javascripts/discourse/app/templates/components/share-popup.hbs b/app/assets/javascripts/discourse/app/templates/components/share-popup.hbs index 548f71e7d9..695be0aeb5 100644 --- a/app/assets/javascripts/discourse/app/templates/components/share-popup.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/share-popup.hbs @@ -34,6 +34,12 @@
{{/if}} - {{d-button action="close" class="btn btn-flat close" icon="times" aria-label="share.close" title="share.close"}} + {{d-button + action=(action "close") + class="btn btn-flat close" + icon="times" + aria-label="share.close" + title="share.close" + }}
diff --git a/app/assets/javascripts/discourse/app/templates/components/tag-groups-form.hbs b/app/assets/javascripts/discourse/app/templates/components/tag-groups-form.hbs index 0fdf7ec3c4..38a950ba19 100644 --- a/app/assets/javascripts/discourse/app/templates/components/tag-groups-form.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/tag-groups-form.hbs @@ -38,7 +38,7 @@ value="public" id="public-permission" selection=buffered.permissionName - onChange=(action "setPermissions")}} + onChange=(action "setPermissionsType")}}
{{radio-button @@ -64,12 +73,20 @@ value="private" id="private-permission" selection=buffered.permissionName - onChange=(action "setPermissions")}} + onChange=(action "setPermissionsType")}}
+ +
+ {{group-chooser + content=allGroups + value=selectedGroupIds + labelProperty="name" + onChange=(action "setPermissionsGroups")}} +
{{d-button diff --git a/app/assets/javascripts/discourse/app/templates/preferences-email.hbs b/app/assets/javascripts/discourse/app/templates/preferences-email.hbs index 447d220403..5dd2eb1826 100644 --- a/app/assets/javascripts/discourse/app/templates/preferences-email.hbs +++ b/app/assets/javascripts/discourse/app/templates/preferences-email.hbs @@ -11,13 +11,7 @@
-

- {{#if model.staff}} - {{i18n "user.change_email.success_staff"}} - {{else}} - {{i18n "user.change_email.success"}} - {{/if}} -

+

{{ successMessage }}

diff --git a/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs b/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs index e53cd172c2..d51e6de26f 100644 --- a/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs +++ b/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs @@ -135,7 +135,9 @@ {{#if siteSettings.automatically_unpin_topics}} {{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.user_option.automatically_unpin_topics class="pref-auto-unpin"}} {{/if}} - {{preference-checkbox labelKey="user.hide_profile_and_presence" checked=model.user_option.hide_profile_and_presence class="pref-hide-profile"}} + {{#if siteSettings.allow_users_to_hide_profile}} + {{preference-checkbox labelKey="user.hide_profile_and_presence" checked=model.user_option.hide_profile_and_presence class="pref-hide-profile"}} + {{/if}} {{#if isiPad}} {{preference-checkbox labelKey="user.enable_physical_keyboard" checked=disableSafariHacks class="pref-safari-hacks"}} {{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/tags/show.hbs b/app/assets/javascripts/discourse/app/templates/tags/show.hbs index dded4a1084..ca59b27c9b 100644 --- a/app/assets/javascripts/discourse/app/templates/tags/show.hbs +++ b/app/assets/javascripts/discourse/app/templates/tags/show.hbs @@ -1,4 +1,4 @@ -{{#d-section tagName="" pageClass="tags" bodyClass=(concat "tag-" tag.id)}} +{{#d-section tagName="" pageClass="tags" bodyClass=(concat "tag-" tag.id (if category.slug (concat " category-" category.slug)) "")}}
{{discourse-banner user=currentUser banner=site.banner}}
diff --git a/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs b/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs index 9396e7f90f..2079edb3e2 100644 --- a/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs +++ b/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs @@ -145,5 +145,9 @@ {{/if}} {{/load-more}} + {{else}} +
+ {{model.error}} +
{{/if}} {{/d-section}} diff --git a/app/assets/javascripts/discourse/app/widgets/link.js b/app/assets/javascripts/discourse/app/widgets/link.js index dc2d52e143..63452b0520 100644 --- a/app/assets/javascripts/discourse/app/widgets/link.js +++ b/app/assets/javascripts/discourse/app/widgets/link.js @@ -97,14 +97,18 @@ export default createWidget("link", { ); } } - return result; }, click(e) { + if (this.attrs.attributes && this.attrs.attributes.target === "_blank") { + return; + } + if (wantsNewWindow(e)) { return; } + e.preventDefault(); if (this.attrs.action) { diff --git a/app/assets/javascripts/discourse/app/widgets/user-menu.js b/app/assets/javascripts/discourse/app/widgets/user-menu.js index b37354b9f9..79379340f7 100644 --- a/app/assets/javascripts/discourse/app/widgets/user-menu.js +++ b/app/assets/javascripts/discourse/app/widgets/user-menu.js @@ -27,8 +27,8 @@ createWidget("user-menu-links", { return { label: "user.preferences", className: "user-preferences-link", - icon: "cog", - href: `${this.attrs.path}/preferences`, + icon: "user", + href: `${this.attrs.path}/summary`, action: UserMenuAction.QUICK_ACCESS, actionParam: QuickAccess.PROFILE, }; diff --git a/test/javascripts/acceptance/about-test.js b/app/assets/javascripts/discourse/tests/acceptance/about-test.js similarity index 69% rename from test/javascripts/acceptance/about-test.js rename to app/assets/javascripts/discourse/tests/acceptance/about-test.js index 755fa7232b..f58b5e4682 100644 --- a/test/javascripts/acceptance/about-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/about-test.js @@ -1,7 +1,8 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("About"); -QUnit.test("viewing", async (assert) => { +test("viewing", async (assert) => { await visit("/about"); assert.ok($("body.about-page").length, "has body class"); diff --git a/test/javascripts/acceptance/account-created-test.js b/app/assets/javascripts/discourse/tests/acceptance/account-created-test.js similarity index 86% rename from test/javascripts/acceptance/account-created-test.js rename to app/assets/javascripts/discourse/tests/acceptance/account-created-test.js index 1680f14088..a03faf24bc 100644 --- a/test/javascripts/acceptance/account-created-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/account-created-test.js @@ -1,9 +1,10 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import PreloadStore from "discourse/lib/preload-store"; acceptance("Account Created"); -QUnit.test("account created - message", async (assert) => { +test("account created - message", async (assert) => { PreloadStore.store("accountCreated", { message: "Hello World", }); @@ -18,7 +19,7 @@ QUnit.test("account created - message", async (assert) => { assert.notOk(exists(".activation-controls")); }); -QUnit.test("account created - resend email", async (assert) => { +test("account created - resend email", async (assert) => { PreloadStore.store("accountCreated", { message: "Hello World", username: "eviltrout", @@ -42,7 +43,7 @@ QUnit.test("account created - resend email", async (assert) => { assert.equal(email, "eviltrout@example.com"); }); -QUnit.test("account created - update email - cancel", async (assert) => { +test("account created - update email - cancel", async (assert) => { PreloadStore.store("accountCreated", { message: "Hello World", username: "eviltrout", @@ -62,7 +63,7 @@ QUnit.test("account created - update email - cancel", async (assert) => { assert.equal(currentPath(), "account-created.index"); }); -QUnit.test("account created - update email - submit", async (assert) => { +test("account created - update email - submit", async (assert) => { PreloadStore.store("accountCreated", { message: "Hello World", username: "eviltrout", diff --git a/test/javascripts/acceptance/admin-emails-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-emails-test.js similarity index 79% rename from test/javascripts/acceptance/admin-emails-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-emails-test.js index 1de408c83e..33d5f161f4 100644 --- a/test/javascripts/acceptance/admin-emails-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-emails-test.js @@ -1,5 +1,6 @@ -import { acceptance } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; acceptance("Admin - Emails", { loggedIn: true }); @@ -16,7 +17,7 @@ Hello, this is a test! This part should be elided.`.trim(); -QUnit.test("shows selected and elided text", async (assert) => { +test("shows selected and elided text", async (assert) => { pretender.post("/admin/email/advanced-test", () => { return [ 200, diff --git a/test/javascripts/acceptance/admin-search-log-term-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-search-log-term-test.js similarity index 69% rename from test/javascripts/acceptance/admin-search-log-term-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-search-log-term-test.js index ea5e96e275..b0b9a000c0 100644 --- a/test/javascripts/acceptance/admin-search-log-term-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-search-log-term-test.js @@ -1,7 +1,8 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { skip } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Admin - Search Log Term", { loggedIn: true }); -QUnit.skip("show search log term details", async (assert) => { +skip("show search log term details", async (assert) => { await visit("/admin/logs/search_logs/term?term=ruby"); assert.ok($("div.search-logs-filter").length, "has the search type filter"); diff --git a/test/javascripts/acceptance/admin-search-logs-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-search-logs-test.js similarity index 74% rename from test/javascripts/acceptance/admin-search-logs-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-search-logs-test.js index fafbd651cd..e7808c4319 100644 --- a/test/javascripts/acceptance/admin-search-logs-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-search-logs-test.js @@ -1,7 +1,8 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { skip } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Admin - Search Logs", { loggedIn: true }); -QUnit.skip("show search logs", async (assert) => { +skip("show search logs", async (assert) => { await visit("/admin/logs/search_logs"); assert.ok($("table.search-logs-list.grid").length, "has the div class"); diff --git a/test/javascripts/acceptance/admin-site-settings-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-site-settings-test.js similarity index 76% rename from test/javascripts/acceptance/admin-site-settings-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-site-settings-test.js index 0780a1b3cb..2b8d7241aa 100644 --- a/test/javascripts/acceptance/admin-site-settings-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-site-settings-test.js @@ -1,5 +1,6 @@ -import { acceptance } from "helpers/qunit-helpers"; -import siteSettingFixture from "fixtures/site_settings"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import siteSettingFixture from "discourse/tests/fixtures/site-settings"; var titleOverride = undefined; @@ -29,7 +30,7 @@ acceptance("Admin - Site Settings", { }, }); -QUnit.test("upload site setting", async (assert) => { +test("upload site setting", async (assert) => { await visit("/admin/site_settings"); assert.ok( @@ -40,7 +41,7 @@ QUnit.test("upload site setting", async (assert) => { assert.ok(exists(".row.setting.upload .undo"), "undo button is present"); }); -QUnit.test("changing value updates dirty state", async (assert) => { +test("changing value updates dirty state", async (assert) => { await visit("/admin/site_settings"); await fillIn("#setting-filter", " title "); assert.equal(count(".row.setting"), 1, "filter returns 1 site setting"); @@ -87,24 +88,21 @@ QUnit.test("changing value updates dirty state", async (assert) => { ); }); -QUnit.test( - "always shows filtered site settings if a filter is set", - async (assert) => { - await visit("/admin/site_settings"); - await fillIn("#setting-filter", "title"); - assert.equal(count(".row.setting"), 1); +test("always shows filtered site settings if a filter is set", async (assert) => { + await visit("/admin/site_settings"); + await fillIn("#setting-filter", "title"); + assert.equal(count(".row.setting"), 1); - // navigate away to the "Dashboard" page - await click(".nav.nav-pills li:nth-child(1) a"); - assert.equal(count(".row.setting"), 0); + // navigate away to the "Dashboard" page + await click(".nav.nav-pills li:nth-child(1) a"); + assert.equal(count(".row.setting"), 0); - // navigate back to the "Settings" page - await click(".nav.nav-pills li:nth-child(2) a"); - assert.equal(count(".row.setting"), 1); - } -); + // navigate back to the "Settings" page + await click(".nav.nav-pills li:nth-child(2) a"); + assert.equal(count(".row.setting"), 1); +}); -QUnit.test("filter settings by plugin name", async (assert) => { +test("filter settings by plugin name", async (assert) => { await visit("/admin/site_settings"); await fillIn("#setting-filter", "plugin:discourse-logo"); @@ -115,12 +113,12 @@ QUnit.test("filter settings by plugin name", async (assert) => { assert.equal(count(".row.setting"), 0); }); -QUnit.test("category name is preserved", async (assert) => { +test("category name is preserved", async (assert) => { await visit("admin/site_settings/category/basic?filter=menu"); assert.equal(currentURL(), "admin/site_settings/category/basic?filter=menu"); }); -QUnit.test("shows all_results if current category has none", async (assert) => { +test("shows all_results if current category has none", async (assert) => { await visit("admin/site_settings"); await click(".admin-nav .basic a"); diff --git a/test/javascripts/acceptance/admin-site-text-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-site-text-test.js similarity index 85% rename from test/javascripts/acceptance/admin-site-text-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-site-text-test.js index 06bed8874f..83bb145a2e 100644 --- a/test/javascripts/acceptance/admin-site-text-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-site-text-test.js @@ -1,8 +1,9 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Admin - Site Texts", { loggedIn: true }); -QUnit.test("search for a key", async (assert) => { +test("search for a key", async (assert) => { await visit("/admin/customize/site_texts"); await fillIn(".site-text-search", "Test"); @@ -23,7 +24,7 @@ QUnit.test("search for a key", async (assert) => { assert.ok(exists(".site-text.overridden")); }); -QUnit.test("edit and revert a site text by key", async (assert) => { +test("edit and revert a site text by key", async (assert) => { await visit("/admin/customize/site_texts/site.test"); assert.equal(find(".title h3").text(), "site.test"); diff --git a/test/javascripts/acceptance/admin-suspend-user-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js similarity index 87% rename from test/javascripts/acceptance/admin-suspend-user-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js index 9f67ff1bee..577d6fc19c 100644 --- a/test/javascripts/acceptance/admin-suspend-user-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Admin - Suspend User", { loggedIn: true, @@ -23,7 +24,7 @@ acceptance("Admin - Suspend User", { }, }); -QUnit.test("suspend a user - cancel", async (assert) => { +test("suspend a user - cancel", async (assert) => { await visit("/admin/users/1234/regular"); await click(".suspend-user"); @@ -34,7 +35,7 @@ QUnit.test("suspend a user - cancel", async (assert) => { assert.equal(find(".suspend-user-modal:visible").length, 0); }); -QUnit.test("suspend a user - cancel with input", async (assert) => { +test("suspend a user - cancel with input", async (assert) => { await visit("/admin/users/1234/regular"); await click(".suspend-user"); @@ -61,7 +62,7 @@ QUnit.test("suspend a user - cancel with input", async (assert) => { assert.equal(find(".bootbox.modal:visible").length, 0); }); -QUnit.test("suspend, then unsuspend a user", async (assert) => { +test("suspend, then unsuspend a user", async (assert) => { const suspendUntilCombobox = selectKit(".suspend-until .combobox"); await visit("/admin/flags/active"); diff --git a/test/javascripts/acceptance/admin-user-badges-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-user-badges-test.js similarity index 54% rename from test/javascripts/acceptance/admin-user-badges-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-user-badges-test.js index 9686b2bba0..0a0b07ee81 100644 --- a/test/javascripts/acceptance/admin-user-badges-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-user-badges-test.js @@ -1,8 +1,9 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Admin - Users Badges", { loggedIn: true }); -QUnit.test("lists badges", async (assert) => { +test("lists badges", async (assert) => { await visit("/admin/users/1/eviltrout/badges"); assert.ok(exists(`span[data-badge-name="Badge 8"]`)); diff --git a/test/javascripts/acceptance/admin-user-emails-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-user-emails-test.js similarity index 80% rename from test/javascripts/acceptance/admin-user-emails-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-user-emails-test.js index 7e21e056aa..618553fb6b 100644 --- a/test/javascripts/acceptance/admin-user-emails-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-user-emails-test.js @@ -1,5 +1,6 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Admin - User Emails", { loggedIn: true }); @@ -31,13 +32,13 @@ const assertMultipleSecondary = (assert, firstEmail, secondEmail) => { ); }; -QUnit.test("viewing self without secondary emails", async (assert) => { +test("viewing self without secondary emails", async (assert) => { await visit("/admin/users/1/eviltrout"); assertNoSecondary(assert); }); -QUnit.test("viewing self with multiple secondary emails", async (assert) => { +test("viewing self with multiple secondary emails", async (assert) => { await visit("/admin/users/3/markvanlan"); assert.equal( @@ -53,14 +54,14 @@ QUnit.test("viewing self with multiple secondary emails", async (assert) => { ); }); -QUnit.test("viewing another user with no secondary email", async (assert) => { +test("viewing another user with no secondary email", async (assert) => { await visit("/admin/users/1234/regular"); await click(`.display-row.secondary-emails button`); assertNoSecondary(assert); }); -QUnit.test("viewing another account with secondary emails", async (assert) => { +test("viewing another account with secondary emails", async (assert) => { await visit("/admin/users/1235/regular1"); await click(`.display-row.secondary-emails button`); diff --git a/test/javascripts/acceptance/admin-user-index-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-user-index-test.js similarity index 86% rename from test/javascripts/acceptance/admin-user-index-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-user-index-test.js index 841b1a6f8b..2f509c05fc 100644 --- a/test/javascripts/acceptance/admin-user-index-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-user-index-test.js @@ -1,6 +1,7 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; acceptance("Admin - User Index", { loggedIn: true, @@ -34,7 +35,7 @@ acceptance("Admin - User Index", { }, }); -QUnit.test("can edit username", async (assert) => { +test("can edit username", async (assert) => { pretender.put("/users/sam/preferences/username", () => [ 200, { @@ -60,7 +61,7 @@ QUnit.test("can edit username", async (assert) => { assert.equal(find(".display-row.username .value").text().trim(), "new-sam"); }); -QUnit.test("will clear unsaved groups when switching user", async (assert) => { +test("will clear unsaved groups when switching user", async (assert) => { await visit("/admin/users/2/sam"); assert.equal( diff --git a/test/javascripts/acceptance/admin-users-list-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js similarity index 89% rename from test/javascripts/acceptance/admin-users-list-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js index db73491332..b6e35e40bb 100644 --- a/test/javascripts/acceptance/admin-users-list-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js @@ -1,16 +1,17 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Admin - Users List", { loggedIn: true }); -QUnit.test("lists users", async (assert) => { +test("lists users", async (assert) => { await visit("/admin/users/list/active"); assert.ok(exists(".users-list .user")); assert.ok(!exists(".user:eq(0) .email small"), "escapes email"); }); -QUnit.test("sorts users", async (assert) => { +test("sorts users", async (assert) => { await visit("/admin/users/list/active"); assert.ok(exists(".users-list .user")); @@ -34,7 +35,7 @@ QUnit.test("sorts users", async (assert) => { ); }); -QUnit.test("toggles email visibility", async (assert) => { +test("toggles email visibility", async (assert) => { await visit("/admin/users/list/active"); assert.ok(exists(".users-list .user")); @@ -56,7 +57,7 @@ QUnit.test("toggles email visibility", async (assert) => { ); }); -QUnit.test("switching tabs", async (assert) => { +test("switching tabs", async (assert) => { const activeUser = "eviltrout"; const suspectUser = "sam"; const activeTitle = I18n.t("admin.users.titles.active"); diff --git a/test/javascripts/acceptance/admin-watched-words-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-watched-words-test.js similarity index 88% rename from test/javascripts/acceptance/admin-watched-words-test.js rename to app/assets/javascripts/discourse/tests/acceptance/admin-watched-words-test.js index 9ee3356a31..087bef8070 100644 --- a/test/javascripts/acceptance/admin-watched-words-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-watched-words-test.js @@ -1,7 +1,8 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Admin - Watched Words", { loggedIn: true }); -QUnit.test("list words in groups", async (assert) => { +test("list words in groups", async (assert) => { await visit("/admin/logs/watched_words/action/block"); assert.ok(exists(".watched-words-list")); @@ -38,7 +39,7 @@ QUnit.test("list words in groups", async (assert) => { assert.ok(!exists(".watched-words-list .watched-word"), "Empty word list."); }); -QUnit.test("add words", async (assert) => { +test("add words", async (assert) => { await visit("/admin/logs/watched_words/action/block"); click(".show-words-checkbox"); @@ -55,7 +56,7 @@ QUnit.test("add words", async (assert) => { assert.equal(found.length, 1); }); -QUnit.test("remove words", async (assert) => { +test("remove words", async (assert) => { await visit("/admin/logs/watched_words/action/block"); await click(".show-words-checkbox"); diff --git a/test/javascripts/acceptance/auth-complete-test.js b/app/assets/javascripts/discourse/tests/acceptance/auth-complete-test.js similarity index 81% rename from test/javascripts/acceptance/auth-complete-test.js rename to app/assets/javascripts/discourse/tests/acceptance/auth-complete-test.js index d7f43d5dd1..9b3cb3a4f0 100644 --- a/test/javascripts/acceptance/auth-complete-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/auth-complete-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Auth Complete", { beforeEach() { const node = document.createElement("meta"); @@ -16,7 +17,7 @@ acceptance("Auth Complete", { }, }); -QUnit.test("when login not required", async (assert) => { +test("when login not required", async (assert) => { await visit("/"); assert.equal(currentPath(), "discovery.latest", "it stays on the homepage"); @@ -27,7 +28,7 @@ QUnit.test("when login not required", async (assert) => { ); }); -QUnit.test("when login required", async function (assert) { +test("when login required", async function (assert) { this.siteSettings.login_required = true; await visit("/"); diff --git a/test/javascripts/acceptance/badges-test.js b/app/assets/javascripts/discourse/tests/acceptance/badges-test.js similarity index 70% rename from test/javascripts/acceptance/badges-test.js rename to app/assets/javascripts/discourse/tests/acceptance/badges-test.js index 2707bb3b37..26ba43123b 100644 --- a/test/javascripts/acceptance/badges-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/badges-test.js @@ -1,9 +1,10 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Badges", { loggedIn: true }); -QUnit.test("Visit Badge Pages", async (assert) => { +test("Visit Badge Pages", async (assert) => { await visit("/badges"); assert.ok($("body.badges-page").length, "has body class"); @@ -16,7 +17,7 @@ QUnit.test("Visit Badge Pages", async (assert) => { assert.ok(!exists(".badge-card:eq(0) script")); }); -QUnit.test("shows correct badge titles to choose from", async (assert) => { +test("shows correct badge titles to choose from", async (assert) => { const availableBadgeTitles = selectKit(".select-kit"); await visit("/badges/50/custombadge"); await availableBadgeTitles.expand(); diff --git a/test/javascripts/acceptance/bookmarks-test.js b/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js similarity index 86% rename from test/javascripts/acceptance/bookmarks-test.js rename to app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js index c897b6fb65..44da9f79c0 100644 --- a/test/javascripts/acceptance/bookmarks-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js @@ -1,12 +1,15 @@ +import { skip } from "qunit"; +import { test } from "qunit"; import I18n from "I18n"; -import selectKit from "helpers/select-kit-helper"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; import { acceptance, loggedInUser, acceptanceUseFakeClock, -} from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; -import { parsePostData } from "helpers/create-pretender"; +} from "discourse/tests/helpers/qunit-helpers"; +import pretender, { + parsePostData, +} from "discourse/tests/helpers/create-pretender"; acceptance("Bookmarking", { loggedIn: true, @@ -235,25 +238,22 @@ acceptance("Bookmarking - Mobile", { }, }); -QUnit.skip( - "Editing a bookmark that has a Later Today reminder, and it is before 6pm today", - async (assert) => { - await acceptanceUseFakeClock("2020-05-04T13:00:00", async () => { - mockSuccessfulBookmarkPost(assert); - await visit("/t/internationalization-localization/280"); - await openBookmarkModal(); - await fillIn("input#bookmark-name", "Test name"); - await click("#tap_tile_later_today"); - await openEditBookmarkModal(); - assert.not( - exists("#bookmark-custom-date > input"), - "it does not show the custom date input" - ); - assert.ok( - exists("#tap_tile_later_today.active"), - "it preselects Later Today" - ); - assert.verifySteps(["later_today"]); - }); - } -); +skip("Editing a bookmark that has a Later Today reminder, and it is before 6pm today", async (assert) => { + await acceptanceUseFakeClock("2020-05-04T13:00:00", async () => { + mockSuccessfulBookmarkPost(assert); + await visit("/t/internationalization-localization/280"); + await openBookmarkModal(); + await fillIn("input#bookmark-name", "Test name"); + await click("#tap_tile_later_today"); + await openEditBookmarkModal(); + assert.not( + exists("#bookmark-custom-date > input"), + "it does not show the custom date input" + ); + assert.ok( + exists("#tap_tile_later_today.active"), + "it preselects Later Today" + ); + assert.verifySteps(["later_today"]); + }); +}); diff --git a/test/javascripts/acceptance/category-banner-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-banner-test.js similarity index 77% rename from test/javascripts/acceptance/category-banner-test.js rename to app/assets/javascripts/discourse/tests/acceptance/category-banner-test.js index ea51c986a3..d2569ce99b 100644 --- a/test/javascripts/acceptance/category-banner-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/category-banner-test.js @@ -1,5 +1,6 @@ -import { acceptance } from "helpers/qunit-helpers"; -import DiscoveryFixtures from "fixtures/discovery_fixtures"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import DiscoveryFixtures from "discourse/tests/fixtures/discovery-fixtures"; acceptance("Category Banners", { pretend(server, helper) { @@ -29,13 +30,13 @@ acceptance("Category Banners", { slug: "test-read-only-with-banner", permission: null, read_only_banner: - "You need to video yourself doing the secret handshake to post here", + "You need to video yourself
doing
the secret handshake to post here", }, ], }, }); -QUnit.test("Does not display category banners when not set", async (assert) => { +test("Does not display category banners when not set", async (assert) => { await visit("/c/test-read-only-without-banner"); await click("#create-topic"); @@ -46,7 +47,7 @@ QUnit.test("Does not display category banners when not set", async (assert) => { ); }); -QUnit.test("Displays category banners when set", async (assert) => { +test("Displays category banners when set", async (assert) => { await visit("/c/test-read-only-with-banner"); await click("#create-topic"); @@ -55,6 +56,10 @@ QUnit.test("Displays category banners when set", async (assert) => { await click(".modal-footer>.btn-primary"); assert.ok(!visible(".bootbox.modal"), "it closes the modal"); assert.ok(visible(".category-read-only-banner"), "it shows a banner"); + assert.ok( + find(".category-read-only-banner .inner").length === 1, + "it allows staff to embed html in the message" + ); }); acceptance("Anonymous Category Banners", { @@ -80,7 +85,7 @@ acceptance("Anonymous Category Banners", { }, }); -QUnit.test("Does not display category banners when set", async (assert) => { +test("Does not display category banners when set", async (assert) => { await visit("/c/test-read-only-with-banner"); assert.ok( !visible(".category-read-only-banner"), diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-chooser-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-chooser-test.js new file mode 100644 index 0000000000..3de106a145 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/category-chooser-test.js @@ -0,0 +1,27 @@ +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; + +acceptance("CategoryChooser", { + loggedIn: true, + settings: { + allow_uncategorized_topics: false, + }, +}); + +test("does not display uncategorized if not allowed", async (assert) => { + const categoryChooser = selectKit(".category-chooser"); + + await visit("/"); + await click("#create-topic"); + + await categoryChooser.expand(); + + assert.ok(categoryChooser.rowByIndex(0).name() !== "uncategorized"); +}); + +test("prefill category when category_id is set", async (assert) => { + await visit("/new-topic?category_id=1"); + + assert.equal(selectKit(".category-chooser").header().value(), 1); +}); diff --git a/test/javascripts/acceptance/category-edit-security-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js similarity index 89% rename from test/javascripts/acceptance/category-edit-security-test.js rename to app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js index e6e0e7fd14..e5abbbda70 100644 --- a/test/javascripts/acceptance/category-edit-security-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js @@ -1,11 +1,12 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Category Edit - security", { loggedIn: true, }); -QUnit.test("default", async (assert) => { +test("default", async (assert) => { await visit("/c/bug"); await click(".edit-category"); @@ -20,7 +21,7 @@ QUnit.test("default", async (assert) => { assert.equal(permission, "Create / Reply / See"); }); -QUnit.test("removing a permission", async (assert) => { +test("removing a permission", async (assert) => { const availableGroups = selectKit(".available-groups"); await visit("/c/bug"); @@ -46,7 +47,7 @@ QUnit.test("removing a permission", async (assert) => { ); }); -QUnit.test("adding a permission", async (assert) => { +test("adding a permission", async (assert) => { const availableGroups = selectKit(".available-groups"); const permissionSelector = selectKit(".permission-selector"); @@ -72,7 +73,7 @@ QUnit.test("adding a permission", async (assert) => { assert.equal(permission, "Reply / See"); }); -QUnit.test("adding a previously removed permission", async (assert) => { +test("adding a previously removed permission", async (assert) => { const availableGroups = selectKit(".available-groups"); await visit("/c/bug"); diff --git a/test/javascripts/acceptance/category-edit-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js similarity index 85% rename from test/javascripts/acceptance/category-edit-test.js rename to app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js index a3a8268c80..2025688a58 100644 --- a/test/javascripts/acceptance/category-edit-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js @@ -1,13 +1,15 @@ -import selectKit from "helpers/select-kit-helper"; +import { skip } from "qunit"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; import DiscourseURL from "discourse/lib/url"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Category Edit", { loggedIn: true, settings: { email_in: true }, }); -QUnit.test("Can open the category modal", async (assert) => { +test("Can open the category modal", async (assert) => { await visit("/c/bug"); await click(".edit-category"); @@ -17,7 +19,7 @@ QUnit.test("Can open the category modal", async (assert) => { assert.ok(!visible(".d-modal"), "it closes the modal"); }); -QUnit.test("Editing the category", async (assert) => { +test("Editing the category", async (assert) => { await visit("/c/bug"); await click(".edit-category"); @@ -46,7 +48,7 @@ QUnit.test("Editing the category", async (assert) => { ); }); -QUnit.skip("Edit the description without loosing progress", async (assert) => { +skip("Edit the description without loosing progress", async (assert) => { let win = { focus: function () {} }; let windowOpen = sandbox.stub(window, "open").returns(win); sandbox.stub(win, "focus"); @@ -61,7 +63,7 @@ QUnit.skip("Edit the description without loosing progress", async (assert) => { ); }); -QUnit.test("Error Saving", async (assert) => { +test("Error Saving", async (assert) => { await visit("/c/bug"); await click(".edit-category"); @@ -72,7 +74,7 @@ QUnit.test("Error Saving", async (assert) => { assert.equal(find("#modal-alert").html(), "duplicate email"); }); -QUnit.test("Subcategory list settings", async (assert) => { +test("Subcategory list settings", async (assert) => { const categoryChooser = selectKit( ".edit-category-tab-general .category-chooser" ); diff --git a/test/javascripts/acceptance/click-track-test.js b/app/assets/javascripts/discourse/tests/acceptance/click-track-test.js similarity index 66% rename from test/javascripts/acceptance/click-track-test.js rename to app/assets/javascripts/discourse/tests/acceptance/click-track-test.js index d8a982d61c..0ae883a975 100644 --- a/test/javascripts/acceptance/click-track-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/click-track-test.js @@ -1,9 +1,10 @@ -import pretender from "helpers/create-pretender"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import pretender from "discourse/tests/helpers/create-pretender"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Click Track", {}); -QUnit.test("Do not track mentions", async (assert) => { +test("Do not track mentions", async (assert) => { pretender.post("/clicks/track", () => assert.ok(false)); await visit("/t/internationalization-localization/280"); diff --git a/test/javascripts/acceptance/composer-actions-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js similarity index 57% rename from test/javascripts/acceptance/composer-actions-test.js rename to app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js index 821665371a..191831f418 100644 --- a/test/javascripts/acceptance/composer-actions-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js @@ -1,6 +1,10 @@ +import { test } from "qunit"; import I18n from "I18n"; -import selectKit from "helpers/select-kit-helper"; -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; import { _clearSnapshots } from "select-kit/components/composer-actions"; import { toggleCheckDraftPopup } from "discourse/controllers/composer"; import Draft from "discourse/models/draft"; @@ -14,27 +18,262 @@ acceptance("Composer Actions", { site: { can_tag_topics: true, }, + pretend(server) { + server.get("/t/130.json", () => { + return [ + 200, + { "Content-Type": "application/json" }, + { + post_stream: { + posts: [ + { + id: 133, + name: null, + username: "bianca", + avatar_template: + "/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png", + created_at: "2020-07-05T09:28:36.371Z", + cooked: + "

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a varius ipsum. Nunc euismod, metus non vulputate malesuada, ligula metus pharetra tortor, vel sodales arcu lacus sed mauris. Nam semper, orci vitae fringilla placerat, dui tellus convallis felis, ultricies laoreet sapien mi et metus. Mauris facilisis, mi fermentum rhoncus feugiat, dolor est vehicula leo, id porta leo ex non enim. In a ligula vel tellus commodo scelerisque non in ex. Pellentesque semper leo quam, nec varius est viverra eget. Donec vehicula sem et massa faucibus tempus.

", + post_number: 1, + post_type: 1, + updated_at: "2020-07-05T09:28:36.371Z", + reply_count: 0, + reply_to_post_number: null, + quote_count: 0, + incoming_link_count: 0, + reads: 1, + readers_count: 0, + score: 0, + yours: true, + topic_id: 130, + topic_slug: "lorem-ipsum-dolor-sit-amet", + display_username: null, + primary_group_name: null, + primary_group_flair_url: null, + primary_group_flair_bg_color: null, + primary_group_flair_color: null, + version: 1, + can_edit: true, + can_delete: false, + can_recover: false, + can_wiki: true, + read: true, + user_title: "Tester", + title_is_group: false, + actions_summary: [ + { + id: 3, + can_act: true, + }, + { + id: 4, + can_act: true, + }, + { + id: 8, + can_act: true, + }, + { + id: 7, + can_act: true, + }, + ], + moderator: false, + admin: true, + staff: true, + user_id: 1, + hidden: false, + trust_level: 0, + deleted_at: null, + user_deleted: false, + edit_reason: null, + can_view_edit_history: true, + wiki: false, + reviewable_id: 0, + reviewable_score_count: 0, + reviewable_score_pending_count: 0, + }, + ], + stream: [133], + }, + timeline_lookup: [[1, 0]], + related_messages: [], + suggested_topics: [], + id: 130, + title: "Lorem ipsum dolor sit amet", + fancy_title: "Lorem ipsum dolor sit amet", + posts_count: 1, + created_at: "2020-07-05T09:28:36.260Z", + views: 1, + reply_count: 0, + like_count: 0, + last_posted_at: "2020-07-05T09:28:36.371Z", + visible: true, + closed: false, + archived: false, + has_summary: false, + archetype: "private_message", + slug: "lorem-ipsum-dolor-sit-amet", + category_id: null, + word_count: 86, + deleted_at: null, + user_id: 1, + featured_link: null, + pinned_globally: false, + pinned_at: null, + pinned_until: null, + image_url: null, + draft: null, + draft_key: "topic_130", + draft_sequence: 0, + posted: true, + unpinned: null, + pinned: false, + current_post_number: 1, + highest_post_number: 1, + last_read_post_number: 1, + last_read_post_id: 133, + deleted_by: null, + has_deleted: false, + actions_summary: [ + { + id: 4, + count: 0, + hidden: false, + can_act: true, + }, + { + id: 8, + count: 0, + hidden: false, + can_act: true, + }, + { + id: 7, + count: 0, + hidden: false, + can_act: true, + }, + ], + chunk_size: 20, + bookmarked: false, + message_archived: false, + topic_timer: null, + message_bus_last_id: 5, + participant_count: 1, + pm_with_non_human_user: false, + show_read_indicator: false, + requested_group_name: null, + thumbnails: null, + tags_disable_ads: false, + details: { + notification_level: 3, + notifications_reason_id: 1, + can_move_posts: true, + can_edit: true, + can_delete: true, + can_remove_allowed_users: true, + can_invite_to: true, + can_invite_via_email: true, + can_create_post: true, + can_reply_as_new_topic: true, + can_flag_topic: true, + can_convert_topic: true, + can_review_topic: true, + can_remove_self_id: 1, + participants: [ + { + id: 1, + username: "bianca", + name: null, + avatar_template: + "/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png", + post_count: 1, + primary_group_name: null, + primary_group_flair_url: null, + primary_group_flair_color: null, + primary_group_flair_bg_color: null, + }, + ], + allowed_users: [ + { + id: 7, + username: "foo", + name: null, + avatar_template: + "/letter_avatar_proxy/v4/letter/f/b19c9b/{size}.png", + }, + ], + created_by: { + id: 1, + username: "bianca", + name: null, + avatar_template: + "/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png", + }, + last_poster: { + id: 1, + username: "bianca", + name: null, + avatar_template: + "/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png", + }, + allowed_groups: [ + { + id: 43, + automatic: false, + name: "foo_group", + user_count: 4, + mentionable_level: 0, + messageable_level: 99, + visibility_level: 0, + automatic_membership_email_domains: "", + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: true, + flair_url: null, + flair_bg_color: "", + flair_color: "", + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + publish_read_state: false, + }, + ], + }, + }, + ]; + }); + }, }); -QUnit.test( - "creating new topic and then reply_as_private_message keeps attributes", - async (assert) => { - await visit("/"); - await click("button#create-topic"); +test("creating new topic and then reply_as_private_message keeps attributes", async (assert) => { + await visit("/"); + await click("button#create-topic"); - await fillIn("#reply-title", "this is the title"); - await fillIn(".d-editor-input", "this is the reply"); + await fillIn("#reply-title", "this is the title"); + await fillIn(".d-editor-input", "this is the reply"); - const composerActions = selectKit(".composer-actions"); - await composerActions.expand(); - await composerActions.selectRowByValue("reply_as_private_message"); + const composerActions = selectKit(".composer-actions"); + await composerActions.expand(); + await composerActions.selectRowByValue("reply_as_private_message"); - assert.ok(find("#reply-title").val(), "this is the title"); - assert.ok(find(".d-editor-input").val(), "this is the reply"); - } -); + assert.ok(find("#reply-title").val(), "this is the title"); + assert.ok(find(".d-editor-input").val(), "this is the reply"); +}); -QUnit.test("replying to post", async (assert) => { +test("replying to post", async (assert) => { const composerActions = selectKit(".composer-actions"); await visit("/t/internationalization-localization/280"); @@ -52,7 +291,7 @@ QUnit.test("replying to post", async (assert) => { assert.equal(composerActions.rowByIndex(5).value(), undefined); }); -QUnit.test("replying to post - reply_as_private_message", async (assert) => { +test("replying to post - reply_as_private_message", async (assert) => { const composerActions = selectKit(".composer-actions"); await visit("/t/internationalization-localization/280"); @@ -67,7 +306,7 @@ QUnit.test("replying to post - reply_as_private_message", async (assert) => { ); }); -QUnit.test("replying to post - reply_to_topic", async (assert) => { +test("replying to post - reply_to_topic", async (assert) => { const composerActions = selectKit(".composer-actions"); await visit("/t/internationalization-localization/280"); @@ -94,7 +333,7 @@ QUnit.test("replying to post - reply_to_topic", async (assert) => { ); }); -QUnit.test("replying to post - toggle_whisper", async (assert) => { +test("replying to post - toggle_whisper", async (assert) => { const composerActions = selectKit(".composer-actions"); await visit("/t/internationalization-localization/280"); @@ -112,7 +351,7 @@ QUnit.test("replying to post - toggle_whisper", async (assert) => { ); }); -QUnit.test("replying to post - reply_as_new_topic", async (assert) => { +test("replying to post - reply_as_new_topic", async (assert) => { sandbox .stub(Draft, "get") .returns(Promise.resolve({ draft: "", draft_sequence: 0 })); @@ -143,7 +382,7 @@ QUnit.test("replying to post - reply_as_new_topic", async (assert) => { sandbox.restore(); }); -QUnit.test("reply_as_new_topic without a new_topic draft", async (assert) => { +test("reply_as_new_topic without a new_topic draft", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".create.reply"); const composerActions = selectKit(".composer-actions"); @@ -152,245 +391,7 @@ QUnit.test("reply_as_new_topic without a new_topic draft", async (assert) => { assert.equal(exists(find(".bootbox")), false); }); -QUnit.test("reply_as_new_group_message", async (assert) => { - // eslint-disable-next-line - server.get("/t/130.json", () => { - return [ - 200, - { "Content-Type": "application/json" }, - { - post_stream: { - posts: [ - { - id: 133, - name: null, - username: "bianca", - avatar_template: - "/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png", - created_at: "2020-07-05T09:28:36.371Z", - cooked: - "

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a varius ipsum. Nunc euismod, metus non vulputate malesuada, ligula metus pharetra tortor, vel sodales arcu lacus sed mauris. Nam semper, orci vitae fringilla placerat, dui tellus convallis felis, ultricies laoreet sapien mi et metus. Mauris facilisis, mi fermentum rhoncus feugiat, dolor est vehicula leo, id porta leo ex non enim. In a ligula vel tellus commodo scelerisque non in ex. Pellentesque semper leo quam, nec varius est viverra eget. Donec vehicula sem et massa faucibus tempus.

", - post_number: 1, - post_type: 1, - updated_at: "2020-07-05T09:28:36.371Z", - reply_count: 0, - reply_to_post_number: null, - quote_count: 0, - incoming_link_count: 0, - reads: 1, - readers_count: 0, - score: 0, - yours: true, - topic_id: 130, - topic_slug: "lorem-ipsum-dolor-sit-amet", - display_username: null, - primary_group_name: null, - primary_group_flair_url: null, - primary_group_flair_bg_color: null, - primary_group_flair_color: null, - version: 1, - can_edit: true, - can_delete: false, - can_recover: false, - can_wiki: true, - read: true, - user_title: "Tester", - title_is_group: false, - actions_summary: [ - { - id: 3, - can_act: true, - }, - { - id: 4, - can_act: true, - }, - { - id: 8, - can_act: true, - }, - { - id: 7, - can_act: true, - }, - ], - moderator: false, - admin: true, - staff: true, - user_id: 1, - hidden: false, - trust_level: 0, - deleted_at: null, - user_deleted: false, - edit_reason: null, - can_view_edit_history: true, - wiki: false, - reviewable_id: 0, - reviewable_score_count: 0, - reviewable_score_pending_count: 0, - }, - ], - stream: [133], - }, - timeline_lookup: [[1, 0]], - related_messages: [], - suggested_topics: [], - id: 130, - title: "Lorem ipsum dolor sit amet", - fancy_title: "Lorem ipsum dolor sit amet", - posts_count: 1, - created_at: "2020-07-05T09:28:36.260Z", - views: 1, - reply_count: 0, - like_count: 0, - last_posted_at: "2020-07-05T09:28:36.371Z", - visible: true, - closed: false, - archived: false, - has_summary: false, - archetype: "private_message", - slug: "lorem-ipsum-dolor-sit-amet", - category_id: null, - word_count: 86, - deleted_at: null, - user_id: 1, - featured_link: null, - pinned_globally: false, - pinned_at: null, - pinned_until: null, - image_url: null, - draft: null, - draft_key: "topic_130", - draft_sequence: 0, - posted: true, - unpinned: null, - pinned: false, - current_post_number: 1, - highest_post_number: 1, - last_read_post_number: 1, - last_read_post_id: 133, - deleted_by: null, - has_deleted: false, - actions_summary: [ - { - id: 4, - count: 0, - hidden: false, - can_act: true, - }, - { - id: 8, - count: 0, - hidden: false, - can_act: true, - }, - { - id: 7, - count: 0, - hidden: false, - can_act: true, - }, - ], - chunk_size: 20, - bookmarked: false, - message_archived: false, - topic_timer: null, - message_bus_last_id: 5, - participant_count: 1, - pm_with_non_human_user: false, - show_read_indicator: false, - requested_group_name: null, - thumbnails: null, - tags_disable_ads: false, - details: { - notification_level: 3, - notifications_reason_id: 1, - can_move_posts: true, - can_edit: true, - can_delete: true, - can_remove_allowed_users: true, - can_invite_to: true, - can_invite_via_email: true, - can_create_post: true, - can_reply_as_new_topic: true, - can_flag_topic: true, - can_convert_topic: true, - can_review_topic: true, - can_remove_self_id: 1, - participants: [ - { - id: 1, - username: "bianca", - name: null, - avatar_template: - "/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png", - post_count: 1, - primary_group_name: null, - primary_group_flair_url: null, - primary_group_flair_color: null, - primary_group_flair_bg_color: null, - }, - ], - allowed_users: [ - { - id: 7, - username: "foo", - name: null, - avatar_template: - "/letter_avatar_proxy/v4/letter/f/b19c9b/{size}.png", - }, - ], - created_by: { - id: 1, - username: "bianca", - name: null, - avatar_template: - "/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png", - }, - last_poster: { - id: 1, - username: "bianca", - name: null, - avatar_template: - "/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png", - }, - allowed_groups: [ - { - id: 43, - automatic: false, - name: "foo_group", - user_count: 4, - mentionable_level: 0, - messageable_level: 99, - visibility_level: 0, - automatic_membership_email_domains: "", - primary_group: false, - title: null, - grant_trust_level: null, - incoming_email: null, - has_messages: true, - flair_url: null, - flair_bg_color: "", - flair_color: "", - bio_raw: null, - bio_cooked: null, - bio_excerpt: null, - public_admission: false, - public_exit: false, - allow_membership_requests: false, - full_name: null, - default_notification_level: 3, - membership_request_template: null, - members_visibility_level: 0, - can_see_members: true, - publish_read_state: false, - }, - ], - }, - }, - ]; - }); - +test("reply_as_new_group_message", async (assert) => { await visit("/t/lorem-ipsum-dolor-sit-amet/130"); await click(".create.reply"); const composerActions = selectKit(".composer-actions"); @@ -405,7 +406,7 @@ QUnit.test("reply_as_new_group_message", async (assert) => { assert.deepEqual(items, ["foo", "foo_group"]); }); -QUnit.test("hide component if no content", async (assert) => { +test("hide component if no content", async (assert) => { await visit("/"); await click("button#create-topic"); @@ -421,7 +422,7 @@ QUnit.test("hide component if no content", async (assert) => { assert.equal(composerActions.rows().length, 2); }); -QUnit.test("interactions", async (assert) => { +test("interactions", async (assert) => { const composerActions = selectKit(".composer-actions"); const quote = "Life is like riding a bicycle."; @@ -498,7 +499,7 @@ QUnit.test("interactions", async (assert) => { assert.equal(composerActions.rows().length, 3); }); -QUnit.test("replying to post - toggle_topic_bump", async (assert) => { +test("replying to post - toggle_topic_bump", async (assert) => { const composerActions = selectKit(".composer-actions"); await visit("/t/internationalization-localization/280"); @@ -526,7 +527,7 @@ QUnit.test("replying to post - toggle_topic_bump", async (assert) => { ); }); -QUnit.test("replying to post as staff", async (assert) => { +test("replying to post as staff", async (assert) => { const composerActions = selectKit(".composer-actions"); updateCurrentUser({ admin: true }); @@ -538,7 +539,7 @@ QUnit.test("replying to post as staff", async (assert) => { assert.equal(composerActions.rowByIndex(4).value(), "toggle_topic_bump"); }); -QUnit.test("replying to post as TL3 user", async (assert) => { +test("replying to post as TL3 user", async (assert) => { const composerActions = selectKit(".composer-actions"); updateCurrentUser({ moderator: false, admin: false, trust_level: 3 }); @@ -556,7 +557,7 @@ QUnit.test("replying to post as TL3 user", async (assert) => { }); }); -QUnit.test("replying to post as TL4 user", async (assert) => { +test("replying to post as TL4 user", async (assert) => { const composerActions = selectKit(".composer-actions"); updateCurrentUser({ moderator: false, admin: false, trust_level: 4 }); @@ -568,25 +569,22 @@ QUnit.test("replying to post as TL4 user", async (assert) => { assert.equal(composerActions.rowByIndex(3).value(), "toggle_topic_bump"); }); -QUnit.test( - "replying to first post - reply_as_private_message", - async (assert) => { - const composerActions = selectKit(".composer-actions"); +test("replying to first post - reply_as_private_message", async (assert) => { + const composerActions = selectKit(".composer-actions"); - await visit("/t/internationalization-localization/280"); - await click("article#post_1 button.reply"); + await visit("/t/internationalization-localization/280"); + await click("article#post_1 button.reply"); - await composerActions.expand(); - await composerActions.selectRowByValue("reply_as_private_message"); + await composerActions.expand(); + await composerActions.selectRowByValue("reply_as_private_message"); - assert.equal(find(".users-input .item:eq(0)").text(), "uwe_keim"); - assert.ok( - find(".d-editor-input").val().indexOf("Continuing the discussion") >= 0 - ); - } -); + assert.equal(find(".users-input .item:eq(0)").text(), "uwe_keim"); + assert.ok( + find(".d-editor-input").val().indexOf("Continuing the discussion") >= 0 + ); +}); -QUnit.test("editing post", async (assert) => { +test("editing post", async (assert) => { const composerActions = selectKit(".composer-actions"); await visit("/t/internationalization-localization/280"); @@ -624,7 +622,7 @@ const stubDraftResponse = () => { ); }; -QUnit.test("shared draft", async (assert) => { +test("shared draft", async (assert) => { stubDraftResponse(); try { toggleCheckDraftPopup(true); @@ -666,7 +664,7 @@ QUnit.test("shared draft", async (assert) => { sandbox.restore(); }); -QUnit.test("reply_as_new_topic with new_topic draft", async (assert) => { +test("reply_as_new_topic with new_topic draft", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".create.reply"); const composerActions = selectKit(".composer-actions"); diff --git a/test/javascripts/acceptance/composer-attachment-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js similarity index 72% rename from test/javascripts/acceptance/composer-attachment-test.js rename to app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js index 3195d30153..01e6b2b3da 100644 --- a/test/javascripts/acceptance/composer-attachment-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-attachment-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; function setupPretender(server, helper) { server.post("/uploads/lookup-urls", () => { @@ -33,7 +34,7 @@ acceptance("Composer Attachment", { }, }); -QUnit.test("attachments are cooked properly", async (assert) => { +test("attachments are cooked properly", async (assert) => { await writeInComposer(assert); assert.equal( find(".d-editor-preview:visible").html().trim(), @@ -51,13 +52,10 @@ acceptance("Composer Attachment - Secure Media Enabled", { }, }); -QUnit.test( - "attachments are cooked properly when secure media is enabled", - async (assert) => { - await writeInComposer(assert); - assert.equal( - find(".d-editor-preview:visible").html().trim(), - '

test

' - ); - } -); +test("attachments are cooked properly when secure media is enabled", async (assert) => { + await writeInComposer(assert); + assert.equal( + find(".d-editor-preview:visible").html().trim(), + '

test

' + ); +}); diff --git a/test/javascripts/acceptance/composer-edit-conflict-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-edit-conflict-test.js similarity index 73% rename from test/javascripts/acceptance/composer-edit-conflict-test.js rename to app/assets/javascripts/discourse/tests/acceptance/composer-edit-conflict-test.js index 56a37a4cf8..1984037be0 100644 --- a/test/javascripts/acceptance/composer-edit-conflict-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-edit-conflict-test.js @@ -1,12 +1,13 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; acceptance("Composer - Edit conflict", { loggedIn: true, }); -QUnit.test("Edit a post that causes an edit conflict", async (assert) => { +test("Edit a post that causes an edit conflict", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".topic-post:eq(0) button.show-more-actions"); await click(".topic-post:eq(0) button.edit"); @@ -47,21 +48,18 @@ function handleDraftPretender(assert) { }); } -QUnit.test( - "Should not send originalText when posting a new reply", - async (assert) => { - handleDraftPretender(assert); +test("Should not send originalText when posting a new reply", async (assert) => { + handleDraftPretender(assert); - await visit("/t/internationalization-localization/280"); - await click(".topic-post:eq(0) button.reply"); - await fillIn( - ".d-editor-input", - "hello world hello world hello world hello world hello world" - ); - } -); + await visit("/t/internationalization-localization/280"); + await click(".topic-post:eq(0) button.reply"); + await fillIn( + ".d-editor-input", + "hello world hello world hello world hello world hello world" + ); +}); -QUnit.test("Should send originalText when editing a reply", async (assert) => { +test("Should send originalText when editing a reply", async (assert) => { handleDraftPretender(assert); await visit("/t/internationalization-localization/280"); diff --git a/test/javascripts/acceptance/composer-hyperlink-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-hyperlink-test.js similarity index 87% rename from test/javascripts/acceptance/composer-hyperlink-test.js rename to app/assets/javascripts/discourse/tests/acceptance/composer-hyperlink-test.js index fb21f25097..cc23b88671 100644 --- a/test/javascripts/acceptance/composer-hyperlink-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-hyperlink-test.js @@ -1,10 +1,11 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Composer - Hyperlink", { loggedIn: true, }); -QUnit.test("add a hyperlink to a reply", async (assert) => { +test("add a hyperlink to a reply", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".topic-post:first-child button.reply"); await fillIn(".d-editor-input", "This is a link to "); @@ -23,8 +24,8 @@ QUnit.test("add a hyperlink to a reply", async (assert) => { assert.equal( find(".d-editor-input").val(), - "This is a link to [Google](http://google.com)", - "adds link with url and text, prepends 'http://'" + "This is a link to [Google](https://google.com)", + "adds link with url and text, prepends 'https://'" ); assert.ok( @@ -42,7 +43,7 @@ QUnit.test("add a hyperlink to a reply", async (assert) => { assert.equal( find(".d-editor-input").val(), "Reset textarea contents.", - "adds link with url and text, prepends 'http://'" + "doesn’t insert anything after cancelling" ); assert.ok( @@ -60,7 +61,7 @@ QUnit.test("add a hyperlink to a reply", async (assert) => { assert.equal( find(".d-editor-input").val(), - "[Reset](http://somelink.com) textarea contents.", + "[Reset](https://somelink.com) textarea contents.", "adds link to a selected text" ); diff --git a/test/javascripts/acceptance/composer-onebox-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-onebox-test.js similarity index 74% rename from test/javascripts/acceptance/composer-onebox-test.js rename to app/assets/javascripts/discourse/tests/acceptance/composer-onebox-test.js index c51ad0cc19..98a3e960ed 100644 --- a/test/javascripts/acceptance/composer-onebox-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-onebox-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Composer - Onebox", { loggedIn: true, @@ -8,15 +9,13 @@ acceptance("Composer - Onebox", { }, }); -QUnit.test( - "Preview update should respect max_oneboxes_per_post site setting", - async (assert) => { - await visit("/t/internationalization-localization/280"); - await click("#topic-footer-buttons .btn.create"); +test("Preview update should respect max_oneboxes_per_post site setting", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-buttons .btn.create"); - await fillIn( - ".d-editor-input", - ` + await fillIn( + ".d-editor-input", + ` http://www.example.com/has-title.html This is another test http://www.example.com/has-title.html @@ -27,11 +26,11 @@ This is another test http://www.example.com/has-title.html http://www.example.com/has-title.html ` - ); + ); - assert.equal( - find(".d-editor-preview:visible").html().trim(), - ` + assert.equal( + find(".d-editor-preview:visible").html().trim(), + `


This is another test This is a great title

http://www.example.com/no-title.html

@@ -39,6 +38,5 @@ This is another test This is a great title

`.trim() - ); - } -); + ); +}); diff --git a/test/javascripts/acceptance/composer-tags-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-tags-test.js similarity index 82% rename from test/javascripts/acceptance/composer-tags-test.js rename to app/assets/javascripts/discourse/tests/acceptance/composer-tags-test.js index da2502158b..056cedb560 100644 --- a/test/javascripts/acceptance/composer-tags-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-tags-test.js @@ -1,6 +1,10 @@ +import { test } from "qunit"; import Category from "discourse/models/category"; -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; -import selectKit from "helpers/select-kit-helper"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; acceptance("Composer - Tags", { loggedIn: true, @@ -14,7 +18,7 @@ acceptance("Composer - Tags", { }, }); -QUnit.test("staff bypass tag validation rule", async (assert) => { +test("staff bypass tag validation rule", async (assert) => { await visit("/"); await click("#create-topic"); @@ -31,7 +35,7 @@ QUnit.test("staff bypass tag validation rule", async (assert) => { assert.notEqual(currentURL(), "/"); }); -QUnit.test("users do not bypass tag validation rule", async (assert) => { +test("users do not bypass tag validation rule", async (assert) => { await visit("/"); await click("#create-topic"); diff --git a/test/javascripts/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js similarity index 74% rename from test/javascripts/acceptance/composer-test.js rename to app/assets/javascripts/discourse/tests/acceptance/composer-test.js index 0639560a88..92c9565db9 100644 --- a/test/javascripts/acceptance/composer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js @@ -1,15 +1,17 @@ +import { skip } from "qunit"; +import { test } from "qunit"; import I18n from "I18n"; import { run } from "@ember/runloop"; -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { toggleCheckDraftPopup } from "discourse/controllers/composer"; import Draft from "discourse/models/draft"; import { Promise } from "rsvp"; acceptance("Composer", { loggedIn: true, - pretend(pretenderServer, helper) { - pretenderServer.post("/uploads/lookup-urls", () => { + pretend(server, helper) { + server.post("/uploads/lookup-urls", () => { return helper.response([]); }); }, @@ -18,7 +20,7 @@ acceptance("Composer", { }, }); -QUnit.skip("Tests the Composer controls", async (assert) => { +skip("Tests the Composer controls", async (assert) => { await visit("/"); assert.ok(exists("#create-topic"), "the create button is visible"); @@ -96,7 +98,7 @@ QUnit.skip("Tests the Composer controls", async (assert) => { assert.ok(!exists(".bootbox.modal"), "the confirmation can be cancelled"); }); -QUnit.test("Composer upload placeholder", async (assert) => { +test("Composer upload placeholder", async (assert) => { await visit("/"); await click("#create-topic"); @@ -189,7 +191,7 @@ QUnit.test("Composer upload placeholder", async (assert) => { ); }); -QUnit.test("Create a topic with server side errors", async (assert) => { +test("Create a topic with server side errors", async (assert) => { await visit("/"); await click("#create-topic"); await fillIn("#reply-title", "this title triggers an error"); @@ -201,7 +203,7 @@ QUnit.test("Create a topic with server side errors", async (assert) => { assert.ok(exists(".d-editor-input"), "the composer input is visible"); }); -QUnit.test("Create a Topic", async (assert) => { +test("Create a Topic", async (assert) => { await visit("/"); await click("#create-topic"); await fillIn("#reply-title", "Internationalization Localization"); @@ -214,7 +216,7 @@ QUnit.test("Create a Topic", async (assert) => { ); }); -QUnit.test("Create an enqueued Topic", async (assert) => { +test("Create an enqueued Topic", async (assert) => { await visit("/"); await click("#create-topic"); await fillIn("#reply-title", "Internationalization Localization"); @@ -227,7 +229,7 @@ QUnit.test("Create an enqueued Topic", async (assert) => { assert.ok(invisible(".d-modal"), "the modal can be dismissed"); }); -QUnit.test("Can display a message and route to a URL", async (assert) => { +test("Can display a message and route to a URL", async (assert) => { await visit("/"); await click("#create-topic"); await fillIn("#reply-title", "This title doesn't matter"); @@ -247,7 +249,7 @@ QUnit.test("Can display a message and route to a URL", async (assert) => { ); }); -QUnit.test("Create a Reply", async (assert) => { +test("Create a Reply", async (assert) => { await visit("/t/internationalization-localization/280"); assert.ok( @@ -267,7 +269,7 @@ QUnit.test("Create a Reply", async (assert) => { ); }); -QUnit.test("Can edit a post after starting a reply", async (assert) => { +test("Can edit a post after starting a reply", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#topic-footer-buttons .create"); @@ -285,7 +287,7 @@ QUnit.test("Can edit a post after starting a reply", async (assert) => { ); }); -QUnit.test("Posting on a different topic", async (assert) => { +test("Posting on a different topic", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#topic-footer-buttons .btn.create"); await fillIn(".d-editor-input", "this is the content for a different topic"); @@ -302,7 +304,7 @@ QUnit.test("Posting on a different topic", async (assert) => { ); }); -QUnit.test("Create an enqueued Reply", async (assert) => { +test("Create an enqueued Reply", async (assert) => { await visit("/t/internationalization-localization/280"); assert.notOk(find(".pending-posts .reviewable-item").length); @@ -326,7 +328,7 @@ QUnit.test("Create an enqueued Reply", async (assert) => { assert.ok(find(".pending-posts .reviewable-item").length); }); -QUnit.test("Edit the first post", async (assert) => { +test("Edit the first post", async (assert) => { await visit("/t/internationalization-localization/280"); assert.ok( @@ -364,7 +366,7 @@ QUnit.test("Edit the first post", async (assert) => { ); }); -QUnit.test("Composer can switch between edits", async (assert) => { +test("Composer can switch between edits", async (assert) => { await visit("/t/this-is-a-test-topic/9"); await click(".topic-post:eq(0) button.edit"); @@ -381,26 +383,23 @@ QUnit.test("Composer can switch between edits", async (assert) => { ); }); -QUnit.test( - "Composer with dirty edit can toggle to another edit", - async (assert) => { - await visit("/t/this-is-a-test-topic/9"); +test("Composer with dirty edit can toggle to another edit", async (assert) => { + await visit("/t/this-is-a-test-topic/9"); - await click(".topic-post:eq(0) button.edit"); - await fillIn(".d-editor-input", "This is a dirty reply"); - await click(".topic-post:eq(1) button.edit"); - assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog"); + await click(".topic-post:eq(0) button.edit"); + await fillIn(".d-editor-input", "This is a dirty reply"); + await click(".topic-post:eq(1) button.edit"); + assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog"); - await click(".modal-footer a:eq(0)"); - assert.equal( - find(".d-editor-input").val().indexOf("This is the second post."), - 0, - "it populates the input with the post text" - ); - } -); + await click(".modal-footer a:eq(0)"); + assert.equal( + find(".d-editor-input").val().indexOf("This is the second post."), + 0, + "it populates the input with the post text" + ); +}); -QUnit.test("Composer can toggle between edit and reply", async (assert) => { +test("Composer can toggle between edit and reply", async (assert) => { await visit("/t/this-is-a-test-topic/9"); await click(".topic-post:eq(0) button.edit"); @@ -419,7 +418,7 @@ QUnit.test("Composer can toggle between edit and reply", async (assert) => { ); }); -QUnit.test("Composer can toggle whispers", async (assert) => { +test("Composer can toggle whispers", async (assert) => { const menu = selectKit(".toolbar-popup-menu-options"); await visit("/t/this-is-a-test-topic/9"); @@ -454,98 +453,91 @@ QUnit.test("Composer can toggle whispers", async (assert) => { ); }); -QUnit.test( - "Composer can toggle layouts (open, fullscreen and draft)", - async (assert) => { - await visit("/t/this-is-a-test-topic/9"); - await click(".topic-post:eq(0) button.reply"); +test("Composer can toggle layouts (open, fullscreen and draft)", async (assert) => { + await visit("/t/this-is-a-test-topic/9"); + await click(".topic-post:eq(0) button.reply"); - assert.ok( - find("#reply-control.open").length === 1, - "it starts in open state by default" - ); + assert.ok( + find("#reply-control.open").length === 1, + "it starts in open state by default" + ); - await click(".toggle-fullscreen"); + await click(".toggle-fullscreen"); - assert.ok( - find("#reply-control.fullscreen").length === 1, - "it expands composer to full screen" - ); + assert.ok( + find("#reply-control.fullscreen").length === 1, + "it expands composer to full screen" + ); - await click(".toggle-fullscreen"); + await click(".toggle-fullscreen"); - assert.ok( - find("#reply-control.open").length === 1, - "it collapses composer to regular size" - ); + assert.ok( + find("#reply-control.open").length === 1, + "it collapses composer to regular size" + ); - await fillIn(".d-editor-input", "This is a dirty reply"); - await click(".toggler"); + await fillIn(".d-editor-input", "This is a dirty reply"); + await click(".toggler"); - assert.ok( - find("#reply-control.draft").length === 1, - "it collapses composer to draft bar" - ); + assert.ok( + find("#reply-control.draft").length === 1, + "it collapses composer to draft bar" + ); - await click(".toggle-fullscreen"); + await click(".toggle-fullscreen"); - assert.ok( - find("#reply-control.open").length === 1, - "from draft, it expands composer back to open state" - ); - } -); + assert.ok( + find("#reply-control.open").length === 1, + "from draft, it expands composer back to open state" + ); +}); -QUnit.test( - "Composer can toggle between reply and createTopic", - async (assert) => { - await visit("/t/this-is-a-test-topic/9"); - await click(".topic-post:eq(0) button.reply"); +test("Composer can toggle between reply and createTopic", async (assert) => { + await visit("/t/this-is-a-test-topic/9"); + await click(".topic-post:eq(0) button.reply"); - await selectKit(".toolbar-popup-menu-options").expand(); - await selectKit(".toolbar-popup-menu-options").selectRowByValue( - "toggleWhisper" - ); + await selectKit(".toolbar-popup-menu-options").expand(); + await selectKit(".toolbar-popup-menu-options").selectRowByValue( + "toggleWhisper" + ); - assert.ok( - find(".composer-fields .whisper .d-icon-far-eye-slash").length === 1, - "it sets the post type to whisper" - ); + assert.ok( + find(".composer-fields .whisper .d-icon-far-eye-slash").length === 1, + "it sets the post type to whisper" + ); - await visit("/"); - assert.ok(exists("#create-topic"), "the create topic button is visible"); + await visit("/"); + assert.ok(exists("#create-topic"), "the create topic button is visible"); - await click("#create-topic"); - assert.ok( - find(".composer-fields .whisper .d-icon-far-eye-slash").length === 0, - "it should reset the state of the composer's model" - ); + await click("#create-topic"); + assert.ok( + find(".composer-fields .whisper .d-icon-far-eye-slash").length === 0, + "it should reset the state of the composer's model" + ); - await selectKit(".toolbar-popup-menu-options").expand(); - await selectKit(".toolbar-popup-menu-options").selectRowByValue( - "toggleInvisible" - ); + await selectKit(".toolbar-popup-menu-options").expand(); + await selectKit(".toolbar-popup-menu-options").selectRowByValue( + "toggleInvisible" + ); - assert.ok( - find(".composer-fields .unlist") - .text() - .indexOf(I18n.t("composer.unlist")) > 0, - "it sets the topic to unlisted" - ); + assert.ok( + find(".composer-fields .unlist").text().indexOf(I18n.t("composer.unlist")) > + 0, + "it sets the topic to unlisted" + ); - await visit("/t/this-is-a-test-topic/9"); + await visit("/t/this-is-a-test-topic/9"); - await click(".topic-post:eq(0) button.reply"); - assert.ok( - find(".composer-fields .whisper") - .text() - .indexOf(I18n.t("composer.unlist")) === -1, - "it should reset the state of the composer's model" - ); - } -); + await click(".topic-post:eq(0) button.reply"); + assert.ok( + find(".composer-fields .whisper") + .text() + .indexOf(I18n.t("composer.unlist")) === -1, + "it should reset the state of the composer's model" + ); +}); -QUnit.test("Composer with dirty reply can toggle to edit", async (assert) => { +test("Composer with dirty reply can toggle to edit", async (assert) => { await visit("/t/this-is-a-test-topic/9"); await click(".topic-post:eq(0) button.reply"); @@ -560,55 +552,49 @@ QUnit.test("Composer with dirty reply can toggle to edit", async (assert) => { ); }); -QUnit.test( - "Composer draft with dirty reply can toggle to edit", - async (assert) => { - await visit("/t/this-is-a-test-topic/9"); +test("Composer draft with dirty reply can toggle to edit", async (assert) => { + await visit("/t/this-is-a-test-topic/9"); - await click(".topic-post:eq(0) button.reply"); - await fillIn(".d-editor-input", "This is a dirty reply"); - await click(".toggler"); - await click(".topic-post:eq(1) button.edit"); - assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog"); - assert.equal( - find(".modal-footer a:eq(1)").text(), - I18n.t("post.abandon.no_value") - ); - await click(".modal-footer a:eq(0)"); - assert.equal( - find(".d-editor-input").val().indexOf("This is the second post."), - 0, - "it populates the input with the post text" - ); - } -); + await click(".topic-post:eq(0) button.reply"); + await fillIn(".d-editor-input", "This is a dirty reply"); + await click(".toggler"); + await click(".topic-post:eq(1) button.edit"); + assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog"); + assert.equal( + find(".modal-footer a:eq(1)").text(), + I18n.t("post.abandon.no_value") + ); + await click(".modal-footer a:eq(0)"); + assert.equal( + find(".d-editor-input").val().indexOf("This is the second post."), + 0, + "it populates the input with the post text" + ); +}); -QUnit.test( - "Composer draft can switch to draft in new context without destroying current draft", - async (assert) => { - await visit("/t/this-is-a-test-topic/9"); +test("Composer draft can switch to draft in new context without destroying current draft", async (assert) => { + await visit("/t/this-is-a-test-topic/9"); - await click(".topic-post:eq(0) button.reply"); - await fillIn(".d-editor-input", "This is a dirty reply"); + await click(".topic-post:eq(0) button.reply"); + await fillIn(".d-editor-input", "This is a dirty reply"); - await click("#site-logo"); - await click("#create-topic"); + await click("#site-logo"); + await click("#create-topic"); - assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog"); - assert.equal( - find(".modal-footer a:eq(1)").text(), - I18n.t("post.abandon.no_save_draft") - ); - await click(".modal-footer a:eq(1)"); - assert.equal( - find(".d-editor-input").val(), - "", - "it populates the input with the post text" - ); - } -); + assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog"); + assert.equal( + find(".modal-footer a:eq(1)").text(), + I18n.t("post.abandon.no_save_draft") + ); + await click(".modal-footer a:eq(1)"); + assert.equal( + find(".d-editor-input").val(), + "", + "it populates the input with the post text" + ); +}); -QUnit.test("Checks for existing draft", async (assert) => { +test("Checks for existing draft", async (assert) => { try { toggleCheckDraftPopup(true); @@ -625,7 +611,7 @@ QUnit.test("Checks for existing draft", async (assert) => { } }); -QUnit.test("Can switch states without abandon popup", async (assert) => { +test("Can switch states without abandon popup", async (assert) => { try { toggleCheckDraftPopup(true); @@ -673,7 +659,7 @@ QUnit.test("Can switch states without abandon popup", async (assert) => { sandbox.restore(); }); -QUnit.test("Loading draft also replaces the recipients", async (assert) => { +test("Loading draft also replaces the recipients", async (assert) => { try { toggleCheckDraftPopup(true); @@ -695,24 +681,21 @@ QUnit.test("Loading draft also replaces the recipients", async (assert) => { } }); -QUnit.test( - "Deleting the text content of the first post in a private message", - async (assert) => { - await visit("/t/34"); +test("Deleting the text content of the first post in a private message", async (assert) => { + await visit("/t/34"); - await click("#post_1 .d-icon-ellipsis-h"); + await click("#post_1 .d-icon-ellipsis-h"); - await click("#post_1 .d-icon-pencil-alt"); + await click("#post_1 .d-icon-pencil-alt"); - await fillIn(".d-editor-input", ""); + await fillIn(".d-editor-input", ""); - assert.equal( - find(".d-editor-container textarea").attr("placeholder"), - I18n.t("composer.reply_placeholder"), - "it should not block because of missing category" - ); - } -); + assert.equal( + find(".d-editor-container textarea").attr("placeholder"), + I18n.t("composer.reply_placeholder"), + "it should not block because of missing category" + ); +}); const assertImageResized = (assert, uploads) => { assert.equal( @@ -722,7 +705,7 @@ const assertImageResized = (assert, uploads) => { ); }; -QUnit.test("Image resizing buttons", async (assert) => { +test("Image resizing buttons", async (assert) => { await visit("/"); await click("#create-topic"); @@ -827,20 +810,3 @@ QUnit.test("Image resizing buttons", async (assert) => { "it does not unescapes script tags in code blocks" ); }); - -QUnit.test("can reply to a private message", async (assert) => { - let submitted; - - /* global server */ - server.post("/posts", () => { - submitted = true; - return [200, { "Content-Type": "application/json" }, {}]; - }); - - await visit("/t/34"); - await click(".topic-post:eq(0) button.reply"); - await fillIn(".d-editor-input", "this is the *content* of the reply"); - await click("#reply-control button.create"); - - assert.ok(submitted); -}); diff --git a/test/javascripts/acceptance/composer-topic-links-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-topic-links-test.js similarity index 78% rename from test/javascripts/acceptance/composer-topic-links-test.js rename to app/assets/javascripts/discourse/tests/acceptance/composer-topic-links-test.js index a962bbc7fd..f2eb648875 100644 --- a/test/javascripts/acceptance/composer-topic-links-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-topic-links-test.js @@ -1,4 +1,8 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Composer topic featured links", { loggedIn: true, @@ -9,7 +13,7 @@ acceptance("Composer topic featured links", { }, }); -QUnit.test("onebox with title", async (assert) => { +test("onebox with title", async (assert) => { await visit("/"); await click("#create-topic"); await fillIn("#reply-title", "http://www.example.com/has-title.html"); @@ -28,7 +32,7 @@ QUnit.test("onebox with title", async (assert) => { ); }); -QUnit.test("onebox result doesn't include a title", async (assert) => { +test("onebox result doesn't include a title", async (assert) => { await visit("/"); await click("#create-topic"); await fillIn("#reply-title", "http://www.example.com/no-title.html"); @@ -47,7 +51,7 @@ QUnit.test("onebox result doesn't include a title", async (assert) => { ); }); -QUnit.test("no onebox result", async (assert) => { +test("no onebox result", async (assert) => { await visit("/"); await click("#create-topic"); await fillIn("#reply-title", "http://www.example.com/nope-onebox.html"); @@ -66,7 +70,7 @@ QUnit.test("no onebox result", async (assert) => { ); }); -QUnit.test("ignore internal links", async (assert) => { +test("ignore internal links", async (assert) => { await visit("/"); await click("#create-topic"); const title = "http://" + window.location.hostname + "/internal-page.html"; @@ -84,7 +88,7 @@ QUnit.test("ignore internal links", async (assert) => { assert.equal(find(".title-input input").val(), title, "title is unchanged"); }); -QUnit.test("link is longer than max title length", async (assert) => { +test("link is longer than max title length", async (assert) => { await visit("/"); await click("#create-topic"); await fillIn( @@ -106,29 +110,26 @@ QUnit.test("link is longer than max title length", async (assert) => { ); }); -QUnit.test( - "onebox with title but extra words in title field", - async (assert) => { - await visit("/"); - await click("#create-topic"); - await fillIn("#reply-title", "http://www.example.com/has-title.html test"); - assert.equal( - find(".d-editor-preview").html().trim().indexOf("onebox"), - -1, - "onebox preview doesn't show" - ); - assert.equal( - find(".d-editor-input").val().length, - 0, - "link isn't put into the post" - ); - assert.equal( - find(".title-input input").val(), - "http://www.example.com/has-title.html test", - "title is unchanged" - ); - } -); +test("onebox with title but extra words in title field", async (assert) => { + await visit("/"); + await click("#create-topic"); + await fillIn("#reply-title", "http://www.example.com/has-title.html test"); + assert.equal( + find(".d-editor-preview").html().trim().indexOf("onebox"), + -1, + "onebox preview doesn't show" + ); + assert.equal( + find(".d-editor-input").val().length, + 0, + "link isn't put into the post" + ); + assert.equal( + find(".title-input input").val(), + "http://www.example.com/has-title.html test", + "title is unchanged" + ); +}); acceptance("Composer topic featured links when uncategorized is not allowed", { loggedIn: true, @@ -140,7 +141,7 @@ acceptance("Composer topic featured links when uncategorized is not allowed", { }, }); -QUnit.test("Pasting a link enables the text input area", async (assert) => { +test("Pasting a link enables the text input area", async (assert) => { updateCurrentUser({ moderator: false, admin: false, trust_level: 1 }); await visit("/"); diff --git a/test/javascripts/acceptance/composer-uncategorized-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-uncategorized-test.js similarity index 61% rename from test/javascripts/acceptance/composer-uncategorized-test.js rename to app/assets/javascripts/discourse/tests/acceptance/composer-uncategorized-test.js index 26ff2f1148..b908e034d2 100644 --- a/test/javascripts/acceptance/composer-uncategorized-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-uncategorized-test.js @@ -1,5 +1,9 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance( "Composer disabled, uncategorized not allowed when any topic_template present", @@ -12,7 +16,7 @@ acceptance( } ); -QUnit.test("Disable body until category is selected", async (assert) => { +test("Disable body until category is selected", async (assert) => { updateCurrentUser({ moderator: false, admin: false, trust_level: 1 }); await visit("/"); @@ -83,36 +87,33 @@ acceptance( } ); -QUnit.test( - "Enable composer/body if no topic templates present", - async (assert) => { - updateCurrentUser({ moderator: false, admin: false, trust_level: 1 }); +test("Enable composer/body if no topic templates present", async (assert) => { + updateCurrentUser({ moderator: false, admin: false, trust_level: 1 }); - await visit("/"); - await click("#create-topic"); - assert.ok(exists(".d-editor-input"), "the composer input is visible"); - assert.ok( - exists(".category-input .popup-tip.bad.hide"), - "category errors are hidden by default" - ); - assert.ok( - find(".d-editor-textarea-wrapper.disabled").length === 0, - "textarea is enabled" - ); + await visit("/"); + await click("#create-topic"); + assert.ok(exists(".d-editor-input"), "the composer input is visible"); + assert.ok( + exists(".category-input .popup-tip.bad.hide"), + "category errors are hidden by default" + ); + assert.ok( + find(".d-editor-textarea-wrapper.disabled").length === 0, + "textarea is enabled" + ); - await click("#reply-control button.create"); - assert.ok( - exists(".category-input .popup-tip.bad"), - "it shows the choose a category error" - ); + await click("#reply-control button.create"); + assert.ok( + exists(".category-input .popup-tip.bad"), + "it shows the choose a category error" + ); - const categoryChooser = selectKit(".category-chooser"); - await categoryChooser.expand(); - await categoryChooser.selectRowByValue(1); + const categoryChooser = selectKit(".category-chooser"); + await categoryChooser.expand(); + await categoryChooser.selectRowByValue(1); - assert.ok( - !exists(".category-input .popup-tip.bad"), - "category error removed after selecting category" - ); - } -); + assert.ok( + !exists(".category-input .popup-tip.bad"), + "category error removed after selecting category" + ); +}); diff --git a/test/javascripts/acceptance/create-account-external-test.js b/app/assets/javascripts/discourse/tests/acceptance/create-account-external-test.js similarity index 82% rename from test/javascripts/acceptance/create-account-external-test.js rename to app/assets/javascripts/discourse/tests/acceptance/create-account-external-test.js index 75466ce96d..d9bdfef09f 100644 --- a/test/javascripts/acceptance/create-account-external-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/create-account-external-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Create Account - external auth", { beforeEach() { @@ -19,7 +20,7 @@ acceptance("Create Account - external auth", { }, }); -QUnit.test("when skip is disabled (default)", async (assert) => { +test("when skip is disabled (default)", async (assert) => { await visit("/"); assert.ok( @@ -30,7 +31,7 @@ QUnit.test("when skip is disabled (default)", async (assert) => { assert.ok(exists("#new-account-username"), "it shows the fields"); }); -QUnit.test("when skip is enabled", async function (assert) { +test("when skip is enabled", async function (assert) { this.siteSettings.external_auth_skip_create_confirm = true; await visit("/"); diff --git a/test/javascripts/acceptance/create-account-user-fields-test.js b/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js similarity index 92% rename from test/javascripts/acceptance/create-account-user-fields-test.js rename to app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js index 8bd6f0ca99..89c88c9d45 100644 --- a/test/javascripts/acceptance/create-account-user-fields-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Create Account - User Fields", { site: { @@ -25,7 +26,7 @@ acceptance("Create Account - User Fields", { }, }); -QUnit.test("create account with user fields", async (assert) => { +test("create account with user fields", async (assert) => { await visit("/"); await click("header .sign-up-button"); diff --git a/test/javascripts/acceptance/custom-html-set-test.js b/app/assets/javascripts/discourse/tests/acceptance/custom-html-set-test.js similarity index 72% rename from test/javascripts/acceptance/custom-html-set-test.js rename to app/assets/javascripts/discourse/tests/acceptance/custom-html-set-test.js index 84c235aba9..2a4277ab9f 100644 --- a/test/javascripts/acceptance/custom-html-set-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/custom-html-set-test.js @@ -1,15 +1,16 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { setCustomHTML } from "discourse/helpers/custom-html"; import PreloadStore from "discourse/lib/preload-store"; acceptance("CustomHTML set"); -QUnit.test("has no custom HTML in the top", async (assert) => { +test("has no custom HTML in the top", async (assert) => { await visit("/static/faq"); assert.ok(!exists("span.custom-html-test"), "it has no markup"); }); -QUnit.test("renders set HTML", async (assert) => { +test("renders set HTML", async (assert) => { setCustomHTML("top", 'HTML'); await visit("/static/faq"); @@ -20,7 +21,7 @@ QUnit.test("renders set HTML", async (assert) => { ); }); -QUnit.test("renders preloaded HTML", async (assert) => { +test("renders preloaded HTML", async (assert) => { PreloadStore.store("customHTML", { top: "monster", }); diff --git a/test/javascripts/acceptance/custom-html-template-test.js b/app/assets/javascripts/discourse/tests/acceptance/custom-html-template-test.js similarity index 69% rename from test/javascripts/acceptance/custom-html-template-test.js rename to app/assets/javascripts/discourse/tests/acceptance/custom-html-template-test.js index 21fff43f41..f74358a381 100644 --- a/test/javascripts/acceptance/custom-html-template-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/custom-html-template-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("CustomHTML template", { beforeEach() { @@ -12,7 +13,7 @@ acceptance("CustomHTML template", { }, }); -QUnit.test("renders custom template", async (assert) => { +test("renders custom template", async (assert) => { await visit("/static/faq"); assert.equal(find("span.top-span").text(), "TOP", "it inserted the template"); }); diff --git a/test/javascripts/acceptance/dashboard-test.js b/app/assets/javascripts/discourse/tests/acceptance/dashboard-test.js similarity index 88% rename from test/javascripts/acceptance/dashboard-test.js rename to app/assets/javascripts/discourse/tests/acceptance/dashboard-test.js index d2296ad1f3..54f618476f 100644 --- a/test/javascripts/acceptance/dashboard-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/dashboard-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Dashboard", { loggedIn: true, @@ -21,13 +22,13 @@ acceptance("Dashboard", { }, }); -QUnit.test("default", async (assert) => { +test("default", async (assert) => { await visit("/admin"); assert.ok(exists(".dashboard"), "has dashboard-next class"); }); -QUnit.test("tabs", async (assert) => { +test("tabs", async (assert) => { await visit("/admin"); assert.ok(exists(".dashboard .navigation-item.general"), "general tab"); @@ -36,7 +37,7 @@ QUnit.test("tabs", async (assert) => { assert.ok(exists(".dashboard .navigation-item.reports"), "reports tab"); }); -QUnit.test("general tab", async (assert) => { +test("general tab", async (assert) => { await visit("/admin"); assert.ok(exists(".admin-report.signups"), "signups report"); assert.ok(exists(".admin-report.posts"), "posts report"); @@ -59,7 +60,7 @@ QUnit.test("general tab", async (assert) => { ); }); -QUnit.test("activity metrics", async (assert) => { +test("activity metrics", async (assert) => { await visit("/admin"); assert.ok(exists(".admin-report.page-view-total-reqs .today-count")); @@ -68,7 +69,7 @@ QUnit.test("activity metrics", async (assert) => { assert.ok(exists(".admin-report.page-view-total-reqs .thirty-days-count")); }); -QUnit.test("reports tab", async (assert) => { +test("reports tab", async (assert) => { await visit("/admin"); await click(".dashboard .navigation-item.reports .navigation-link"); @@ -102,7 +103,7 @@ QUnit.test("reports tab", async (assert) => { ); }); -QUnit.test("reports filters", async (assert) => { +test("reports filters", async (assert) => { await visit( '/admin/reports/signups_with_groups?end_date=2018-07-16&filters=%7B"group"%3A88%7D&start_date=2018-06-16' ); @@ -123,7 +124,7 @@ acceptance("Dashboard: dashboard_visible_tabs", { }, }); -QUnit.test("visible tabs", async (assert) => { +test("visible tabs", async (assert) => { await visit("/admin"); assert.ok(exists(".dashboard .navigation-item.general"), "general tab"); @@ -143,7 +144,7 @@ acceptance("Dashboard: dashboard_hidden_reports", { }, }); -QUnit.test("hidden reports", async (assert) => { +test("hidden reports", async (assert) => { await visit("/admin"); assert.ok(exists(".admin-report.signups.is-visible"), "signups report"); diff --git a/test/javascripts/acceptance/email-notice-test.js b/app/assets/javascripts/discourse/tests/acceptance/email-notice-test.js similarity index 74% rename from test/javascripts/acceptance/email-notice-test.js rename to app/assets/javascripts/discourse/tests/acceptance/email-notice-test.js index 2a664140f5..5707aae706 100644 --- a/test/javascripts/acceptance/email-notice-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/email-notice-test.js @@ -1,10 +1,14 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Email Disabled Banner", { loggedIn: true, }); -QUnit.test("when disabled", async function (assert) { +test("when disabled", async function (assert) { this.siteSettings.disable_emails = "no"; await visit("/"); assert.notOk( @@ -13,7 +17,7 @@ QUnit.test("when disabled", async function (assert) { ); }); -QUnit.test("when enabled", async function (assert) { +test("when enabled", async function (assert) { this.siteSettings.disable_emails = "yes"; await visit("/latest"); assert.ok( @@ -22,7 +26,7 @@ QUnit.test("when enabled", async function (assert) { ); }); -QUnit.test("when non-staff", async function (assert) { +test("when non-staff", async function (assert) { this.siteSettings.disable_emails = "non-staff"; await visit("/"); assert.ok( diff --git a/app/assets/javascripts/discourse/tests/acceptance/emoji-picker-test.js b/app/assets/javascripts/discourse/tests/acceptance/emoji-picker-test.js new file mode 100644 index 0000000000..f356963665 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/emoji-picker-test.js @@ -0,0 +1,137 @@ +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; + +acceptance("EmojiPicker", { + loggedIn: true, + beforeEach() { + this.emojiStore = this.container.lookup("service:emoji-store"); + this.emojiStore.reset(); + }, + afterEach() { + this.emojiStore.reset(); + }, +}); + +test("emoji picker can be opened/closed", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-buttons .btn.create"); + + await click("button.emoji.btn"); + assert.ok(exists(".emoji-picker.opened"), "it opens the picker"); + + await click("button.emoji.btn"); + assert.notOk(exists(".emoji-picker.opened"), "it closes the picker"); +}); + +test("emoji picker triggers event when picking emoji", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-buttons .btn.create"); + await click("button.emoji.btn"); + await click(".emoji-picker-emoji-area img.emoji[title='grinning']"); + + assert.equal( + find(".d-editor-input").val(), + ":grinning:", + "it adds the emoji code in the editor when selected" + ); +}); + +test("emoji picker adds leading whitespace before emoji", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-buttons .btn.create"); + + // Whitespace should be added on text + await fillIn(".d-editor-input", "This is a test input"); + await click("button.emoji.btn"); + await click(".emoji-picker-emoji-area img.emoji[title='grinning']"); + assert.equal( + find(".d-editor-input").val(), + "This is a test input :grinning:", + "it adds the emoji code and a leading whitespace when there is text" + ); + + // Whitespace should not be added on whitespace + await fillIn(".d-editor-input", "This is a test input "); + await click(".emoji-picker-emoji-area img.emoji[title='grinning']"); + + assert.equal( + find(".d-editor-input").val(), + "This is a test input :grinning:", + "it adds the emoji code and no leading whitespace when user already entered whitespace" + ); +}); + +test("emoji picker has a list of recently used emojis", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-buttons .btn.create"); + await click("button.emoji.btn"); + await click(".emoji-picker-emoji-area img.emoji[title='grinning']"); + + assert.ok( + exists( + ".emoji-picker .section.recent .section-group img.emoji[title='grinning']" + ), + "it shows recent selected emoji" + ); + + assert.ok( + exists('.emoji-picker .category-button[data-section="recent"]'), + "it shows recent category icon" + ); + + await click(".emoji-picker .trash-recent"); + + assert.notOk( + exists( + ".emoji-picker .section.recent .section-group img.emoji[title='grinning']" + ), + "it has cleared recent emojis" + ); + + assert.notOk( + exists('.emoji-picker .section[data-section="recent"]'), + "it hides recent section" + ); + + assert.notOk( + exists('.emoji-picker .category-button[data-section="recent"]'), + "it hides recent category icon" + ); +}); + +test("emoji picker correctly orders recently used emojis", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-buttons .btn.create"); + await click("button.emoji.btn"); + await click(".emoji-picker-emoji-area img.emoji[title='sunglasses']"); + await click(".emoji-picker-emoji-area img.emoji[title='grinning']"); + + assert.equal( + find('.section[data-section="recent"] .section-group img.emoji').length, + 2, + "it has multiple recent emojis" + ); + + assert.equal( + /grinning/.test( + find(".section.recent .section-group img.emoji").first().attr("src") + ), + true, + "it puts the last used emoji in first" + ); +}); + +test("emoji picker persists state", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-buttons .btn.create"); + await click("button.emoji.btn"); + await click(".emoji-picker button.diversity-scale.medium-dark"); + await click("button.emoji.btn"); + await click("button.emoji.btn"); + + assert.ok( + exists(".emoji-picker button.diversity-scale.medium-dark .d-icon"), + true, + "it stores diversity scale" + ); +}); diff --git a/test/javascripts/acceptance/emoji-test.js b/app/assets/javascripts/discourse/tests/acceptance/emoji-test.js similarity index 81% rename from test/javascripts/acceptance/emoji-test.js rename to app/assets/javascripts/discourse/tests/acceptance/emoji-test.js index 00218ffc3c..e3bcf210ad 100644 --- a/test/javascripts/acceptance/emoji-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/emoji-test.js @@ -1,9 +1,10 @@ +import { test } from "qunit"; import { IMAGE_VERSION as v } from "pretty-text/emoji/version"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Emoji", { loggedIn: true }); -QUnit.test("emoji is cooked properly", async (assert) => { +test("emoji is cooked properly", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#topic-footer-buttons .btn.create"); @@ -14,7 +15,7 @@ QUnit.test("emoji is cooked properly", async (assert) => { ); }); -QUnit.test("skin toned emoji is cooked properly", async (assert) => { +test("skin toned emoji is cooked properly", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#topic-footer-buttons .btn.create"); diff --git a/test/javascripts/acceptance/encoded-category-test.js b/app/assets/javascripts/discourse/tests/acceptance/encoded-category-test.js similarity index 88% rename from test/javascripts/acceptance/encoded-category-test.js rename to app/assets/javascripts/discourse/tests/acceptance/encoded-category-test.js index d31e71d662..f3e1a66f8b 100644 --- a/test/javascripts/acceptance/encoded-category-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/encoded-category-test.js @@ -1,5 +1,6 @@ -import { acceptance } from "helpers/qunit-helpers"; -import DiscoveryFixtures from "fixtures/discovery_fixtures"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import DiscoveryFixtures from "discourse/tests/fixtures/discovery-fixtures"; acceptance("Encoded Sub Category Discovery", { pretend(server, helper) { @@ -41,7 +42,7 @@ acceptance("Encoded Sub Category Discovery", { }, }); -QUnit.test("Visit subcategory by slug", async (assert) => { +test("Visit subcategory by slug", async (assert) => { let bodySelector = "body.category-\\%E6\\%BC\\%A2\\%E5\\%AD\\%97-parent-\\%E6\\%BC\\%A2\\%E5\\%AD\\%97-subcategory"; await visit("/c/%E6%BC%A2%E5%AD%97-parent/%E6%BC%A2%E5%AD%97-subcategory"); diff --git a/test/javascripts/acceptance/enforce-second-factor-test.js b/app/assets/javascripts/discourse/tests/acceptance/enforce-second-factor-test.js similarity index 87% rename from test/javascripts/acceptance/enforce-second-factor-test.js rename to app/assets/javascripts/discourse/tests/acceptance/enforce-second-factor-test.js index 6f59631d87..945bad39a2 100644 --- a/test/javascripts/acceptance/enforce-second-factor-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/enforce-second-factor-test.js @@ -1,4 +1,8 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Enforce Second Factor", { loggedIn: true, @@ -12,7 +16,7 @@ acceptance("Enforce Second Factor", { }, }); -QUnit.test("as an admin", async function (assert) { +test("as an admin", async function (assert) { await visit("/u/eviltrout/preferences/second-factor"); this.siteSettings.enforce_second_factor = "staff"; @@ -34,7 +38,7 @@ QUnit.test("as an admin", async function (assert) { ); }); -QUnit.test("as a user", async function (assert) { +test("as a user", async function (assert) { updateCurrentUser({ moderator: false, admin: false }); await visit("/u/eviltrout/preferences/second-factor"); @@ -58,7 +62,7 @@ QUnit.test("as a user", async function (assert) { ); }); -QUnit.test("as an anonymous user", async function (assert) { +test("as an anonymous user", async function (assert) { updateCurrentUser({ moderator: false, admin: false, is_anonymous: true }); await visit("/u/eviltrout/preferences/second-factor"); diff --git a/test/javascripts/acceptance/forgot-password-test.js b/app/assets/javascripts/discourse/tests/acceptance/forgot-password-test.js similarity index 92% rename from test/javascripts/acceptance/forgot-password-test.js rename to app/assets/javascripts/discourse/tests/acceptance/forgot-password-test.js index bbd532a651..56b606eab8 100644 --- a/test/javascripts/acceptance/forgot-password-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/forgot-password-test.js @@ -1,5 +1,6 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; let userFound = false; @@ -13,7 +14,7 @@ acceptance("Forgot password", { }, }); -QUnit.test("requesting password reset", async (assert) => { +test("requesting password reset", async (assert) => { await visit("/"); await click("header .login-button"); await click("#forgot-password-link"); diff --git a/test/javascripts/acceptance/group-card-mobile-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-card-mobile-test.js similarity index 80% rename from test/javascripts/acceptance/group-card-mobile-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-card-mobile-test.js index d7a5e8083c..0b90426056 100644 --- a/test/javascripts/acceptance/group-card-mobile-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-card-mobile-test.js @@ -1,9 +1,10 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { skip } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import DiscourseURL from "discourse/lib/url"; acceptance("Group Card - Mobile", { mobileView: true }); -QUnit.skip("group card", async (assert) => { +skip("group card", async (assert) => { await visit("/t/-/301/1"); assert.ok( invisible(".group-card"), diff --git a/test/javascripts/acceptance/group-card-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-card-test.js similarity index 78% rename from test/javascripts/acceptance/group-card-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-card-test.js index 06a1d596c5..89113098af 100644 --- a/test/javascripts/acceptance/group-card-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-card-test.js @@ -1,9 +1,10 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { skip } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import DiscourseURL from "discourse/lib/url"; acceptance("Group Card"); -QUnit.skip("group card", async (assert) => { +skip("group card", async (assert) => { await visit("/t/-/301/1"); assert.ok(invisible(".group-card"), "user card is invisible by default"); diff --git a/test/javascripts/acceptance/group-index-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-index-test.js similarity index 80% rename from test/javascripts/acceptance/group-index-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-index-test.js index a35956958e..1abdcb3f43 100644 --- a/test/javascripts/acceptance/group-index-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-index-test.js @@ -1,9 +1,13 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Group Members"); -QUnit.test("Viewing Members as anon user", async (assert) => { +test("Viewing Members as anon user", async (assert) => { await visit("/g/discourse"); assert.ok( @@ -26,7 +30,7 @@ QUnit.test("Viewing Members as anon user", async (assert) => { acceptance("Group Members", { loggedIn: true }); -QUnit.test("Viewing Members as a group owner", async (assert) => { +test("Viewing Members as a group owner", async (assert) => { updateCurrentUser({ moderator: false, admin: false }); await visit("/g/discourse"); @@ -39,7 +43,7 @@ QUnit.test("Viewing Members as a group owner", async (assert) => { ); }); -QUnit.test("Viewing Members as an admin user", async (assert) => { +test("Viewing Members as an admin user", async (assert) => { await visit("/g/discourse"); assert.ok( diff --git a/test/javascripts/acceptance/group-manage-categories-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-categories-test.js similarity index 74% rename from test/javascripts/acceptance/group-manage-categories-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-manage-categories-test.js index af3d850f05..918970d03c 100644 --- a/test/javascripts/acceptance/group-manage-categories-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-categories-test.js @@ -1,7 +1,11 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Managing Group Category Notification Defaults"); -QUnit.test("As an anonymous user", async (assert) => { +test("As an anonymous user", async (assert) => { await visit("/g/discourse/manage/categories"); assert.ok( @@ -12,7 +16,7 @@ QUnit.test("As an anonymous user", async (assert) => { acceptance("Managing Group Category Notification Defaults", { loggedIn: true }); -QUnit.test("As an admin", async (assert) => { +test("As an admin", async (assert) => { await visit("/g/discourse/manage/categories"); assert.ok( @@ -21,7 +25,7 @@ QUnit.test("As an admin", async (assert) => { ); }); -QUnit.test("As a group owner", async (assert) => { +test("As a group owner", async (assert) => { updateCurrentUser({ moderator: false, admin: false }); await visit("/g/discourse/manage/categories"); diff --git a/test/javascripts/acceptance/group-manage-interaction-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-interaction-test.js similarity index 89% rename from test/javascripts/acceptance/group-manage-interaction-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-manage-interaction-test.js index 0f2d2b741c..5631f4f5ec 100644 --- a/test/javascripts/acceptance/group-manage-interaction-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-interaction-test.js @@ -1,4 +1,8 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Managing Group Interaction Settings", { loggedIn: true, @@ -7,7 +11,7 @@ acceptance("Managing Group Interaction Settings", { }, }); -QUnit.test("As an admin", async (assert) => { +test("As an admin", async (assert) => { updateCurrentUser({ moderator: false, admin: true, @@ -47,7 +51,7 @@ QUnit.test("As an admin", async (assert) => { ); }); -QUnit.test("As a group owner", async (assert) => { +test("As a group owner", async (assert) => { updateCurrentUser({ moderator: false, admin: false, diff --git a/test/javascripts/acceptance/group-manage-logs-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-logs-test.js similarity index 95% rename from test/javascripts/acceptance/group-manage-logs-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-manage-logs-test.js index 0e1976ddc5..c666c2eb6b 100644 --- a/test/javascripts/acceptance/group-manage-logs-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-logs-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Group logs", { loggedIn: true, @@ -92,7 +93,7 @@ acceptance("Group logs", { }, }); -QUnit.test("Browsing group logs", async (assert) => { +test("Browsing group logs", async (assert) => { await visit("/g/snorlax/manage/logs"); assert.ok( find("tr.group-manage-logs-row").length === 2, diff --git a/test/javascripts/acceptance/group-manage-membership-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-membership-test.js similarity index 91% rename from test/javascripts/acceptance/group-manage-membership-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-manage-membership-test.js index 0e2af2fee5..acb0e76072 100644 --- a/test/javascripts/acceptance/group-manage-membership-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-membership-test.js @@ -1,11 +1,15 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; -import selectKit from "helpers/select-kit-helper"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; acceptance("Managing Group Membership", { loggedIn: true, }); -QUnit.test("As an admin", async (assert) => { +test("As an admin", async (assert) => { updateCurrentUser({ can_create_group: true }); await visit("/g/alternative-group/manage/membership"); @@ -72,7 +76,7 @@ QUnit.test("As an admin", async (assert) => { assert.equal(emailDomains.header().value(), "foo.com"); }); -QUnit.test("As a group owner", async (assert) => { +test("As a group owner", async (assert) => { updateCurrentUser({ moderator: false, admin: false }); await visit("/g/discourse/manage/membership"); diff --git a/test/javascripts/acceptance/group-manage-profile-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-profile-test.js similarity index 79% rename from test/javascripts/acceptance/group-manage-profile-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-manage-profile-test.js index 34c744dee7..2d8b5de924 100644 --- a/test/javascripts/acceptance/group-manage-profile-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-profile-test.js @@ -1,7 +1,11 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Managing Group Profile"); -QUnit.test("As an anonymous user", async (assert) => { +test("As an anonymous user", async (assert) => { await visit("/g/discourse/manage/profile"); assert.ok( @@ -12,7 +16,7 @@ QUnit.test("As an anonymous user", async (assert) => { acceptance("Managing Group Profile", { loggedIn: true }); -QUnit.test("As an admin", async (assert) => { +test("As an admin", async (assert) => { await visit("/g/discourse/manage/profile"); assert.ok( @@ -33,7 +37,7 @@ QUnit.test("As an admin", async (assert) => { ); }); -QUnit.test("As a group owner", async (assert) => { +test("As a group owner", async (assert) => { updateCurrentUser({ moderator: false, admin: false, diff --git a/test/javascripts/acceptance/group-manage-tags-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-tags-test.js similarity index 73% rename from test/javascripts/acceptance/group-manage-tags-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-manage-tags-test.js index e71c43f64c..d8cf22ed58 100644 --- a/test/javascripts/acceptance/group-manage-tags-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-tags-test.js @@ -1,7 +1,11 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Managing Group Tag Notification Defaults"); -QUnit.test("As an anonymous user", async (assert) => { +test("As an anonymous user", async (assert) => { await visit("/g/discourse/manage/tags"); assert.ok( @@ -12,7 +16,7 @@ QUnit.test("As an anonymous user", async (assert) => { acceptance("Managing Group Tag Notification Defaults", { loggedIn: true }); -QUnit.test("As an admin", async (assert) => { +test("As an admin", async (assert) => { await visit("/g/discourse/manage/tags"); assert.ok( @@ -21,7 +25,7 @@ QUnit.test("As an admin", async (assert) => { ); }); -QUnit.test("As a group owner", async (assert) => { +test("As a group owner", async (assert) => { updateCurrentUser({ moderator: false, admin: false }); await visit("/g/discourse/manage/tags"); diff --git a/test/javascripts/acceptance/group-requests-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-requests-test.js similarity index 94% rename from test/javascripts/acceptance/group-requests-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-requests-test.js index 39759517f9..42bd1a6120 100644 --- a/test/javascripts/acceptance/group-requests-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-requests-test.js @@ -1,5 +1,6 @@ -import { acceptance } from "helpers/qunit-helpers"; -import { parsePostData } from "helpers/create-pretender"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import { parsePostData } from "discourse/tests/helpers/create-pretender"; let requests = []; @@ -80,7 +81,7 @@ acceptance("Group Requests", { }, }); -QUnit.test("Group Requests", async (assert) => { +test("Group Requests", async (assert) => { await visit("/g/Macdonald/requests"); assert.equal(find(".group-members tr").length, 2); diff --git a/test/javascripts/acceptance/group-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-test.js similarity index 86% rename from test/javascripts/acceptance/group-test.js rename to app/assets/javascripts/discourse/tests/acceptance/group-test.js index 3990c05558..4dba725f8f 100644 --- a/test/javascripts/acceptance/group-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-test.js @@ -1,7 +1,8 @@ +import { test } from "qunit"; import I18n from "I18n"; -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; let groupArgs = { settings: { @@ -22,7 +23,7 @@ const response = (object) => { return [200, { "Content-Type": "application/json" }, object]; }; -QUnit.test("Anonymous Viewing Group", async function (assert) { +test("Anonymous Viewing Group", async function (assert) { await visit("/g/discourse"); assert.equal( @@ -77,7 +78,7 @@ QUnit.test("Anonymous Viewing Group", async function (assert) { ); }); -QUnit.test("Anonymous Viewing Automatic Group", async (assert) => { +test("Anonymous Viewing Automatic Group", async (assert) => { await visit("/g/moderators"); assert.equal( @@ -89,7 +90,7 @@ QUnit.test("Anonymous Viewing Automatic Group", async (assert) => { acceptance("Group", Object.assign({ loggedIn: true }, groupArgs)); -QUnit.test("User Viewing Group", async (assert) => { +test("User Viewing Group", async (assert) => { await visit("/g"); await click(".group-index-request"); @@ -122,28 +123,25 @@ QUnit.test("User Viewing Group", async (assert) => { ); }); -QUnit.test( - "Admin viewing group messages when there are no messages", - async (assert) => { - pretender.get( - "/topics/private-messages-group/eviltrout/discourse.json", - () => { - return response({ topic_list: { topics: [] } }); - } - ); +test("Admin viewing group messages when there are no messages", async (assert) => { + pretender.get( + "/topics/private-messages-group/eviltrout/discourse.json", + () => { + return response({ topic_list: { topics: [] } }); + } + ); - await visit("/g/discourse"); - await click(".nav-pills li a[title='Messages']"); + await visit("/g/discourse"); + await click(".nav-pills li a[title='Messages']"); - assert.equal( - find(".alert").text().trim(), - I18n.t("choose_topic.none_found"), - "it should display the right alert" - ); - } -); + assert.equal( + find(".alert").text().trim(), + I18n.t("choose_topic.none_found"), + "it should display the right alert" + ); +}); -QUnit.test("Admin viewing group messages", async (assert) => { +test("Admin viewing group messages", async (assert) => { pretender.get( "/topics/private-messages-group/eviltrout/discourse.json", () => { @@ -238,7 +236,7 @@ QUnit.test("Admin viewing group messages", async (assert) => { ); }); -QUnit.test("Admin Viewing Group", async (assert) => { +test("Admin Viewing Group", async (assert) => { await visit("/g/discourse"); assert.ok( diff --git a/test/javascripts/acceptance/groups-index-test.js b/app/assets/javascripts/discourse/tests/acceptance/groups-index-test.js similarity index 88% rename from test/javascripts/acceptance/groups-index-test.js rename to app/assets/javascripts/discourse/tests/acceptance/groups-index-test.js index 5868920f8b..48adf928b5 100644 --- a/test/javascripts/acceptance/groups-index-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/groups-index-test.js @@ -1,8 +1,9 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Groups"); -QUnit.test("Browsing Groups", async (assert) => { +test("Browsing Groups", async (assert) => { await visit("/g?username=eviltrout"); assert.equal(count(".group-box"), 1, "it displays user's groups"); diff --git a/test/javascripts/acceptance/groups-new-test.js b/app/assets/javascripts/discourse/tests/acceptance/groups-new-test.js similarity index 89% rename from test/javascripts/acceptance/groups-new-test.js rename to app/assets/javascripts/discourse/tests/acceptance/groups-new-test.js index 85954e6521..b43f40b045 100644 --- a/test/javascripts/acceptance/groups-new-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/groups-new-test.js @@ -1,9 +1,10 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("New Group"); -QUnit.test("As an anon user", async (assert) => { +test("As an anon user", async (assert) => { await visit("/g"); assert.equal( @@ -15,7 +16,7 @@ QUnit.test("As an anon user", async (assert) => { acceptance("New Group", { loggedIn: true }); -QUnit.test("Creating a new group", async (assert) => { +test("Creating a new group", async (assert) => { await visit("/g"); await click(".groups-header-new"); diff --git a/test/javascripts/acceptance/hamburger-menu-test.js b/app/assets/javascripts/discourse/tests/acceptance/hamburger-menu-test.js similarity index 70% rename from test/javascripts/acceptance/hamburger-menu-test.js rename to app/assets/javascripts/discourse/tests/acceptance/hamburger-menu-test.js index 11fe87742d..25b95d2805 100644 --- a/test/javascripts/acceptance/hamburger-menu-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/hamburger-menu-test.js @@ -1,4 +1,8 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Opening the hamburger menu with some reviewables", { loggedIn: true, @@ -7,7 +11,7 @@ acceptance("Opening the hamburger menu with some reviewables", { }, }); -QUnit.test("As a staff member", async (assert) => { +test("As a staff member", async (assert) => { updateCurrentUser({ moderator: true, admin: false }); await visit("/"); diff --git a/test/javascripts/acceptance/hashtags-test.js b/app/assets/javascripts/discourse/tests/acceptance/hashtags-test.js similarity index 86% rename from test/javascripts/acceptance/hashtags-test.js rename to app/assets/javascripts/discourse/tests/acceptance/hashtags-test.js index 4a8559ea6d..a5e99a0b54 100644 --- a/test/javascripts/acceptance/hashtags-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/hashtags-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Category and Tag Hashtags", { loggedIn: true, @@ -16,7 +17,7 @@ acceptance("Category and Tag Hashtags", { }, }); -QUnit.test("hashtags are cooked properly", async (assert) => { +test("hashtags are cooked properly", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#topic-footer-buttons .btn.create"); diff --git a/test/javascripts/acceptance/invite-accept-test.js b/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js similarity index 94% rename from test/javascripts/acceptance/invite-accept-test.js rename to app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js index eb8d85431e..3c24b3c21b 100644 --- a/test/javascripts/acceptance/invite-accept-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import PreloadStore from "discourse/lib/preload-store"; acceptance("Invite Accept", { @@ -7,7 +8,7 @@ acceptance("Invite Accept", { }, }); -QUnit.test("Invite Acceptance Page", async (assert) => { +test("Invite Acceptance Page", async (assert) => { PreloadStore.store("invite_info", { invited_by: { id: 123, diff --git a/test/javascripts/acceptance/invite-show-user-fields-test.js b/app/assets/javascripts/discourse/tests/acceptance/invite-show-user-fields-test.js similarity index 92% rename from test/javascripts/acceptance/invite-show-user-fields-test.js rename to app/assets/javascripts/discourse/tests/acceptance/invite-show-user-fields-test.js index f27b91850f..1883acf188 100644 --- a/test/javascripts/acceptance/invite-show-user-fields-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/invite-show-user-fields-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import PreloadStore from "discourse/lib/preload-store"; acceptance("Accept Invite - User Fields", { @@ -26,7 +27,7 @@ acceptance("Accept Invite - User Fields", { }, }); -QUnit.test("accept invite with user fields", async (assert) => { +test("accept invite with user fields", async (assert) => { PreloadStore.store("invite_info", { invited_by: { id: 123, diff --git a/test/javascripts/acceptance/jump-to-test.js b/app/assets/javascripts/discourse/tests/acceptance/jump-to-test.js similarity index 88% rename from test/javascripts/acceptance/jump-to-test.js rename to app/assets/javascripts/discourse/tests/acceptance/jump-to-test.js index fca677999f..7bf47d2494 100644 --- a/test/javascripts/acceptance/jump-to-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/jump-to-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Jump to", { loggedIn: true, @@ -20,7 +21,7 @@ acceptance("Jump to", { }, }); -QUnit.test("default", async (assert) => { +test("default", async (assert) => { await visit("/t/internationalization-localization/280"); await click("nav#topic-progress .nums"); await click("button.jump-to-post"); @@ -37,7 +38,7 @@ QUnit.test("default", async (assert) => { ); }); -QUnit.test("invalid date", async (assert) => { +test("invalid date", async (assert) => { await visit("/t/internationalization-localization/280"); await click("nav#topic-progress .nums"); await click("button.jump-to-post"); diff --git a/test/javascripts/acceptance/keyboard-shortcuts-test.js b/app/assets/javascripts/discourse/tests/acceptance/keyboard-shortcuts-test.js similarity index 90% rename from test/javascripts/acceptance/keyboard-shortcuts-test.js rename to app/assets/javascripts/discourse/tests/acceptance/keyboard-shortcuts-test.js index 76c3bca928..fc91ab988b 100644 --- a/test/javascripts/acceptance/keyboard-shortcuts-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/keyboard-shortcuts-test.js @@ -1,5 +1,6 @@ -import { acceptance } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; acceptance("Keyboard Shortcuts", { loggedIn: true }); diff --git a/test/javascripts/acceptance/login-redirect-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-redirect-test.js similarity index 64% rename from test/javascripts/acceptance/login-redirect-test.js rename to app/assets/javascripts/discourse/tests/acceptance/login-redirect-test.js index a1a0ebf38f..b69449a8c0 100644 --- a/test/javascripts/acceptance/login-redirect-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/login-redirect-test.js @@ -1,7 +1,8 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Login redirect"); -QUnit.test("redirects login to default homepage", async function (assert) { +test("redirects login to default homepage", async function (assert) { await visit("/login"); assert.equal( currentPath(), @@ -16,7 +17,7 @@ acceptance("Login redirect - categories default", { }, }); -QUnit.test("when site setting is categories", async function (assert) { +test("when site setting is categories", async function (assert) { await visit("/login"); assert.equal( currentPath(), diff --git a/test/javascripts/acceptance/login-required-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-required-test.js similarity index 79% rename from test/javascripts/acceptance/login-required-test.js rename to app/assets/javascripts/discourse/tests/acceptance/login-required-test.js index a34fc49a39..f51993d737 100644 --- a/test/javascripts/acceptance/login-required-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/login-required-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Login Required", { settings: { @@ -6,7 +7,7 @@ acceptance("Login Required", { }, }); -QUnit.test("redirect", async (assert) => { +test("redirect", async (assert) => { await visit("/latest"); assert.equal(currentPath(), "login", "it redirects them to login"); diff --git a/test/javascripts/acceptance/login-with-email-and-hide-email-address-taken-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-and-hide-email-address-taken-test.js similarity index 77% rename from test/javascripts/acceptance/login-with-email-and-hide-email-address-taken-test.js rename to app/assets/javascripts/discourse/tests/acceptance/login-with-email-and-hide-email-address-taken-test.js index 6bdfb5ad7b..be8c5f69ce 100644 --- a/test/javascripts/acceptance/login-with-email-and-hide-email-address-taken-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-and-hide-email-address-taken-test.js @@ -1,6 +1,7 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; acceptance("Login with email - hide email address taken", { settings: { @@ -18,7 +19,7 @@ acceptance("Login with email - hide email address taken", { }, }); -QUnit.test("with hide_email_address_taken enabled", async (assert) => { +test("with hide_email_address_taken enabled", async (assert) => { await visit("/"); await click("header .login-button"); await fillIn("#login-account-name", "someuser@example.com"); diff --git a/test/javascripts/acceptance/login-with-email-and-no-social-logins-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-and-no-social-logins-test.js similarity index 68% rename from test/javascripts/acceptance/login-with-email-and-no-social-logins-test.js rename to app/assets/javascripts/discourse/tests/acceptance/login-with-email-and-no-social-logins-test.js index ccf32b18be..20be2900f8 100644 --- a/test/javascripts/acceptance/login-with-email-and-no-social-logins-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-and-no-social-logins-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Login with email - no social logins", { settings: { @@ -9,14 +10,14 @@ acceptance("Login with email - no social logins", { }, }); -QUnit.test("with login with email enabled", async (assert) => { +test("with login with email enabled", async (assert) => { await visit("/"); await click("header .login-button"); assert.ok(exists(".login-with-email-button")); }); -QUnit.test("with login with email disabled", async (assert) => { +test("with login with email disabled", async (assert) => { await visit("/"); await click("header .login-button"); diff --git a/test/javascripts/acceptance/login-with-email-disabled-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-disabled-test.js similarity index 74% rename from test/javascripts/acceptance/login-with-email-disabled-test.js rename to app/assets/javascripts/discourse/tests/acceptance/login-with-email-disabled-test.js index 9203cbffe0..8512658ed5 100644 --- a/test/javascripts/acceptance/login-with-email-disabled-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-disabled-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Login with email disabled", { settings: { @@ -7,7 +8,7 @@ acceptance("Login with email disabled", { }, }); -QUnit.test("with email button", async (assert) => { +test("with email button", async (assert) => { await visit("/"); await click("header .login-button"); diff --git a/test/javascripts/acceptance/login-with-email-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js similarity index 93% rename from test/javascripts/acceptance/login-with-email-test.js rename to app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js index 786c56ddf7..1005e33736 100644 --- a/test/javascripts/acceptance/login-with-email-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js @@ -1,5 +1,6 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; let userFound = false; @@ -15,7 +16,7 @@ acceptance("Login with email", { }, }); -QUnit.test("with email button", async (assert) => { +test("with email button", async (assert) => { await visit("/"); await click("header .login-button"); diff --git a/test/javascripts/acceptance/mobile-discovery-test.js b/app/assets/javascripts/discourse/tests/acceptance/mobile-discovery-test.js similarity index 68% rename from test/javascripts/acceptance/mobile-discovery-test.js rename to app/assets/javascripts/discourse/tests/acceptance/mobile-discovery-test.js index a83a7872e7..8e5d3aa0f0 100644 --- a/test/javascripts/acceptance/mobile-discovery-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/mobile-discovery-test.js @@ -1,7 +1,8 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Topic Discovery - Mobile", { mobileView: true }); -QUnit.test("Visit Discovery Pages", async (assert) => { +test("Visit Discovery Pages", async (assert) => { await visit("/"); assert.ok(exists(".topic-list"), "The list of topics was rendered"); assert.ok(exists(".topic-list .topic-list-item"), "has topics"); diff --git a/test/javascripts/acceptance/mobile-sign-in-test.js b/app/assets/javascripts/discourse/tests/acceptance/mobile-sign-in-test.js similarity index 58% rename from test/javascripts/acceptance/mobile-sign-in-test.js rename to app/assets/javascripts/discourse/tests/acceptance/mobile-sign-in-test.js index b5a9e604c3..63f98611e4 100644 --- a/test/javascripts/acceptance/mobile-sign-in-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/mobile-sign-in-test.js @@ -1,8 +1,9 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Signing In - Mobile", { mobileView: true }); -QUnit.test("sign in", async (assert) => { +test("sign in", async (assert) => { await visit("/"); await click("header .login-button"); assert.ok(exists("#login-form"), "it shows the login modal"); diff --git a/test/javascripts/acceptance/mobile-users-test.js b/app/assets/javascripts/discourse/tests/acceptance/mobile-users-test.js similarity index 52% rename from test/javascripts/acceptance/mobile-users-test.js rename to app/assets/javascripts/discourse/tests/acceptance/mobile-users-test.js index 1b67837bff..bb636b60fc 100644 --- a/test/javascripts/acceptance/mobile-users-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/mobile-users-test.js @@ -1,8 +1,9 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("User Directory - Mobile", { mobileView: true }); -QUnit.test("Visit Page", async (assert) => { +test("Visit Page", async (assert) => { await visit("/u"); assert.ok(exists(".directory .user"), "has a list of users"); }); diff --git a/test/javascripts/acceptance/modal-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal-test.js similarity index 91% rename from test/javascripts/acceptance/modal-test.js rename to app/assets/javascripts/discourse/tests/acceptance/modal-test.js index 8414007d7a..f6bde32267 100644 --- a/test/javascripts/acceptance/modal-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/modal-test.js @@ -1,6 +1,11 @@ +import { skip } from "qunit"; +import { test } from "qunit"; import I18n from "I18n"; import { run } from "@ember/runloop"; -import { acceptance, controllerFor } from "helpers/qunit-helpers"; +import { + acceptance, + controllerFor, +} from "discourse/tests/helpers/qunit-helpers"; import showModal from "discourse/lib/show-modal"; acceptance("Modal", { @@ -21,7 +26,7 @@ acceptance("Modal", { }, }); -QUnit.skip("modal", async function (assert) { +skip("modal", async function (assert) { await visit("/"); assert.ok( @@ -71,7 +76,7 @@ QUnit.skip("modal", async function (assert) { ); }); -QUnit.test("rawTitle in modal panels", async function (assert) { +test("rawTitle in modal panels", async function (assert) { Ember.TEMPLATES["modal/test-raw-title-panels"] = Ember.HTMLBars.compile(""); const panels = [ { id: "test1", rawTitle: "Test 1" }, @@ -88,7 +93,7 @@ QUnit.test("rawTitle in modal panels", async function (assert) { ); }); -QUnit.test("modal title", async function (assert) { +test("modal title", async function (assert) { Ember.TEMPLATES["modal/test-title"] = Ember.HTMLBars.compile(""); Ember.TEMPLATES["modal/test-title-with-body"] = Ember.HTMLBars.compile( "{{#d-modal-body}}test{{/d-modal-body}}" @@ -123,7 +128,7 @@ QUnit.test("modal title", async function (assert) { acceptance("Modal Keyboard Events", { loggedIn: true }); -QUnit.test("modal-keyboard-events", async function (assert) { +test("modal-keyboard-events", async function (assert) { await visit("/t/internationalization-localization/280"); await click(".toggle-admin-menu"); diff --git a/test/javascripts/acceptance/new-message-test.js b/app/assets/javascripts/discourse/tests/acceptance/new-message-test.js similarity index 76% rename from test/javascripts/acceptance/new-message-test.js rename to app/assets/javascripts/discourse/tests/acceptance/new-message-test.js index 155cea28a8..f264d2da25 100644 --- a/test/javascripts/acceptance/new-message-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/new-message-test.js @@ -1,8 +1,9 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("New Message"); -QUnit.test("accessing new-message route when logged out", async (assert) => { +test("accessing new-message route when logged out", async (assert) => { await visit( "/new-message?username=charlie&title=message%20title&body=message%20body" ); @@ -11,7 +12,7 @@ QUnit.test("accessing new-message route when logged out", async (assert) => { }); acceptance("New Message", { loggedIn: true }); -QUnit.test("accessing new-message route when logged in", async (assert) => { +test("accessing new-message route when logged in", async (assert) => { await visit( "/new-message?username=charlie&title=message%20title&body=message%20body" ); diff --git a/test/javascripts/acceptance/new-topic-test.js b/app/assets/javascripts/discourse/tests/acceptance/new-topic-test.js similarity index 69% rename from test/javascripts/acceptance/new-topic-test.js rename to app/assets/javascripts/discourse/tests/acceptance/new-topic-test.js index ad75ca3a11..9cebe072a6 100644 --- a/test/javascripts/acceptance/new-topic-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/new-topic-test.js @@ -1,16 +1,17 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("New Topic"); -QUnit.test("accessing new-topic route when logged out", async (assert) => { +test("accessing new-topic route when logged out", async (assert) => { await visit("/new-topic?title=topic%20title&body=topic%20body"); assert.ok(exists(".modal.login-modal"), "it shows the login modal"); }); acceptance("New Topic", { loggedIn: true }); -QUnit.test("accessing new-topic route when logged in", async (assert) => { +test("accessing new-topic route when logged in", async (assert) => { await visit("/new-topic?title=topic%20title&body=topic%20body&category=bug"); assert.ok(exists(".composer-fields"), "it opens composer"); diff --git a/test/javascripts/acceptance/notifications-filter-test.js b/app/assets/javascripts/discourse/tests/acceptance/notifications-filter-test.js similarity index 82% rename from test/javascripts/acceptance/notifications-filter-test.js rename to app/assets/javascripts/discourse/tests/acceptance/notifications-filter-test.js index 6b35838def..300dc4ec40 100644 --- a/test/javascripts/acceptance/notifications-filter-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/notifications-filter-test.js @@ -1,5 +1,6 @@ -import { acceptance } from "helpers/qunit-helpers"; -import selectKit from "helpers/select-kit-helper"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; acceptance("NotificationsFilter", { loggedIn: true, diff --git a/test/javascripts/acceptance/page-publishing-test.js b/app/assets/javascripts/discourse/tests/acceptance/page-publishing-test.js similarity index 88% rename from test/javascripts/acceptance/page-publishing-test.js rename to app/assets/javascripts/discourse/tests/acceptance/page-publishing-test.js index d84681c922..7ccfe91e54 100644 --- a/test/javascripts/acceptance/page-publishing-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/page-publishing-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Page Publishing", { loggedIn: true, @@ -22,7 +23,7 @@ acceptance("Page Publishing", { }); }, }); -QUnit.test("can publish a page via modal", async (assert) => { +test("can publish a page via modal", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".topic-post:eq(0) button.show-more-actions"); await click(".topic-post:eq(0) button.show-post-admin-menu"); diff --git a/test/javascripts/acceptance/password-reset-test.js b/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js similarity index 92% rename from test/javascripts/acceptance/password-reset-test.js rename to app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js index f8dd9ebc0e..88e966d55d 100644 --- a/test/javascripts/acceptance/password-reset-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js @@ -1,7 +1,8 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import PreloadStore from "discourse/lib/preload-store"; -import { parsePostData } from "helpers/create-pretender"; +import { parsePostData } from "discourse/tests/helpers/create-pretender"; acceptance("Password Reset", { pretend(server, helper) { @@ -54,7 +55,7 @@ acceptance("Password Reset", { }, }); -QUnit.test("Password Reset Page", async (assert) => { +test("Password Reset Page", async (assert) => { PreloadStore.store("password_reset", { is_developer: false }); await visit("/u/password-reset/myvalidtoken"); @@ -86,7 +87,7 @@ QUnit.test("Password Reset Page", async (assert) => { assert.ok(!exists(".password-reset form"), "form is gone"); }); -QUnit.test("Password Reset Page With Second Factor", async (assert) => { +test("Password Reset Page With Second Factor", async (assert) => { PreloadStore.store("password_reset", { is_developer: false, second_factor_required: true, diff --git a/test/javascripts/acceptance/personal-message-test.js b/app/assets/javascripts/discourse/tests/acceptance/personal-message-test.js similarity index 68% rename from test/javascripts/acceptance/personal-message-test.js rename to app/assets/javascripts/discourse/tests/acceptance/personal-message-test.js index f21f8c406f..0d0f58027d 100644 --- a/test/javascripts/acceptance/personal-message-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/personal-message-test.js @@ -1,11 +1,12 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Personal Message", { loggedIn: true, }); -QUnit.test("footer edit button", async (assert) => { +test("footer edit button", async (assert) => { await visit("/t/pm-for-testing/12"); assert.ok( @@ -14,7 +15,7 @@ QUnit.test("footer edit button", async (assert) => { ); }); -QUnit.test("suggested messages", async (assert) => { +test("suggested messages", async (assert) => { await visit("/t/pm-for-testing/12"); assert.equal( diff --git a/test/javascripts/acceptance/plugin-keyboard-shortcut-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-keyboard-shortcut-test.js similarity index 93% rename from test/javascripts/acceptance/plugin-keyboard-shortcut-test.js rename to app/assets/javascripts/discourse/tests/acceptance/plugin-keyboard-shortcut-test.js index 790331bd0b..12717a15e3 100644 --- a/test/javascripts/acceptance/plugin-keyboard-shortcut-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-keyboard-shortcut-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { withPluginApi } from "discourse/lib/plugin-api"; import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts"; import KeyboardShortcutInitializer from "discourse/initializers/keyboard-shortcuts"; diff --git a/test/javascripts/acceptance/plugin-outlet-connector-class-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-connector-class-test.js similarity index 93% rename from test/javascripts/acceptance/plugin-outlet-connector-class-test.js rename to app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-connector-class-test.js index 92416f1f22..d996bd8a04 100644 --- a/test/javascripts/acceptance/plugin-outlet-connector-class-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-connector-class-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { extraConnectorClass } from "discourse/lib/plugin-connectors"; import { action } from "@ember/object"; @@ -64,7 +65,7 @@ acceptance("Plugin Outlet - Connector Class", { }, }); -QUnit.test("Renders a template into the outlet", async (assert) => { +test("Renders a template into the outlet", async (assert) => { await visit("/u/eviltrout"); assert.ok( find(".user-profile-primary-outlet.hello").length === 1, diff --git a/test/javascripts/acceptance/plugin-outlet-decorator-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-decorator-test.js similarity index 61% rename from test/javascripts/acceptance/plugin-outlet-decorator-test.js rename to app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-decorator-test.js index 94ecb7d5a1..e295308115 100644 --- a/test/javascripts/acceptance/plugin-outlet-decorator-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-decorator-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { withPluginApi } from "discourse/lib/plugin-api"; const PREFIX = "javascripts/single-test/connectors"; @@ -38,24 +39,21 @@ acceptance("Plugin Outlet - Decorator", { }, }); -QUnit.test( - "Calls the plugin callback with the rendered outlet", - async (assert) => { - await visit("/"); +test("Calls the plugin callback with the rendered outlet", async (assert) => { + await visit("/"); - const fooConnector = find(".discovery-list-container-top-outlet.foo ")[0]; - const barConnector = find(".discovery-list-container-top-outlet.bar ")[0]; + const fooConnector = find(".discovery-list-container-top-outlet.foo ")[0]; + const barConnector = find(".discovery-list-container-top-outlet.bar ")[0]; - assert.ok(exists(fooConnector)); - assert.equal(fooConnector.style.backgroundColor, "yellow"); - assert.equal(barConnector.style.backgroundColor, ""); + assert.ok(exists(fooConnector)); + assert.equal(fooConnector.style.backgroundColor, "yellow"); + assert.equal(barConnector.style.backgroundColor, ""); - await visit("/c/bug"); + await visit("/c/bug"); - assert.ok(fooConnector.classList.contains("in-category")); + assert.ok(fooConnector.classList.contains("in-category")); - await visit("/"); + await visit("/"); - assert.notOk(fooConnector.classList.contains("in-category")); - } -); + assert.notOk(fooConnector.classList.contains("in-category")); +}); diff --git a/test/javascripts/acceptance/plugin-outlet-multi-template-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js similarity index 87% rename from test/javascripts/acceptance/plugin-outlet-multi-template-test.js rename to app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js index 1b48bc664a..6fedbb1505 100644 --- a/test/javascripts/acceptance/plugin-outlet-multi-template-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { clearCache } from "discourse/lib/plugin-connectors"; const HELLO = "javascripts/multi-test/connectors/user-profile-primary/hello"; @@ -23,7 +24,7 @@ acceptance("Plugin Outlet - Multi Template", { }, }); -QUnit.test("Renders a template into the outlet", async (assert) => { +test("Renders a template into the outlet", async (assert) => { await visit("/u/eviltrout"); assert.ok( find(".user-profile-primary-outlet.hello").length === 1, diff --git a/test/javascripts/acceptance/plugin-outlet-single-template-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-single-template-test.js similarity index 78% rename from test/javascripts/acceptance/plugin-outlet-single-template-test.js rename to app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-single-template-test.js index d7460c9d61..68a30530a9 100644 --- a/test/javascripts/acceptance/plugin-outlet-single-template-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-single-template-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; const CONNECTOR = "javascripts/single-test/connectors/user-profile-primary/hello"; @@ -14,7 +15,7 @@ acceptance("Plugin Outlet - Single Template", { }, }); -QUnit.test("Renders a template into the outlet", async (assert) => { +test("Renders a template into the outlet", async (assert) => { await visit("/u/eviltrout"); assert.ok( find(".user-profile-primary-outlet.hello").length === 1, diff --git a/app/assets/javascripts/discourse/tests/acceptance/post-admin-menu-test.js b/app/assets/javascripts/discourse/tests/acceptance/post-admin-menu-test.js new file mode 100644 index 0000000000..bc80964a31 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/post-admin-menu-test.js @@ -0,0 +1,33 @@ +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; + +acceptance("Post - Admin Menu Anonymous Users", { loggedIn: false }); + +test("Enter as a anon user", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click(".show-more-actions"); + + assert.ok(exists("#topic"), "The topic was rendered"); + assert.ok( + exists("#post_1 .post-controls .edit"), + "The edit button was not rendered" + ); + assert.ok( + !exists(".show-post-admin-menu"), + "The wrench button was not rendered" + ); +}); + +acceptance("Post - Admin Menu", { loggedIn: true }); + +test("Enter as a user with group moderator permissions", async (assert) => { + await visit("/t/topic-for-group-moderators/2480"); + await click(".show-more-actions"); + await click(".show-post-admin-menu"); + + assert.ok( + exists("#post_1 .post-controls .edit"), + "The edit button was rendered" + ); + assert.ok(exists(".add-notice"), "The add notice button was rendered"); +}); diff --git a/test/javascripts/acceptance/preferences-test.js b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js similarity index 88% rename from test/javascripts/acceptance/preferences-test.js rename to app/assets/javascripts/discourse/tests/acceptance/preferences-test.js index ba256f72cb..82639e9f5e 100644 --- a/test/javascripts/acceptance/preferences-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js @@ -1,6 +1,10 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; -import selectKit from "helpers/select-kit-helper"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; import User from "discourse/models/user"; function preferencesPretender(server, helper) { @@ -66,7 +70,7 @@ acceptance("User Preferences", { pretend: preferencesPretender, }); -QUnit.test("update some fields", async (assert) => { +test("update some fields", async (assert) => { await visit("/u/eviltrout/preferences"); assert.ok($("body.user-preferences-page").length, "has the body class"); @@ -121,12 +125,12 @@ QUnit.test("update some fields", async (assert) => { ); }); -QUnit.test("username", async (assert) => { +test("username", async (assert) => { await visit("/u/eviltrout/preferences/username"); assert.ok(exists("#change_username"), "it has the input element"); }); -QUnit.test("email", async (assert) => { +test("email", async (assert) => { await visit("/u/eviltrout/preferences/email"); assert.ok(exists("#change-email"), "it has the input element"); @@ -140,7 +144,7 @@ QUnit.test("email", async (assert) => { ); }); -QUnit.test("email field always shows up", async (assert) => { +test("email field always shows up", async (assert) => { await visit("/u/eviltrout/preferences/email"); assert.ok(exists("#change-email"), "it has the input element"); @@ -154,7 +158,7 @@ QUnit.test("email field always shows up", async (assert) => { assert.ok(exists("#change-email"), "it has the input element"); }); -QUnit.test("connected accounts", async (assert) => { +test("connected accounts", async (assert) => { await visit("/u/eviltrout/preferences/account"); assert.ok( @@ -175,7 +179,7 @@ QUnit.test("connected accounts", async (assert) => { .indexOf("Connect") > -1; }); -QUnit.test("second factor totp", async (assert) => { +test("second factor totp", async (assert) => { await visit("/u/eviltrout/preferences/second-factor"); assert.ok(exists("#password"), "it has a password input"); @@ -195,7 +199,7 @@ QUnit.test("second factor totp", async (assert) => { ); }); -QUnit.test("second factor security keys", async (assert) => { +test("second factor security keys", async (assert) => { await visit("/u/eviltrout/preferences/second-factor"); assert.ok(exists("#password"), "it has a password input"); @@ -221,7 +225,7 @@ QUnit.test("second factor security keys", async (assert) => { } }); -QUnit.test("default avatar selector", async (assert) => { +test("default avatar selector", async (assert) => { await visit("/u/eviltrout/preferences"); await click(".pref-avatar .btn"); @@ -257,7 +261,7 @@ acceptance("Second Factor Backups", { }); }, }); -QUnit.test("second factor backup", async (assert) => { +test("second factor backup", async (assert) => { updateCurrentUser({ second_factor_enabled: true }); await visit("/u/eviltrout/preferences/second-factor"); await click(".edit-2fa-backup"); @@ -284,7 +288,7 @@ acceptance("Avatar selector when selectable avatars is enabled", { }, }); -QUnit.test("selectable avatars", async (assert) => { +test("selectable avatars", async (assert) => { await visit("/u/eviltrout/preferences"); await click(".pref-avatar .btn"); @@ -298,7 +302,7 @@ acceptance("User Preferences when badges are disabled", { pretend: preferencesPretender, }); -QUnit.test("visit my preferences", async (assert) => { +test("visit my preferences", async (assert) => { await visit("/u/eviltrout/preferences"); assert.ok($("body.user-preferences-page").length, "has the body class"); assert.equal( @@ -309,7 +313,7 @@ QUnit.test("visit my preferences", async (assert) => { assert.ok(exists(".user-preferences"), "it shows the preferences"); }); -QUnit.test("recently connected devices", async (assert) => { +test("recently connected devices", async (assert) => { await visit("/u/eviltrout/preferences"); assert.equal( @@ -364,7 +368,7 @@ acceptance( } ); -QUnit.test("setting featured topic on profile", async (assert) => { +test("setting featured topic on profile", async (assert) => { await visit("/u/eviltrout/preferences/profile"); assert.ok( @@ -415,7 +419,7 @@ acceptance("Custom User Fields", { pretend: preferencesPretender, }); -QUnit.test("can select an option from a dropdown", async (assert) => { +test("can select an option from a dropdown", async (assert) => { await visit("/u/eviltrout/preferences/profile"); assert.ok(exists(".user-field"), "it has at least one user field"); await click(".user-field.dropdown"); @@ -438,22 +442,19 @@ acceptance( } ); -QUnit.test( - "selecting bookmarks as home directs home to bookmarks", - async (assert) => { - await visit("/u/eviltrout/preferences/interface"); - assert.ok(exists(".home .combo-box"), "it has a home selector combo-box"); +test("selecting bookmarks as home directs home to bookmarks", async (assert) => { + await visit("/u/eviltrout/preferences/interface"); + assert.ok(exists(".home .combo-box"), "it has a home selector combo-box"); - const field = selectKit(".home .combo-box"); - await field.expand(); - await field.selectRowByValue("6"); - await click(".save-changes"); - await visit("/"); - assert.ok(exists(".topic-list"), "The list of topics was rendered"); - assert.equal( - currentPath(), - "discovery.bookmarks", - "it navigates to bookmarks" - ); - } -); + const field = selectKit(".home .combo-box"); + await field.expand(); + await field.selectRowByValue("6"); + await click(".save-changes"); + await visit("/"); + assert.ok(exists(".topic-list"), "The list of topics was rendered"); + assert.equal( + currentPath(), + "discovery.bookmarks", + "it navigates to bookmarks" + ); +}); diff --git a/test/javascripts/acceptance/raw-plugin-outlet-test.js b/app/assets/javascripts/discourse/tests/acceptance/raw-plugin-outlet-test.js similarity index 80% rename from test/javascripts/acceptance/raw-plugin-outlet-test.js rename to app/assets/javascripts/discourse/tests/acceptance/raw-plugin-outlet-test.js index 580f5ad3d4..32705c3f77 100644 --- a/test/javascripts/acceptance/raw-plugin-outlet-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/raw-plugin-outlet-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import compile from "handlebars-compiler"; import { addRawTemplate, @@ -21,7 +22,7 @@ acceptance("Raw Plugin Outlet", { }, }); -QUnit.test("Renders the raw plugin outlet", async (assert) => { +test("Renders the raw plugin outlet", async (assert) => { await visit("/"); assert.ok(find(".topic-lala").length > 0, "it renders the outlet"); assert.equal( diff --git a/test/javascripts/acceptance/redirect-to-top-test.js b/app/assets/javascripts/discourse/tests/acceptance/redirect-to-top-test.js similarity index 76% rename from test/javascripts/acceptance/redirect-to-top-test.js rename to app/assets/javascripts/discourse/tests/acceptance/redirect-to-top-test.js index f207c00971..17691af824 100644 --- a/test/javascripts/acceptance/redirect-to-top-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/redirect-to-top-test.js @@ -1,5 +1,9 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; -import DiscoveryFixtures from "fixtures/discovery_fixtures"; +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; +import DiscoveryFixtures from "discourse/tests/fixtures/discovery-fixtures"; acceptance("Redirect to Top", { pretend(server, helper) { @@ -16,7 +20,7 @@ acceptance("Redirect to Top", { loggedIn: true, }); -QUnit.test("redirects categories to weekly top", async (assert) => { +test("redirects categories to weekly top", async (assert) => { updateCurrentUser({ should_be_redirected_to_top: true, redirected_to_top: { @@ -29,7 +33,7 @@ QUnit.test("redirects categories to weekly top", async (assert) => { assert.equal(currentPath(), "discovery.topWeekly", "it works for categories"); }); -QUnit.test("redirects latest to monthly top", async (assert) => { +test("redirects latest to monthly top", async (assert) => { updateCurrentUser({ should_be_redirected_to_top: true, redirected_to_top: { @@ -42,7 +46,7 @@ QUnit.test("redirects latest to monthly top", async (assert) => { assert.equal(currentPath(), "discovery.topMonthly", "it works for latest"); }); -QUnit.test("redirects root to All top", async (assert) => { +test("redirects root to All top", async (assert) => { updateCurrentUser({ should_be_redirected_to_top: true, redirected_to_top: { diff --git a/test/javascripts/acceptance/reports-test.js b/app/assets/javascripts/discourse/tests/acceptance/reports-test.js similarity index 71% rename from test/javascripts/acceptance/reports-test.js rename to app/assets/javascripts/discourse/tests/acceptance/reports-test.js index 93cbbff368..7841043184 100644 --- a/test/javascripts/acceptance/reports-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/reports-test.js @@ -1,10 +1,11 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Reports", { loggedIn: true, }); -QUnit.test("Visit reports page", async (assert) => { +test("Visit reports page", async (assert) => { await visit("/admin/reports"); assert.equal($(".reports-list .report").length, 1); @@ -19,7 +20,7 @@ QUnit.test("Visit reports page", async (assert) => { ); }); -QUnit.test("Visit report page", async (assert) => { +test("Visit report page", async (assert) => { await visit("/admin/reports/staff_logins"); assert.ok(exists(".export-csv-btn")); diff --git a/test/javascripts/acceptance/review-test.js b/app/assets/javascripts/discourse/tests/acceptance/review-test.js similarity index 87% rename from test/javascripts/acceptance/review-test.js rename to app/assets/javascripts/discourse/tests/acceptance/review-test.js index 62fc40d7f4..996dedcb22 100644 --- a/test/javascripts/acceptance/review-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/review-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Review", { loggedIn: true, @@ -7,7 +8,7 @@ acceptance("Review", { const user = ".reviewable-item[data-reviewable-id=1234]"; -QUnit.test("It returns a list of reviewable items", async (assert) => { +test("It returns a list of reviewable items", async (assert) => { await visit("/review"); assert.ok(find(".reviewable-item").length, "has a list of items"); @@ -26,7 +27,7 @@ QUnit.test("It returns a list of reviewable items", async (assert) => { ); }); -QUnit.test("Grouped by topic", async (assert) => { +test("Grouped by topic", async (assert) => { await visit("/review/topics"); assert.ok( find(".reviewable-topic").length, @@ -34,7 +35,7 @@ QUnit.test("Grouped by topic", async (assert) => { ); }); -QUnit.test("Settings", async (assert) => { +test("Settings", async (assert) => { await visit("/review/settings"); assert.ok(find(".reviewable-score-type").length, "has a list of bonuses"); @@ -47,7 +48,7 @@ QUnit.test("Settings", async (assert) => { assert.ok(find(".reviewable-settings .saved").length, "it saved"); }); -QUnit.test("Flag related", async (assert) => { +test("Flag related", async (assert) => { await visit("/review"); assert.ok( @@ -63,7 +64,7 @@ QUnit.test("Flag related", async (assert) => { assert.equal(find(".reviewable-flagged-post .reviewable-score").length, 2); }); -QUnit.test("Flag related", async (assert) => { +test("Flag related", async (assert) => { await visit("/review/1"); assert.ok( @@ -72,13 +73,13 @@ QUnit.test("Flag related", async (assert) => { ); }); -QUnit.test("Clicking the buttons triggers actions", async (assert) => { +test("Clicking the buttons triggers actions", async (assert) => { await visit("/review"); await click(`${user} .reviewable-action.approve`); assert.equal(find(user).length, 0, "it removes the reviewable on success"); }); -QUnit.test("Editing a reviewable", async (assert) => { +test("Editing a reviewable", async (assert) => { const topic = ".reviewable-item[data-reviewable-id=4321]"; await visit("/review"); assert.ok(find(`${topic} .reviewable-action.approve`).length); diff --git a/test/javascripts/acceptance/search-full-test.js b/app/assets/javascripts/discourse/tests/acceptance/search-full-test.js similarity index 60% rename from test/javascripts/acceptance/search-full-test.js rename to app/assets/javascripts/discourse/tests/acceptance/search-full-test.js index 4388ac7106..00639c47d2 100644 --- a/test/javascripts/acceptance/search-full-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/search-full-test.js @@ -1,5 +1,11 @@ -import selectKit from "helpers/select-kit-helper"; -import { selectDate, acceptance, waitFor } from "helpers/qunit-helpers"; +import { skip } from "qunit"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { + selectDate, + acceptance, + waitFor, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Search - Full Page", { settings: { tagging_enabled: true }, @@ -83,7 +89,7 @@ acceptance("Search - Full Page", { }, }); -QUnit.test("perform various searches", async (assert) => { +test("perform various searches", async (assert) => { await visit("/search"); assert.ok($("body.search-page").length, "has body class"); @@ -103,7 +109,7 @@ QUnit.test("perform various searches", async (assert) => { assert.ok(find(".fps-topic").length === 1, "has one post"); }); -QUnit.test("escape search term", async (assert) => { +test("escape search term", async (assert) => { await visit("/search"); await fillIn(".search-query", "@gmail.com"); @@ -115,7 +121,7 @@ QUnit.test("escape search term", async (assert) => { ); }); -QUnit.skip("update username through advanced search ui", async (assert) => { +skip("update username through advanced search ui", async (assert) => { await visit("/search"); await fillIn(".search-query", "none"); await fillIn(".search-advanced-options .user-selector", "admin"); @@ -148,7 +154,7 @@ QUnit.skip("update username through advanced search ui", async (assert) => { }); }); -QUnit.test("update category through advanced search ui", async (assert) => { +test("update category through advanced search ui", async (assert) => { const categoryChooser = selectKit( ".search-advanced-options .category-chooser" ); @@ -172,106 +178,94 @@ QUnit.test("update category through advanced search ui", async (assert) => { ); }); -QUnit.test( - "update in:title filter through advanced search ui", - async (assert) => { - await visit("/search"); - await fillIn(".search-query", "none"); - await click(".search-advanced-options .in-title"); +test("update in:title filter through advanced search ui", async (assert) => { + await visit("/search"); + await fillIn(".search-query", "none"); + await click(".search-advanced-options .in-title"); - assert.ok( - exists(".search-advanced-options .in-title:checked"), - 'has "in title" populated' - ); - assert.equal( - find(".search-query").val(), - "none in:title", - 'has updated search term to "none in:title"' - ); + assert.ok( + exists(".search-advanced-options .in-title:checked"), + 'has "in title" populated' + ); + assert.equal( + find(".search-query").val(), + "none in:title", + 'has updated search term to "none in:title"' + ); - await fillIn(".search-query", "none in:titleasd"); + await fillIn(".search-query", "none in:titleasd"); - assert.not( - exists(".search-advanced-options .in-title:checked"), - "does not populate title only checkbox" - ); - } -); + assert.not( + exists(".search-advanced-options .in-title:checked"), + "does not populate title only checkbox" + ); +}); -QUnit.test( - "update in:likes filter through advanced search ui", - async (assert) => { - await visit("/search"); - await fillIn(".search-query", "none"); - await click(".search-advanced-options .in-likes"); +test("update in:likes filter through advanced search ui", async (assert) => { + await visit("/search"); + await fillIn(".search-query", "none"); + await click(".search-advanced-options .in-likes"); - assert.ok( - exists(".search-advanced-options .in-likes:checked"), - 'has "I liked" populated' - ); - assert.equal( - find(".search-query").val(), - "none in:likes", - 'has updated search term to "none in:likes"' - ); - } -); + assert.ok( + exists(".search-advanced-options .in-likes:checked"), + 'has "I liked" populated' + ); + assert.equal( + find(".search-query").val(), + "none in:likes", + 'has updated search term to "none in:likes"' + ); +}); -QUnit.test( - "update in:personal filter through advanced search ui", - async (assert) => { - await visit("/search"); - await fillIn(".search-query", "none"); - await click(".search-advanced-options .in-private"); +test("update in:personal filter through advanced search ui", async (assert) => { + await visit("/search"); + await fillIn(".search-query", "none"); + await click(".search-advanced-options .in-private"); - assert.ok( - exists(".search-advanced-options .in-private:checked"), - 'has "are in my messages" populated' - ); + assert.ok( + exists(".search-advanced-options .in-private:checked"), + 'has "are in my messages" populated' + ); - assert.equal( - find(".search-query").val(), - "none in:personal", - 'has updated search term to "none in:personal"' - ); + assert.equal( + find(".search-query").val(), + "none in:personal", + 'has updated search term to "none in:personal"' + ); - await fillIn(".search-query", "none in:personal-direct"); + await fillIn(".search-query", "none in:personal-direct"); - assert.not( - exists(".search-advanced-options .in-private:checked"), - "does not populate messages checkbox" - ); - } -); + assert.not( + exists(".search-advanced-options .in-private:checked"), + "does not populate messages checkbox" + ); +}); -QUnit.test( - "update in:seen filter through advanced search ui", - async (assert) => { - await visit("/search"); - await fillIn(".search-query", "none"); - await click(".search-advanced-options .in-seen"); +test("update in:seen filter through advanced search ui", async (assert) => { + await visit("/search"); + await fillIn(".search-query", "none"); + await click(".search-advanced-options .in-seen"); - assert.ok( - exists(".search-advanced-options .in-seen:checked"), - "it should check the right checkbox" - ); + assert.ok( + exists(".search-advanced-options .in-seen:checked"), + "it should check the right checkbox" + ); - assert.equal( - find(".search-query").val(), - "none in:seen", - "it should update the search term" - ); + assert.equal( + find(".search-query").val(), + "none in:seen", + "it should update the search term" + ); - await fillIn(".search-query", "none in:seenasdan"); + await fillIn(".search-query", "none in:seenasdan"); - assert.not( - exists(".search-advanced-options .in-seen:checked"), - "does not populate seen checkbox" - ); - } -); + assert.not( + exists(".search-advanced-options .in-seen:checked"), + "does not populate seen checkbox" + ); +}); -QUnit.test("update in filter through advanced search ui", async (assert) => { +test("update in filter through advanced search ui", async (assert) => { const inSelector = selectKit(".search-advanced-options .select-kit#in"); await visit("/search"); @@ -292,7 +286,7 @@ QUnit.test("update in filter through advanced search ui", async (assert) => { ); }); -QUnit.test("update status through advanced search ui", async (assert) => { +test("update status through advanced search ui", async (assert) => { const statusSelector = selectKit( ".search-advanced-options .select-kit#status" ); @@ -315,35 +309,29 @@ QUnit.test("update status through advanced search ui", async (assert) => { ); }); -QUnit.test( - "doesn't update status filter header if wrong value entered through searchbox", - async (assert) => { - const statusSelector = selectKit( - ".search-advanced-options .select-kit#status" - ); +test("doesn't update status filter header if wrong value entered through searchbox", async (assert) => { + const statusSelector = selectKit( + ".search-advanced-options .select-kit#status" + ); - await visit("/search"); + await visit("/search"); - await fillIn(".search-query", "status:none"); + await fillIn(".search-query", "status:none"); - assert.equal(statusSelector.header().label(), "any", 'has "any" populated'); - } -); + assert.equal(statusSelector.header().label(), "any", 'has "any" populated'); +}); -QUnit.test( - "doesn't update in filter header if wrong value entered through searchbox", - async (assert) => { - const inSelector = selectKit(".search-advanced-options .select-kit#in"); +test("doesn't update in filter header if wrong value entered through searchbox", async (assert) => { + const inSelector = selectKit(".search-advanced-options .select-kit#in"); - await visit("/search"); + await visit("/search"); - await fillIn(".search-query", "in:none"); + await fillIn(".search-query", "in:none"); - assert.equal(inSelector.header().label(), "any", 'has "any" populated'); - } -); + assert.equal(inSelector.header().label(), "any", 'has "any" populated'); +}); -QUnit.test("update post time through advanced search ui", async (assert) => { +test("update post time through advanced search ui", async (assert) => { await visit("/search?expanded=true&q=after:2018-08-22"); assert.equal( @@ -376,27 +364,41 @@ QUnit.test("update post time through advanced search ui", async (assert) => { ); }); -QUnit.test( - "update min post count through advanced search ui", - async (assert) => { - await visit("/search"); - await fillIn(".search-query", "none"); - await fillIn("#search-min-post-count", "5"); +test("update min post count through advanced search ui", async (assert) => { + await visit("/search"); + await fillIn(".search-query", "none"); + await fillIn("#search-min-post-count", "5"); - assert.equal( - find(".search-advanced-options #search-min-post-count").val(), - "5", - 'has "5" populated' - ); - assert.equal( - find(".search-query").val(), - "none min_post_count:5", - 'has updated search term to "none min_post_count:5"' - ); - } -); + assert.equal( + find(".search-advanced-options #search-min-post-count").val(), + "5", + 'has "5" populated' + ); + assert.equal( + find(".search-query").val(), + "none min_posts:5", + 'has updated search term to "none min_posts:5"' + ); +}); -QUnit.test("validate advanced search when initially empty", async (assert) => { +test("update max post count through advanced search ui", async (assert) => { + await visit("/search"); + await fillIn(".search-query", "none"); + await fillIn("#search-max-post-count", "5"); + + assert.equal( + find(".search-advanced-options #search-max-post-count").val(), + "5", + 'has "5" populated' + ); + assert.equal( + find(".search-query").val(), + "none max_posts:5", + 'has updated search term to "none max_posts:5"' + ); +}); + +test("validate advanced search when initially empty", async (assert) => { await visit("/search?expanded=true"); await click(".search-advanced-options .in-likes"); diff --git a/test/javascripts/acceptance/search-mobile-test.js b/app/assets/javascripts/discourse/tests/acceptance/search-mobile-test.js similarity index 86% rename from test/javascripts/acceptance/search-mobile-test.js rename to app/assets/javascripts/discourse/tests/acceptance/search-mobile-test.js index 47a110eeff..e1d38800ae 100644 --- a/test/javascripts/acceptance/search-mobile-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/search-mobile-test.js @@ -1,8 +1,9 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Search - Mobile", { mobileView: true }); -QUnit.test("search", async (assert) => { +test("search", async (assert) => { await visit("/"); await click("#search-button"); diff --git a/test/javascripts/acceptance/search-test.js b/app/assets/javascripts/discourse/tests/acceptance/search-test.js similarity index 90% rename from test/javascripts/acceptance/search-test.js rename to app/assets/javascripts/discourse/tests/acceptance/search-test.js index eda6a22e16..6cfc09e267 100644 --- a/test/javascripts/acceptance/search-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/search-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; const emptySearchContextCallbacks = []; @@ -17,7 +18,7 @@ let searchArgs = { acceptance("Search", searchArgs); -QUnit.test("search", async (assert) => { +test("search", async (assert) => { await visit("/"); await click("#search-button"); @@ -43,7 +44,7 @@ QUnit.test("search", async (assert) => { assert.ok(exists(".search-advanced-options"), "advanced search is expanded"); }); -QUnit.test("search for a tag", async (assert) => { +test("search for a tag", async (assert) => { await visit("/"); await click("#search-button"); @@ -53,7 +54,7 @@ QUnit.test("search for a tag", async (assert) => { assert.ok(exists(".search-menu .results ul li"), "it shows results"); }); -QUnit.test("search scope checkbox", async (assert) => { +test("search scope checkbox", async (assert) => { await visit("/tag/important"); await click("#search-button"); assert.ok( @@ -86,7 +87,7 @@ QUnit.test("search scope checkbox", async (assert) => { ); }); -QUnit.test("Search with context", async (assert) => { +test("Search with context", async (assert) => { await visit("/t/internationalization-localization/280/1"); await click("#search-button"); @@ -126,7 +127,7 @@ QUnit.test("Search with context", async (assert) => { assert.ok(!$(".search-context input[type=checkbox]").is(":checked")); }); -QUnit.test("Right filters are shown to anonymous users", async (assert) => { +test("Right filters are shown to anonymous users", async (assert) => { const inSelector = selectKit(".select-kit#in"); await visit("/search?expanded=true"); @@ -151,7 +152,7 @@ QUnit.test("Right filters are shown to anonymous users", async (assert) => { acceptance("Search", Object.assign({ loggedIn: true, searchArgs })); -QUnit.test("Right filters are shown to logged-in users", async (assert) => { +test("Right filters are shown to logged-in users", async (assert) => { const inSelector = selectKit(".select-kit#in"); await visit("/search?expanded=true"); @@ -183,7 +184,7 @@ acceptance( }) ); -QUnit.test("displays tags", async (assert) => { +test("displays tags", async (assert) => { await visit("/"); await click("#search-button"); diff --git a/test/javascripts/acceptance/share-and-invite-desktop-test.js b/app/assets/javascripts/discourse/tests/acceptance/share-and-invite-desktop-test.js similarity index 70% rename from test/javascripts/acceptance/share-and-invite-desktop-test.js rename to app/assets/javascripts/discourse/tests/acceptance/share-and-invite-desktop-test.js index 445f17701e..c2c12e5018 100644 --- a/test/javascripts/acceptance/share-and-invite-desktop-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/share-and-invite-desktop-test.js @@ -1,10 +1,11 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Share and Invite modal - desktop", { loggedIn: true, }); -QUnit.test("Topic footer button", async (assert) => { +test("Topic footer button", async (assert) => { await visit("/t/internationalization-localization/280"); assert.ok( @@ -64,9 +65,28 @@ QUnit.test("Topic footer button", async (assert) => { ); }); -QUnit.test("Post date link", async (assert) => { +test("Post date link", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#post_2 .post-info.post-date a"); assert.ok(exists("#share-link"), "it shows the share modal"); }); + +acceptance("Share url with badges disabled - desktop", { + loggedIn: true, + settings: { + enable_badges: false, + }, +}); + +test("topic footer button - badges disabled - desktop", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click("#topic-footer-button-share-and-invite"); + + assert.notOk( + find(".share-and-invite.modal .modal-panel.share .topic-share-url") + .val() + .includes("?u=eviltrout"), + "it doesn't add the username param when badges are disabled" + ); +}); diff --git a/test/javascripts/acceptance/share-and-invite-mobile-test.js b/app/assets/javascripts/discourse/tests/acceptance/share-and-invite-mobile-test.js similarity index 62% rename from test/javascripts/acceptance/share-and-invite-mobile-test.js rename to app/assets/javascripts/discourse/tests/acceptance/share-and-invite-mobile-test.js index c21ae36b00..f67a5203aa 100644 --- a/test/javascripts/acceptance/share-and-invite-mobile-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/share-and-invite-mobile-test.js @@ -1,12 +1,13 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Share and Invite modal - mobile", { loggedIn: true, mobileView: true, }); -QUnit.test("Topic footer mobile button", async (assert) => { +test("Topic footer mobile button", async (assert) => { await visit("/t/internationalization-localization/280"); assert.ok( @@ -54,9 +55,32 @@ QUnit.test("Topic footer mobile button", async (assert) => { ); }); -QUnit.test("Post date link", async (assert) => { +test("Post date link", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#post_2 .post-info.post-date a"); assert.ok(exists("#share-link"), "it shows the share modal"); }); + +acceptance("Share url with badges disabled - mobile", { + loggedIn: true, + mobileView: true, + settings: { + enable_badges: false, + }, +}); + +test("topic footer button - badges disabled - mobile", async (assert) => { + await visit("/t/internationalization-localization/280"); + + const subject = selectKit(".topic-footer-mobile-dropdown"); + await subject.expand(); + await subject.selectRowByValue("share-and-invite"); + + assert.notOk( + find(".share-and-invite.modal .modal-panel.share .topic-share-url") + .val() + .includes("?u=eviltrout"), + "it doesn't add the username param when badges are disabled" + ); +}); diff --git a/test/javascripts/acceptance/shared-drafts-test.js b/app/assets/javascripts/discourse/tests/acceptance/shared-drafts-test.js similarity index 67% rename from test/javascripts/acceptance/shared-drafts-test.js rename to app/assets/javascripts/discourse/tests/acceptance/shared-drafts-test.js index 7f46522f3e..caaceef158 100644 --- a/test/javascripts/acceptance/shared-drafts-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/shared-drafts-test.js @@ -1,9 +1,10 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Shared Drafts", { loggedIn: true }); -QUnit.test("Viewing", async (assert) => { +test("Viewing", async (assert) => { await visit("/t/some-topic/9"); assert.ok(find(".shared-draft-controls").length === 1); let categoryChooser = selectKit(".shared-draft-controls .category-chooser"); diff --git a/test/javascripts/acceptance/sign-in-test.js b/app/assets/javascripts/discourse/tests/acceptance/sign-in-test.js similarity index 91% rename from test/javascripts/acceptance/sign-in-test.js rename to app/assets/javascripts/discourse/tests/acceptance/sign-in-test.js index d3c8dbe75a..1f2e5adae5 100644 --- a/test/javascripts/acceptance/sign-in-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sign-in-test.js @@ -1,7 +1,9 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { skip } from "qunit"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Signing In"); -QUnit.test("sign in", async (assert) => { +test("sign in", async (assert) => { await visit("/"); await click("header .login-button"); assert.ok(exists(".login-modal"), "it shows the login modal"); @@ -25,7 +27,7 @@ QUnit.test("sign in", async (assert) => { ); }); -QUnit.test("sign in - not activated", async (assert) => { +test("sign in - not activated", async (assert) => { await visit("/"); await click("header .login-button"); assert.ok(exists(".login-modal"), "it shows the login modal"); @@ -47,7 +49,7 @@ QUnit.test("sign in - not activated", async (assert) => { assert.ok(!exists(".modal-body small"), "it escapes the email address"); }); -QUnit.test("sign in - not activated - edit email", async (assert) => { +test("sign in - not activated - edit email", async (assert) => { await visit("/"); await click("header .login-button"); assert.ok(exists(".login-modal"), "it shows the login modal"); @@ -68,7 +70,7 @@ QUnit.test("sign in - not activated - edit email", async (assert) => { assert.equal(find(".modal-body b").text(), "different@example.com"); }); -QUnit.skip("second factor", async (assert) => { +skip("second factor", async (assert) => { await visit("/"); await click("header .login-button"); @@ -101,7 +103,7 @@ QUnit.skip("second factor", async (assert) => { ); }); -QUnit.skip("security key", async (assert) => { +skip("security key", async (assert) => { await visit("/"); await click("header .login-button"); @@ -127,7 +129,7 @@ QUnit.skip("security key", async (assert) => { assert.not(exists("#login-button:visible"), "hides the login button"); }); -QUnit.test("create account", async (assert) => { +test("create account", async (assert) => { await visit("/"); await click("header .sign-up-button"); @@ -163,7 +165,7 @@ QUnit.test("create account", async (assert) => { ); }); -QUnit.test("second factor backup - valid token", async (assert) => { +test("second factor backup - valid token", async (assert) => { await visit("/"); await click("header .login-button"); await fillIn("#login-account-name", "eviltrout"); @@ -179,7 +181,7 @@ QUnit.test("second factor backup - valid token", async (assert) => { ); }); -QUnit.test("second factor backup - invalid token", async (assert) => { +test("second factor backup - invalid token", async (assert) => { await visit("/"); await click("header .login-button"); await fillIn("#login-account-name", "eviltrout"); diff --git a/test/javascripts/acceptance/static-test.js b/app/assets/javascripts/discourse/tests/acceptance/static-test.js similarity index 85% rename from test/javascripts/acceptance/static-test.js rename to app/assets/javascripts/discourse/tests/acceptance/static-test.js index d8aa41060f..c201bc4c0f 100644 --- a/test/javascripts/acceptance/static-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/static-test.js @@ -1,7 +1,8 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Static"); -QUnit.test("Static Pages", async (assert) => { +test("Static Pages", async (assert) => { await visit("/faq"); assert.ok($("body.static-faq").length, "has the body class"); assert.ok(exists(".body-page"), "The content is present"); diff --git a/app/assets/javascripts/discourse/tests/acceptance/tag-groups-test.js b/app/assets/javascripts/discourse/tests/acceptance/tag-groups-test.js new file mode 100644 index 0000000000..c3183d6ad1 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/tag-groups-test.js @@ -0,0 +1,74 @@ +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; + +acceptance("Tag Groups", { + loggedIn: true, + settings: { tagging_enabled: true }, + pretend(server, helper) { + server.post("/tag_groups", () => { + return helper.response({ + tag_group: { + id: 42, + name: "test tag group", + tag_names: ["monkey"], + parent_tag_name: [], + one_per_topic: false, + permissions: { everyone: 1 }, + }, + }); + }); + + server.get("/groups/search.json", () => { + return helper.response([ + { + id: 88, + name: "tl1", + }, + { + id: 89, + name: "tl2", + }, + ]); + }); + }, +}); + +test("tag groups can be saved and deleted", async (assert) => { + const tags = selectKit(".tag-chooser"); + + await visit("/tag_groups"); + await click(".content-list .btn"); + + await fillIn(".tag-group-content h1 input", "test tag group"); + await tags.expand(); + await tags.selectRowByValue("monkey"); + + await click(".tag-group-content .btn.btn-default"); + + await click(".tag-chooser .choice:first"); + assert.ok(!find(".tag-group-content .btn.btn-danger")[0].disabled); +}); + +QUnit.test( + "tag groups can have multiple groups added to them", + async (assert) => { + const tags = selectKit(".tag-chooser"); + const groups = selectKit(".group-chooser"); + + await visit("/tag_groups"); + await click(".content-list .btn"); + + await fillIn(".tag-group-content h1 input", "test tag group"); + await tags.expand(); + await tags.selectRowByValue("monkey"); + + await click("#private-permission"); + assert.ok(find(".tag-group-content .btn.btn-default:disabled").length); + + await groups.expand(); + await groups.selectRowByIndex(1); + await groups.selectRowByIndex(0); + assert.ok(!find(".tag-group-content .btn.btn-default")[0].disabled); + } +); diff --git a/test/javascripts/acceptance/tags-intersection-test.js b/app/assets/javascripts/discourse/tests/acceptance/tags-intersection-test.js similarity index 86% rename from test/javascripts/acceptance/tags-intersection-test.js rename to app/assets/javascripts/discourse/tests/acceptance/tags-intersection-test.js index ad0b6ddf9a..cf7f7a26b2 100644 --- a/test/javascripts/acceptance/tags-intersection-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/tags-intersection-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Tags intersection", { loggedIn: true, @@ -28,7 +29,7 @@ acceptance("Tags intersection", { }, }); -QUnit.test("Populate tags when creating new topic", async (assert) => { +test("Populate tags when creating new topic", async (assert) => { await visit("/tags/intersection/first/second"); await click("#create-topic"); diff --git a/test/javascripts/acceptance/tags-test.js b/app/assets/javascripts/discourse/tests/acceptance/tags-test.js similarity index 96% rename from test/javascripts/acceptance/tags-test.js rename to app/assets/javascripts/discourse/tests/acceptance/tags-test.js index 44dbd9dbb0..81226a4730 100644 --- a/test/javascripts/acceptance/tags-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/tags-test.js @@ -1,9 +1,13 @@ -import { updateCurrentUser, acceptance } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import { test } from "qunit"; +import { + updateCurrentUser, + acceptance, +} from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; acceptance("Tags", { loggedIn: true }); -QUnit.test("list the tags", async (assert) => { +test("list the tags", async (assert) => { await visit("/tags"); assert.ok($("body.tags-page").length, "has the body class"); @@ -20,7 +24,7 @@ acceptance("Tags listed by group", { }, }); -QUnit.test("list the tags in groups", async (assert) => { +test("list the tags in groups", async (assert) => { await visit("/tags"); assert.equal( $(".tag-list").length, diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-admin-menu-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-admin-menu-test.js new file mode 100644 index 0000000000..9cc2605aa3 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-admin-menu-test.js @@ -0,0 +1,47 @@ +import { test } from "qunit"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; + +acceptance("Topic - Admin Menu Anonymous Users", { loggedIn: false }); + +test("Enter as a regular user", async (assert) => { + await visit("/t/internationalization-localization/280"); + assert.ok(exists("#topic"), "The topic was rendered"); + assert.ok( + !exists(".toggle-admin-menu"), + "The admin menu button was not rendered" + ); +}); + +acceptance("Topic - Admin Menu", { loggedIn: true }); + +test("Enter as a user with group moderator permissions", async (assert) => { + updateCurrentUser({ moderator: false, admin: false, trust_level: 1 }); + + await visit("/t/topic-for-group-moderators/2480"); + assert.ok(exists("#topic"), "The topic was rendered"); + assert.ok(exists(".toggle-admin-menu"), "The admin menu button was rendered"); +}); + +test("Enter as a user with moderator and admin permissions", async (assert) => { + updateCurrentUser({ moderator: true, admin: true, trust_level: 4 }); + + await visit("/t/internationalization-localization/280"); + assert.ok(exists("#topic"), "The topic was rendered"); + assert.ok(exists(".toggle-admin-menu"), "The admin menu button was rendered"); +}); + +test("Toggle the menu as admin focuses the first item", async (assert) => { + updateCurrentUser({ admin: true }); + + await visit("/t/internationalization-localization/280"); + assert.ok(exists("#topic"), "The topic was rendered"); + await click(".toggle-admin-menu"); + + assert.equal( + document.activeElement, + document.querySelector(".topic-admin-multi-select > button") + ); +}); diff --git a/test/javascripts/acceptance/topic-anonymous-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-anonymous-test.js similarity index 74% rename from test/javascripts/acceptance/topic-anonymous-test.js rename to app/assets/javascripts/discourse/tests/acceptance/topic-anonymous-test.js index a5cfe42f22..477e2fe579 100644 --- a/test/javascripts/acceptance/topic-anonymous-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-anonymous-test.js @@ -1,7 +1,8 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Topic - Anonymous"); -QUnit.test("Enter a Topic", async (assert) => { +test("Enter a Topic", async (assert) => { await visit("/t/internationalization-localization/280/1"); assert.ok(exists("#topic"), "The topic was rendered"); assert.ok(exists("#topic .cooked"), "The topic has cooked posts"); @@ -11,24 +12,24 @@ QUnit.test("Enter a Topic", async (assert) => { ); }); -QUnit.test("Enter without an id", async (assert) => { +test("Enter without an id", async (assert) => { await visit("/t/internationalization-localization"); assert.ok(exists("#topic"), "The topic was rendered"); }); -QUnit.test("Enter a 404 topic", async (assert) => { +test("Enter a 404 topic", async (assert) => { await visit("/t/not-found/404"); assert.ok(!exists("#topic"), "The topic was not rendered"); assert.ok(exists(".topic-error"), "An error message is displayed"); }); -QUnit.test("Enter without access", async (assert) => { +test("Enter without access", async (assert) => { await visit("/t/i-dont-have-access/403"); assert.ok(!exists("#topic"), "The topic was not rendered"); assert.ok(exists(".topic-error"), "An error message is displayed"); }); -QUnit.test("Enter with 500 errors", async (assert) => { +test("Enter with 500 errors", async (assert) => { await visit("/t/throws-error/500"); assert.ok(!exists("#topic"), "The topic was not rendered"); assert.ok(exists(".topic-error"), "An error message is displayed"); diff --git a/test/javascripts/acceptance/topic-discovery-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js similarity index 80% rename from test/javascripts/acceptance/topic-discovery-test.js rename to app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js index 8f6ee95824..5b2b32e529 100644 --- a/test/javascripts/acceptance/topic-discovery-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js @@ -1,6 +1,7 @@ +import { test } from "qunit"; import DiscourseURL from "discourse/lib/url"; -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import MessageBus from "message-bus-client"; acceptance("Topic Discovery", { @@ -9,7 +10,7 @@ acceptance("Topic Discovery", { }, }); -QUnit.test("Visit Discovery Pages", async (assert) => { +test("Visit Discovery Pages", async (assert) => { await visit("/"); assert.ok($("body.navigation-topics").length, "has the default navigation"); assert.ok(exists(".topic-list"), "The list of topics was rendered"); @@ -68,7 +69,7 @@ QUnit.test("Visit Discovery Pages", async (assert) => { ); }); -QUnit.test("Clearing state after leaving a category", async (assert) => { +test("Clearing state after leaving a category", async (assert) => { await visit("/c/dev"); assert.ok( exists(".topic-list-item[data-topic-id=11994] .topic-excerpt"), @@ -81,7 +82,7 @@ QUnit.test("Clearing state after leaving a category", async (assert) => { ); }); -QUnit.test("Live update unread state", async (assert) => { +test("Live update unread state", async (assert) => { await visit("/"); assert.ok( exists(".topic-list-item:not(.visited) a[data-topic-id='11995']"), @@ -110,21 +111,18 @@ QUnit.test("Live update unread state", async (assert) => { ); }); -QUnit.test( - "Using period chooser when query params are present", - async (assert) => { - await visit("/top?f=foo&d=bar"); +test("Using period chooser when query params are present", async (assert) => { + await visit("/top?f=foo&d=bar"); - sandbox.stub(DiscourseURL, "routeTo"); + sandbox.stub(DiscourseURL, "routeTo"); - const periodChooser = selectKit(".period-chooser"); + const periodChooser = selectKit(".period-chooser"); - await periodChooser.expand(); - await periodChooser.selectRowByValue("yearly"); + await periodChooser.expand(); + await periodChooser.selectRowByValue("yearly"); - assert.ok( - DiscourseURL.routeTo.calledWith("/top/yearly?f=foo&d=bar"), - "it keeps the query params" - ); - } -); + assert.ok( + DiscourseURL.routeTo.calledWith("/top/yearly?f=foo&d=bar"), + "it keeps the query params" + ); +}); diff --git a/test/javascripts/acceptance/topic-edit-timer-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-edit-timer-test.js similarity index 93% rename from test/javascripts/acceptance/topic-edit-timer-test.js rename to app/assets/javascripts/discourse/tests/acceptance/topic-edit-timer-test.js index 6a2c091227..125fab78fa 100644 --- a/test/javascripts/acceptance/topic-edit-timer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-edit-timer-test.js @@ -1,5 +1,10 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { skip } from "qunit"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { + acceptance, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; acceptance("Topic - Edit timer", { loggedIn: true, @@ -19,7 +24,7 @@ acceptance("Topic - Edit timer", { }, }); -QUnit.test("default", async (assert) => { +test("default", async (assert) => { updateCurrentUser({ moderator: true }); const futureDateInputSelector = selectKit(".future-date-input-selector"); @@ -31,7 +36,7 @@ QUnit.test("default", async (assert) => { assert.equal(futureDateInputSelector.header().value(), null); }); -QUnit.test("autoclose - specific time", async (assert) => { +test("autoclose - specific time", async (assert) => { updateCurrentUser({ moderator: true }); const futureDateInputSelector = selectKit(".future-date-input-selector"); @@ -50,7 +55,7 @@ QUnit.test("autoclose - specific time", async (assert) => { assert.ok(regex.test(html)); }); -QUnit.skip("autoclose", async (assert) => { +skip("autoclose", async (assert) => { updateCurrentUser({ moderator: true }); const futureDateInputSelector = selectKit(".future-date-input-selector"); @@ -103,7 +108,7 @@ QUnit.skip("autoclose", async (assert) => { assert.ok(regex3.test(html3)); }); -QUnit.test("close temporarily", async (assert) => { +test("close temporarily", async (assert) => { updateCurrentUser({ moderator: true }); const timerType = selectKit(".select-kit.timer-type"); const futureDateInputSelector = selectKit(".future-date-input-selector"); @@ -141,7 +146,7 @@ QUnit.test("close temporarily", async (assert) => { assert.ok(regex2.test(html2)); }); -QUnit.test("schedule", async (assert) => { +test("schedule", async (assert) => { updateCurrentUser({ moderator: true }); const timerType = selectKit(".select-kit.timer-type"); const categoryChooser = selectKit(".modal-body .category-chooser"); @@ -174,7 +179,7 @@ QUnit.test("schedule", async (assert) => { assert.ok(regex.test(text)); }); -QUnit.test("TL4 can't auto-delete", async (assert) => { +test("TL4 can't auto-delete", async (assert) => { updateCurrentUser({ moderator: false, admin: false, trust_level: 4 }); await visit("/t/internationalization-localization"); @@ -188,7 +193,7 @@ QUnit.test("TL4 can't auto-delete", async (assert) => { assert.ok(!timerType.rowByValue("delete").exists()); }); -QUnit.test("auto delete", async (assert) => { +test("auto delete", async (assert) => { updateCurrentUser({ moderator: true }); const timerType = selectKit(".select-kit.timer-type"); const futureDateInputSelector = selectKit(".future-date-input-selector"); @@ -214,7 +219,7 @@ QUnit.test("auto delete", async (assert) => { assert.ok(regex.test(html)); }); -QUnit.test("Inline delete timer", async (assert) => { +test("Inline delete timer", async (assert) => { updateCurrentUser({ moderator: true }); const futureDateInputSelector = selectKit(".future-date-input-selector"); diff --git a/test/javascripts/acceptance/topic-footer-buttons-mobile-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-footer-buttons-mobile-test.js similarity index 83% rename from test/javascripts/acceptance/topic-footer-buttons-mobile-test.js rename to app/assets/javascripts/discourse/tests/acceptance/topic-footer-buttons-mobile-test.js index c8ebaa0c09..9f9c20b196 100644 --- a/test/javascripts/acceptance/topic-footer-buttons-mobile-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-footer-buttons-mobile-test.js @@ -1,8 +1,9 @@ +import { test } from "qunit"; import I18n from "I18n"; -import selectKit from "helpers/select-kit-helper"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; import { withPluginApi } from "discourse/lib/plugin-api"; import { clearTopicFooterButtons } from "discourse/lib/register-topic-footer-button"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; let _test; @@ -35,7 +36,7 @@ acceptance("Topic footer buttons mobile", { }, }); -QUnit.test("default", async (assert) => { +test("default", async (assert) => { await visit("/t/internationalization-localization/280"); assert.equal(_test, null); diff --git a/test/javascripts/acceptance/topic-list-tracker-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-list-tracker-test.js similarity index 77% rename from test/javascripts/acceptance/topic-list-tracker-test.js rename to app/assets/javascripts/discourse/tests/acceptance/topic-list-tracker-test.js index c58000843e..c19784c9ce 100644 --- a/test/javascripts/acceptance/topic-list-tracker-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-list-tracker-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { nextTopicUrl, previousTopicUrl, @@ -6,7 +7,7 @@ import { } from "discourse/lib/topic-list-tracker"; acceptance("Topic list tracking"); -QUnit.test("Navigation", async (assert) => { +test("Navigation", async (assert) => { await visit("/"); let url = await nextTopicUrl(); assert.equal(url, "/t/error-after-upgrade-to-0-9-7-9/11557"); diff --git a/test/javascripts/acceptance/topic-move-posts-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-move-posts-test.js similarity index 92% rename from test/javascripts/acceptance/topic-move-posts-test.js rename to app/assets/javascripts/discourse/tests/acceptance/topic-move-posts-test.js index 96fec0a9e9..4c35d63b9e 100644 --- a/test/javascripts/acceptance/topic-move-posts-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-move-posts-test.js @@ -1,8 +1,9 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Topic move posts", { loggedIn: true }); -QUnit.test("default", async (assert) => { +test("default", async (assert) => { await visit("/t/internationalization-localization"); await click(".toggle-admin-menu"); await click(".topic-admin-multi-select .btn"); @@ -45,7 +46,7 @@ QUnit.test("default", async (assert) => { ); }); -QUnit.test("moving all posts", async (assert) => { +test("moving all posts", async (assert) => { await visit("/t/internationalization-localization"); await click(".toggle-admin-menu"); await click(".topic-admin-multi-select .btn"); @@ -81,7 +82,7 @@ QUnit.test("moving all posts", async (assert) => { ); }); -QUnit.test("moving posts from personal message", async (assert) => { +test("moving posts from personal message", async (assert) => { await visit("/t/pm-for-testing/12"); await click(".toggle-admin-menu"); await click(".topic-admin-multi-select .btn"); @@ -117,7 +118,7 @@ QUnit.test("moving posts from personal message", async (assert) => { ); }); -QUnit.test("group moderator moving posts", async (assert) => { +test("group moderator moving posts", async (assert) => { await visit("/t/topic-for-group-moderators/2480"); await click(".toggle-admin-menu"); await click(".topic-admin-multi-select .btn"); diff --git a/test/javascripts/acceptance/topic-notifications-button-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-notifications-button-test.js similarity index 85% rename from test/javascripts/acceptance/topic-notifications-button-test.js rename to app/assets/javascripts/discourse/tests/acceptance/topic-notifications-button-test.js index 48e65e0b3c..0b91fe4ecb 100644 --- a/test/javascripts/acceptance/topic-notifications-button-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-notifications-button-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Topic Notifications button", { loggedIn: true, @@ -10,7 +11,7 @@ acceptance("Topic Notifications button", { }, }); -QUnit.test("Updating topic notification level", async (assert) => { +test("Updating topic notification level", async (assert) => { const notificationOptions = selectKit( "#topic-footer-buttons .topic-notifications-options" ); diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-quote-button-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-quote-button-test.js new file mode 100644 index 0000000000..fff821e3ae --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-quote-button-test.js @@ -0,0 +1,114 @@ +import { test } from "qunit"; +import I18n from "I18n"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; + +function selectText(selector) { + const range = document.createRange(); + const node = document.querySelector(selector); + range.selectNodeContents(node); + + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); +} + +acceptance("Topic - Quote button - logged in", { + loggedIn: true, + settings: { + share_quote_visibility: "anonymous", + share_quote_buttons: "twitter|email", + }, +}); + +test("Does not show the quote share buttons by default", async (assert) => { + await visit("/t/internationalization-localization/280"); + selectText("#post_5 blockquote"); + assert.ok(exists(".insert-quote"), "it shows the quote button"); + assert.equal( + find(".quote-sharing").length, + 0, + "it does not show quote sharing" + ); +}); + +test("Shows quote share buttons with the right site settings", async function (assert) { + this.siteSettings.share_quote_visibility = "all"; + + await visit("/t/internationalization-localization/280"); + selectText("#post_5 blockquote"); + + assert.ok(exists(".quote-sharing"), "it shows the quote sharing options"); + assert.ok( + exists(`.quote-sharing .btn[title='${I18n.t("share.twitter")}']`), + "it includes the twitter share button" + ); + assert.ok( + exists(`.quote-sharing .btn[title='${I18n.t("share.email")}']`), + "it includes the email share button" + ); +}); + +acceptance("Topic - Quote button - anonymous", { + loggedIn: false, + settings: { + share_quote_visibility: "anonymous", + share_quote_buttons: "twitter|email", + }, +}); + +test("Shows quote share buttons with the right site settings", async function (assert) { + await visit("/t/internationalization-localization/280"); + selectText("#post_5 blockquote"); + + assert.ok(find(".quote-sharing"), "it shows the quote sharing options"); + assert.ok( + exists(`.quote-sharing .btn[title='${I18n.t("share.twitter")}']`), + "it includes the twitter share button" + ); + assert.ok( + exists(`.quote-sharing .btn[title='${I18n.t("share.email")}']`), + "it includes the email share button" + ); + assert.equal( + find(".insert-quote").length, + 0, + "it does not show the quote button" + ); +}); + +test("Shows single share button when site setting only has one item", async function (assert) { + this.siteSettings.share_quote_buttons = "twitter"; + + await visit("/t/internationalization-localization/280"); + selectText("#post_5 blockquote"); + + assert.ok(exists(".quote-sharing"), "it shows the quote sharing options"); + assert.ok( + exists(`.quote-sharing .btn[title='${I18n.t("share.twitter")}']`), + "it includes the twitter share button" + ); + assert.equal( + find(".quote-share-label").length, + 0, + "it does not show the Share label" + ); +}); + +test("Shows nothing when visibility is disabled", async function (assert) { + this.siteSettings.share_quote_visibility = "none"; + + await visit("/t/internationalization-localization/280"); + selectText("#post_5 blockquote"); + + assert.equal( + find(".quote-sharing").length, + 0, + "it does not show quote sharing" + ); + + assert.equal( + find(".insert-quote").length, + 0, + "it does not show the quote button" + ); +}); diff --git a/test/javascripts/acceptance/topic-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js similarity index 68% rename from test/javascripts/acceptance/topic-test.js rename to app/assets/javascripts/discourse/tests/acceptance/topic-test.js index 8e673168aa..091e4564da 100644 --- a/test/javascripts/acceptance/topic-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js @@ -1,7 +1,9 @@ +import { skip } from "qunit"; +import { test } from "qunit"; import I18n from "I18n"; import { withPluginApi } from "discourse/lib/plugin-api"; -import selectKit from "helpers/select-kit-helper"; -import { acceptance } from "helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { IMAGE_VERSION as v } from "pretty-text/emoji/version"; acceptance("Topic", { @@ -13,7 +15,7 @@ acceptance("Topic", { }, }); -QUnit.test("Reply as new topic", async (assert) => { +test("Reply as new topic", async (assert) => { await visit("/t/internationalization-localization/280"); await click("button.share:eq(0)"); await click(".reply-as-new-topic a"); @@ -32,7 +34,7 @@ QUnit.test("Reply as new topic", async (assert) => { ); }); -QUnit.test("Reply as new message", async (assert) => { +test("Reply as new message", async (assert) => { await visit("/t/pm-for-testing/12"); await click("button.share:eq(0)"); await click(".reply-as-new-topic a"); @@ -66,14 +68,14 @@ QUnit.test("Reply as new message", async (assert) => { ); }); -QUnit.test("Share Modal", async (assert) => { +test("Share Modal", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".topic-post:first-child button.share"); assert.ok(exists("#share-link"), "it shows the share modal"); }); -QUnit.test("Showing and hiding the edit controls", async (assert) => { +test("Showing and hiding the edit controls", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#topic-title .d-icon-pencil-alt"); @@ -89,7 +91,7 @@ QUnit.test("Showing and hiding the edit controls", async (assert) => { assert.ok(!exists("#edit-title"), "it hides the editing controls"); }); -QUnit.test("Updating the topic title and category", async (assert) => { +test("Updating the topic title and category", async (assert) => { const categoryChooser = selectKit(".title-wrapper .category-chooser"); await visit("/t/internationalization-localization/280"); @@ -112,7 +114,7 @@ QUnit.test("Updating the topic title and category", async (assert) => { ); }); -QUnit.test("Marking a topic as wiki", async (assert) => { +test("Marking a topic as wiki", async (assert) => { await visit("/t/internationalization-localization/280"); assert.ok(find("a.wiki").length === 0, "it does not show the wiki icon"); @@ -124,7 +126,7 @@ QUnit.test("Marking a topic as wiki", async (assert) => { assert.ok(find("a.wiki").length === 1, "it shows the wiki icon"); }); -QUnit.test("Visit topic routes", async (assert) => { +test("Visit topic routes", async (assert) => { await visit("/t/12"); assert.equal( @@ -142,7 +144,7 @@ QUnit.test("Visit topic routes", async (assert) => { ); }); -QUnit.test("Updating the topic title with emojis", async (assert) => { +test("Updating the topic title with emojis", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#topic-title .d-icon-pencil-alt"); @@ -152,12 +154,12 @@ QUnit.test("Updating the topic title with emojis", async (assert) => { assert.equal( find(".fancy-title").html().trim(), - `emojis title bike blonde_woman:t6`, + `emojis title bike blonde_woman:t6`, "it displays the new title with emojis" ); }); -QUnit.test("Updating the topic title with unicode emojis", async (assert) => { +test("Updating the topic title with unicode emojis", async (assert) => { await visit("/t/internationalization-localization/280"); await click("#topic-title .d-icon-pencil-alt"); @@ -167,31 +169,28 @@ QUnit.test("Updating the topic title with unicode emojis", async (assert) => { assert.equal( find(".fancy-title").html().trim(), - `emojis title man_farmerpray`, + `emojis title man_farmerpray`, "it displays the new title with escaped unicode emojis" ); }); -QUnit.test( - "Updating the topic title with unicode emojis without whitespaces", - async function (assert) { - this.siteSettings.enable_inline_emoji_translation = true; - await visit("/t/internationalization-localization/280"); - await click("#topic-title .d-icon-pencil-alt"); +test("Updating the topic title with unicode emojis without whitespaces", async function (assert) { + this.siteSettings.enable_inline_emoji_translation = true; + await visit("/t/internationalization-localization/280"); + await click("#topic-title .d-icon-pencil-alt"); - await fillIn("#edit-title", "Test🙂Title"); + await fillIn("#edit-title", "Test🙂Title"); - await click("#topic-title .submit-edit"); + await click("#topic-title .submit-edit"); - assert.equal( - find(".fancy-title").html().trim(), - `Testslightly_smiling_faceTitle`, - "it displays the new title with escaped unicode emojis" - ); - } -); + assert.equal( + find(".fancy-title").html().trim(), + `Testslightly_smiling_faceTitle`, + "it displays the new title with escaped unicode emojis" + ); +}); -QUnit.test("Suggested topics", async (assert) => { +test("Suggested topics", async (assert) => { await visit("/t/internationalization-localization/280"); assert.equal( @@ -200,7 +199,7 @@ QUnit.test("Suggested topics", async (assert) => { ); }); -QUnit.skip("Deleting a topic", async (assert) => { +skip("Deleting a topic", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".topic-post:eq(0) button.show-more-actions"); await click(".widget-button.delete"); @@ -208,7 +207,7 @@ QUnit.skip("Deleting a topic", async (assert) => { assert.ok(exists(".widget-button.recover"), "it shows the recover button"); }); -QUnit.test("Group category moderator posts", async (assert) => { +test("Group category moderator posts", async (assert) => { await visit("/t/topic-for-group-moderators/2480"); assert.ok(exists(".category-moderator"), "it has a class applied"); @@ -223,7 +222,7 @@ acceptance("Topic featured links", { }, }); -QUnit.skip("remove featured link", async (assert) => { +skip("remove featured link", async (assert) => { await visit("/t/-/299/1"); assert.ok( exists(".title-wrapper .topic-featured-link"), @@ -241,7 +240,7 @@ QUnit.skip("remove featured link", async (assert) => { assert.ok(!exists(".title-wrapper .topic-featured-link"), "link is gone"); }); -QUnit.test("Converting to a public topic", async (assert) => { +test("Converting to a public topic", async (assert) => { await visit("/t/test-pm/34"); assert.ok(exists(".private_message")); await click(".toggle-admin-menu"); @@ -255,7 +254,7 @@ QUnit.test("Converting to a public topic", async (assert) => { assert.ok(!exists(".private_message")); }); -QUnit.test("Unpinning unlisted topic", async (assert) => { +test("Unpinning unlisted topic", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".toggle-admin-menu"); @@ -269,7 +268,7 @@ QUnit.test("Unpinning unlisted topic", async (assert) => { assert.ok(exists(".topic-admin-pin"), "it should show the multi select menu"); }); -QUnit.test("selecting posts", async (assert) => { +test("selecting posts", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".toggle-admin-menu"); await click(".topic-admin-multi-select .btn"); @@ -285,7 +284,7 @@ QUnit.test("selecting posts", async (assert) => { ); }); -QUnit.test("select below", async (assert) => { +test("select below", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".toggle-admin-menu"); await click(".topic-admin-multi-select .btn"); @@ -308,7 +307,7 @@ QUnit.test("select below", async (assert) => { ); }); -QUnit.test("View Hidden Replies", async (assert) => { +test("View Hidden Replies", async (assert) => { await visit("/t/internationalization-localization/280"); await click(".gap"); @@ -325,7 +324,7 @@ function selectText(selector) { selection.addRange(range); } -QUnit.test("Quoting a quote keeps the original poster name", async (assert) => { +test("Quoting a quote keeps the original poster name", async (assert) => { await visit("/t/internationalization-localization/280"); selectText("#post_5 blockquote"); await click(".quote-button .insert-quote"); @@ -337,51 +336,56 @@ QUnit.test("Quoting a quote keeps the original poster name", async (assert) => { ); }); -QUnit.test( - "Quoting a quote with the Reply button keeps the original poster name", - async (assert) => { - await visit("/t/internationalization-localization/280"); - selectText("#post_5 blockquote"); - await click(".reply"); +test("Quoting a quote of a different topic keeps the original topic title", async (assert) => { + await visit("/t/internationalization-localization/280"); + selectText("#post_9 blockquote"); + await click(".quote-button .insert-quote"); - assert.ok( - find(".d-editor-input") - .val() - .indexOf('quote="codinghorror said, post:3, topic:280"') !== -1 - ); - } -); + assert.ok( + find(".d-editor-input") + .val() + .indexOf( + 'quote="A new topic with a link to another topic, post:3, topic:62"' + ) !== -1 + ); +}); -QUnit.test( - "Quoting a quote with replyAsNewTopic keeps the original poster name", - async (assert) => { - await visit("/t/internationalization-localization/280"); - selectText("#post_5 blockquote"); - await keyEvent(document, "keypress", "j".charCodeAt(0)); - await keyEvent(document, "keypress", "t".charCodeAt(0)); +test("Quoting a quote with the Reply button keeps the original poster name", async (assert) => { + await visit("/t/internationalization-localization/280"); + selectText("#post_5 blockquote"); + await click(".reply"); - assert.ok( - find(".d-editor-input") - .val() - .indexOf('quote="codinghorror said, post:3, topic:280"') !== -1 - ); - } -); + assert.ok( + find(".d-editor-input") + .val() + .indexOf('quote="codinghorror said, post:3, topic:280"') !== -1 + ); +}); -QUnit.test( - "Quoting by selecting text can mark the quote as full", - async (assert) => { - await visit("/t/internationalization-localization/280"); - selectText("#post_5 .cooked"); - await click(".quote-button .insert-quote"); +test("Quoting a quote with replyAsNewTopic keeps the original poster name", async (assert) => { + await visit("/t/internationalization-localization/280"); + selectText("#post_5 blockquote"); + await keyEvent(document, "keypress", "j".charCodeAt(0)); + await keyEvent(document, "keypress", "t".charCodeAt(0)); - assert.ok( - find(".d-editor-input") - .val() - .indexOf('quote="pekka, post:5, topic:280, full:true"') !== -1 - ); - } -); + assert.ok( + find(".d-editor-input") + .val() + .indexOf('quote="codinghorror said, post:3, topic:280"') !== -1 + ); +}); + +test("Quoting by selecting text can mark the quote as full", async (assert) => { + await visit("/t/internationalization-localization/280"); + selectText("#post_5 .cooked"); + await click(".quote-button .insert-quote"); + + assert.ok( + find(".d-editor-input") + .val() + .indexOf('quote="pekka, post:5, topic:280, full:true"') !== -1 + ); +}); acceptance("Topic with title decorated", { loggedIn: true, @@ -394,7 +398,7 @@ acceptance("Topic with title decorated", { }, }); -QUnit.test("Decorate topic title", async (assert) => { +test("Decorate topic title", async (assert) => { await visit("/t/internationalization-localization/280"); assert.ok( diff --git a/test/javascripts/acceptance/unknown-test.js b/app/assets/javascripts/discourse/tests/acceptance/unknown-test.js similarity index 75% rename from test/javascripts/acceptance/unknown-test.js rename to app/assets/javascripts/discourse/tests/acceptance/unknown-test.js index 5584c7087e..f762eb2851 100644 --- a/test/javascripts/acceptance/unknown-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/unknown-test.js @@ -1,13 +1,14 @@ -import { acceptance } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; acceptance("Unknown"); -QUnit.test("Permalink Unknown URL", async (assert) => { +test("Permalink Unknown URL", async (assert) => { await visit("/url-that-doesn't-exist"); assert.ok(exists(".page-not-found"), "The not found content is present"); }); -QUnit.test("Permalink URL to a Topic", async (assert) => { +test("Permalink URL to a Topic", async (assert) => { pretender.get("/permalink-check.json", () => { return [ 200, @@ -24,7 +25,7 @@ QUnit.test("Permalink URL to a Topic", async (assert) => { assert.ok(exists(".topic-post")); }); -QUnit.test("Permalink URL to a static page", async (assert) => { +test("Permalink URL to a static page", async (assert) => { pretender.get("/permalink-check.json", () => { return [ 200, diff --git a/test/javascripts/acceptance/user-anonymous-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-anonymous-test.js similarity index 83% rename from test/javascripts/acceptance/user-anonymous-test.js rename to app/assets/javascripts/discourse/tests/acceptance/user-anonymous-test.js index aa14b9f3ee..91ff16297c 100644 --- a/test/javascripts/acceptance/user-anonymous-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-anonymous-test.js @@ -1,4 +1,5 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("User Anonymous"); function hasStream(assert) { @@ -11,13 +12,13 @@ function hasTopicList(assert) { assert.ok(count(".topic-list tr") > 0, "it has a topic list"); } -QUnit.test("Root URL", async (assert) => { +test("Root URL", async (assert) => { await visit("/u/eviltrout"); assert.ok($("body.user-summary-page").length, "has the body class"); assert.equal(currentPath(), "user.summary", "it defaults to summary"); }); -QUnit.test("Filters", async (assert) => { +test("Filters", async (assert) => { await visit("/u/eviltrout/activity"); assert.ok($("body.user-activity-page").length, "has the body class"); hasStream(assert); @@ -31,13 +32,13 @@ QUnit.test("Filters", async (assert) => { assert.ok(exists(".user-stream.filter-5"), "stream has filter class"); }); -QUnit.test("Badges", async (assert) => { +test("Badges", async (assert) => { await visit("/u/eviltrout/badges"); assert.ok($("body.user-badges-page").length, "has the body class"); assert.ok(exists(".user-badges-list .badge-card"), "shows a badge"); }); -QUnit.test("Restricted Routes", async (assert) => { +test("Restricted Routes", async (assert) => { await visit("/u/eviltrout/preferences"); assert.equal( diff --git a/test/javascripts/acceptance/user-bookmarks-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-bookmarks-test.js similarity index 86% rename from test/javascripts/acceptance/user-bookmarks-test.js rename to app/assets/javascripts/discourse/tests/acceptance/user-bookmarks-test.js index fc1f689ffa..7537c2f91f 100644 --- a/test/javascripts/acceptance/user-bookmarks-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-bookmarks-test.js @@ -1,7 +1,8 @@ -import { acceptance } from "helpers/qunit-helpers"; -import selectKit from "helpers/select-kit-helper"; -import pretender from "helpers/create-pretender"; -import userFixtures from "fixtures/user_fixtures"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import pretender from "discourse/tests/helpers/create-pretender"; +import userFixtures from "discourse/tests/fixtures/user-fixtures"; acceptance("User's bookmarks", { loggedIn: true, diff --git a/test/javascripts/acceptance/user-card-mobile-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-card-mobile-test.js similarity index 81% rename from test/javascripts/acceptance/user-card-mobile-test.js rename to app/assets/javascripts/discourse/tests/acceptance/user-card-mobile-test.js index 6b35a46879..8cc4223147 100644 --- a/test/javascripts/acceptance/user-card-mobile-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-card-mobile-test.js @@ -1,9 +1,10 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { skip } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import DiscourseURL from "discourse/lib/url"; acceptance("User Card - Mobile", { mobileView: true }); -QUnit.skip("user card", async (assert) => { +skip("user card", async (assert) => { await visit("/t/internationalization-localization/280"); assert.ok( invisible(".user-card"), diff --git a/test/javascripts/acceptance/user-card-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-card-test.js similarity index 75% rename from test/javascripts/acceptance/user-card-test.js rename to app/assets/javascripts/discourse/tests/acceptance/user-card-test.js index 4c1e81c07a..13bf3e5268 100644 --- a/test/javascripts/acceptance/user-card-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-card-test.js @@ -1,8 +1,10 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { skip } from "qunit"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import DiscourseURL from "discourse/lib/url"; -import pretender from "helpers/create-pretender"; -import userFixtures from "fixtures/user_fixtures"; +import pretender from "discourse/tests/helpers/create-pretender"; +import userFixtures from "discourse/tests/fixtures/user-fixtures"; import User from "discourse/models/user"; acceptance("User Card - Show Local Time", { @@ -10,7 +12,7 @@ acceptance("User Card - Show Local Time", { settings: { display_local_time_in_user_card: true }, }); -QUnit.skip("user card local time", async (assert) => { +skip("user card local time", async (assert) => { User.current().changeTimezone("Australia/Brisbane"); let cardResponse = Object.assign({}, userFixtures["/u/eviltrout/card.json"]); cardResponse.user.timezone = "Australia/Perth"; @@ -60,32 +62,29 @@ QUnit.skip("user card local time", async (assert) => { ); }); -QUnit.test( - "user card local time - does not update timezone for another user", - async (assert) => { - User.current().changeTimezone("Australia/Brisbane"); - let cardResponse = Object.assign({}, userFixtures["/u/charlie/card.json"]); - delete cardResponse.user.timezone; +test("user card local time - does not update timezone for another user", async (assert) => { + User.current().changeTimezone("Australia/Brisbane"); + let cardResponse = Object.assign({}, userFixtures["/u/charlie/card.json"]); + delete cardResponse.user.timezone; - pretender.get("/u/charlie/card.json", () => [ - 200, - { "Content-Type": "application/json" }, - cardResponse, - ]); + pretender.get("/u/charlie/card.json", () => [ + 200, + { "Content-Type": "application/json" }, + cardResponse, + ]); - await visit("/t/internationalization-localization/280"); - await click("a[data-user-card=charlie]:first"); + await visit("/t/internationalization-localization/280"); + await click("a[data-user-card=charlie]:first"); - assert.not( - exists(".user-card .local-time"), - "it does not show the local time if the user card returns a null/undefined timezone for another user" - ); - } -); + assert.not( + exists(".user-card .local-time"), + "it does not show the local time if the user card returns a null/undefined timezone for another user" + ); +}); acceptance("User Card", { loggedIn: true }); -QUnit.skip("user card", async (assert) => { +skip("user card", async (assert) => { await visit("/t/internationalization-localization/280"); assert.ok(invisible(".user-card"), "user card is invisible by default"); diff --git a/test/javascripts/acceptance/user-drafts-stream-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-drafts-stream-test.js similarity index 77% rename from test/javascripts/acceptance/user-drafts-stream-test.js rename to app/assets/javascripts/discourse/tests/acceptance/user-drafts-stream-test.js index a892c2a8be..f5e4972cc1 100644 --- a/test/javascripts/acceptance/user-drafts-stream-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-drafts-stream-test.js @@ -1,8 +1,9 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("User Drafts", { loggedIn: true }); -QUnit.test("Stream", async (assert) => { +test("Stream", async (assert) => { await visit("/u/eviltrout/activity/drafts"); assert.ok(find(".user-stream-item").length === 3, "has drafts"); @@ -13,7 +14,7 @@ QUnit.test("Stream", async (assert) => { ); }); -QUnit.test("Stream - resume draft", async (assert) => { +test("Stream - resume draft", async (assert) => { await visit("/u/eviltrout/activity/drafts"); assert.ok(find(".user-stream-item").length > 0, "has drafts"); diff --git a/test/javascripts/acceptance/user-preferences-interface-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js similarity index 88% rename from test/javascripts/acceptance/user-preferences-interface-test.js rename to app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js index 1cc396a984..f92eb1cc20 100644 --- a/test/javascripts/acceptance/user-preferences-interface-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js @@ -1,5 +1,6 @@ -import { acceptance } from "helpers/qunit-helpers"; -import selectKit from "helpers/select-kit-helper"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; import Site from "discourse/models/site"; import Session from "discourse/models/session"; import cookie, { removeCookie } from "discourse/lib/cookie"; @@ -8,7 +9,7 @@ acceptance("User Preferences - Interface", { loggedIn: true, }); -QUnit.test("font size change", async (assert) => { +test("font size change", async (assert) => { removeCookie("text_size"); const savePreferences = async () => { @@ -53,15 +54,12 @@ QUnit.test("font size change", async (assert) => { removeCookie("text_size"); }); -QUnit.test( - "does not show option to disable dark mode by default", - async (assert) => { - await visit("/u/eviltrout/preferences/interface"); - assert.equal($(".control-group.dark-mode").length, 0); - } -); +test("does not show option to disable dark mode by default", async (assert) => { + await visit("/u/eviltrout/preferences/interface"); + assert.equal($(".control-group.dark-mode").length, 0); +}); -QUnit.test("shows light/dark color scheme pickers", async (assert) => { +test("shows light/dark color scheme pickers", async (assert) => { let site = Site.current(); site.set("user_color_schemes", [ { id: 2, name: "Cool Breeze" }, @@ -87,7 +85,7 @@ acceptance("User Preferences Color Schemes (with default dark scheme)", { pretend: interfacePretender, }); -QUnit.test("show option to disable dark mode", async (assert) => { +test("show option to disable dark mode", async (assert) => { await visit("/u/eviltrout/preferences/interface"); assert.ok( @@ -96,7 +94,7 @@ QUnit.test("show option to disable dark mode", async (assert) => { ); }); -QUnit.test("no color scheme picker by default", async (assert) => { +test("no color scheme picker by default", async (assert) => { let site = Site.current(); site.set("user_color_schemes", []); @@ -104,7 +102,7 @@ QUnit.test("no color scheme picker by default", async (assert) => { assert.equal($(".control-group.color-scheme").length, 0); }); -QUnit.test("light color scheme picker", async (assert) => { +test("light color scheme picker", async (assert) => { let site = Site.current(); site.set("user_color_schemes", [{ id: 2, name: "Cool Breeze" }]); @@ -117,7 +115,7 @@ QUnit.test("light color scheme picker", async (assert) => { ); }); -QUnit.test("light and dark color scheme pickers", async (assert) => { +test("light and dark color scheme pickers", async (assert) => { let site = Site.current(); let session = Session.current(); session.userDarkSchemeId = 1; // same as default set in site settings diff --git a/test/javascripts/acceptance/user-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-test.js similarity index 82% rename from test/javascripts/acceptance/user-test.js rename to app/assets/javascripts/discourse/tests/acceptance/user-test.js index f4b28465bc..f170e731b2 100644 --- a/test/javascripts/acceptance/user-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-test.js @@ -1,11 +1,12 @@ -import { acceptance } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; import Draft from "discourse/models/draft"; import { Promise } from "rsvp"; acceptance("User", { loggedIn: true }); -QUnit.test("Invalid usernames", async (assert) => { +test("Invalid usernames", async (assert) => { pretender.get("/u/eviltrout%2F..%2F..%2F.json", () => { return [400, { "Content-Type": "application/json" }, {}]; }); @@ -15,23 +16,23 @@ QUnit.test("Invalid usernames", async (assert) => { assert.equal(currentPath(), "exception-unknown"); }); -QUnit.test("Unicode usernames", async (assert) => { +test("Unicode usernames", async (assert) => { await visit("/u/%E3%83%A9%E3%82%A4%E3%82%AA%E3%83%B3/summary"); assert.equal(currentPath(), "user.summary"); }); -QUnit.test("Invites", async (assert) => { +test("Invites", async (assert) => { await visit("/u/eviltrout/invited/pending"); assert.ok($("body.user-invites-page").length, "has the body class"); }); -QUnit.test("Messages", async (assert) => { +test("Messages", async (assert) => { await visit("/u/eviltrout/messages"); assert.ok($("body.user-messages-page").length, "has the body class"); }); -QUnit.test("Notifications", async (assert) => { +test("Notifications", async (assert) => { await visit("/u/eviltrout/notifications"); assert.ok($("body.user-notifications-page").length, "has the body class"); @@ -44,7 +45,7 @@ QUnit.test("Notifications", async (assert) => { ); }); -QUnit.test("Root URL - Viewing Self", async (assert) => { +test("Root URL - Viewing Self", async (assert) => { await visit("/u/eviltrout"); assert.ok($("body.user-activity-page").length, "has the body class"); assert.equal( @@ -55,7 +56,7 @@ QUnit.test("Root URL - Viewing Self", async (assert) => { assert.ok(exists(".container.viewing-self"), "has the viewing-self class"); }); -QUnit.test("Viewing Summary", async (assert) => { +test("Viewing Summary", async (assert) => { await visit("/u/eviltrout/summary"); assert.ok(exists(".replies-section li a"), "replies"); @@ -68,7 +69,7 @@ QUnit.test("Viewing Summary", async (assert) => { assert.ok(exists(".top-categories-section .category-link"), "top categories"); }); -QUnit.test("Viewing Drafts", async (assert) => { +test("Viewing Drafts", async (assert) => { sandbox.stub(Draft, "get").returns( Promise.resolve({ draft: null, diff --git a/test/javascripts/acceptance/users-test.js b/app/assets/javascripts/discourse/tests/acceptance/users-test.js similarity index 69% rename from test/javascripts/acceptance/users-test.js rename to app/assets/javascripts/discourse/tests/acceptance/users-test.js index ecb7e4dc77..7147fd4cac 100644 --- a/test/javascripts/acceptance/users-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/users-test.js @@ -1,24 +1,25 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("User Directory"); -QUnit.test("Visit Page", async (assert) => { +test("Visit Page", async (assert) => { await visit("/u"); assert.ok($("body.users-page").length, "has the body class"); assert.ok(exists(".directory table tr"), "has a list of users"); }); -QUnit.test("Visit All Time", async (assert) => { +test("Visit All Time", async (assert) => { await visit("/u?period=all"); assert.ok(exists(".time-read"), "has time read column"); }); -QUnit.test("Visit Without Usernames", async (assert) => { +test("Visit Without Usernames", async (assert) => { await visit("/u?exclude_usernames=system"); assert.ok($("body.users-page").length, "has the body class"); assert.ok(exists(".directory table tr"), "has a list of users"); }); -QUnit.test("Visit With Group Filter", async (assert) => { +test("Visit With Group Filter", async (assert) => { await visit("/u?group=trust_level_0"); assert.ok($("body.users-page").length, "has the body class"); assert.ok(exists(".directory table tr"), "has a list of users"); diff --git a/test/javascripts/fixtures/about.js b/app/assets/javascripts/discourse/tests/fixtures/about.js similarity index 100% rename from test/javascripts/fixtures/about.js rename to app/assets/javascripts/discourse/tests/fixtures/about.js diff --git a/test/javascripts/fixtures/badges_fixture.js b/app/assets/javascripts/discourse/tests/fixtures/badges-fixture.js similarity index 100% rename from test/javascripts/fixtures/badges_fixture.js rename to app/assets/javascripts/discourse/tests/fixtures/badges-fixture.js diff --git a/test/javascripts/fixtures/category-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/category-fixtures.js similarity index 100% rename from test/javascripts/fixtures/category-fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/category-fixtures.js diff --git a/app/assets/javascripts/discourse/tests/fixtures/concerns/notification-types.js b/app/assets/javascripts/discourse/tests/fixtures/concerns/notification-types.js new file mode 100644 index 0000000000..222a849104 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/fixtures/concerns/notification-types.js @@ -0,0 +1,34 @@ +// DO NOT EDIT THIS FILE!!! +// Update it by running `rake javascript:update_constants` + + +export const NOTIFICATION_TYPES = { + mentioned: 1, + replied: 2, + quoted: 3, + edited: 4, + liked: 5, + private_message: 6, + invited_to_private_message: 7, + invitee_accepted: 8, + posted: 9, + moved_post: 10, + linked: 11, + granted_badge: 12, + invited_to_topic: 13, + custom: 14, + group_mentioned: 15, + group_message_summary: 16, + watching_first_post: 17, + topic_reminder: 18, + liked_consolidated: 19, + post_approved: 20, + code_review_commit_approved: 21, + membership_request_accepted: 22, + membership_request_consolidated: 23, + bookmark_reminder: 24, + reaction: 25, + votes_released: 26, + event_reminder: 27, + event_invitation: 28, +}; diff --git a/test/javascripts/fixtures/dashboard-general.js b/app/assets/javascripts/discourse/tests/fixtures/dashboard-general.js similarity index 100% rename from test/javascripts/fixtures/dashboard-general.js rename to app/assets/javascripts/discourse/tests/fixtures/dashboard-general.js diff --git a/test/javascripts/fixtures/dashboard.js b/app/assets/javascripts/discourse/tests/fixtures/dashboard.js similarity index 100% rename from test/javascripts/fixtures/dashboard.js rename to app/assets/javascripts/discourse/tests/fixtures/dashboard.js diff --git a/test/javascripts/fixtures/directory-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/directory-fixtures.js similarity index 100% rename from test/javascripts/fixtures/directory-fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/directory-fixtures.js diff --git a/test/javascripts/fixtures/discovery_fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/discovery-fixtures.js similarity index 100% rename from test/javascripts/fixtures/discovery_fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/discovery-fixtures.js diff --git a/test/javascripts/fixtures/draft.js b/app/assets/javascripts/discourse/tests/fixtures/draft.js similarity index 100% rename from test/javascripts/fixtures/draft.js rename to app/assets/javascripts/discourse/tests/fixtures/draft.js diff --git a/test/javascripts/fixtures/drafts.js b/app/assets/javascripts/discourse/tests/fixtures/drafts.js similarity index 100% rename from test/javascripts/fixtures/drafts.js rename to app/assets/javascripts/discourse/tests/fixtures/drafts.js diff --git a/test/javascripts/fixtures/group-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/group-fixtures.js similarity index 100% rename from test/javascripts/fixtures/group-fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/group-fixtures.js diff --git a/test/javascripts/fixtures/groups-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/groups-fixtures.js similarity index 100% rename from test/javascripts/fixtures/groups-fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/groups-fixtures.js diff --git a/test/javascripts/fixtures/notification_fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/notification-fixtures.js similarity index 79% rename from test/javascripts/fixtures/notification_fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/notification-fixtures.js index 9ab4d8d76e..ac2fe07fe9 100644 --- a/test/javascripts/fixtures/notification_fixtures.js +++ b/app/assets/javascripts/discourse/tests/fixtures/notification-fixtures.js @@ -1,5 +1,5 @@ /*jshint maxlen:10000000 */ -import { NOTIFICATION_TYPES } from "fixtures/concerns/notification-types"; +import { NOTIFICATION_TYPES } from "./concerns/notification-types"; export default { "/notifications": { @@ -11,13 +11,13 @@ export default { post_number: 2, topic_id: 1234, slug: "a-slug", - data: { topic_title: "some title", display_username: "velesin" } + data: { topic_title: "some title", display_username: "velesin" }, }, { id: 456, notification_type: NOTIFICATION_TYPES.liked_consolidated, read: false, - data: { display_username: "aquaman", count: "5" } + data: { display_username: "aquaman", count: "5" }, }, { id: 789, @@ -30,8 +30,8 @@ export default { group_id: 41, group_name: "test", inbox_count: 5, - username: "test2" - } + username: "test2", + }, }, { id: 1234, @@ -40,7 +40,7 @@ export default { post_number: null, topic_id: null, slug: null, - data: { display_username: "test1" } + data: { display_username: "test1" }, }, { id: 5678, @@ -49,8 +49,8 @@ export default { post_number: null, topic_id: null, slug: null, - data: { group_id: 41, group_name: "test" } - } - ] - } + data: { group_id: 41, group_name: "test" }, + }, + ], + }, }; diff --git a/test/javascripts/fixtures/poll.js b/app/assets/javascripts/discourse/tests/fixtures/poll.js similarity index 100% rename from test/javascripts/fixtures/poll.js rename to app/assets/javascripts/discourse/tests/fixtures/poll.js diff --git a/test/javascripts/fixtures/post.js b/app/assets/javascripts/discourse/tests/fixtures/post.js similarity index 100% rename from test/javascripts/fixtures/post.js rename to app/assets/javascripts/discourse/tests/fixtures/post.js diff --git a/test/javascripts/fixtures/private_messages_fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/private-messages-fixtures.js similarity index 100% rename from test/javascripts/fixtures/private_messages_fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/private-messages-fixtures.js diff --git a/test/javascripts/fixtures/problems.js b/app/assets/javascripts/discourse/tests/fixtures/problems.js similarity index 100% rename from test/javascripts/fixtures/problems.js rename to app/assets/javascripts/discourse/tests/fixtures/problems.js diff --git a/test/javascripts/fixtures/reports_bulk.js b/app/assets/javascripts/discourse/tests/fixtures/reports-bulk.js similarity index 100% rename from test/javascripts/fixtures/reports_bulk.js rename to app/assets/javascripts/discourse/tests/fixtures/reports-bulk.js diff --git a/test/javascripts/fixtures/reports.js b/app/assets/javascripts/discourse/tests/fixtures/reports.js similarity index 100% rename from test/javascripts/fixtures/reports.js rename to app/assets/javascripts/discourse/tests/fixtures/reports.js diff --git a/test/javascripts/fixtures/search-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js similarity index 100% rename from test/javascripts/fixtures/search-fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js diff --git a/test/javascripts/fixtures/session-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js similarity index 100% rename from test/javascripts/fixtures/session-fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js diff --git a/test/javascripts/fixtures/site-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js similarity index 91% rename from test/javascripts/fixtures/site-fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js index bdb8504b8f..2cd86c81e5 100644 --- a/test/javascripts/fixtures/site-fixtures.js +++ b/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js @@ -1,4 +1,4 @@ -import { NOTIFICATION_TYPES } from "fixtures/concerns/notification-types"; +import { NOTIFICATION_TYPES } from "./concerns/notification-types"; export default { "site.json": { @@ -11,7 +11,7 @@ export default { regular: 1, moderator_action: 2, small_action: 3, - whisper: 4 + whisper: 4, }, groups: [ { id: 1, name: "admins" }, @@ -22,7 +22,7 @@ export default { { id: 11, name: "trust_level_1" }, { id: 12, name: "trust_level_2" }, { id: 13, name: "trust_level_3" }, - { id: 14, name: "trust_level_4" } + { id: 14, name: "trust_level_4" }, ], filters: [ "latest", @@ -32,7 +32,7 @@ export default { "read", "posted", "search", - "bookmarks" + "bookmarks", ], periods: ["all", "yearly", "quarterly", "monthly", "weekly", "daily"], top_menu_items: [ @@ -44,7 +44,7 @@ export default { "posted", "categories", "top", - "bookmarks" + "bookmarks", ], anonymous_top_menu_items: ["latest", "top", "categories"], uncategorized_category_id: 17, @@ -67,7 +67,7 @@ export default { background_url: null, show_subcategory_list: false, default_view: "latest", - topic_template: "my topic template" + topic_template: "my topic template", }, { id: 10, @@ -85,7 +85,7 @@ export default { notification_level: null, background_url: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 26, @@ -102,7 +102,7 @@ export default { permission: 1, parent_category_id: 2, notification_level: null, - background_url: null + background_url: null, }, { id: 7, @@ -121,7 +121,7 @@ export default { background_url: null, show_subcategory_list: true, default_view: "latest", - subcategory_list_style: "boxes_with_featured_topics" + subcategory_list_style: "boxes_with_featured_topics", }, { id: 6, @@ -139,7 +139,7 @@ export default { notification_level: null, background_url: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 24, @@ -154,7 +154,7 @@ export default { read_restricted: true, permission: 1, notification_level: null, - background_url: null + background_url: null, }, { id: 28, @@ -171,7 +171,7 @@ export default { permission: 1, parent_category_id: 7, notification_level: null, - background_url: null + background_url: null, }, { id: 27, @@ -188,7 +188,7 @@ export default { permission: 1, parent_category_id: 7, notification_level: null, - background_url: null + background_url: null, }, { id: 4, @@ -206,7 +206,7 @@ export default { notification_level: null, background_url: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 14, @@ -224,7 +224,7 @@ export default { notification_level: null, background_url: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 12, @@ -241,7 +241,7 @@ export default { permission: 1, notification_level: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 13, @@ -258,7 +258,7 @@ export default { permission: 1, notification_level: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 5, @@ -275,7 +275,7 @@ export default { permission: 1, notification_level: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 11, @@ -292,7 +292,7 @@ export default { permission: 1, notification_level: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 22, @@ -308,7 +308,7 @@ export default { read_restricted: false, permission: 1, parent_category_id: 5, - notification_level: null + notification_level: null, }, { id: 1, @@ -326,7 +326,7 @@ export default { notification_level: null, can_edit: true, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 17, @@ -343,7 +343,7 @@ export default { permission: 1, notification_level: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 21, @@ -359,7 +359,7 @@ export default { read_restricted: false, permission: 1, parent_category_id: 6, - notification_level: null + notification_level: null, }, { id: 8, @@ -376,7 +376,7 @@ export default { permission: 1, notification_level: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 9, @@ -393,7 +393,7 @@ export default { permission: 1, notification_level: null, show_subcategory_list: false, - default_view: "latest" + default_view: "latest", }, { id: 2, @@ -411,8 +411,8 @@ export default { notification_level: null, show_subcategory_list: true, default_view: "latest", - subcategory_list_style: "boxes" - } + subcategory_list_style: "boxes", + }, ], post_action_types: [ { @@ -424,7 +424,7 @@ export default { is_flag: false, icon: null, id: 1, - is_custom_flag: false + is_custom_flag: false, }, { name_key: "like", @@ -435,7 +435,7 @@ export default { is_flag: false, icon: "heart", id: 2, - is_custom_flag: false + is_custom_flag: false, }, { name_key: "off_topic", @@ -447,7 +447,7 @@ export default { is_flag: true, icon: null, id: 3, - is_custom_flag: false + is_custom_flag: false, }, { name_key: "inappropriate", @@ -460,7 +460,7 @@ export default { is_flag: true, icon: null, id: 4, - is_custom_flag: false + is_custom_flag: false, }, { name_key: "vote", @@ -471,7 +471,7 @@ export default { is_flag: false, icon: null, id: 5, - is_custom_flag: false + is_custom_flag: false, }, { name_key: "spam", @@ -483,7 +483,7 @@ export default { is_flag: true, icon: null, id: 8, - is_custom_flag: false + is_custom_flag: false, }, { name_key: "notify_user", @@ -496,7 +496,7 @@ export default { is_flag: true, icon: null, id: 6, - is_custom_flag: true + is_custom_flag: true, }, { name_key: "notify_moderators", @@ -508,8 +508,8 @@ export default { is_flag: true, icon: null, id: 7, - is_custom_flag: true - } + is_custom_flag: true, + }, ], topic_flag_types: [ { @@ -521,7 +521,7 @@ export default { is_flag: true, icon: null, id: 4, - is_custom_flag: false + is_custom_flag: false, }, { name_key: "spam", @@ -532,7 +532,7 @@ export default { is_flag: true, icon: null, id: 8, - is_custom_flag: false + is_custom_flag: false, }, { name_key: "notify_moderators", @@ -543,42 +543,42 @@ export default { is_flag: true, icon: null, id: 7, - is_custom_flag: true - } + is_custom_flag: true, + }, ], trust_levels: [ { id: 0, - name: "new user" + name: "new user", }, { id: 1, - name: "basic user" + name: "basic user", }, { id: 2, - name: "member" + name: "member", }, { id: 3, - name: "regular" + name: "regular", }, { id: 4, - name: "leader" - } + name: "leader", + }, ], archetypes: [ { id: "regular", name: "Regular Topic", - options: [] + options: [], }, { id: "banner", name: "translation missing: en.archetypes.banner.title", - options: [] - } + options: [], + }, ], auth_providers: [ { @@ -590,9 +590,9 @@ export default { frame_width: 580, frame_height: 400, can_connect: true, - can_revoke: true - } - ] - } - } + can_revoke: true, + }, + ], + }, + }, }; diff --git a/test/javascripts/fixtures/site_settings.js b/app/assets/javascripts/discourse/tests/fixtures/site-settings.js similarity index 100% rename from test/javascripts/fixtures/site_settings.js rename to app/assets/javascripts/discourse/tests/fixtures/site-settings.js diff --git a/test/javascripts/fixtures/static_fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/static-fixtures.js similarity index 100% rename from test/javascripts/fixtures/static_fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/static-fixtures.js diff --git a/test/javascripts/fixtures/top_fixture.js b/app/assets/javascripts/discourse/tests/fixtures/top-fixtures.js similarity index 100% rename from test/javascripts/fixtures/top_fixture.js rename to app/assets/javascripts/discourse/tests/fixtures/top-fixtures.js diff --git a/test/javascripts/fixtures/topic.js b/app/assets/javascripts/discourse/tests/fixtures/topic.js similarity index 99% rename from test/javascripts/fixtures/topic.js rename to app/assets/javascripts/discourse/tests/fixtures/topic.js index beddf91d94..0eaa20ad1d 100644 --- a/test/javascripts/fixtures/topic.js +++ b/app/assets/javascripts/discourse/tests/fixtures/topic.js @@ -851,7 +851,7 @@ export default { uploaded_avatar_id: 5253, created_at: "2013-02-07T14:14:12.666Z", cooked: - '

\n\n

Yeah, that\'s why I\'ve always been a friend of message_error_nametooshort placeholders, until I asked the SO question linked above. The accepted answer makes a good argument against those placeholders: you want translations to break even on small changes in the English original because the translations will probably need to reflect the change, too. Maybe that\'s not the case right now as new stuff is being checked in pretty much every couple of hours, but in the long run, it\'ll be overwhelmingly true.

', + '\n

repost after a reload thank you!

', post_number: 9, post_type: 1, updated_at: "2013-02-07T14:18:09.569Z", diff --git a/test/javascripts/fixtures/user-badges.js b/app/assets/javascripts/discourse/tests/fixtures/user-badges.js similarity index 100% rename from test/javascripts/fixtures/user-badges.js rename to app/assets/javascripts/discourse/tests/fixtures/user-badges.js diff --git a/test/javascripts/fixtures/user_fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js similarity index 100% rename from test/javascripts/fixtures/user_fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js diff --git a/test/javascripts/fixtures/watched-words-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/watched-words-fixtures.js similarity index 100% rename from test/javascripts/fixtures/watched-words-fixtures.js rename to app/assets/javascripts/discourse/tests/fixtures/watched-words-fixtures.js diff --git a/test/javascripts/helpers/assertions.js b/app/assets/javascripts/discourse/tests/helpers/assertions.js similarity index 100% rename from test/javascripts/helpers/assertions.js rename to app/assets/javascripts/discourse/tests/helpers/assertions.js diff --git a/test/javascripts/helpers/component-test.js b/app/assets/javascripts/discourse/tests/helpers/component-test.js similarity index 93% rename from test/javascripts/helpers/component-test.js rename to app/assets/javascripts/discourse/tests/helpers/component-test.js index 7fd5727544..5509343240 100644 --- a/test/javascripts/helpers/component-test.js +++ b/app/assets/javascripts/discourse/tests/helpers/component-test.js @@ -1,11 +1,12 @@ import EmberObject from "@ember/object"; -import createStore from "helpers/create-store"; +import createStore from "discourse/tests/helpers/create-store"; import { autoLoadModules } from "discourse/initializers/auto-load-modules"; import TopicTrackingState from "discourse/models/topic-tracking-state"; import User from "discourse/models/user"; import Site from "discourse/models/site"; import Session from "discourse/models/session"; -import { currentSettings } from "helpers/site-settings"; +import { currentSettings } from "discourse/tests/helpers/site-settings"; +import { test } from "qunit"; export default function (name, opts) { opts = opts || {}; diff --git a/test/javascripts/helpers/create-pretender.js b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js similarity index 99% rename from test/javascripts/helpers/create-pretender.js rename to app/assets/javascripts/discourse/tests/helpers/create-pretender.js index 5632aa3d2b..5bc446fd59 100644 --- a/test/javascripts/helpers/create-pretender.js +++ b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js @@ -1,4 +1,5 @@ import User from "discourse/models/user"; +import Pretender from "pretender"; export function parsePostData(query) { const result = {}; @@ -40,6 +41,10 @@ export let fixturesByUrl; export default new Pretender(); +export function pretenderHelpers() { + return { parsePostData, response, success }; +} + export function applyDefaultHandlers(pretender) { // Autoload any `*-pretender` files Object.keys(requirejs.entries).forEach((e) => { @@ -416,7 +421,7 @@ export function applyDefaultHandlers(pretender) { pretender.post("/u/action/send_activation_email", success); pretender.put("/u/update-activation-email", success); - pretender.get("/u/hp.json", function () { + pretender.get("/session/hp.json", function () { return response({ value: "32faff1b1ef1ac3", challenge: "61a3de0ccf086fb9604b76e884d75801", diff --git a/test/javascripts/helpers/create-store.js b/app/assets/javascripts/discourse/tests/helpers/create-store.js similarity index 96% rename from test/javascripts/helpers/create-store.js rename to app/assets/javascripts/discourse/tests/helpers/create-store.js index 4556e8011c..80eb23704c 100644 --- a/test/javascripts/helpers/create-store.js +++ b/app/assets/javascripts/discourse/tests/helpers/create-store.js @@ -4,7 +4,7 @@ import KeyValueStore from "discourse/lib/key-value-store"; import TopicListAdapter from "discourse/adapters/topic-list"; import TopicTrackingState from "discourse/models/topic-tracking-state"; import { buildResolver } from "discourse-common/resolver"; -import { currentSettings } from "helpers/site-settings"; +import { currentSettings } from "discourse/tests/helpers/site-settings"; const CatAdapter = RestAdapter.extend({ primaryKey: "cat_id", diff --git a/test/javascripts/helpers/d-editor-helper.js b/app/assets/javascripts/discourse/tests/helpers/d-editor-helper.js similarity index 100% rename from test/javascripts/helpers/d-editor-helper.js rename to app/assets/javascripts/discourse/tests/helpers/d-editor-helper.js diff --git a/test/javascripts/helpers/fixture-pretender.js b/app/assets/javascripts/discourse/tests/helpers/fixture-pretender.js similarity index 90% rename from test/javascripts/helpers/fixture-pretender.js rename to app/assets/javascripts/discourse/tests/helpers/fixture-pretender.js index 8ed9babff4..32ad7ef595 100644 --- a/test/javascripts/helpers/fixture-pretender.js +++ b/app/assets/javascripts/discourse/tests/helpers/fixture-pretender.js @@ -4,7 +4,7 @@ export default function (helpers) { // Load any fixtures automatically Object.keys(require._eak_seen).forEach((entry) => { - if (/^fixtures/.test(entry)) { + if (/^discourse\/tests\/fixtures/.test(entry)) { const fixture = require(entry, null, null, true); if (fixture && fixture.default) { const obj = fixture.default; diff --git a/test/javascripts/helpers/html-helper.js b/app/assets/javascripts/discourse/tests/helpers/html-helper.js similarity index 100% rename from test/javascripts/helpers/html-helper.js rename to app/assets/javascripts/discourse/tests/helpers/html-helper.js diff --git a/test/javascripts/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js similarity index 89% rename from test/javascripts/helpers/qunit-helpers.js rename to app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js index 668ef6db30..f69f7361fe 100644 --- a/test/javascripts/helpers/qunit-helpers.js +++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js @@ -1,9 +1,7 @@ import { Promise } from "rsvp"; import { isEmpty } from "@ember/utils"; import { later } from "@ember/runloop"; -/* global QUnit, resetSite */ - -import sessionFixtures from "fixtures/session-fixtures"; +import sessionFixtures from "discourse/tests/fixtures/session-fixtures"; import HeaderComponent from "discourse/components/site-header"; import { forceMobile, resetMobile } from "discourse/lib/mobile"; import { resetPluginApi } from "discourse/lib/plugin-api"; @@ -26,12 +24,20 @@ import { resetCustomPostMessageCallbacks } from "discourse/controllers/topic"; import { _clearSnapshots } from "select-kit/components/composer-actions"; import User from "discourse/models/user"; import { mapRoutes } from "discourse/mapping-router"; -import { currentSettings, mergeSettings } from "helpers/site-settings"; +import { + currentSettings, + mergeSettings, +} from "discourse/tests/helpers/site-settings"; import { getOwner } from "discourse-common/lib/get-owner"; import { setTopicList } from "discourse/lib/topic-list-tracker"; import { setURLContainer } from "discourse/lib/url"; import { setDefaultOwner } from "discourse-common/lib/get-owner"; import bootbox from "bootbox"; +import { moduleFor } from "ember-qunit"; +import QUnit, { module } from "qunit"; +import siteFixtures from "discourse/tests/fixtures/site-fixtures"; +import Site from "discourse/models/site"; +import createStore from "discourse/tests/helpers/create-store"; export function currentUser() { return User.create(sessionFixtures["/session/current.json"].current_user); @@ -109,6 +115,13 @@ $.fn.modal = AcceptanceModal; let _pretenderCallbacks = {}; +export function resetSite(siteSettings, extras) { + let siteAttrs = $.extend({}, siteFixtures["site.json"].site, extras || {}); + siteAttrs.store = createStore(); + siteAttrs.siteSettings = siteSettings; + return Site.resetCurrent(Site.create(siteAttrs)); +} + export function applyPretender(name, server, helper) { const cb = _pretenderCallbacks[name]; if (cb) { @@ -131,7 +144,7 @@ export function controllerModule(name, args = {}) { } export function discourseModule(name, hooks) { - QUnit.module(name, { + module(name, { beforeEach() { this.container = getOwner(this); this.siteSettings = currentSettings(); @@ -147,14 +160,19 @@ export function discourseModule(name, hooks) { }); } +export function addPretenderCallback(name, fn) { + if (name && fn) { + _pretenderCallbacks[name] = fn; + } +} + export function acceptance(name, options) { + name = `Acceptance: ${name}`; options = options || {}; - if (options.pretend) { - _pretenderCallbacks[name] = options.pretend; - } + addPretenderCallback(name, options.pretend); - QUnit.module("Acceptance: " + name, { + module(name, { beforeEach() { resetMobile(); diff --git a/test/javascripts/helpers/review-pretender.js b/app/assets/javascripts/discourse/tests/helpers/review-pretender.js similarity index 100% rename from test/javascripts/helpers/review-pretender.js rename to app/assets/javascripts/discourse/tests/helpers/review-pretender.js diff --git a/test/javascripts/helpers/select-kit-helper.js b/app/assets/javascripts/discourse/tests/helpers/select-kit-helper.js similarity index 99% rename from test/javascripts/helpers/select-kit-helper.js rename to app/assets/javascripts/discourse/tests/helpers/select-kit-helper.js index 63c45adc3e..8e5932e424 100644 --- a/test/javascripts/helpers/select-kit-helper.js +++ b/app/assets/javascripts/discourse/tests/helpers/select-kit-helper.js @@ -1,3 +1,4 @@ +import { moduleForComponent } from "ember-qunit"; import { isEmpty } from "@ember/utils"; function checkSelectKitIsNotExpanded(selector) { diff --git a/test/javascripts/helpers/site-settings.js b/app/assets/javascripts/discourse/tests/helpers/site-settings.js similarity index 99% rename from test/javascripts/helpers/site-settings.js rename to app/assets/javascripts/discourse/tests/helpers/site-settings.js index 4d2242cc88..ba52f3f9b5 100644 --- a/test/javascripts/helpers/site-settings.js +++ b/app/assets/javascripts/discourse/tests/helpers/site-settings.js @@ -101,7 +101,6 @@ const ORIGINAL_SETTINGS = { }; let siteSettings = Object.assign({}, ORIGINAL_SETTINGS); -Discourse.SiteSettings = siteSettings; export function currentSettings() { return siteSettings; diff --git a/test/javascripts/helpers/site.js b/app/assets/javascripts/discourse/tests/helpers/site.js similarity index 100% rename from test/javascripts/helpers/site.js rename to app/assets/javascripts/discourse/tests/helpers/site.js diff --git a/test/javascripts/helpers/store-pretender.js b/app/assets/javascripts/discourse/tests/helpers/store-pretender.js similarity index 100% rename from test/javascripts/helpers/store-pretender.js rename to app/assets/javascripts/discourse/tests/helpers/store-pretender.js diff --git a/test/javascripts/helpers/textarea-selection-helper.js b/app/assets/javascripts/discourse/tests/helpers/textarea-selection-helper.js similarity index 100% rename from test/javascripts/helpers/textarea-selection-helper.js rename to app/assets/javascripts/discourse/tests/helpers/textarea-selection-helper.js diff --git a/test/javascripts/helpers/widget-test.js b/app/assets/javascripts/discourse/tests/helpers/widget-test.js similarity index 50% rename from test/javascripts/helpers/widget-test.js rename to app/assets/javascripts/discourse/tests/helpers/widget-test.js index 6cd3fbd16f..91f8daaa48 100644 --- a/test/javascripts/helpers/widget-test.js +++ b/app/assets/javascripts/discourse/tests/helpers/widget-test.js @@ -1,9 +1,14 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; +import { addPretenderCallback } from "discourse/tests/helpers/qunit-helpers"; export function moduleForWidget(name, options = {}) { + let fullName = `widget:${name}`; + addPretenderCallback(fullName, options.pretend); + moduleForComponent( name, - `widget:${name}`, + fullName, Object.assign( { integration: true }, { beforeEach: options.beforeEach, afterEach: options.afterEach } diff --git a/test/javascripts/components/ace-editor-test.js b/app/assets/javascripts/discourse/tests/integration/components/ace-editor-test.js similarity index 91% rename from test/javascripts/components/ace-editor-test.js rename to app/assets/javascripts/discourse/tests/integration/components/ace-editor-test.js index b5ed8303fb..44792a75e9 100644 --- a/test/javascripts/components/ace-editor-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/ace-editor-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; moduleForComponent("ace-editor", { integration: true }); diff --git a/test/javascripts/components/admin-report-test.js b/app/assets/javascripts/discourse/tests/integration/components/admin-report-test.js similarity index 95% rename from test/javascripts/components/admin-report-test.js rename to app/assets/javascripts/discourse/tests/integration/components/admin-report-test.js index 56ac97de5a..d288b3deab 100644 --- a/test/javascripts/components/admin-report-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/admin-report-test.js @@ -1,5 +1,6 @@ -import componentTest from "helpers/component-test"; -import pretender from "helpers/create-pretender"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; +import pretender from "discourse/tests/helpers/create-pretender"; moduleForComponent("admin-report", { integration: true, diff --git a/test/javascripts/components/badge-title-test.js b/app/assets/javascripts/discourse/tests/integration/components/badge-title-test.js similarity index 77% rename from test/javascripts/components/badge-title-test.js rename to app/assets/javascripts/discourse/tests/integration/components/badge-title-test.js index 33a2f1eac9..4d9b0c9f26 100644 --- a/test/javascripts/components/badge-title-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/badge-title-test.js @@ -1,7 +1,8 @@ -import selectKit from "helpers/select-kit-helper"; -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; import EmberObject from "@ember/object"; -import pretender from "helpers/create-pretender"; +import pretender from "discourse/tests/helpers/create-pretender"; moduleForComponent("badge-title", { integration: true }); diff --git a/test/javascripts/components/cook-text-test.js b/app/assets/javascripts/discourse/tests/integration/components/cook-text-test.js similarity index 84% rename from test/javascripts/components/cook-text-test.js rename to app/assets/javascripts/discourse/tests/integration/components/cook-text-test.js index 051e6efdb7..b0e10f4b1f 100644 --- a/test/javascripts/components/cook-text-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/cook-text-test.js @@ -1,5 +1,6 @@ -import componentTest from "helpers/component-test"; -import pretender from "helpers/create-pretender"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; +import pretender from "discourse/tests/helpers/create-pretender"; import { resetCache } from "pretty-text/upload-short-url"; moduleForComponent("cook-text", { integration: true }); diff --git a/test/javascripts/components/d-button-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-button-test.js similarity index 97% rename from test/javascripts/components/d-button-test.js rename to app/assets/javascripts/discourse/tests/integration/components/d-button-test.js index ce41d19428..759938d11e 100644 --- a/test/javascripts/components/d-button-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/d-button-test.js @@ -1,5 +1,6 @@ +import { moduleForComponent } from "ember-qunit"; import I18n from "I18n"; -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("d-button", { integration: true }); componentTest("icon only button", { diff --git a/test/javascripts/components/d-editor-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js similarity index 98% rename from test/javascripts/components/d-editor-test.js rename to app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js index fec3ae228a..b207338bf0 100644 --- a/test/javascripts/components/d-editor-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js @@ -1,13 +1,14 @@ +import { moduleForComponent } from "ember-qunit"; import I18n from "I18n"; import { next } from "@ember/runloop"; import { clearToolbarCallbacks } from "discourse/components/d-editor"; -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; import { withPluginApi } from "discourse/lib/plugin-api"; -import formatTextWithSelection from "helpers/d-editor-helper"; +import formatTextWithSelection from "discourse/tests/helpers/d-editor-helper"; import { setTextareaSelection, getTextareaSelection, -} from "helpers/textarea-selection-helper"; +} from "discourse/tests/helpers/textarea-selection-helper"; moduleForComponent("d-editor", { integration: true }); diff --git a/test/javascripts/components/d-icon-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-icon-test.js similarity index 86% rename from test/javascripts/components/d-icon-test.js rename to app/assets/javascripts/discourse/tests/integration/components/d-icon-test.js index 7ee20a999a..60db5afac9 100644 --- a/test/javascripts/components/d-icon-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/d-icon-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("d-icon", { integration: true }); diff --git a/test/javascripts/components/date-input-test.js b/app/assets/javascripts/discourse/tests/integration/components/date-input-test.js similarity index 91% rename from test/javascripts/components/date-input-test.js rename to app/assets/javascripts/discourse/tests/integration/components/date-input-test.js index 769f935429..96b7cbad0a 100644 --- a/test/javascripts/components/date-input-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/date-input-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("date-input", { integration: true }); diff --git a/test/javascripts/components/date-time-input-range-test.js b/app/assets/javascripts/discourse/tests/integration/components/date-time-input-range-test.js similarity index 88% rename from test/javascripts/components/date-time-input-range-test.js rename to app/assets/javascripts/discourse/tests/integration/components/date-time-input-range-test.js index 7e0f76f461..9425e4feda 100644 --- a/test/javascripts/components/date-time-input-range-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/date-time-input-range-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("date-time-input-range", { integration: true }); diff --git a/test/javascripts/components/date-time-input-test.js b/app/assets/javascripts/discourse/tests/integration/components/date-time-input-test.js similarity index 93% rename from test/javascripts/components/date-time-input-test.js rename to app/assets/javascripts/discourse/tests/integration/components/date-time-input-test.js index c859fe26e3..37812fe4df 100644 --- a/test/javascripts/components/date-time-input-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/date-time-input-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("date-time-input", { integration: true }); diff --git a/test/javascripts/components/group-membership-button-test.js b/app/assets/javascripts/discourse/tests/integration/components/group-membership-button-test.js similarity index 88% rename from test/javascripts/components/group-membership-button-test.js rename to app/assets/javascripts/discourse/tests/integration/components/group-membership-button-test.js index 2d17c0ed6d..331c3daffc 100644 --- a/test/javascripts/components/group-membership-button-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/group-membership-button-test.js @@ -1,6 +1,9 @@ +import { test } from "qunit"; +import { moduleFor } from "ember-qunit"; + moduleFor("component:group-membership-button"); -QUnit.test("canJoinGroup", function (assert) { +test("canJoinGroup", function (assert) { this.subject().setProperties({ model: { public_admission: false, is_group_user: true }, }); @@ -28,7 +31,7 @@ QUnit.test("canJoinGroup", function (assert) { ); }); -QUnit.test("canLeaveGroup", function (assert) { +test("canLeaveGroup", function (assert) { this.subject().setProperties({ model: { public_exit: false, is_group_user: false }, }); @@ -56,7 +59,7 @@ QUnit.test("canLeaveGroup", function (assert) { ); }); -QUnit.test("canRequestMembership", function (assert) { +test("canRequestMembership", function (assert) { this.subject().setProperties({ model: { allow_membership_requests: true, is_group_user: true }, }); @@ -76,7 +79,7 @@ QUnit.test("canRequestMembership", function (assert) { ); }); -QUnit.test("userIsGroupUser", function (assert) { +test("userIsGroupUser", function (assert) { this.subject().setProperties({ model: { is_group_user: true }, }); diff --git a/test/javascripts/components/highlighted-code-test.js b/app/assets/javascripts/discourse/tests/integration/components/highlighted-code-test.js similarity index 88% rename from test/javascripts/components/highlighted-code-test.js rename to app/assets/javascripts/discourse/tests/integration/components/highlighted-code-test.js index 5159a22af7..c4a86900fc 100644 --- a/test/javascripts/components/highlighted-code-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/highlighted-code-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; const LONG_CODE_BLOCK = "puts a\n".repeat(15000); diff --git a/test/javascripts/components/html-safe-helper-test.js b/app/assets/javascripts/discourse/tests/integration/components/html-safe-helper-test.js similarity index 72% rename from test/javascripts/components/html-safe-helper-test.js rename to app/assets/javascripts/discourse/tests/integration/components/html-safe-helper-test.js index 08fbb28e3c..28eba894a8 100644 --- a/test/javascripts/components/html-safe-helper-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/html-safe-helper-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("html-safe-helper", { integration: true }); componentTest("default", { diff --git a/test/javascripts/components/iframed-html-test.js b/app/assets/javascripts/discourse/tests/integration/components/iframed-html-test.js similarity index 84% rename from test/javascripts/components/iframed-html-test.js rename to app/assets/javascripts/discourse/tests/integration/components/iframed-html-test.js index d95e045429..ff1fcc4ce7 100644 --- a/test/javascripts/components/iframed-html-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/iframed-html-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("iframed-html", { integration: true }); diff --git a/test/javascripts/components/image-uploader-test.js b/app/assets/javascripts/discourse/tests/integration/components/image-uploader-test.js similarity index 93% rename from test/javascripts/components/image-uploader-test.js rename to app/assets/javascripts/discourse/tests/integration/components/image-uploader-test.js index ffe1d28285..5b714a3a44 100644 --- a/test/javascripts/components/image-uploader-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/image-uploader-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("image-uploader", { integration: true }); componentTest("with image", { diff --git a/test/javascripts/components/keyboard-shortcuts-test.js b/app/assets/javascripts/discourse/tests/integration/components/keyboard-shortcuts-test.js similarity index 91% rename from test/javascripts/components/keyboard-shortcuts-test.js rename to app/assets/javascripts/discourse/tests/integration/components/keyboard-shortcuts-test.js index 19ccbe946f..4b88456758 100644 --- a/test/javascripts/components/keyboard-shortcuts-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/keyboard-shortcuts-test.js @@ -1,9 +1,10 @@ +import { test, module } from "qunit"; import DiscourseURL from "discourse/lib/url"; var testMouseTrap; import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts"; -QUnit.module("lib:keyboard-shortcuts", { +module("lib:keyboard-shortcuts", { beforeEach() { var _bindings = {}; @@ -115,21 +116,21 @@ Object.keys(functionBindings).forEach((func) => { }); }); -QUnit.test("selectDown calls _moveSelection with 1", (assert) => { +test("selectDown calls _moveSelection with 1", (assert) => { var stub = sandbox.stub(KeyboardShortcuts, "_moveSelection"); KeyboardShortcuts.selectDown(); assert.ok(stub.calledWith(1), "_moveSelection is called with 1"); }); -QUnit.test("selectUp calls _moveSelection with -1", (assert) => { +test("selectUp calls _moveSelection with -1", (assert) => { var stub = sandbox.stub(KeyboardShortcuts, "_moveSelection"); KeyboardShortcuts.selectUp(); assert.ok(stub.calledWith(-1), "_moveSelection is called with -1"); }); -QUnit.test("goBack calls history.back", (assert) => { +test("goBack calls history.back", (assert) => { var called = false; sandbox.stub(history, "back").callsFake(function () { called = true; @@ -139,14 +140,14 @@ QUnit.test("goBack calls history.back", (assert) => { assert.ok(called, "history.back is called"); }); -QUnit.test("nextSection calls _changeSection with 1", (assert) => { +test("nextSection calls _changeSection with 1", (assert) => { var spy = sandbox.spy(KeyboardShortcuts, "_changeSection"); KeyboardShortcuts.nextSection(); assert.ok(spy.calledWith(1), "_changeSection is called with 1"); }); -QUnit.test("prevSection calls _changeSection with -1", (assert) => { +test("prevSection calls _changeSection with -1", (assert) => { var spy = sandbox.spy(KeyboardShortcuts, "_changeSection"); KeyboardShortcuts.prevSection(); diff --git a/test/javascripts/components/load-more-test.js b/app/assets/javascripts/discourse/tests/integration/components/load-more-test.js similarity index 83% rename from test/javascripts/components/load-more-test.js rename to app/assets/javascripts/discourse/tests/integration/components/load-more-test.js index 0d10793ee6..d5a9310b3d 100644 --- a/test/javascripts/components/load-more-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/load-more-test.js @@ -1,5 +1,6 @@ +import { moduleForComponent } from "ember-qunit"; import { configureEyeline } from "discourse/lib/eyeline"; -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("load-more", { integration: true }); diff --git a/test/javascripts/components/secret-value-list-test.js b/app/assets/javascripts/discourse/tests/integration/components/secret-value-list-test.js similarity index 95% rename from test/javascripts/components/secret-value-list-test.js rename to app/assets/javascripts/discourse/tests/integration/components/secret-value-list-test.js index 7ee2f9b004..c07ab3a5d5 100644 --- a/test/javascripts/components/secret-value-list-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/secret-value-list-test.js @@ -1,5 +1,6 @@ +import { moduleForComponent } from "ember-qunit"; import I18n from "I18n"; -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("secret-value-list", { integration: true }); componentTest("adding a value", { diff --git a/test/javascripts/components/select-kit/api-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/api-test.js similarity index 96% rename from test/javascripts/components/select-kit/api-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/api-test.js index 1b4efdae39..3954ba077c 100644 --- a/test/javascripts/components/select-kit/api-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/api-test.js @@ -1,9 +1,9 @@ -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; import selectKit, { testSelectKitModule, setDefaultState, DEFAULT_CONTENT, -} from "helpers/select-kit-helper"; +} from "discourse/tests/helpers/select-kit-helper"; import { withPluginApi } from "discourse/lib/plugin-api"; import { clearCallbacks } from "select-kit/mixins/plugin-api"; diff --git a/test/javascripts/components/select-kit/category-chooser-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/category-chooser-test.js similarity index 73% rename from test/javascripts/components/select-kit/category-chooser-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/category-chooser-test.js index 57e3fe6860..5ccdf32e38 100644 --- a/test/javascripts/components/select-kit/category-chooser-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/category-chooser-test.js @@ -1,6 +1,7 @@ +import createStore from "discourse/tests/helpers/create-store"; import I18n from "I18n"; -import componentTest from "helpers/component-test"; -import { testSelectKitModule } from "helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; +import { testSelectKitModule } from "discourse/tests/helpers/select-kit-helper"; testSelectKitModule("category-chooser"); @@ -144,3 +145,52 @@ componentTest("with allowed uncategorized and none", { assert.equal(this.subject.header().label(), "root none label"); }, }); + +componentTest("filter is case insensitive", { + template: template(), + + async test(assert) { + await this.subject.expand(); + await this.subject.fillInFilter("bug"); + + assert.ok(this.subject.rows().length, 1); + assert.equal(this.subject.rowByIndex(0).name(), "bug"); + + await this.subject.emptyFilter(); + await this.subject.fillInFilter("Bug"); + + assert.ok(this.subject.rows().length, 1); + assert.equal(this.subject.rowByIndex(0).name(), "bug"); + }, +}); + +componentTest("filter works with non english characters", { + template: ` + {{category-chooser + value=value + content=content + }} + `, + + beforeEach() { + const store = createStore(); + const nonEnglishCat = store.createRecord("category", { + id: 1, + name: "chữ Quốc ngữ", + }); + const englishCat = store.createRecord("category", { + id: 2, + name: "Baz", + }); + + this.set("content", [nonEnglishCat, englishCat]); + }, + + async test(assert) { + await this.subject.expand(); + await this.subject.fillInFilter("hữ"); + + assert.ok(this.subject.rows().length, 1); + assert.equal(this.subject.rowByIndex(0).name(), "chữ Quốc ngữ"); + }, +}); diff --git a/test/javascripts/components/select-kit/category-drop-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/category-drop-test.js similarity index 98% rename from test/javascripts/components/select-kit/category-drop-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/category-drop-test.js index 914f68d155..c52cbe3850 100644 --- a/test/javascripts/components/select-kit/category-drop-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/category-drop-test.js @@ -1,8 +1,8 @@ import I18n from "I18n"; import DiscourseURL from "discourse/lib/url"; import Category from "discourse/models/category"; -import componentTest from "helpers/component-test"; -import { testSelectKitModule } from "helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; +import { testSelectKitModule } from "discourse/tests/helpers/select-kit-helper"; import { NO_CATEGORIES_ID, ALL_CATEGORIES_ID, diff --git a/test/javascripts/components/select-kit/combo-box-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/combo-box-test.js similarity index 91% rename from test/javascripts/components/select-kit/combo-box-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/combo-box-test.js index 7ceb8599ac..ccbd9f29b1 100644 --- a/test/javascripts/components/select-kit/combo-box-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/combo-box-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("select-kit/combo-box", { integration: true, diff --git a/test/javascripts/components/select-kit/dropdown-select-box-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/dropdown-select-box-test.js similarity index 92% rename from test/javascripts/components/select-kit/dropdown-select-box-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/dropdown-select-box-test.js index 4819533d13..817be1aadc 100644 --- a/test/javascripts/components/select-kit/dropdown-select-box-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/dropdown-select-box-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("select-kit/dropdown-select-box", { integration: true, diff --git a/test/javascripts/components/select-kit/list-setting-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/list-setting-test.js similarity index 82% rename from test/javascripts/components/select-kit/list-setting-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/list-setting-test.js index b1f0571624..a702859684 100644 --- a/test/javascripts/components/select-kit/list-setting-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/list-setting-test.js @@ -1,5 +1,5 @@ -import componentTest from "helpers/component-test"; -import { testSelectKitModule } from "helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; +import { testSelectKitModule } from "discourse/tests/helpers/select-kit-helper"; testSelectKitModule("list-setting"); diff --git a/test/javascripts/components/select-kit/mini-tag-chooser-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/mini-tag-chooser-test.js similarity index 91% rename from test/javascripts/components/select-kit/mini-tag-chooser-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/mini-tag-chooser-test.js index 24ecb7c5d0..8c54e0cd5d 100644 --- a/test/javascripts/components/select-kit/mini-tag-chooser-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/mini-tag-chooser-test.js @@ -1,6 +1,6 @@ import I18n from "I18n"; -import componentTest from "helpers/component-test"; -import { testSelectKitModule } from "helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; +import { testSelectKitModule } from "discourse/tests/helpers/select-kit-helper"; testSelectKitModule("mini-tag-chooser"); diff --git a/test/javascripts/components/select-kit/multi-select-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/multi-select-test.js similarity index 88% rename from test/javascripts/components/select-kit/multi-select-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/multi-select-test.js index f501fa3a19..1f97c1fdec 100644 --- a/test/javascripts/components/select-kit/multi-select-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/multi-select-test.js @@ -1,5 +1,5 @@ -import componentTest from "helpers/component-test"; -import { testSelectKitModule } from "helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; +import { testSelectKitModule } from "discourse/tests/helpers/select-kit-helper"; testSelectKitModule("multi-select"); diff --git a/test/javascripts/components/select-kit/notifications-button-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/notifications-button-test.js similarity index 87% rename from test/javascripts/components/select-kit/notifications-button-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/notifications-button-test.js index 50c168d19b..463a75ff38 100644 --- a/test/javascripts/components/select-kit/notifications-button-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/notifications-button-test.js @@ -1,8 +1,8 @@ -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; import { testSelectKitModule, setDefaultState, -} from "helpers/select-kit-helper"; +} from "discourse/tests/helpers/select-kit-helper"; testSelectKitModule("notifications-button"); diff --git a/test/javascripts/components/select-kit/pinned-options-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/pinned-options-test.js similarity index 87% rename from test/javascripts/components/select-kit/pinned-options-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/pinned-options-test.js index e5503c4307..32d24a0f6d 100644 --- a/test/javascripts/components/select-kit/pinned-options-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/pinned-options-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; import Topic from "discourse/models/topic"; const buildTopic = function (pinned = true) { diff --git a/test/javascripts/components/select-kit/single-select-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/single-select-test.js similarity index 97% rename from test/javascripts/components/select-kit/single-select-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/single-select-test.js index 7e81a1f74b..e7473b0c1d 100644 --- a/test/javascripts/components/select-kit/single-select-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/single-select-test.js @@ -1,6 +1,6 @@ import I18n from "I18n"; -import componentTest from "helpers/component-test"; -import { testSelectKitModule } from "helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; +import { testSelectKitModule } from "discourse/tests/helpers/select-kit-helper"; testSelectKitModule("single-select"); diff --git a/test/javascripts/components/select-kit/tag-drop-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/tag-drop-test.js similarity index 90% rename from test/javascripts/components/select-kit/tag-drop-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/tag-drop-test.js index 65f7702436..be6223d831 100644 --- a/test/javascripts/components/select-kit/tag-drop-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/tag-drop-test.js @@ -1,9 +1,9 @@ import I18n from "I18n"; -import componentTest from "helpers/component-test"; -import { testSelectKitModule } from "helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; +import { testSelectKitModule } from "discourse/tests/helpers/select-kit-helper"; import Site from "discourse/models/site"; import { set } from "@ember/object"; -import pretender from "helpers/create-pretender"; +import pretender from "discourse/tests/helpers/create-pretender"; testSelectKitModule("tag-drop", { beforeEach() { diff --git a/test/javascripts/components/select-kit/topic-notifications-button-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-button-test.js similarity index 89% rename from test/javascripts/components/select-kit/topic-notifications-button-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-button-test.js index 16567159b8..6656bda882 100644 --- a/test/javascripts/components/select-kit/topic-notifications-button-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-button-test.js @@ -1,6 +1,7 @@ +import { moduleForComponent } from "ember-qunit"; import I18n from "I18n"; -import selectKit from "helpers/select-kit-helper"; -import componentTest from "helpers/component-test"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; import Topic from "discourse/models/topic"; const buildTopic = function (level, archetype = "regular") { diff --git a/test/javascripts/components/select-kit/topic-notifications-options-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-options-test.js similarity index 91% rename from test/javascripts/components/select-kit/topic-notifications-options-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-options-test.js index fb30a393db..9a072ebaec 100644 --- a/test/javascripts/components/select-kit/topic-notifications-options-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-options-test.js @@ -1,6 +1,7 @@ +import { moduleForComponent } from "ember-qunit"; import I18n from "I18n"; -import selectKit from "helpers/select-kit-helper"; -import componentTest from "helpers/component-test"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; import Topic from "discourse/models/topic"; const buildTopic = function (archetype) { diff --git a/test/javascripts/components/select-kit/user-chooser-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/user-chooser-test.js similarity index 79% rename from test/javascripts/components/select-kit/user-chooser-test.js rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/user-chooser-test.js index eed4e3b659..c3d006fa3b 100644 --- a/test/javascripts/components/select-kit/user-chooser-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/user-chooser-test.js @@ -1,5 +1,5 @@ -import componentTest from "helpers/component-test"; -import { testSelectKitModule } from "helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; +import { testSelectKitModule } from "discourse/tests/helpers/select-kit-helper"; testSelectKitModule("user-chooser"); diff --git a/test/javascripts/components/share-button-test.js b/app/assets/javascripts/discourse/tests/integration/components/share-button-test.js similarity index 76% rename from test/javascripts/components/share-button-test.js rename to app/assets/javascripts/discourse/tests/integration/components/share-button-test.js index e8bac153d7..9a137ff183 100644 --- a/test/javascripts/components/share-button-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/share-button-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("share-button", { integration: true }); diff --git a/test/javascripts/components/share-button.js b/app/assets/javascripts/discourse/tests/integration/components/share-button.js similarity index 100% rename from test/javascripts/components/share-button.js rename to app/assets/javascripts/discourse/tests/integration/components/share-button.js diff --git a/test/javascripts/components/simple-list-test.js b/app/assets/javascripts/discourse/tests/integration/components/simple-list-test.js similarity index 94% rename from test/javascripts/components/simple-list-test.js rename to app/assets/javascripts/discourse/tests/integration/components/simple-list-test.js index 4150381898..c66c8d7c5c 100644 --- a/test/javascripts/components/simple-list-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/simple-list-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("simple-list", { integration: true }); componentTest("adding a value", { diff --git a/test/javascripts/components/text-field-test.js b/app/assets/javascripts/discourse/tests/integration/components/text-field-test.js similarity index 95% rename from test/javascripts/components/text-field-test.js rename to app/assets/javascripts/discourse/tests/integration/components/text-field-test.js index 11d43bd211..21eb8d00c6 100644 --- a/test/javascripts/components/text-field-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/text-field-test.js @@ -1,5 +1,6 @@ +import { moduleForComponent } from "ember-qunit"; import I18n from "I18n"; -import componentTest from "helpers/component-test"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("text-field", { integration: true }); diff --git a/test/javascripts/components/time-input-test.js b/app/assets/javascripts/discourse/tests/integration/components/time-input-test.js similarity index 86% rename from test/javascripts/components/time-input-test.js rename to app/assets/javascripts/discourse/tests/integration/components/time-input-test.js index eed1195b41..6773b1b8e8 100644 --- a/test/javascripts/components/time-input-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/time-input-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("time-input", { integration: true, diff --git a/test/javascripts/components/user-selector-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-selector-test.js similarity index 93% rename from test/javascripts/components/user-selector-test.js rename to app/assets/javascripts/discourse/tests/integration/components/user-selector-test.js index d09816e3cf..4e54429693 100644 --- a/test/javascripts/components/user-selector-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/user-selector-test.js @@ -1,4 +1,5 @@ -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("user-selector", { integration: true }); diff --git a/test/javascripts/components/value-list-test.js b/app/assets/javascripts/discourse/tests/integration/components/value-list-test.js similarity index 94% rename from test/javascripts/components/value-list-test.js rename to app/assets/javascripts/discourse/tests/integration/components/value-list-test.js index e0769901da..e1708c5865 100644 --- a/test/javascripts/components/value-list-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/value-list-test.js @@ -1,5 +1,6 @@ -import selectKit from "helpers/select-kit-helper"; -import componentTest from "helpers/component-test"; +import { moduleForComponent } from "ember-qunit"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import componentTest from "discourse/tests/helpers/component-test"; moduleForComponent("value-list", { integration: true }); componentTest("adding a value", { diff --git a/test/javascripts/widgets/actions-summary-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/actions-summary-test.js similarity index 86% rename from test/javascripts/widgets/actions-summary-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/actions-summary-test.js index 038ab9ac37..9a48443590 100644 --- a/test/javascripts/widgets/actions-summary-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/actions-summary-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("actions-summary"); diff --git a/test/javascripts/widgets/avatar-flair-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/avatar-flair-test.js similarity index 91% rename from test/javascripts/widgets/avatar-flair-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/avatar-flair-test.js index 99e1556b64..7d435a63a3 100644 --- a/test/javascripts/widgets/avatar-flair-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/avatar-flair-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("avatar-flair"); diff --git a/test/javascripts/widgets/button-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/button-test.js similarity index 93% rename from test/javascripts/widgets/button-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/button-test.js index 51574c5e52..b0bcab35e1 100644 --- a/test/javascripts/widgets/button-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/button-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("button"); diff --git a/test/javascripts/widgets/default-notification-item-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/default-notification-item-test.js similarity index 90% rename from test/javascripts/widgets/default-notification-item-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/default-notification-item-test.js index 1c0c4fa364..993785323a 100644 --- a/test/javascripts/widgets/default-notification-item-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/default-notification-item-test.js @@ -1,6 +1,9 @@ import EmberObject from "@ember/object"; -import pretender from "helpers/create-pretender"; -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import pretender from "discourse/tests/helpers/create-pretender"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("default-notification-item"); diff --git a/test/javascripts/widgets/hamburger-menu-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/hamburger-menu-test.js similarity index 98% rename from test/javascripts/widgets/hamburger-menu-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/hamburger-menu-test.js index 661b8e662a..b4ad25bdc6 100644 --- a/test/javascripts/widgets/hamburger-menu-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/hamburger-menu-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; import { NotificationLevels } from "discourse/lib/notification-levels"; moduleForWidget("hamburger-menu"); diff --git a/test/javascripts/widgets/header-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/header-test.js similarity index 96% rename from test/javascripts/widgets/header-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/header-test.js index ddd699ef10..14350d12e6 100644 --- a/test/javascripts/widgets/header-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/header-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("header"); diff --git a/test/javascripts/widgets/home-logo-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/home-logo-test.js similarity index 98% rename from test/javascripts/widgets/home-logo-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/home-logo-test.js index d12a376c6f..021057bd3b 100644 --- a/test/javascripts/widgets/home-logo-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/home-logo-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; import Session from "discourse/models/session"; moduleForWidget("home-logo"); diff --git a/test/javascripts/widgets/post-links-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/post-links-test.js similarity index 94% rename from test/javascripts/widgets/post-links-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/post-links-test.js index 1eea33c997..1062a74dcd 100644 --- a/test/javascripts/widgets/post-links-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/post-links-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("post-links"); diff --git a/test/javascripts/widgets/post-menu-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/post-menu-test.js similarity index 92% rename from test/javascripts/widgets/post-menu-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/post-menu-test.js index f95776f1b1..c20c8d251a 100644 --- a/test/javascripts/widgets/post-menu-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/post-menu-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; import { withPluginApi } from "discourse/lib/plugin-api"; moduleForWidget("post-menu"); diff --git a/test/javascripts/widgets/post-stream-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/post-stream-test.js similarity index 97% rename from test/javascripts/widgets/post-stream-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/post-stream-test.js index c9b58e98af..daff6490ea 100644 --- a/test/javascripts/widgets/post-stream-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/post-stream-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; import Topic from "discourse/models/topic"; import Post from "discourse/models/post"; diff --git a/test/javascripts/widgets/post-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/post-test.js similarity index 99% rename from test/javascripts/widgets/post-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/post-test.js index 802b5e76ce..1e888b0e28 100644 --- a/test/javascripts/widgets/post-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/post-test.js @@ -1,6 +1,9 @@ import I18n from "I18n"; import EmberObject from "@ember/object"; -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("post"); diff --git a/test/javascripts/widgets/poster-name-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/poster-name-test.js similarity index 95% rename from test/javascripts/widgets/poster-name-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/poster-name-test.js index 8f308f409d..30a72e7c2c 100644 --- a/test/javascripts/widgets/poster-name-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/poster-name-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("poster-name"); diff --git a/test/javascripts/widgets/quick-access-item-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/quick-access-item-test.js similarity index 89% rename from test/javascripts/widgets/quick-access-item-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/quick-access-item-test.js index 5101d2a481..5300656f59 100644 --- a/test/javascripts/widgets/quick-access-item-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/quick-access-item-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("quick-access-item"); diff --git a/test/javascripts/widgets/small-user-list-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/small-user-list-test.js similarity index 86% rename from test/javascripts/widgets/small-user-list-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/small-user-list-test.js index 2e6ef1ea1f..f955df662d 100644 --- a/test/javascripts/widgets/small-user-list-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/small-user-list-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("small-user-list"); diff --git a/test/javascripts/widgets/topic-admin-menu-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/topic-admin-menu-test.js similarity index 95% rename from test/javascripts/widgets/topic-admin-menu-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/topic-admin-menu-test.js index 3351e3ee1a..07b6f21d64 100644 --- a/test/javascripts/widgets/topic-admin-menu-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/topic-admin-menu-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; import Topic from "discourse/models/topic"; import Category from "discourse/models/category"; diff --git a/test/javascripts/widgets/topic-participant-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/topic-participant-test.js similarity index 93% rename from test/javascripts/widgets/topic-participant-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/topic-participant-test.js index 4aafd112bf..51ae5d2ac0 100644 --- a/test/javascripts/widgets/topic-participant-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/topic-participant-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("topic-participant"); diff --git a/test/javascripts/widgets/topic-status-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/topic-status-test.js similarity index 91% rename from test/javascripts/widgets/topic-status-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/topic-status-test.js index d9a677dda4..31c7786cc1 100644 --- a/test/javascripts/widgets/topic-status-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/topic-status-test.js @@ -1,4 +1,7 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; import TopicStatusIcons from "discourse/helpers/topic-status-icons"; moduleForWidget("topic-status"); diff --git a/test/javascripts/widgets/user-menu-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/user-menu-test.js similarity index 98% rename from test/javascripts/widgets/user-menu-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/user-menu-test.js index 3a286dd838..601be8a455 100644 --- a/test/javascripts/widgets/user-menu-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/user-menu-test.js @@ -1,6 +1,9 @@ import I18n from "I18n"; import DiscourseURL from "discourse/lib/url"; -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("user-menu"); diff --git a/test/javascripts/widgets/widget-dropdown-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/widget-dropdown-test.js similarity index 98% rename from test/javascripts/widgets/widget-dropdown-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/widget-dropdown-test.js index 09796d5dc8..f3d58c3767 100644 --- a/test/javascripts/widgets/widget-dropdown-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/widget-dropdown-test.js @@ -1,5 +1,8 @@ import I18n from "I18n"; -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; moduleForWidget("widget-dropdown"); diff --git a/test/javascripts/widgets/widget-test.js b/app/assets/javascripts/discourse/tests/integration/widgets/widget-test.js similarity index 99% rename from test/javascripts/widgets/widget-test.js rename to app/assets/javascripts/discourse/tests/integration/widgets/widget-test.js index 01ff5f5796..1d807bfdbf 100644 --- a/test/javascripts/widgets/widget-test.js +++ b/app/assets/javascripts/discourse/tests/integration/widgets/widget-test.js @@ -1,6 +1,9 @@ import I18n from "I18n"; import { next } from "@ember/runloop"; -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; import { createWidget } from "discourse/widgets/widget"; import { withPluginApi } from "discourse/lib/plugin-api"; import { Promise } from "rsvp"; diff --git a/test/javascripts/plugin_tests.js.erb b/app/assets/javascripts/discourse/tests/plugin_tests.js.erb similarity index 100% rename from test/javascripts/plugin_tests.js.erb rename to app/assets/javascripts/discourse/tests/plugin_tests.js.erb diff --git a/app/assets/javascripts/discourse/tests/setup-tests.js b/app/assets/javascripts/discourse/tests/setup-tests.js new file mode 100644 index 0000000000..8f8f689d2c --- /dev/null +++ b/app/assets/javascripts/discourse/tests/setup-tests.js @@ -0,0 +1,191 @@ +import { + resetSettings, + currentSettings, +} from "discourse/tests/helpers/site-settings"; +import { getOwner, setDefaultOwner } from "discourse-common/lib/get-owner"; +import { setupURL, setupS3CDN } from "discourse-common/lib/get-url"; +import { createHelperContext } from "discourse-common/lib/helpers"; +import { buildResolver } from "discourse-common/resolver"; +import createPretender, { + pretenderHelpers, + applyDefaultHandlers, +} from "discourse/tests/helpers/create-pretender"; +import { flushMap } from "discourse/models/store"; +import { ScrollingDOMMethods } from "discourse/mixins/scrolling"; +import DiscourseURL from "discourse/lib/url"; +import { + resetSite, + applyPretender, +} from "discourse/tests/helpers/qunit-helpers"; +import PreloadStore from "discourse/lib/preload-store"; +import User from "discourse/models/user"; +import Session from "discourse/models/session"; +import { clearAppEventsCache } from "discourse/services/app-events"; +import QUnit from "qunit"; +import MessageBus from "message-bus-client"; +import deprecated from "discourse-common/lib/deprecated"; +import sinon from "sinon"; +import { setResolver } from "@ember/test-helpers"; + +export default function setupTests(App) { + setResolver(buildResolver("discourse").create({ namespace: App })); + + sinon.config = { + injectIntoThis: false, + injectInto: null, + properties: ["spy", "stub", "mock", "clock", "sandbox"], + useFakeTimers: true, + useFakeServer: false, + }; + + // Stop the message bus so we don't get ajax calls + MessageBus.stop(); + + document.write( + '
' + ); + document.write( + "" + ); + + App.rootElement = "#ember-testing"; + App.setupForTesting(); + App.injectTestHelpers(); + App.SiteSettings = currentSettings(); + App.start(); + + // disable logster error reporting + if (window.Logster) { + window.Logster.enabled = false; + } else { + window.Logster = { enabled: false }; + } + + let server; + + Object.defineProperty(window, "server", { + get() { + deprecated( + "Accessing the global variable `server` is deprecated. Use a `pretend()` method instead.", + { + since: "2.6.0.beta.3", + dropFrom: "2.6.0", + } + ); + return server; + }, + }); + + QUnit.testStart(function (ctx) { + let settings = resetSettings(); + server = createPretender; + applyDefaultHandlers(server); + server.handlers = []; + + server.prepareBody = function (body) { + if (body && typeof body === "object") { + return JSON.stringify(body); + } + return body; + }; + + if (QUnit.config.logAllRequests) { + server.handledRequest = function (verb, path) { + // eslint-disable-next-line no-console + console.log("REQ: " + verb + " " + path); + }; + } + + server.unhandledRequest = function (verb, path) { + if (QUnit.config.logAllRequests) { + // eslint-disable-next-line no-console + console.log("REQ: " + verb + " " + path + " missing"); + } + + const error = + "Unhandled request in test environment: " + path + " (" + verb + ")"; + + window.console.error(error); + throw new Error(error); + }; + + server.checkPassthrough = (request) => + request.requestHeaders["Discourse-Script"]; + + applyPretender(ctx.module, server, pretenderHelpers()); + + setupURL(null, "http://localhost:3000", ""); + setupS3CDN(null, null); + + Session.resetCurrent(); + User.resetCurrent(); + let site = resetSite(settings); + createHelperContext({ + siteSettings: settings, + capabilities: {}, + site, + }); + + DiscourseURL.redirectedTo = null; + DiscourseURL.redirectTo = function (url) { + DiscourseURL.redirectedTo = url; + }; + + PreloadStore.reset(); + + window.sandbox = sinon; + window.sandbox.stub(ScrollingDOMMethods, "screenNotFull"); + window.sandbox.stub(ScrollingDOMMethods, "bindOnScroll"); + window.sandbox.stub(ScrollingDOMMethods, "unbindOnScroll"); + + // Unless we ever need to test this, let's leave it off. + $.fn.autocomplete = function () {}; + }); + + QUnit.testDone(function () { + window.sandbox.restore(); + + // Destroy any modals + $(".modal-backdrop").remove(); + flushMap(); + + // ensures any event not removed is not leaking between tests + // most likely in intialisers, other places (controller, component...) + // should be fixed in code + clearAppEventsCache(getOwner(this)); + + MessageBus.unsubscribe("*"); + server = null; + window.Mousetrap.reset(); + }); + + // Load ES6 tests + function getUrlParameter(name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"); + var results = regex.exec(location.search); + return results === null + ? "" + : decodeURIComponent(results[1].replace(/\+/g, " ")); + } + + let skipCore = getUrlParameter("qunit_skip_core") === "1"; + let pluginPath = getUrlParameter("qunit_single_plugin") + ? "/" + getUrlParameter("qunit_single_plugin") + "/" + : "/plugins/"; + + Object.keys(requirejs.entries).forEach(function (entry) { + let isTest = /\-test/.test(entry); + let regex = new RegExp(pluginPath); + let isPlugin = regex.test(entry); + + if (isTest && (!skipCore || isPlugin)) { + require(entry, null, null, true); + } + }); + + // forces 0 as duration for all jquery animations + jQuery.fx.off = true; + setDefaultOwner(App.__container__); + resetSite(); +} diff --git a/app/assets/javascripts/discourse/tests/test_helper.js b/app/assets/javascripts/discourse/tests/test_helper.js new file mode 100644 index 0000000000..d84c9b15b6 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/test_helper.js @@ -0,0 +1,47 @@ +// discourse-skip-module + +//= require env +//= require jquery.debug +//= require jquery.ui.widget +//= require ember.debug +//= require message-bus +//= require qunit/qunit/qunit +//= require ember-qunit +//= require fake_xml_http_request +//= require route-recognizer +//= require pretender/pretender +//= require locales/i18n +//= require locales/en_US +//= require discourse-loader + +// Our base application +//= require vendor +//= require discourse-shims +//= require pretty-text-bundle +//= require markdown-it-bundle +//= require application +//= require admin + +// These are not loaded in prod or development +// But we need them for testing handlebars templates in qunit +//= require handlebars +//= require ember-template-compiler + +// Test helpers +//= require sinon/pkg/sinon +//= require_tree ./helpers +//= require break_string + +// Finally, the tests themselves +//= require_tree ./fixtures +//= require_tree ./acceptance +//= require_tree ./integration +//= require_tree ./unit +//= require_tree ../../admin/tests/admin +//= require plugin_tests +//= require setup-tests +//= require test-shims +//= require jquery.magnific-popup.min.js + +let setupTests = require("discourse/tests/setup-tests").default; +setupTests(window.Discourse); diff --git a/test/javascripts/controllers/avatar-selector-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/avatar-selector-test.js similarity index 53% rename from test/javascripts/controllers/avatar-selector-test.js rename to app/assets/javascripts/discourse/tests/unit/controllers/avatar-selector-test.js index c6e2301f4d..aff8c19d28 100644 --- a/test/javascripts/controllers/avatar-selector-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/avatar-selector-test.js @@ -1,4 +1,7 @@ +import EmberObject from "@ember/object"; import { mapRoutes } from "discourse/mapping-router"; +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; moduleFor("controller:avatar-selector", "controller:avatar-selector", { beforeEach() { @@ -7,32 +10,36 @@ moduleFor("controller:avatar-selector", "controller:avatar-selector", { needs: ["controller:modal"], }); -QUnit.test("avatarTemplate", function (assert) { +test("avatarTemplate", function (assert) { const avatarSelectorController = this.subject(); - avatarSelectorController.setProperties({ - selected: "system", - user: { - system_avatar_upload_id: 1, - gravatar_avatar_upload_id: 2, - custom_avatar_upload_id: 3, - }, + const user = EmberObject.create({ + avatar_template: "avatar", + system_avatar_template: "system", + gravatar_avatar_template: "gravatar", + + system_avatar_upload_id: 1, + gravatar_avatar_upload_id: 2, + custom_avatar_upload_id: 3, }); + avatarSelectorController.setProperties({ user }); + + user.set("avatar_template", "system"); assert.equal( avatarSelectorController.get("selectedUploadId"), 1, "we are using system by default" ); - avatarSelectorController.set("selected", "gravatar"); + user.set("avatar_template", "gravatar"); assert.equal( avatarSelectorController.get("selectedUploadId"), 2, "we are using gravatar when set" ); - avatarSelectorController.set("selected", "custom"); + user.set("avatar_template", "avatar"); assert.equal( avatarSelectorController.get("selectedUploadId"), 3, diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/bookmark-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/bookmark-test.js new file mode 100644 index 0000000000..2c0ff30c0c --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/controllers/bookmark-test.js @@ -0,0 +1,254 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; +import { logIn } from "discourse/tests/helpers/qunit-helpers"; +import User from "discourse/models/user"; +import KeyboardShortcutInitializer from "discourse/initializers/keyboard-shortcuts"; +import { REMINDER_TYPES } from "discourse/lib/bookmark"; +import { fakeTime } from "discourse/tests/helpers/qunit-helpers"; + +let BookmarkController; + +moduleFor("controller:bookmark", { + beforeEach() { + logIn(); + KeyboardShortcutInitializer.initialize(this.container); + BookmarkController = this.subject({ + currentUser: User.current(), + site: { isMobileDevice: false }, + }); + BookmarkController.onShow(); + }, + + afterEach() { + sandbox.restore(); + }, +}); + +function mockMomentTz(dateString) { + fakeTime(dateString, BookmarkController.userTimezone); +} + +test("showLaterToday when later today is tomorrow do not show", function (assert) { + mockMomentTz("2019-12-11T22:00:00"); + + assert.equal(BookmarkController.get("showLaterToday"), false); +}); + +test("showLaterToday when later today is after 5pm but before 6pm", function (assert) { + mockMomentTz("2019-12-11T15:00:00"); + assert.equal(BookmarkController.get("showLaterToday"), true); +}); + +test("showLaterToday when now is after the cutoff time (5pm)", function (assert) { + mockMomentTz("2019-12-11T17:00:00"); + assert.equal(BookmarkController.get("showLaterToday"), false); +}); + +test("showLaterToday when later today is before the end of the day, show", function (assert) { + mockMomentTz("2019-12-11T10:00:00"); + + assert.equal(BookmarkController.get("showLaterToday"), true); +}); + +test("nextWeek gets next week correctly", function (assert) { + mockMomentTz("2019-12-11T08:00:00"); + + assert.equal( + BookmarkController.nextWeek().format("YYYY-MM-DD"), + "2019-12-18" + ); +}); + +test("nextMonth gets next month correctly", function (assert) { + mockMomentTz("2019-12-11T08:00:00"); + + assert.equal( + BookmarkController.nextMonth().format("YYYY-MM-DD"), + "2020-01-11" + ); +}); + +test("laterThisWeek gets 2 days from now", function (assert) { + mockMomentTz("2019-12-10T08:00:00"); + + assert.equal( + BookmarkController.laterThisWeek().format("YYYY-MM-DD"), + "2019-12-12" + ); +}); + +test("laterThisWeek returns null if we are at Thursday already", function (assert) { + mockMomentTz("2019-12-12T08:00:00"); + + assert.equal(BookmarkController.laterThisWeek(), null); +}); + +test("showLaterThisWeek returns true if < Thursday", function (assert) { + mockMomentTz("2019-12-10T08:00:00"); + + assert.equal(BookmarkController.showLaterThisWeek, true); +}); + +test("showLaterThisWeek returns false if > Thursday", function (assert) { + mockMomentTz("2019-12-12T08:00:00"); + + assert.equal(BookmarkController.showLaterThisWeek, false); +}); +test("tomorrow gets tomorrow correctly", function (assert) { + mockMomentTz("2019-12-11T08:00:00"); + + assert.equal( + BookmarkController.tomorrow().format("YYYY-MM-DD"), + "2019-12-12" + ); +}); + +test("startOfDay changes the time of the provided date to 8:00am correctly", function (assert) { + let dt = moment.tz( + "2019-12-11T11:37:16", + BookmarkController.currentUser.resolvedTimezone( + BookmarkController.currentUser + ) + ); + + assert.equal( + BookmarkController.startOfDay(dt).format("YYYY-MM-DD HH:mm:ss"), + "2019-12-11 08:00:00" + ); +}); + +test("laterToday gets 3 hours from now and if before half-past, it rounds down", function (assert) { + mockMomentTz("2019-12-11T08:13:00"); + + assert.equal( + BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"), + "2019-12-11 11:00:00" + ); +}); + +test("laterToday gets 3 hours from now and if after half-past, it rounds up to the next hour", function (assert) { + mockMomentTz("2019-12-11T08:43:00"); + + assert.equal( + BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"), + "2019-12-11 12:00:00" + ); +}); + +test("laterToday is capped to 6pm. later today at 3pm = 6pm, 3:30pm = 6pm, 4pm = 6pm, 4:59pm = 6pm", function (assert) { + mockMomentTz("2019-12-11T15:00:00"); + + assert.equal( + BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"), + "2019-12-11 18:00:00", + "3pm should max to 6pm" + ); + + mockMomentTz("2019-12-11T15:31:00"); + + assert.equal( + BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"), + "2019-12-11 18:00:00", + "3:30pm should max to 6pm" + ); + + mockMomentTz("2019-12-11T16:00:00"); + + assert.equal( + BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"), + "2019-12-11 18:00:00", + "4pm should max to 6pm" + ); + + mockMomentTz("2019-12-11T16:59:00"); + + assert.equal( + BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"), + "2019-12-11 18:00:00", + "4:59pm should max to 6pm" + ); +}); + +test("showLaterToday returns false if >= 5PM", function (assert) { + mockMomentTz("2019-12-11T17:00:01"); + assert.equal(BookmarkController.showLaterToday, false); +}); + +test("showLaterToday returns false if >= 5PM", function (assert) { + mockMomentTz("2019-12-11T17:00:01"); + assert.equal(BookmarkController.showLaterToday, false); +}); + +test("reminderAt - custom - defaults to 8:00am if the time is not selected", function (assert) { + BookmarkController.customReminderDate = "2028-12-12"; + BookmarkController.selectedReminderType = + BookmarkController.reminderTypes.CUSTOM; + const reminderAt = BookmarkController._reminderAt(); + assert.equal(BookmarkController.customReminderTime, "08:00"); + assert.equal( + reminderAt.toString(), + moment + .tz( + "2028-12-12 08:00", + BookmarkController.currentUser.resolvedTimezone( + BookmarkController.currentUser + ) + ) + .toString(), + "the custom date and time are parsed correctly with default time" + ); +}); + +test("loadLastUsedCustomReminderDatetime fills the custom reminder date + time if present in localStorage", function (assert) { + mockMomentTz("2019-12-11T08:00:00"); + localStorage.lastCustomBookmarkReminderDate = "2019-12-12"; + localStorage.lastCustomBookmarkReminderTime = "08:00"; + + BookmarkController._loadLastUsedCustomReminderDatetime(); + + assert.equal(BookmarkController.lastCustomReminderDate, "2019-12-12"); + assert.equal(BookmarkController.lastCustomReminderTime, "08:00"); +}); + +test("loadLastUsedCustomReminderDatetime does not fills the custom reminder date + time if the datetime in localStorage is < now", function (assert) { + mockMomentTz("2019-12-11T08:00:00"); + localStorage.lastCustomBookmarkReminderDate = "2019-12-11"; + localStorage.lastCustomBookmarkReminderTime = "07:00"; + + BookmarkController._loadLastUsedCustomReminderDatetime(); + + assert.equal(BookmarkController.lastCustomReminderDate, null); + assert.equal(BookmarkController.lastCustomReminderTime, null); +}); + +test("user timezone updates when the modal is shown", function (assert) { + User.current().changeTimezone(null); + let stub = sandbox.stub(moment.tz, "guess").returns("Europe/Moscow"); + BookmarkController.onShow(); + assert.equal(BookmarkController.userHasTimezoneSet, true); + assert.equal( + BookmarkController.userTimezone, + "Europe/Moscow", + "the user does not have their timezone set and a timezone is guessed" + ); + User.current().changeTimezone("Australia/Brisbane"); + BookmarkController.onShow(); + assert.equal(BookmarkController.userHasTimezoneSet, true); + assert.equal( + BookmarkController.userTimezone, + "Australia/Brisbane", + "the user does their timezone set" + ); + stub.restore(); +}); + +test("opening the modal with an existing bookmark with reminder at prefills the custom reminder type", function (assert) { + let name = "test"; + let reminderAt = "2020-05-15T09:45:00"; + BookmarkController.model = { id: 1, name: name, reminderAt: reminderAt }; + BookmarkController.onShow(); + assert.equal(BookmarkController.selectedReminderType, REMINDER_TYPES.CUSTOM); + assert.equal(BookmarkController.customReminderDate, "2020-05-15"); + assert.equal(BookmarkController.customReminderTime, "09:45"); + assert.equal(BookmarkController.model.name, name); +}); diff --git a/test/javascripts/controllers/create-account-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/create-account-test.js similarity index 96% rename from test/javascripts/controllers/create-account-test.js rename to app/assets/javascripts/discourse/tests/unit/controllers/create-account-test.js index 1ab7aebf3b..45f66b4e63 100644 --- a/test/javascripts/controllers/create-account-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/create-account-test.js @@ -1,5 +1,6 @@ +import { test } from "qunit"; import I18n from "I18n"; -import { controllerModule } from "helpers/qunit-helpers"; +import { controllerModule } from "discourse/tests/helpers/qunit-helpers"; controllerModule("controller:create-account", { needs: ["controller:modal", "controller:login"], diff --git a/test/javascripts/controllers/history-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/history-test.js similarity index 96% rename from test/javascripts/controllers/history-test.js rename to app/assets/javascripts/discourse/tests/unit/controllers/history-test.js index 20385f3638..5a04d41433 100644 --- a/test/javascripts/controllers/history-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/history-test.js @@ -1,6 +1,8 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; moduleFor("controller:history"); -QUnit.test("displayEdit", async function (assert) { +test("displayEdit", async function (assert) { const HistoryController = this.subject(); HistoryController.setProperties({ diff --git a/test/javascripts/controllers/preferences-account-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/preferences-account-test.js similarity index 86% rename from test/javascripts/controllers/preferences-account-test.js rename to app/assets/javascripts/discourse/tests/unit/controllers/preferences-account-test.js index e686c705e1..ce0b1016cd 100644 --- a/test/javascripts/controllers/preferences-account-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/preferences-account-test.js @@ -1,7 +1,9 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; import EmberObject from "@ember/object"; moduleFor("controller:preferences/account"); -QUnit.test("updating of associated accounts", function (assert) { +test("updating of associated accounts", function (assert) { const controller = this.subject({ siteSettings: { enable_google_oauth2_logins: true, diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/preferences-second-factor-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/preferences-second-factor-test.js new file mode 100644 index 0000000000..53e249d3ac --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/controllers/preferences-second-factor-test.js @@ -0,0 +1,13 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; +moduleFor("controller:preferences/second-factor"); + +test("displayOAuthWarning when OAuth login methods are enabled", function (assert) { + const controller = this.subject({ + siteSettings: { + enable_google_oauth2_logins: true, + }, + }); + + assert.equal(controller.get("displayOAuthWarning"), true); +}); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js new file mode 100644 index 0000000000..2c3eaa32cc --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js @@ -0,0 +1,205 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; +import EmberObject from "@ember/object"; +import { mapRoutes } from "discourse/mapping-router"; +import createStore from "discourse/tests/helpers/create-store"; + +moduleFor("controller:reorder-categories", "controller:reorder-categories", { + beforeEach() { + this.registry.register("router:main", mapRoutes()); + }, + needs: ["controller:modal"], +}); + +test("reorder set unique position number", function (assert) { + const store = createStore(); + + const categories = []; + for (let i = 0; i < 3; ++i) { + categories.push(store.createRecord("category", { id: i, position: 0 })); + } + + const site = EmberObject.create({ categories: categories }); + const reorderCategoriesController = this.subject({ site }); + + reorderCategoriesController.reorder(); + + reorderCategoriesController + .get("categoriesOrdered") + .forEach((category, index) => { + assert.equal(category.get("position"), index); + }); +}); + +test("reorder places subcategories after their parent categories, while maintaining the relative order", function (assert) { + const store = createStore(); + + const parent = store.createRecord("category", { + id: 1, + position: 1, + slug: "parent", + }); + const child1 = store.createRecord("category", { + id: 2, + position: 3, + slug: "child1", + parent_category_id: 1, + }); + const child2 = store.createRecord("category", { + id: 3, + position: 0, + slug: "child2", + parent_category_id: 1, + }); + const other = store.createRecord("category", { + id: 4, + position: 2, + slug: "other", + }); + + const categories = [child2, parent, other, child1]; + const expectedOrderSlugs = ["parent", "child2", "child1", "other"]; + + const site = EmberObject.create({ categories: categories }); + const reorderCategoriesController = this.subject({ site }); + + reorderCategoriesController.reorder(); + + assert.deepEqual( + reorderCategoriesController.get("categoriesOrdered").mapBy("slug"), + expectedOrderSlugs + ); +}); + +test("changing the position number of a category should place it at given position", function (assert) { + const store = createStore(); + + const elem1 = store.createRecord("category", { + id: 1, + position: 0, + slug: "foo", + }); + + const elem2 = store.createRecord("category", { + id: 2, + position: 1, + slug: "bar", + }); + + const elem3 = store.createRecord("category", { + id: 3, + position: 2, + slug: "test", + }); + + const categories = [elem1, elem2, elem3]; + const site = EmberObject.create({ categories: categories }); + const reorderCategoriesController = this.subject({ site }); + + reorderCategoriesController.actions.change.call( + reorderCategoriesController, + elem1, + { target: { value: "2" } } + ); + + assert.deepEqual( + reorderCategoriesController.get("categoriesOrdered").mapBy("slug"), + ["test", "bar", "foo"] + ); +}); + +test("changing the position number of a category should place it at given position and respect children", function (assert) { + const store = createStore(); + + const elem1 = store.createRecord("category", { + id: 1, + position: 0, + slug: "foo", + }); + + const child1 = store.createRecord("category", { + id: 4, + position: 1, + slug: "foochild", + parent_category_id: 1, + }); + + const elem2 = store.createRecord("category", { + id: 2, + position: 2, + slug: "bar", + }); + + const elem3 = store.createRecord("category", { + id: 3, + position: 3, + slug: "test", + }); + + const categories = [elem1, child1, elem2, elem3]; + const site = EmberObject.create({ categories: categories }); + const reorderCategoriesController = this.subject({ site }); + + reorderCategoriesController.actions.change.call( + reorderCategoriesController, + elem1, + { target: { value: 3 } } + ); + + assert.deepEqual( + reorderCategoriesController.get("categoriesOrdered").mapBy("slug"), + ["test", "bar", "foo", "foochild"] + ); +}); + +test("changing the position through click on arrow of a category should place it at given position and respect children", function (assert) { + const store = createStore(); + + const elem1 = store.createRecord("category", { + id: 1, + position: 0, + slug: "foo", + }); + + const child1 = store.createRecord("category", { + id: 4, + position: 1, + slug: "foochild", + parent_category_id: 1, + }); + + const child2 = store.createRecord("category", { + id: 5, + position: 2, + slug: "foochildchild", + parent_category_id: 4, + }); + + const elem2 = store.createRecord("category", { + id: 2, + position: 3, + slug: "bar", + }); + + const elem3 = store.createRecord("category", { + id: 3, + position: 4, + slug: "test", + }); + + const categories = [elem1, child1, child2, elem2, elem3]; + const site = EmberObject.create({ categories: categories }); + const reorderCategoriesController = this.subject({ site }); + + reorderCategoriesController.reorder(); + + reorderCategoriesController.actions.moveDown.call( + reorderCategoriesController, + elem1 + ); + + assert.deepEqual( + reorderCategoriesController.get("categoriesOrdered").mapBy("slug"), + ["bar", "foo", "foochild", "foochildchild", "test"] + ); +}); diff --git a/test/javascripts/controllers/topic-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js similarity index 88% rename from test/javascripts/controllers/topic-test.js rename to app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js index 197b6b11b0..d552ef1f7b 100644 --- a/test/javascripts/controllers/topic-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js @@ -1,10 +1,12 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; import EmberObject from "@ember/object"; import { next } from "@ember/runloop"; import Topic from "discourse/models/topic"; import { Placeholder } from "discourse/lib/posts-with-placeholders"; import User from "discourse/models/user"; import { Promise } from "rsvp"; -import pretender from "helpers/create-pretender"; +import pretender from "discourse/tests/helpers/create-pretender"; moduleFor("controller:topic", "controller:topic", { needs: [ @@ -25,7 +27,7 @@ function topicWithStream(streamDetails) { return topic; } -QUnit.test("editTopic", function (assert) { +test("editTopic", function (assert) { const model = Topic.create(); const controller = this.subject({ model }); @@ -60,7 +62,7 @@ QUnit.test("editTopic", function (assert) { ); }); -QUnit.test("toggleMultiSelect", function (assert) { +test("toggleMultiSelect", function (assert) { const model = Topic.create(); const controller = this.subject({ model }); @@ -100,7 +102,7 @@ QUnit.test("toggleMultiSelect", function (assert) { ); }); -QUnit.test("selectedPosts", function (assert) { +test("selectedPosts", function (assert) { let model = topicWithStream({ posts: [{ id: 1 }, { id: 2 }, { id: 3 }] }); const controller = this.subject({ model }); @@ -117,7 +119,7 @@ QUnit.test("selectedPosts", function (assert) { ); }); -QUnit.test("selectedAllPosts", function (assert) { +test("selectedAllPosts", function (assert) { let model = topicWithStream({ stream: [1, 2, 3] }); const controller = this.subject({ model }); @@ -147,7 +149,7 @@ QUnit.test("selectedAllPosts", function (assert) { ); }); -QUnit.test("selectedPostsUsername", function (assert) { +test("selectedPostsUsername", function (assert) { let model = topicWithStream({ posts: [ { id: 1, username: "gary" }, @@ -198,7 +200,7 @@ QUnit.test("selectedPostsUsername", function (assert) { ); }); -QUnit.test("showSelectedPostsAtBottom", function (assert) { +test("showSelectedPostsAtBottom", function (assert) { const site = EmberObject.create({ mobileView: false }); const model = Topic.create({ posts_count: 3 }); const controller = this.subject({ model, site }); @@ -220,7 +222,7 @@ QUnit.test("showSelectedPostsAtBottom", function (assert) { ); }); -QUnit.test("canDeleteSelected", function (assert) { +test("canDeleteSelected", function (assert) { const currentUser = User.create({ admin: false }); this.registry.register("current-user:main", currentUser, { instantiate: false, @@ -271,7 +273,7 @@ QUnit.test("canDeleteSelected", function (assert) { ); }); -QUnit.test("Can split/merge topic", function (assert) { +test("Can split/merge topic", function (assert) { let model = topicWithStream({ posts: [ { id: 1, post_number: 1, post_type: 1 }, @@ -316,7 +318,7 @@ QUnit.test("Can split/merge topic", function (assert) { ); }); -QUnit.test("canChangeOwner", function (assert) { +test("canChangeOwner", function (assert) { const currentUser = User.create({ admin: false }); this.registry.register("current-user:main", currentUser, { instantiate: false, @@ -358,7 +360,7 @@ QUnit.test("canChangeOwner", function (assert) { ); }); -QUnit.test("canMergePosts", function (assert) { +test("canMergePosts", function (assert) { let model = topicWithStream({ posts: [ { id: 1, username: "gary", can_delete: true }, @@ -405,7 +407,7 @@ QUnit.test("canMergePosts", function (assert) { ); }); -QUnit.test("Select/deselect all", function (assert) { +test("Select/deselect all", function (assert) { let model = topicWithStream({ stream: [1, 2, 3] }); const controller = this.subject({ model }); @@ -432,7 +434,7 @@ QUnit.test("Select/deselect all", function (assert) { ); }); -QUnit.test("togglePostSelection", function (assert) { +test("togglePostSelection", function (assert) { const controller = this.subject(); const selectedPostIds = controller.get("selectedPostIds"); @@ -455,7 +457,7 @@ QUnit.test("togglePostSelection", function (assert) { ); }); -// QUnit.test("selectReplies", function(assert) { +// test("selectReplies", function(assert) { // const controller = this.subject(); // const selectedPostIds = controller.get("selectedPostIds"); // @@ -468,7 +470,7 @@ QUnit.test("togglePostSelection", function (assert) { // assert.equal(selectedPostIds[2], 100, "selected post #100"); // }); -QUnit.test("selectBelow", function (assert) { +test("selectBelow", function (assert) { const site = EmberObject.create({ post_types: { small_action: 3, whisper: 4 }, }); @@ -493,7 +495,7 @@ QUnit.test("selectBelow", function (assert) { assert.equal(selectedPostIds[3], 8, "also selected 3rd post below post #3"); }); -QUnit.test("topVisibleChanged", function (assert) { +test("topVisibleChanged", function (assert) { let model = topicWithStream({ posts: [{ id: 1 }], }); @@ -509,38 +511,35 @@ QUnit.test("topVisibleChanged", function (assert) { ); }); -QUnit.test( - "deletePost - no modal is shown if post does not have replies", - function (assert) { - pretender.get("/posts/2/reply-ids.json", () => { - return [200, { "Content-Type": "application/json" }, []]; - }); +test("deletePost - no modal is shown if post does not have replies", function (assert) { + pretender.get("/posts/2/reply-ids.json", () => { + return [200, { "Content-Type": "application/json" }, []]; + }); - let destroyed; - const post = EmberObject.create({ - id: 2, - post_number: 2, - can_delete: true, - reply_count: 3, - destroy: () => { - destroyed = true; - return Promise.resolve(); - }, - }); + let destroyed; + const post = EmberObject.create({ + id: 2, + post_number: 2, + can_delete: true, + reply_count: 3, + destroy: () => { + destroyed = true; + return Promise.resolve(); + }, + }); - const currentUser = EmberObject.create({ moderator: true }); - let model = topicWithStream({ - stream: [2, 3, 4], - posts: [post, { id: 3 }, { id: 4 }], - }); - const controller = this.subject({ model, currentUser }); + const currentUser = EmberObject.create({ moderator: true }); + let model = topicWithStream({ + stream: [2, 3, 4], + posts: [post, { id: 3 }, { id: 4 }], + }); + const controller = this.subject({ model, currentUser }); - const done = assert.async(); - controller.send("deletePost", post); + const done = assert.async(); + controller.send("deletePost", post); - next(() => { - assert.ok(destroyed, "post was destroyed"); - done(); - }); - } -); + next(() => { + assert.ok(destroyed, "post was destroyed"); + done(); + }); +}); diff --git a/test/javascripts/ember/resolver-test.js b/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js similarity index 53% rename from test/javascripts/ember/resolver-test.js rename to app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js index 62877d4dcc..771a1594ac 100644 --- a/test/javascripts/ember/resolver-test.js +++ b/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js @@ -1,3 +1,4 @@ +import { test, module } from "qunit"; import { setResolverOption, buildResolver } from "discourse-common/resolver"; let originalTemplates; @@ -17,7 +18,7 @@ function setTemplates(lookupTemplateStrings) { const DiscourseResolver = buildResolver("discourse"); -QUnit.module("lib:resolver", { +module("lib:resolver", { beforeEach() { originalTemplates = Ember.TEMPLATES; Ember.TEMPLATES = {}; @@ -30,7 +31,7 @@ QUnit.module("lib:resolver", { }, }); -QUnit.test("finds templates in top level dir", (assert) => { +test("finds templates in top level dir", (assert) => { setTemplates(["foobar", "fooBar", "foo_bar", "foo.bar"]); lookupTemplate(assert, "template:foobar", "foobar", "by lowcased name"); @@ -39,7 +40,7 @@ QUnit.test("finds templates in top level dir", (assert) => { lookupTemplate(assert, "template:foo.bar", "foo.bar", "by dotted name"); }); -QUnit.test("finds templates in first-level subdir", (assert) => { +test("finds templates in first-level subdir", (assert) => { setTemplates(["foo/bar_baz"]); lookupTemplate( @@ -68,33 +69,30 @@ QUnit.test("finds templates in first-level subdir", (assert) => { ); }); -QUnit.test( - "resolves precedence between overlapping top level dir and first level subdir templates", - (assert) => { - setTemplates(["fooBar", "foo_bar", "foo.bar", "foo/bar"]); +test("resolves precedence between overlapping top level dir and first level subdir templates", (assert) => { + setTemplates(["fooBar", "foo_bar", "foo.bar", "foo/bar"]); - lookupTemplate( - assert, - "template:foo.bar", - "foo/bar", - "preferring first level subdir for dotted name" - ); - lookupTemplate( - assert, - "template:fooBar", - "fooBar", - "preferring top level dir for camel cased name" - ); - lookupTemplate( - assert, - "template:foo_bar", - "foo_bar", - "preferring top level dir for underscored name" - ); - } -); + lookupTemplate( + assert, + "template:foo.bar", + "foo/bar", + "preferring first level subdir for dotted name" + ); + lookupTemplate( + assert, + "template:fooBar", + "fooBar", + "preferring top level dir for camel cased name" + ); + lookupTemplate( + assert, + "template:foo_bar", + "foo_bar", + "preferring top level dir for underscored name" + ); +}); -QUnit.test("finds templates in subdir deeper than one level", (assert) => { +test("finds templates in subdir deeper than one level", (assert) => { setTemplates(["foo/bar/baz/qux"]); lookupTemplate( @@ -148,7 +146,7 @@ QUnit.test("finds templates in subdir deeper than one level", (assert) => { ); }); -QUnit.test("resolves mobile templates to 'mobile/' namespace", (assert) => { +test("resolves mobile templates to 'mobile/' namespace", (assert) => { setTemplates(["mobile/foo", "bar", "mobile/bar", "baz"]); setResolverOption("mobileView", true); @@ -173,94 +171,85 @@ QUnit.test("resolves mobile templates to 'mobile/' namespace", (assert) => { ); }); -QUnit.test( - "resolves plugin templates to 'javascripts/' namespace", - (assert) => { - setTemplates(["javascripts/foo", "bar", "javascripts/bar", "baz"]); +test("resolves plugin templates to 'javascripts/' namespace", (assert) => { + setTemplates(["javascripts/foo", "bar", "javascripts/bar", "baz"]); - lookupTemplate( - assert, - "template:foo", - "javascripts/foo", - "finding plugin version even if normal one is not present" - ); - lookupTemplate( - assert, - "template:bar", - "javascripts/bar", - "preferring plugin version when both versions are present" - ); - lookupTemplate( - assert, - "template:baz", - "baz", - "falling back to a normal version when plugin version is not present" - ); - } -); + lookupTemplate( + assert, + "template:foo", + "javascripts/foo", + "finding plugin version even if normal one is not present" + ); + lookupTemplate( + assert, + "template:bar", + "javascripts/bar", + "preferring plugin version when both versions are present" + ); + lookupTemplate( + assert, + "template:baz", + "baz", + "falling back to a normal version when plugin version is not present" + ); +}); -QUnit.test( - "resolves templates with 'admin' prefix to 'admin/templates/' namespace", - (assert) => { - setTemplates([ - "admin/templates/foo", - "adminBar", - "admin_bar", - "admin.bar", - "admin/templates/bar", - ]); +test("resolves templates with 'admin' prefix to 'admin/templates/' namespace", (assert) => { + setTemplates([ + "admin/templates/foo", + "adminBar", + "admin_bar", + "admin.bar", + "admin/templates/bar", + ]); - lookupTemplate( - assert, - "template:adminFoo", - "admin/templates/foo", - "when prefix is separated by camel case" - ); - lookupTemplate( - assert, - "template:admin_foo", - "admin/templates/foo", - "when prefix is separated by underscore" - ); - lookupTemplate( - assert, - "template:admin.foo", - "admin/templates/foo", - "when prefix is separated by dot" - ); + lookupTemplate( + assert, + "template:adminFoo", + "admin/templates/foo", + "when prefix is separated by camel case" + ); + lookupTemplate( + assert, + "template:admin_foo", + "admin/templates/foo", + "when prefix is separated by underscore" + ); + lookupTemplate( + assert, + "template:admin.foo", + "admin/templates/foo", + "when prefix is separated by dot" + ); - lookupTemplate( - assert, - "template:adminfoo", - undefined, - "but not when prefix is not separated in any way" - ); - lookupTemplate( - assert, - "template:adminBar", - "adminBar", - "but not when template with the exact camel cased name exists" - ); - lookupTemplate( - assert, - "template:admin_bar", - "admin_bar", - "but not when template with the exact underscored name exists" - ); - lookupTemplate( - assert, - "template:admin.bar", - "admin.bar", - "but not when template with the exact dotted name exists" - ); - } -); + lookupTemplate( + assert, + "template:adminfoo", + undefined, + "but not when prefix is not separated in any way" + ); + lookupTemplate( + assert, + "template:adminBar", + "adminBar", + "but not when template with the exact camel cased name exists" + ); + lookupTemplate( + assert, + "template:admin_bar", + "admin_bar", + "but not when template with the exact underscored name exists" + ); + lookupTemplate( + assert, + "template:admin.bar", + "admin.bar", + "but not when template with the exact dotted name exists" + ); +}); -QUnit.test( - "returns 'not_found' template when template name cannot be resolved", - (assert) => { - setTemplates(["not_found"]); +test("returns 'not_found' template when template name cannot be resolved", (assert) => { + setTemplates(["not_found"]); - lookupTemplate(assert, "template:foo/bar/baz", "not_found", ""); - } -); + lookupTemplate(assert, "template:foo/bar/baz", "not_found", ""); +}); diff --git a/test/javascripts/lib/bbcode-test.js b/app/assets/javascripts/discourse/tests/unit/lib/bbcode-test.js similarity index 68% rename from test/javascripts/lib/bbcode-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/bbcode-test.js index f8b7225335..41530ac39c 100644 --- a/test/javascripts/lib/bbcode-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/bbcode-test.js @@ -1,8 +1,9 @@ +import { test, module } from "qunit"; import { parseBBCodeTag } from "pretty-text/engines/discourse-markdown/bbcode-block"; -QUnit.module("lib:pretty-text:bbcode"); +module("lib:pretty-text:bbcode"); -QUnit.test("block with multiple quoted attributes", (assert) => { +test("block with multiple quoted attributes", (assert) => { const parsed = parseBBCodeTag('[test one="foo" two="bar bar"]', 0, 30); assert.equal(parsed.tag, "test"); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/bookmark-test.js b/app/assets/javascripts/discourse/tests/unit/lib/bookmark-test.js new file mode 100644 index 0000000000..7f12fbcce9 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/lib/bookmark-test.js @@ -0,0 +1,46 @@ +import { test, module } from "qunit"; +import { formattedReminderTime } from "discourse/lib/bookmark"; +import { fakeTime } from "discourse/tests/helpers/qunit-helpers"; + +module("lib:bookmark", { + beforeEach() { + fakeTime("2020-04-11 08:00:00", "Australia/Brisbane"); + }, + + afterEach() { + sandbox.restore(); + }, +}); + +test("formattedReminderTime works when the reminder time is tomorrow", (assert) => { + let reminderAt = "2020-04-12 09:45:00"; + let reminderAtDate = moment + .tz(reminderAt, "Australia/Brisbane") + .format("H:mm a"); + assert.equal( + formattedReminderTime(reminderAt, "Australia/Brisbane"), + "tomorrow at " + reminderAtDate + ); +}); + +test("formattedReminderTime works when the reminder time is today", (assert) => { + let reminderAt = "2020-04-11 09:45:00"; + let reminderAtDate = moment + .tz(reminderAt, "Australia/Brisbane") + .format("H:mm a"); + assert.equal( + formattedReminderTime(reminderAt, "Australia/Brisbane"), + "today at " + reminderAtDate + ); +}); + +test("formattedReminderTime works when the reminder time is in the future", (assert) => { + let reminderAt = "2020-04-15 09:45:00"; + let reminderAtDate = moment + .tz(reminderAt, "Australia/Brisbane") + .format("H:mm a"); + assert.equal( + formattedReminderTime(reminderAt, "Australia/Brisbane"), + "at Apr 15, 2020 " + reminderAtDate + ); +}); diff --git a/test/javascripts/lib/break-string-test.js b/app/assets/javascripts/discourse/tests/unit/lib/break-string-test.js similarity index 83% rename from test/javascripts/lib/break-string-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/break-string-test.js index 32bd03fb28..1a711d6c7d 100644 --- a/test/javascripts/lib/break-string-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/break-string-test.js @@ -1,8 +1,9 @@ +import { test, module } from "qunit"; /* global BreakString:true */ -QUnit.module("lib:breakString", {}); +module("lib:breakString", {}); -QUnit.test("breakString", (assert) => { +test("breakString", (assert) => { var b = function (s, hint) { return new BreakString(s).break(hint); }; diff --git a/test/javascripts/lib/category-badge-test.js b/app/assets/javascripts/discourse/tests/unit/lib/category-badge-test.js similarity index 89% rename from test/javascripts/lib/category-badge-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/category-badge-test.js index 35dd231850..005d729e86 100644 --- a/test/javascripts/lib/category-badge-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/category-badge-test.js @@ -1,16 +1,17 @@ -import createStore from "helpers/create-store"; -import { discourseModule } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import createStore from "discourse/tests/helpers/create-store"; +import { discourseModule } from "discourse/tests/helpers/qunit-helpers"; import Site from "discourse/models/site"; discourseModule("lib:category-link"); import { categoryBadgeHTML } from "discourse/helpers/category-link"; -QUnit.test("categoryBadge without a category", (assert) => { +test("categoryBadge without a category", (assert) => { assert.blank(categoryBadgeHTML(), "it returns no HTML"); }); -QUnit.test("Regular categoryBadge", (assert) => { +test("Regular categoryBadge", (assert) => { const store = createStore(); const category = store.createRecord("category", { name: "hello", @@ -37,7 +38,7 @@ QUnit.test("Regular categoryBadge", (assert) => { ); }); -QUnit.test("undefined color", (assert) => { +test("undefined color", (assert) => { const store = createStore(); const noColor = store.createRecord("category", { name: "hello", id: 123 }); const tag = $.parseHTML(categoryBadgeHTML(noColor))[0]; @@ -48,7 +49,7 @@ QUnit.test("undefined color", (assert) => { ); }); -QUnit.test("topic count", (assert) => { +test("topic count", (assert) => { const store = createStore(); const category = store.createRecord("category", { name: "hello", id: 123 }); @@ -63,7 +64,7 @@ QUnit.test("topic count", (assert) => { ); }); -QUnit.test("allowUncategorized", (assert) => { +test("allowUncategorized", (assert) => { const store = createStore(); const uncategorized = store.createRecord("category", { name: "uncategorized", @@ -85,7 +86,7 @@ QUnit.test("allowUncategorized", (assert) => { ); }); -QUnit.test("category names are wrapped in dir-spans", function (assert) { +test("category names are wrapped in dir-spans", function (assert) { this.siteSettings.support_mixed_text_direction = true; const store = createStore(); const rtlCategory = store.createRecord("category", { @@ -110,7 +111,7 @@ QUnit.test("category names are wrapped in dir-spans", function (assert) { assert.equal(dirSpan.dir, "ltr"); }); -QUnit.test("recursive", function (assert) { +test("recursive", function (assert) { const store = createStore(); const foo = store.createRecord("category", { diff --git a/test/javascripts/lib/click-track-edit-history-test.js b/app/assets/javascripts/discourse/tests/unit/lib/click-track-edit-history-test.js similarity index 79% rename from test/javascripts/lib/click-track-edit-history-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/click-track-edit-history-test.js index c0e10fabbd..c3f78758bc 100644 --- a/test/javascripts/lib/click-track-edit-history-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/click-track-edit-history-test.js @@ -1,10 +1,11 @@ +import { module, skip } from "qunit"; import DiscourseURL from "discourse/lib/url"; import ClickTrack from "discourse/lib/click-track"; -import { fixture, logIn } from "helpers/qunit-helpers"; +import { fixture, logIn } from "discourse/tests/helpers/qunit-helpers"; import User from "discourse/models/user"; -import pretender from "helpers/create-pretender"; +import pretender from "discourse/tests/helpers/create-pretender"; -QUnit.module("lib:click-track-edit-history", { +module("lib:click-track-edit-history", { beforeEach() { logIn(); @@ -58,7 +59,7 @@ function generateClickEventOn(selector) { return $.Event("click", { currentTarget: fixture(selector).first() }); } -QUnit.skip("tracks internal URLs", async (assert) => { +skip("tracks internal URLs", async (assert) => { assert.expect(2); sandbox.stub(DiscourseURL, "origin").returns("http://discuss.domain.com"); @@ -74,7 +75,7 @@ QUnit.skip("tracks internal URLs", async (assert) => { assert.notOk(track(generateClickEventOn("#same-site"))); }); -QUnit.skip("tracks external URLs", async (assert) => { +skip("tracks external URLs", async (assert) => { assert.expect(2); const done = assert.async(); @@ -89,22 +90,19 @@ QUnit.skip("tracks external URLs", async (assert) => { assert.notOk(track(generateClickEventOn("a"))); }); -QUnit.skip( - "tracks external URLs when opening in another window", - async (assert) => { - assert.expect(3); - User.currentProp("external_links_in_new_tab", true); +skip("tracks external URLs when opening in another window", async (assert) => { + assert.expect(3); + User.currentProp("external_links_in_new_tab", true); - const done = assert.async(); - pretender.post("/clicks/track", (request) => { - assert.equal( - request.requestBody, - "url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337" - ); - done(); - }); + const done = assert.async(); + pretender.post("/clicks/track", (request) => { + assert.equal( + request.requestBody, + "url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337" + ); + done(); + }); - assert.notOk(track(generateClickEventOn("a"))); - assert.ok(window.open.calledWith("http://www.google.com", "_blank")); - } -); + assert.notOk(track(generateClickEventOn("a"))); + assert.ok(window.open.calledWith("http://www.google.com", "_blank")); +}); diff --git a/test/javascripts/lib/click-track-profile-page-test.js b/app/assets/javascripts/discourse/tests/unit/lib/click-track-profile-page-test.js similarity index 89% rename from test/javascripts/lib/click-track-profile-page-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/click-track-profile-page-test.js index 84c0c437cd..28fe7faba8 100644 --- a/test/javascripts/lib/click-track-profile-page-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/click-track-profile-page-test.js @@ -1,9 +1,10 @@ +import { module, skip } from "qunit"; import DiscourseURL from "discourse/lib/url"; import ClickTrack from "discourse/lib/click-track"; -import { fixture, logIn } from "helpers/qunit-helpers"; -import pretender from "helpers/create-pretender"; +import { fixture, logIn } from "discourse/tests/helpers/qunit-helpers"; +import pretender from "discourse/tests/helpers/create-pretender"; -QUnit.module("lib:click-track-profile-page", { +module("lib:click-track-profile-page", { beforeEach() { logIn(); @@ -51,7 +52,7 @@ function generateClickEventOn(selector) { return $.Event("click", { currentTarget: fixture(selector).first() }); } -QUnit.skip("tracks internal URLs", async (assert) => { +skip("tracks internal URLs", async (assert) => { assert.expect(2); sandbox.stub(DiscourseURL, "origin").returns("http://discuss.domain.com"); @@ -64,7 +65,7 @@ QUnit.skip("tracks internal URLs", async (assert) => { assert.notOk(track(generateClickEventOn("#same-site"))); }); -QUnit.skip("tracks external URLs", async (assert) => { +skip("tracks external URLs", async (assert) => { assert.expect(2); const done = assert.async(); @@ -79,7 +80,7 @@ QUnit.skip("tracks external URLs", async (assert) => { assert.notOk(track(generateClickEventOn("a"))); }); -QUnit.skip("tracks external URLs in other posts", async (assert) => { +skip("tracks external URLs in other posts", async (assert) => { assert.expect(2); const done = assert.async(); diff --git a/test/javascripts/lib/click-track-test.js b/app/assets/javascripts/discourse/tests/unit/lib/click-track-test.js similarity index 70% rename from test/javascripts/lib/click-track-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/click-track-test.js index 55d29ee425..0528478fc9 100644 --- a/test/javascripts/lib/click-track-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/click-track-test.js @@ -1,11 +1,13 @@ +import { skip } from "qunit"; +import { test, module } from "qunit"; import { later } from "@ember/runloop"; import DiscourseURL from "discourse/lib/url"; import ClickTrack from "discourse/lib/click-track"; -import { fixture, logIn } from "helpers/qunit-helpers"; +import { fixture, logIn } from "discourse/tests/helpers/qunit-helpers"; import User from "discourse/models/user"; -import pretender from "helpers/create-pretender"; +import pretender from "discourse/tests/helpers/create-pretender"; -QUnit.module("lib:click-track", { +module("lib:click-track", { beforeEach() { logIn(); @@ -51,7 +53,7 @@ function generateClickEventOn(selector) { return $.Event("click", { currentTarget: fixture(selector).first() }); } -QUnit.skip("tracks internal URLs", async (assert) => { +skip("tracks internal URLs", async (assert) => { assert.expect(2); sandbox.stub(DiscourseURL, "origin").returns("http://discuss.domain.com"); @@ -67,11 +69,11 @@ QUnit.skip("tracks internal URLs", async (assert) => { assert.notOk(track(generateClickEventOn("#same-site"))); }); -QUnit.test("does not track elements with no href", async (assert) => { +test("does not track elements with no href", async (assert) => { assert.ok(track(generateClickEventOn(".a-without-href"))); }); -QUnit.test("does not track attachments", async (assert) => { +test("does not track attachments", async (assert) => { sandbox.stub(DiscourseURL, "origin").returns("http://discuss.domain.com"); pretender.post("/clicks/track", () => assert.ok(false)); @@ -84,7 +86,7 @@ QUnit.test("does not track attachments", async (assert) => { ); }); -QUnit.skip("tracks external URLs", async (assert) => { +skip("tracks external URLs", async (assert) => { assert.expect(2); const done = assert.async(); @@ -99,75 +101,69 @@ QUnit.skip("tracks external URLs", async (assert) => { assert.notOk(track(generateClickEventOn("a"))); }); -QUnit.skip( - "tracks external URLs when opening in another window", - async (assert) => { - assert.expect(3); - User.currentProp("external_links_in_new_tab", true); +skip("tracks external URLs when opening in another window", async (assert) => { + assert.expect(3); + User.currentProp("external_links_in_new_tab", true); - const done = assert.async(); - pretender.post("/clicks/track", (request) => { - assert.ok( - request.requestBody, - "url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337" - ); - done(); - }); + const done = assert.async(); + pretender.post("/clicks/track", (request) => { + assert.ok( + request.requestBody, + "url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337" + ); + done(); + }); - assert.notOk(track(generateClickEventOn("a"))); - assert.ok(window.open.calledWith("http://www.google.com", "_blank")); - } -); + assert.notOk(track(generateClickEventOn("a"))); + assert.ok(window.open.calledWith("http://www.google.com", "_blank")); +}); -QUnit.test("does not track clicks on lightboxes", async (assert) => { +test("does not track clicks on lightboxes", async (assert) => { assert.notOk(track(generateClickEventOn(".lightbox"))); }); -QUnit.test("does not track clicks when forcibly disabled", async (assert) => { +test("does not track clicks when forcibly disabled", async (assert) => { assert.notOk(track(generateClickEventOn(".no-track-link"))); }); -QUnit.test("does not track clicks on back buttons", async (assert) => { +test("does not track clicks on back buttons", async (assert) => { assert.notOk(track(generateClickEventOn(".back"))); }); -QUnit.test("does not track right clicks inside quotes", async (assert) => { +test("does not track right clicks inside quotes", async (assert) => { const event = generateClickEventOn(".quote a:first-child"); event.which = 3; assert.ok(track(event)); }); -QUnit.test("does not track clicks links in quotes", async (assert) => { +test("does not track clicks links in quotes", async (assert) => { User.currentProp("external_links_in_new_tab", true); assert.notOk(track(generateClickEventOn(".quote a:last-child"))); assert.ok(window.open.calledWith("https://google.com/", "_blank")); }); -QUnit.test("does not track clicks on category badges", async (assert) => { +test("does not track clicks on category badges", async (assert) => { assert.notOk(track(generateClickEventOn(".hashtag"))); }); -QUnit.test("does not track clicks on mailto", async (assert) => { +test("does not track clicks on mailto", async (assert) => { assert.ok(track(generateClickEventOn(".mailto"))); }); -QUnit.test( - "removes the href and put it as a data attribute", - async (assert) => { - User.currentProp("external_links_in_new_tab", true); +test("removes the href and put it as a data attribute", async (assert) => { + User.currentProp("external_links_in_new_tab", true); - assert.notOk(track(generateClickEventOn("a"))); + assert.notOk(track(generateClickEventOn("a"))); - var $link = fixture("a").first(); - assert.ok($link.hasClass("no-href")); - assert.equal($link.data("href"), "http://www.google.com/"); - assert.blank($link.attr("href")); - assert.ok($link.data("auto-route")); - assert.ok(window.open.calledWith("http://www.google.com/", "_blank")); - } -); + var $link = fixture("a").first(); + assert.ok($link.hasClass("no-href")); + assert.equal($link.data("href"), "http://www.google.com/"); + assert.blank($link.attr("href")); + assert.ok($link.data("auto-route")); + assert.ok(window.open.calledWith("http://www.google.com/", "_blank")); +}); -QUnit.test("restores the href after a while", async (assert) => { +test("restores the href after a while", async (assert) => { assert.expect(2); assert.notOk(track(generateClickEventOn("a"))); @@ -187,24 +183,24 @@ function badgeClickCount(assert, id, expected) { assert.equal(parseInt($badge.html(), 10), expected); } -QUnit.test("does not update badge clicks on my own link", async (assert) => { +test("does not update badge clicks on my own link", async (assert) => { sandbox.stub(User, "currentProp").withArgs("id").returns(314); badgeClickCount(assert, "with-badge", 1); }); -QUnit.test("does not update badge clicks in my own post", async (assert) => { +test("does not update badge clicks in my own post", async (assert) => { sandbox.stub(User, "currentProp").withArgs("id").returns(3141); badgeClickCount(assert, "with-badge-but-not-mine", 1); }); -QUnit.test("updates badge counts correctly", async (assert) => { +test("updates badge counts correctly", async (assert) => { badgeClickCount(assert, "inside-onebox", 1); badgeClickCount(assert, "inside-onebox-forced", 2); badgeClickCount(assert, "with-badge", 2); }); function testOpenInANewTab(description, clickEventModifier) { - QUnit.test(description, async (assert) => { + test(description, async (assert) => { var clickEvent = generateClickEventOn("a"); clickEventModifier(clickEvent); assert.ok(track(clickEvent)); diff --git a/test/javascripts/lib/computed-test.js b/app/assets/javascripts/discourse/tests/unit/lib/computed-test.js similarity index 91% rename from test/javascripts/lib/computed-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/computed-test.js index 6bb386fdba..748b17969b 100644 --- a/test/javascripts/lib/computed-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/computed-test.js @@ -1,3 +1,4 @@ +import { test } from "qunit"; import I18n from "I18n"; import EmberObject from "@ember/object"; import { @@ -10,7 +11,7 @@ import { htmlSafe, } from "discourse/lib/computed"; import { setPrefix } from "discourse-common/lib/get-url"; -import { discourseModule } from "helpers/qunit-helpers"; +import { discourseModule } from "discourse/tests/helpers/qunit-helpers"; discourseModule("lib:computed", { beforeEach() { @@ -24,7 +25,7 @@ discourseModule("lib:computed", { }, }); -QUnit.test("setting", function (assert) { +test("setting", function (assert) { let t = EmberObject.extend({ siteSettings: this.siteSettings, vehicle: setting("vehicle"), @@ -43,7 +44,7 @@ QUnit.test("setting", function (assert) { ); }); -QUnit.test("propertyEqual", (assert) => { +test("propertyEqual", (assert) => { var t = EmberObject.extend({ same: propertyEqual("cookies", "biscuits"), }).create({ @@ -56,7 +57,7 @@ QUnit.test("propertyEqual", (assert) => { assert.ok(!t.get("same"), "it isn't true when one property is different"); }); -QUnit.test("propertyNotEqual", (assert) => { +test("propertyNotEqual", (assert) => { var t = EmberObject.extend({ diff: propertyNotEqual("cookies", "biscuits"), }).create({ @@ -69,7 +70,7 @@ QUnit.test("propertyNotEqual", (assert) => { assert.ok(t.get("diff"), "it is true when one property is different"); }); -QUnit.test("fmt", (assert) => { +test("fmt", (assert) => { var t = EmberObject.extend({ exclaimyUsername: fmt("username", "!!! %@ !!!"), multiple: fmt("username", "mood", "%@ is %@"), @@ -103,7 +104,7 @@ QUnit.test("fmt", (assert) => { ); }); -QUnit.test("i18n", (assert) => { +test("i18n", (assert) => { var t = EmberObject.extend({ exclaimyUsername: i18n("username", "!!! %@ !!!"), multiple: i18n("username", "mood", "%@ is %@"), @@ -137,7 +138,7 @@ QUnit.test("i18n", (assert) => { ); }); -QUnit.test("url", (assert) => { +test("url", (assert) => { var t, testClass; testClass = EmberObject.extend({ @@ -160,7 +161,7 @@ QUnit.test("url", (assert) => { ); }); -QUnit.test("htmlSafe", (assert) => { +test("htmlSafe", (assert) => { const cookies = "

cookies and biscuits

"; const t = EmberObject.extend({ desc: htmlSafe("cookies"), diff --git a/test/javascripts/lib/emoji-store-test.js b/app/assets/javascripts/discourse/tests/unit/lib/emoji-store-test.js similarity index 74% rename from test/javascripts/lib/emoji-store-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/emoji-store-test.js index 8068b2b506..adf9c0c22c 100644 --- a/test/javascripts/lib/emoji-store-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/emoji-store-test.js @@ -1,4 +1,5 @@ -import { discourseModule } from "helpers/qunit-helpers"; +import { test } from "qunit"; +import { discourseModule } from "discourse/tests/helpers/qunit-helpers"; discourseModule("lib:emoji-emojiStore", { beforeEach() { @@ -10,22 +11,22 @@ discourseModule("lib:emoji-emojiStore", { }, }); -QUnit.test("defaults", function (assert) { +test("defaults", function (assert) { assert.deepEqual(this.emojiStore.favorites, []); assert.equal(this.emojiStore.diversity, 1); }); -QUnit.test("diversity", function (assert) { +test("diversity", function (assert) { this.emojiStore.diversity = 2; assert.equal(this.emojiStore.diversity, 2); }); -QUnit.test("favorites", function (assert) { +test("favorites", function (assert) { this.emojiStore.favorites = ["smile"]; assert.deepEqual(this.emojiStore.favorites, ["smile"]); }); -QUnit.test("track", function (assert) { +test("track", function (assert) { this.emojiStore.track("woman:t4"); assert.deepEqual(this.emojiStore.favorites, ["woman:t4"]); this.emojiStore.track("otter"); diff --git a/test/javascripts/lib/emoji-test.js b/app/assets/javascripts/discourse/tests/unit/lib/emoji-test.js similarity index 58% rename from test/javascripts/lib/emoji-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/emoji-test.js index 3a3fed402b..b8490e22f1 100644 --- a/test/javascripts/lib/emoji-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/emoji-test.js @@ -1,11 +1,12 @@ +import { test } from "qunit"; import { emojiSearch } from "pretty-text/emoji"; import { IMAGE_VERSION as v } from "pretty-text/emoji/version"; import { emojiUnescape } from "discourse/lib/text"; -import { discourseModule } from "helpers/qunit-helpers"; +import { discourseModule } from "discourse/tests/helpers/qunit-helpers"; discourseModule("lib:emoji"); -QUnit.test("emojiUnescape", function (assert) { +test("emojiUnescape", function (assert) { const testUnescape = (input, expected, description, settings = {}) => { const originalSettings = {}; for (const [key, value] of Object.entries(settings)) { @@ -32,12 +33,12 @@ QUnit.test("emojiUnescape", function (assert) { ); testUnescape( "emoticons :)", - `emoticons slight_smile`, + `emoticons slight_smile`, "emoticons are still supported" ); testUnescape( "With emoji :O: :frog: :smile:", - `With emoji O frog smile`, + `With emoji O frog smile`, "title with emoji" ); testUnescape( @@ -47,27 +48,27 @@ QUnit.test("emojiUnescape", function (assert) { ); testUnescape( "(:frog:) :)", - `(frog) slight_smile`, + `(frog) slight_smile`, "non-word characters allowed next to emoji" ); testUnescape( ":smile: hi", - `smile hi`, + `smile hi`, "start of line" ); testUnescape( "hi :smile:", - `hi smile`, + `hi smile`, "end of line" ); testUnescape( "hi :blonde_woman:t4:", - `hi blonde_woman:t4`, + `hi blonde_woman:t4`, "support for skin tones" ); testUnescape( "hi :blonde_woman:t4: :blonde_man:t6:", - `hi blonde_woman:t4 blonde_man:t6`, + `hi blonde_woman:t4 blonde_man:t6`, "support for multiple skin tones" ); testUnescape( @@ -95,7 +96,7 @@ QUnit.test("emojiUnescape", function (assert) { ); testUnescape( "Hello 😊 World", - `Hello blush World`, + `Hello blush World`, "emoji from Unicode emoji" ); testUnescape( @@ -108,7 +109,7 @@ QUnit.test("emojiUnescape", function (assert) { ); testUnescape( "Hello😊World", - `HelloblushWorld`, + `HelloblushWorld`, "emoji from Unicode emoji when inline translation enabled", { enable_inline_emoji_translation: true, @@ -124,13 +125,13 @@ QUnit.test("emojiUnescape", function (assert) { ); testUnescape( "hi:smile:", - `hismile`, + `hismile`, "emoji when inline translation enabled", { enable_inline_emoji_translation: true } ); }); -QUnit.test("Emoji search", (assert) => { +test("Emoji search", (assert) => { // able to find an alias assert.equal(emojiSearch("+1").length, 1); diff --git a/test/javascripts/lib/formatter-test.js b/app/assets/javascripts/discourse/tests/unit/lib/formatter-test.js similarity index 95% rename from test/javascripts/lib/formatter-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/formatter-test.js index 14becfdcf6..15de4b568a 100644 --- a/test/javascripts/lib/formatter-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/formatter-test.js @@ -1,3 +1,4 @@ +import { test } from "qunit"; import { relativeAge, autoUpdatingRelativeAge, @@ -6,7 +7,8 @@ import { longDate, durationTiny, } from "discourse/lib/formatter"; -import { discourseModule } from "helpers/qunit-helpers"; +import { discourseModule } from "discourse/tests/helpers/qunit-helpers"; +import sinon from "sinon"; discourseModule("lib:formatter", { beforeEach() { @@ -48,7 +50,7 @@ function strip(html) { return $(html).text(); } -QUnit.test("formating medium length dates", function (assert) { +test("formating medium length dates", function (assert) { let shortDateYear = shortDateTester("MMM D, 'YY"); assert.equal( @@ -119,7 +121,7 @@ QUnit.test("formating medium length dates", function (assert) { assert.equal(strip(formatDays(10, { format: "medium" })), shortDateYear(10)); }); -QUnit.test("formating tiny dates", function (assert) { +test("formating tiny dates", function (assert) { let shortDateYear = shortDateTester("MMM 'YY"); assert.equal(formatMins(0), "1m"); @@ -182,7 +184,7 @@ QUnit.test("formating tiny dates", function (assert) { this.siteSettings.relative_date_duration = originalValue; }); -QUnit.test("autoUpdatingRelativeAge", function (assert) { +test("autoUpdatingRelativeAge", function (assert) { var d = moment().subtract(1, "day").toDate(); var $elem = $(autoUpdatingRelativeAge(d)); @@ -212,7 +214,7 @@ QUnit.test("autoUpdatingRelativeAge", function (assert) { assert.equal($elem.html(), "1 day"); }); -QUnit.test("updateRelativeAge", function (assert) { +test("updateRelativeAge", function (assert) { var d = new Date(); var $elem = $(autoUpdatingRelativeAge(d)); $elem.data("time", d.getTime() - 2 * 60 * 1000); @@ -230,7 +232,7 @@ QUnit.test("updateRelativeAge", function (assert) { assert.equal($elem.html(), "2 mins ago"); }); -QUnit.test("number", function (assert) { +test("number", function (assert) { assert.equal(number(123), "123", "it returns a string version of the number"); assert.equal(number("123"), "123", "it works with a string command"); assert.equal(number(NaN), "0", "it returns 0 for NaN"); @@ -261,7 +263,7 @@ QUnit.test("number", function (assert) { ); }); -QUnit.test("durationTiny", function (assert) { +test("durationTiny", function (assert) { assert.equal(durationTiny(), "—", "undefined is a dash"); assert.equal(durationTiny(null), "—", "null is a dash"); assert.equal(durationTiny(0), "< 1m", "0 seconds shows as < 1m"); diff --git a/test/javascripts/lib/get-url-test.js b/app/assets/javascripts/discourse/tests/unit/lib/get-url-test.js similarity index 87% rename from test/javascripts/lib/get-url-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/get-url-test.js index d11ee283e2..65506c85df 100644 --- a/test/javascripts/lib/get-url-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/get-url-test.js @@ -1,3 +1,4 @@ +import { test, module } from "qunit"; import { default as getURL, setupURL, @@ -9,21 +10,21 @@ import { withoutPrefix, } from "discourse-common/lib/get-url"; -QUnit.module("lib:get-url"); +module("lib:get-url"); -QUnit.test("isAbsoluteURL", (assert) => { +test("isAbsoluteURL", (assert) => { setupURL(null, "https://example.com", "/forum"); assert.ok(isAbsoluteURL("https://example.com/test/thing")); assert.ok(!isAbsoluteURL("http://example.com/test/thing")); assert.ok(!isAbsoluteURL("https://discourse.org/test/thing")); }); -QUnit.test("getAbsoluteURL", (assert) => { +test("getAbsoluteURL", (assert) => { setupURL(null, "https://example.com", "/forum"); assert.equal(getAbsoluteURL("/cool/path"), "https://example.com/cool/path"); }); -QUnit.test("withoutPrefix", (assert) => { +test("withoutPrefix", (assert) => { setPrefix("/eviltrout"); assert.equal(withoutPrefix("/eviltrout/hello"), "/hello"); assert.equal(withoutPrefix("/eviltrout/"), "/"); @@ -40,7 +41,7 @@ QUnit.test("withoutPrefix", (assert) => { assert.equal(withoutPrefix("/"), "/"); }); -QUnit.test("getURL with empty paths", (assert) => { +test("getURL with empty paths", (assert) => { setupURL(null, "https://example.com", "/"); assert.equal(getURL("/"), "/"); assert.equal(getURL(""), ""); @@ -52,7 +53,7 @@ QUnit.test("getURL with empty paths", (assert) => { assert.equal(getURL(""), ""); }); -QUnit.test("getURL on subfolder install", (assert) => { +test("getURL on subfolder install", (assert) => { setupURL(null, "", "/forum"); assert.equal(getURL("/"), "/forum/", "root url has subfolder"); assert.equal( @@ -80,7 +81,7 @@ QUnit.test("getURL on subfolder install", (assert) => { ); }); -QUnit.test("getURLWithCDN on subfolder install with S3", (assert) => { +test("getURLWithCDN on subfolder install with S3", (assert) => { setupURL(null, "", "/forum"); setupS3CDN( "//test.s3-us-west-1.amazonaws.com/site", diff --git a/test/javascripts/lib/highlight-search-test.js.es6 b/app/assets/javascripts/discourse/tests/unit/lib/highlight-search-test.js similarity index 79% rename from test/javascripts/lib/highlight-search-test.js.es6 rename to app/assets/javascripts/discourse/tests/unit/lib/highlight-search-test.js index 47c1ab3ed6..34958ade38 100644 --- a/test/javascripts/lib/highlight-search-test.js.es6 +++ b/app/assets/javascripts/discourse/tests/unit/lib/highlight-search-test.js @@ -1,9 +1,10 @@ import highlightSearch, { CLASS_NAME } from "discourse/lib/highlight-search"; -import { fixture } from "helpers/qunit-helpers"; +import { fixture } from "discourse/tests/helpers/qunit-helpers"; +import { module, test } from "qunit"; -QUnit.module("lib:highlight-search"); +module("lib:highlight-search"); -QUnit.test("highlighting text", (assert) => { +test("highlighting text", (assert) => { fixture().html( `

This is some text to highlight

@@ -25,7 +26,7 @@ QUnit.test("highlighting text", (assert) => { ); }); -QUnit.test("highlighting unicode text", (assert) => { +test("highlighting unicode text", (assert) => { fixture().html( `

This is some தமிழ் & русский text to highlight

diff --git a/test/javascripts/lib/i18n-test.js b/app/assets/javascripts/discourse/tests/unit/lib/i18n-test.js similarity index 95% rename from test/javascripts/lib/i18n-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/i18n-test.js index 7a8c20498f..7b713bb2e5 100644 --- a/test/javascripts/lib/i18n-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/i18n-test.js @@ -1,5 +1,6 @@ +import { test, module } from "qunit"; import I18n from "I18n"; -QUnit.module("lib:i18n", { +module("lib:i18n", { _locale: I18n.locale, _fallbackLocale: I18n.fallbackLocale, _translations: I18n.translations, @@ -98,12 +99,12 @@ QUnit.module("lib:i18n", { }, }); -QUnit.test("defaults", (assert) => { +test("defaults", (assert) => { assert.equal(I18n.defaultLocale, "en", "it has English as default locale"); assert.ok(I18n.pluralizationRules["en"], "it has English pluralizer"); }); -QUnit.test("translations", (assert) => { +test("translations", (assert) => { assert.equal( I18n.t("topic.reply.title"), "Répondre", @@ -122,7 +123,7 @@ QUnit.test("translations", (assert) => { assert.equal(I18n.t("hello.universe"), "", "allows empty strings"); }); -QUnit.test("extra translations", (assert) => { +test("extra translations", (assert) => { I18n.locale = "pl_PL"; I18n.extras = { en: { @@ -194,7 +195,7 @@ QUnit.test("extra translations", (assert) => { ); }); -QUnit.test("pluralizations", (assert) => { +test("pluralizations", (assert) => { assert.equal(I18n.t("character_count", { count: 0 }), "0 ZERO"); assert.equal(I18n.t("character_count", { count: 1 }), "1 ONE"); assert.equal(I18n.t("character_count", { count: 2 }), "2 TWO"); @@ -210,7 +211,7 @@ QUnit.test("pluralizations", (assert) => { assert.equal(I18n.t("word_count", { count: 100 }), "100 words"); }); -QUnit.test("fallback", (assert) => { +test("fallback", (assert) => { assert.equal( I18n.t("days", { count: 1 }), "1 day", @@ -242,7 +243,7 @@ QUnit.test("fallback", (assert) => { ); }); -QUnit.test("Dollar signs are properly escaped", (assert) => { +test("Dollar signs are properly escaped", (assert) => { assert.equal( I18n.t("dollar_sign", { description: "$& $&", diff --git a/test/javascripts/lib/icon-library-test.js b/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js similarity index 80% rename from test/javascripts/lib/icon-library-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js index fbfae424e7..209dfcea2a 100644 --- a/test/javascripts/lib/icon-library-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js @@ -1,12 +1,13 @@ +import { test, module } from "qunit"; import { iconHTML, iconNode, convertIconClass, } from "discourse-common/lib/icon-library"; -QUnit.module("lib:icon-library"); +module("lib:icon-library"); -QUnit.test("return icon markup", (assert) => { +test("return icon markup", (assert) => { assert.ok(iconHTML("bars").indexOf('use xlink:href="#bars"') > -1); const nodeIcon = iconNode("bars"); @@ -17,7 +18,7 @@ QUnit.test("return icon markup", (assert) => { ); }); -QUnit.test("convert icon names", (assert) => { +test("convert icon names", (assert) => { const fa5Icon = convertIconClass("fab fa-facebook"); assert.ok(iconHTML(fa5Icon).indexOf("fab-facebook") > -1, "FA 5 syntax"); diff --git a/test/javascripts/lib/key-value-store-test.js b/app/assets/javascripts/discourse/tests/unit/lib/key-value-store-test.js similarity index 69% rename from test/javascripts/lib/key-value-store-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/key-value-store-test.js index 70d4469c4e..5af532ef57 100644 --- a/test/javascripts/lib/key-value-store-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/key-value-store-test.js @@ -1,14 +1,15 @@ +import { test, module } from "qunit"; import KeyValueStore from "discourse/lib/key-value-store"; -QUnit.module("lib:key-value-store"); +module("lib:key-value-store"); -QUnit.test("it's able to get the result back from the store", (assert) => { +test("it's able to get the result back from the store", (assert) => { const store = new KeyValueStore("_test"); store.set({ key: "bob", value: "uncle" }); assert.equal(store.get("bob"), "uncle"); }); -QUnit.test("is able to nuke the store", (assert) => { +test("is able to nuke the store", (assert) => { const store = new KeyValueStore("_test"); store.set({ key: "bob1", value: "uncle" }); store.abandonLocal(); diff --git a/test/javascripts/lib/link-mentions-test.js b/app/assets/javascripts/discourse/tests/unit/lib/link-mentions-test.js similarity index 87% rename from test/javascripts/lib/link-mentions-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/link-mentions-test.js index 5f4908d457..507714524d 100644 --- a/test/javascripts/lib/link-mentions-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/link-mentions-test.js @@ -1,13 +1,14 @@ +import { test, module } from "qunit"; import { fetchUnseenMentions, linkSeenMentions, } from "discourse/lib/link-mentions"; import { Promise } from "rsvp"; -import pretender from "helpers/create-pretender"; +import pretender from "discourse/tests/helpers/create-pretender"; -QUnit.module("lib:link-mentions"); +module("lib:link-mentions"); -QUnit.test("linkSeenMentions replaces users and groups", async (assert) => { +test("linkSeenMentions replaces users and groups", async (assert) => { pretender.get("/u/is_local_username", () => [ 200, { "Content-Type": "application/json" }, diff --git a/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js b/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js new file mode 100644 index 0000000000..03a3533f46 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js @@ -0,0 +1,43 @@ +import { skip } from "qunit"; +import { test, module } from "qunit"; +import { loadScript, cacheBuster } from "discourse/lib/load-script"; +import { PUBLIC_JS_VERSIONS as jsVersions } from "discourse/lib/public-js-versions"; + +module("lib:load-script"); + +skip("load with a script tag, and callbacks are only executed after script is loaded", async (assert) => { + assert.ok( + typeof window.ace === "undefined", + "ensures ace is not previously loaded" + ); + + const src = "/javascripts/ace/ace.js"; + + await loadScript(src); + assert.ok( + typeof window.ace !== "undefined", + "callbacks should only be executed after the script has fully loaded" + ); +}); + +test("works when a value is not present", (assert) => { + assert.equal( + cacheBuster("/javascripts/my-script.js"), + "/javascripts/my-script.js" + ); + assert.equal( + cacheBuster("/javascripts/my-project/script.js"), + "/javascripts/my-project/script.js" + ); +}); + +test("generates URLs with version number in the query params", (assert) => { + assert.equal( + cacheBuster("/javascripts/pikaday.js"), + `/javascripts/${jsVersions["pikaday.js"]}` + ); + assert.equal( + cacheBuster("/javascripts/ace/ace.js"), + `/javascripts/${jsVersions["ace/ace.js"]}` + ); +}); diff --git a/test/javascripts/lib/oneboxer-test.js b/app/assets/javascripts/discourse/tests/unit/lib/oneboxer-test.js similarity index 87% rename from test/javascripts/lib/oneboxer-test.js rename to app/assets/javascripts/discourse/tests/unit/lib/oneboxer-test.js index 512f2fc304..06ad0f599a 100644 --- a/test/javascripts/lib/oneboxer-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/oneboxer-test.js @@ -1,7 +1,8 @@ +import { test, module } from "qunit"; import { load } from "pretty-text/oneboxer"; import { ajax } from "discourse/lib/ajax"; import { failedCache, localCache } from "pretty-text/oneboxer-cache"; -import { stringToHTML } from "helpers/html-helper"; +import { stringToHTML } from "discourse/tests/helpers/html-helper"; function loadOnebox(element) { return load({ @@ -14,9 +15,9 @@ function loadOnebox(element) { }); } -QUnit.module("lib:oneboxer"); +module("lib:oneboxer"); -QUnit.test("load - failed onebox", async (assert) => { +test("load - failed onebox", async (assert) => { let element = document.createElement("A"); element.setAttribute("href", "http://somebadurl.com"); @@ -34,7 +35,7 @@ QUnit.test("load - failed onebox", async (assert) => { ); }); -QUnit.test("load - successful onebox", async (assert) => { +test("load - successful onebox", async (assert) => { const html = `
diff --git a/app/assets/javascripts/wizard/templates/components/font-previews.hbs b/app/assets/javascripts/wizard/templates/components/font-previews.hbs deleted file mode 100644 index 623118246a..0000000000 --- a/app/assets/javascripts/wizard/templates/components/font-previews.hbs +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/app/assets/javascripts/wizard/templates/components/popular-themes.hbs b/app/assets/javascripts/wizard/templates/components/popular-themes.hbs deleted file mode 100644 index 774f8c0a27..0000000000 --- a/app/assets/javascripts/wizard/templates/components/popular-themes.hbs +++ /dev/null @@ -1,5 +0,0 @@ -{{#each popular_components as |theme|}} - - {{theme.name}} - -{{/each}} diff --git a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js b/app/assets/javascripts/wizard/test/acceptance/wizard-test.js index bcee8791e1..62921abf4e 100644 --- a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js +++ b/app/assets/javascripts/wizard/test/acceptance/wizard-test.js @@ -1,8 +1,9 @@ +import { test, module } from "qunit"; import { run } from "@ember/runloop"; import startApp from "wizard/test/helpers/start-app"; var wizard; -QUnit.module("Acceptance: wizard", { +module("Acceptance: wizard", { beforeEach() { wizard = startApp(); }, diff --git a/app/assets/javascripts/wizard/test/components/invite-list-test.js b/app/assets/javascripts/wizard/test/components/invite-list-test.js index 40aafb9b9a..3da3946d1a 100644 --- a/app/assets/javascripts/wizard/test/components/invite-list-test.js +++ b/app/assets/javascripts/wizard/test/components/invite-list-test.js @@ -1,3 +1,4 @@ +import { moduleForComponent } from "ember-qunit"; import { componentTest } from "wizard/test/helpers/component-test"; moduleForComponent("invite-list", { integration: true }); diff --git a/app/assets/javascripts/wizard/test/helpers/component-test.js b/app/assets/javascripts/wizard/test/helpers/component-test.js index b7812b787e..2039321af5 100644 --- a/app/assets/javascripts/wizard/test/helpers/component-test.js +++ b/app/assets/javascripts/wizard/test/helpers/component-test.js @@ -1,4 +1,5 @@ import initializer from "wizard/initializers/load-helpers"; +import { test } from "qunit"; export function componentTest(name, opts) { opts = opts || {}; diff --git a/app/assets/javascripts/wizard/test/models/wizard-field-test.js b/app/assets/javascripts/wizard/test/models/wizard-field-test.js index f50f5af79c..396b16882c 100644 --- a/app/assets/javascripts/wizard/test/models/wizard-field-test.js +++ b/app/assets/javascripts/wizard/test/models/wizard-field-test.js @@ -1,3 +1,5 @@ +import { moduleFor } from "ember-qunit"; +import { test } from "qunit"; import WizardField from "wizard/models/wizard-field"; moduleFor("model:wizard-field"); diff --git a/app/assets/javascripts/wizard/test/test_helper.js b/app/assets/javascripts/wizard/test/test_helper.js index 1a48e2450c..c7e818c053 100644 --- a/app/assets/javascripts/wizard/test/test_helper.js +++ b/app/assets/javascripts/wizard/test/test_helper.js @@ -6,12 +6,15 @@ //= require ember.debug //= require locales/i18n //= require locales/en_US +//= require route-recognizer/dist/route-recognizer +//= require fake_xml_http_request +//= require pretender/pretender +//= require qunit/qunit/qunit +//= require ember-qunit //= require discourse-loader //= require jquery.debug //= require handlebars //= require ember-template-compiler -//= require qunit/qunit/qunit -//= require ember-qunit //= require wizard-application //= require wizard-vendor //= require helpers/assertions @@ -19,10 +22,8 @@ //= require_tree ./acceptance //= require_tree ./models //= require_tree ./components -//= require fake_xml_http_request -//= require route-recognizer/dist/route-recognizer -//= require pretender/pretender //= require ./wizard-pretender +//= require test-shims // Trick JSHint into allow document.write var d = document; diff --git a/app/assets/javascripts/wizard/test/wizard-pretender.js b/app/assets/javascripts/wizard/test/wizard-pretender.js index 96ca2352e5..e9dccfb3fb 100644 --- a/app/assets/javascripts/wizard/test/wizard-pretender.js +++ b/app/assets/javascripts/wizard/test/wizard-pretender.js @@ -1,3 +1,5 @@ +import Pretender from "pretender"; + // TODO: This file has some copied and pasted functions from `create-pretender` - would be good // to centralize that code at some point. diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index f194318ddd..46161547e8 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -302,7 +302,12 @@ ol.category-breadcrumb { } div.education { - color: var(--primary-med-or-secondary-med); + color: var(--primary); + padding: 1em 2.5em 1em 1em; + margin-bottom: 2em; + border-top: 3px solid var(--primary-low); + border-bottom: 1px solid var(--primary-low); + .badge-notification.new-posts { vertical-align: text-bottom; } diff --git a/app/assets/stylesheets/common/base/cat_reorder.scss b/app/assets/stylesheets/common/base/cat_reorder.scss index 2a35f9b935..ac50234a65 100644 --- a/app/assets/stylesheets/common/base/cat_reorder.scss +++ b/app/assets/stylesheets/common/base/cat_reorder.scss @@ -4,9 +4,10 @@ padding-bottom: 0.5em; } } - input { - width: 2.333em; - padding: 4px; + input[type="text"] { + max-width: 2.5em; + padding: 0.35em; + text-align: center; @include breakpoint(mobile-extra-large) { width: 2em; } diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss index 18e96c3c52..d185bc5b09 100644 --- a/app/assets/stylesheets/common/base/discourse.scss +++ b/app/assets/stylesheets/common/base/discourse.scss @@ -90,6 +90,7 @@ h3, h4, h5, h6 { + font-family: $heading-font-family; margin-top: 0; margin-bottom: 0.5rem; } @@ -566,6 +567,8 @@ table { } .control-label { + font-family: $heading-font-family; + font-weight: bold; font-size: $font-up-2; line-height: $line-height-large; diff --git a/app/assets/stylesheets/common/base/emoji.scss b/app/assets/stylesheets/common/base/emoji.scss index 434e94cf3c..ecbdaab49b 100644 --- a/app/assets/stylesheets/common/base/emoji.scss +++ b/app/assets/stylesheets/common/base/emoji.scss @@ -65,7 +65,6 @@ sup img.emoji { .section { margin-bottom: 1em; - content-visibility: auto; .trash-recent { background: none; diff --git a/app/assets/stylesheets/common/base/group.scss b/app/assets/stylesheets/common/base/group.scss index 642bf2568a..3ecab3780b 100644 --- a/app/assets/stylesheets/common/base/group.scss +++ b/app/assets/stylesheets/common/base/group.scss @@ -5,7 +5,6 @@ span.mention-group { color: var(--primary-high-or-secondary-low); padding: 2px 4px; - background: rgba(var(--primary-rgb), 0.12); border-radius: 8px; font-weight: 600; font-size: $font-down-1; diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 4b47880aea..267326fb4d 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -90,8 +90,14 @@ } } -.hamburger-panel .panel-body { - overflow-y: auto; +.hamburger-panel { + .panel-body { + overflow-y: auto; + } + + span.badge-category { + max-width: 100px; + } } .menu-links.columned { @@ -179,10 +185,6 @@ font-size: $font-down-1; } - span.badge-category { - max-width: 100px; - } - div.discourse-tags { font-size: $font-down-1; } diff --git a/app/assets/stylesheets/common/base/search.scss b/app/assets/stylesheets/common/base/search.scss index b3a445b04f..e4d85303ac 100644 --- a/app/assets/stylesheets/common/base/search.scss +++ b/app/assets/stylesheets/common/base/search.scss @@ -163,6 +163,16 @@ min-width: unset; } } + + .count-group { + .count { + width: 45%; + } + .count-dash { + padding-left: 6px; + vertical-align: middle; + } + } } } } diff --git a/app/assets/stylesheets/common/base/tagging.scss b/app/assets/stylesheets/common/base/tagging.scss index e3ce31e3f4..8222c0fa05 100644 --- a/app/assets/stylesheets/common/base/tagging.scss +++ b/app/assets/stylesheets/common/base/tagging.scss @@ -256,6 +256,10 @@ header .discourse-tag { h1 input { width: 100%; } + .group-access-control { + margin-left: 44px; + margin-bottom: 10px; + } } .group-tags-list .tag-chooser { width: 100%; diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index cc8666133c..24a318e2fb 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -782,3 +782,9 @@ .timezone-input { margin-bottom: 0.5em; } + +.user-invites-page { + .invite-error { + grid-column: 1 / span 2; + } +} diff --git a/app/assets/stylesheets/common/foundation/variables.scss b/app/assets/stylesheets/common/foundation/variables.scss index 82692c8db1..021a689a02 100644 --- a/app/assets/stylesheets/common/foundation/variables.scss +++ b/app/assets/stylesheets/common/foundation/variables.scss @@ -48,6 +48,7 @@ $base-font-size: 0.938em !default; // eq. to 15px $base-font-size-larger: 1.063em !default; // eq. to 17px $base-font-size-largest: 1.118em !default; // eq. to 19px $base-font-family: var(--font-family) !default; +$heading-font-family: var(--heading-font-family) !default; // Font-size defintions, multiplier ^ (step / interval) $font-up-6: 2.296em; diff --git a/app/assets/stylesheets/common/select-kit/single-select.scss b/app/assets/stylesheets/common/select-kit/single-select.scss index 8692177c1d..edb1274160 100644 --- a/app/assets/stylesheets/common/select-kit/single-select.scss +++ b/app/assets/stylesheets/common/select-kit/single-select.scss @@ -14,4 +14,10 @@ border-color: var(--tertiary); } } + + &.is-disabled { + .select-kit-header { + opacity: 0.5; + } + } } diff --git a/app/assets/stylesheets/desktop/login.scss b/app/assets/stylesheets/desktop/login.scss index c5bdc7bdab..dac1f731c5 100644 --- a/app/assets/stylesheets/desktop/login.scss +++ b/app/assets/stylesheets/desktop/login.scss @@ -294,6 +294,9 @@ h2 { margin-bottom: 12px; } + input { + width: 80%; + } } .password-reset, diff --git a/app/assets/stylesheets/embed.scss b/app/assets/stylesheets/embed.scss index 0eddb4068f..34519eb267 100644 --- a/app/assets/stylesheets/embed.scss +++ b/app/assets/stylesheets/embed.scss @@ -2,23 +2,23 @@ @import "./vendor/normalize-ext"; @import "./common/foundation/base"; @import "./common/foundation/variables"; -@import "./common/foundation/colors"; +@import "./color_definitions"; @import "./common/foundation/mixins"; @import "./common/components/buttons"; article.post { - border-bottom: 1px solid #ddd; + border-bottom: 1px solid var(--primary-low); img.avatar { border-radius: 50%; } &.deleted { - background-color: #ffe5e5; + background-color: var(--danger-low); } .quote .title { - border-left: 5px solid $primary-very-low; + border-left: 5px solid var(--primary-very-low); padding: 10px 10px 0 12px; .avatar { margin-right: 7px; @@ -33,7 +33,7 @@ article.post { blockquote { padding: 10px 8px 10px 13px; margin: 0 0 10px 0; - border-left: 5px solid $primary-very-low; + border-left: 5px solid var(--primary-very-low); p { margin: 0 0 10px 0; } @@ -44,7 +44,7 @@ article.post { .post-date { float: right; - color: #aaa; + color: var(--primary-low-mid); font-size: $font-down-1; margin: 10px 4px 0 0; } @@ -83,19 +83,19 @@ article.post { margin: 0 0 10px 0; a { - color: #5c5c5c; + color: var(--primary-high); } a.staff { - background-color: #ffffc2; + background-color: var(--highlight-low); } a.new-user { - color: $primary-low-mid-or-secondary-high; + color: var(--primary-low-mid); } span.title { font-weight: normal; - color: #999; + color: var(--primary-medium); } } @@ -110,12 +110,12 @@ img.emoji { margin: 10px 20px 6px 0; display: inline-block; float: right; - color: #0088cc; + color: var(--tertiary); } .replies { font-size: $font-0; - color: #999; + color: var(--primary-medium); } .clearfix { @@ -127,7 +127,7 @@ header.discourse { padding-right: 10px; padding-bottom: 8px; font-size: $font-up-2; - border-bottom: 3px solid #ddd; + border-bottom: 3px solid var(--primary-low); display: flex; flex-direction: row; @@ -150,7 +150,7 @@ footer { .button { color: white; padding: 6px 8px; - background-color: #0088cc; + background-color: var(--tertiary); display: inline-block; } } @@ -182,7 +182,7 @@ div.lightbox-wrapper { margin: 0.5rem; &:hover { - background: #006da3; + background: var(--tertiary-hover); } span { @@ -211,16 +211,16 @@ div.lightbox-wrapper { clear: both; .main-link { - border-bottom: 1px solid $primary-low; + border-bottom: 1px solid var(--primary-low); padding: 0.5rem; width: 100%; a { - color: $primary; + color: var(--primary); } a:visited { - color: $primary-medium; + color: var(--primary-medium); } } @@ -249,7 +249,7 @@ div.lightbox-wrapper { .topic-last-posted-at, .topic-created-at, .topic-stats { - color: $primary-medium; + color: var(--primary-medium); } } diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index 72945296fb..2b8a34217e 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -29,6 +29,10 @@ span.badge-posts { .topic-post { nav.post-controls { color: var(--primary-low-mid-or-secondary-high); + &.expanded { + // on small devices with many buttons this can overflow + overflow-x: scroll; + } .actions { display: flex; diff --git a/test/stylesheets/test_helper.css b/app/assets/stylesheets/test_helper.css similarity index 100% rename from test/stylesheets/test_helper.css rename to app/assets/stylesheets/test_helper.css diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index c5287f2fac..97ef15dbcd 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -9,16 +9,736 @@ @import "common/select-kit/*"; @import "common/components/svg"; +$bubbles-mask: svg-uri( + ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +' +); + body.wizard { - background-color: #fff; - background-image: asset-url("/images/wizard/bubbles.png"); + background-color: var(--secondary); background-repeat: repeat; background-position: left top; - color: #444; + color: var(--primary-very-high); line-height: $line-height-large; font-size: 15px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + + #wizard-main { + display: flex; + flex-direction: column; + justify-content: center; + min-height: 100%; + } + + #wizard-main:before { + mask: $bubbles-mask; + -webkit-mask: $bubbles-mask; + mask-size: 30%; + -webkit-mask-size: 30%; + background-color: var(--primary-low-mid); + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + content: ""; + opacity: 0.6; + } } .finish-installation { @@ -32,16 +752,20 @@ body.wizard { } .help-text { - color: #999; + color: var(--primary-medium); } } .discourse-logo { - background-image: asset-url("/images/wizard/discourse.png"); - height: 30px; - width: 110px; - background-size: 110px 30px; - background-repeat: no-repeat; + &, + svg { + height: 30px; + width: 110px; + } + + .logo-contour { + fill: var(--primary); + } } .wizard-warning { @@ -67,6 +791,10 @@ body.wizard { width: 400px; } +.hidden { + display: none; +} + .wizard-canvas { position: absolute; top: 0; @@ -80,8 +808,8 @@ body.wizard { .wizard-step-privacy { label[for="privacy_options"] .field-description { - color: #444; margin-bottom: 1em; + font-weight: bold; } .field-privacy-options { @@ -132,8 +860,7 @@ body.wizard { } } -.wizard-step-colors, -.wizard-step-fonts { +.wizard-step-colors { max-height: 465px; overflow-y: auto; .grid { @@ -154,7 +881,7 @@ body.wizard { display: none; } .is-selected { - box-shadow: 0 0 0 5px $tertiary; + box-shadow: 0 0 0 5px var(--tertiary); } div { display: flex; @@ -178,18 +905,34 @@ body.wizard { } } +.wizard-step-fonts { + .dropdown-field { + float: left; + margin-right: 1.5em; + } + .component-field { + clear: both; + } +} + .wizard-column { position: relative; z-index: 11; - background-color: white; - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + background-color: var(--secondary); + box-shadow: 0 5px 10px rgba(var(--primary-rgb), 0.15); box-sizing: border-box; margin: 1.5em auto; padding: 0; max-width: 700px; min-width: 280px; width: 100%; - border: 1px solid #ccc; + border: 1px solid var(--primary-low-mid); + border-radius: 5px; + + a { + text-decoration: none; + color: var(--tertiary); + } .preloaded-font-styles { font-size: 1px; @@ -198,10 +941,6 @@ body.wizard { .wizard-step-contents { height: 550px; margin-bottom: 2em; - a { - text-decoration: none; - color: #6699ff; - } } .wizard-column-contents { @@ -217,17 +956,20 @@ body.wizard { } .wizard-step-banner { margin-bottom: 2em; - width: 660px; + width: 620px; + display: block; } .wizard-footer { - border-top: 1px solid #ccc; - background-color: #eee; + border-top: 1px solid var(--primary-low-mid); + background-color: var(--primary-low); padding: 0.5em; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; } .wizard-progress { - border: 1px solid #a3c1ff; + border: 1px solid var(--tertiary-high); width: 200px; height: 1.4em; @@ -237,13 +979,13 @@ body.wizard { } .white { - background: white; + background: var(--secondary); width: 200px; z-index: 11; } .black { - background: black; + background: var(--primary); transition: width 0.3s; z-index: 12; } @@ -252,13 +994,13 @@ body.wizard { position: absolute; font-size: $font-0; mix-blend-mode: difference; - color: white; + color: var(--secondary-or-primary); z-index: 13; left: 1.5em; } .screen { - background-color: #a3c1ff; + background-color: var(--tertiary-high); mix-blend-mode: screen; width: 200px; z-index: 14; @@ -268,7 +1010,7 @@ body.wizard { .action-link { margin-right: 1em; text-decoration: none; - color: #6699ff; + color: var(--tertiary); } .wizard-btn { @@ -279,10 +1021,10 @@ body.wizard { transition: background-color 0.3s; margin-right: 0.5em; text-decoration: none; - - background-color: #fff; - color: #333; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + background-color: var(--secondary); + color: var(--primary-very-high); + box-shadow: 0 1px 4px rgba(var(--always-black-rgb), 0.4); + cursor: pointer; &.small { padding: 0.25em 0.5em; @@ -291,19 +1033,16 @@ body.wizard { &:hover, &:focus { - background-color: #eee; + background-color: var(--primary-low); } &:active { - background-color: #ddd; - } - - &:disabled { - background-color: #ccc; + background-color: var(--primary-low-mid); } + &:disabled, &.disabled { - background-color: #ccc; + background-color: var(--primary-medium); } .d-icon-chevron-right { @@ -317,39 +1056,39 @@ body.wizard { } .wizard-btn.primary { - background-color: #6699ff; - color: white; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.6); + background-color: var(--tertiary); + color: var(--secondary); + box-shadow: 0 1px 4px rgba(var(--always-black-rgb), 0.6); &:hover, &:focus { - background-color: #80b3ff; + background-color: var(--tertiary-hover); } &:active { - background-color: #4d80e6; + background-color: var(--tertiary-high); } &:disabled { - background-color: #000167; + background-color: var(--tertiary-low); } } .wizard-btn.danger { - background-color: #e60000; - color: white; + background-color: var(--danger); + color: var(--secondary); &:hover, &:focus { - background-color: #cc0000; + background-color: var(--danger-hover); } &:active { - background-color: #b30000; + background-color: var(--danger-medium); } &:disabled { - background-color: #990000; + background-color: var(--danger-low); } } @@ -381,45 +1120,26 @@ body.wizard { } } - .wizard-btn.back { - background-color: #fff; - color: #333; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); - - &:hover, - &:focus { - background-color: #eee; - } - - &:active { - background-color: #ddd; - } - - &:disabled { - background-color: #ccc; - } - } - button.wizard-btn:last-child { margin-right: 0; } button.wizard-btn.done, button.wizard-btn.finish { - color: #fff; - background-color: #33b333; + color: var(--secondary); + background-color: var(--success); &:hover, &:focus { - background-color: #4dcd4d; + background-color: var(--success-hover); } &:active { - background-color: #66e666; + background-color: var(--success-medium); } &:disabled { - background-color: #006700; + background-color: var(--success-low); } } } @@ -449,7 +1169,6 @@ body.wizard { } padding: 0.1em; - border: 1px dotted #bbb; } .wizard-field { @@ -462,53 +1181,29 @@ body.wizard { } .field-error-description { - color: red; + color: var(--danger); font-weight: bold; } .field-description { - color: #999; margin-top: 0.5em; - - a { - color: #7b68ee; - } } margin-bottom: 2em; } } -.wizard-step-themes-further-reading { - .wizard-field .input-area { - margin-top: 0; - } - - .popular-themes { - display: flex; - a.popular-theme-item { - background: #f9f9f9; - padding: 8px; - margin: 0px 4px; - width: 25%; - &:hover, - &:focus { - background: #f3f3f3; - } - } - } -} - .textarea-field { textarea { width: 100%; height: 10em; + background: var(--secondary); } &.invalid { textarea { padding: 3px; - border: 4px solid red; + border: 4px solid var(--danger); } } } @@ -518,14 +1213,15 @@ body.wizard { width: 100%; font-size: $font-up-1; padding: 6px; - border: 1px solid #ccc; + background-color: var(--secondary); + border: 1px solid var(--primary-low-mid); transition: border-color 0.5s; } &.invalid { input { padding: 3px; - border: 4px solid red; + border: 4px solid var(--danger); } } } @@ -546,6 +1242,7 @@ body.wizard { margin-top: 0.25em; margin-left: 1.75em; color: #777; + color: var(--primary-low-mid); } } @@ -624,17 +1321,6 @@ body.wizard { .wizard-column-contents { padding: 1em !important; } - .wizard-step-themes-further-reading { - .popular-themes { - a.popular-theme-item { - width: 33.3%; - &:nth-child(4), - &:nth-child(5) { - display: none; - } - } - } - } .emoji-preview img { width: 16px !important; height: 16px !important; diff --git a/app/controllers/admin/site_settings_controller.rb b/app/controllers/admin/site_settings_controller.rb index f8695f2f0c..2455a5b4b6 100644 --- a/app/controllers/admin/site_settings_controller.rb +++ b/app/controllers/admin/site_settings_controller.rb @@ -16,6 +16,10 @@ class Admin::SiteSettingsController < Admin::AdminController value.strip! if value.is_a?(String) raise_access_hidden_setting(id) + if SiteSetting.type_supervisor.get_type(id) == :uploaded_image_list + value = Upload.get_from_urls(value.split("|")).to_a + end + if SiteSetting.type_supervisor.get_type(id) == :upload value = Upload.get_from_url(value) || "" end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f8cc316c5c..672280d1f0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -47,6 +47,9 @@ class ApplicationController < ActionController::Base after_action :dont_cache_page after_action :conditionally_allow_site_embedding + HONEYPOT_KEY ||= 'HONEYPOT_KEY' + CHALLENGE_KEY ||= 'CHALLENGE_KEY' + layout :set_layout def has_escaped_fragment? @@ -152,13 +155,15 @@ class ApplicationController < ActionController::Base rescue_from RateLimiter::LimitExceeded do |e| retry_time_in_seconds = e&.available_in - render_json_error( - e.description, - type: :rate_limit, - status: 429, - extras: { wait_seconds: retry_time_in_seconds }, - headers: { 'Retry-After': retry_time_in_seconds }, - ) + with_resolved_locale do + render_json_error( + e.description, + type: :rate_limit, + status: 429, + extras: { wait_seconds: retry_time_in_seconds }, + headers: { 'Retry-After': retry_time_in_seconds } + ) + end end rescue_from Discourse::NotLoggedIn do |e| @@ -170,11 +175,15 @@ class ApplicationController < ActionController::Base end rescue_from Discourse::InvalidParameters do |e| - message = I18n.t('invalid_params', message: e.message) + opts = { + custom_message: 'invalid_params', + custom_message_params: { message: e.message } + } + if (request.format && request.format.json?) || request.xhr? || !request.get? - rescue_discourse_actions(:invalid_parameters, 400, include_ember: true, custom_message_translated: message) + rescue_discourse_actions(:invalid_parameters, 400, opts.merge(include_ember: true)) else - rescue_discourse_actions(:not_found, 400, custom_message_translated: message) + rescue_discourse_actions(:not_found, 400, opts) end end @@ -238,10 +247,8 @@ class ApplicationController < ActionController::Base message = title = nil with_resolved_locale(check_current_user: false) do - if opts[:custom_message_translated] - title = message = opts[:custom_message_translated] - elsif opts[:custom_message] - title = message = I18n.t(opts[:custom_message]) + if opts[:custom_message] + title = message = I18n.t(opts[:custom_message], opts[:custom_message_params] || {}) else message = I18n.t(type) if status_code == 403 @@ -322,15 +329,15 @@ class ApplicationController < ActionController::Base current_user.reload current_user.publish_notifications_state cookie_args = {} - cookie_args[:path] = Discourse.base_uri if Discourse.base_uri.present? + cookie_args[:path] = Discourse.base_path if Discourse.base_path.present? cookies.delete('cn', cookie_args) end end end def with_resolved_locale(check_current_user: true) - if check_current_user && current_user - locale = current_user.effective_locale + if check_current_user && (user = current_user rescue nil) + locale = user.effective_locale else if SiteSetting.set_locale_from_accept_language_header locale = locale_from_header @@ -829,6 +836,14 @@ class ApplicationController < ActionController::Base protected + def honeypot_value + secure_session[HONEYPOT_KEY] ||= SecureRandom.hex + end + + def challenge_value + secure_session[CHALLENGE_KEY] ||= SecureRandom.hex + end + def render_post_json(post, add_raw: true) post_serializer = PostSerializer.new(post, scope: guardian, root: false) post_serializer.add_raw = add_raw diff --git a/app/controllers/draft_controller.rb b/app/controllers/draft_controller.rb index 22cedd3836..3cc77a3291 100644 --- a/app/controllers/draft_controller.rb +++ b/app/controllers/draft_controller.rb @@ -22,7 +22,8 @@ class DraftController < ApplicationController params[:draft_key], params[:sequence].to_i, params[:data], - params[:owner] + params[:owner], + force_save: params[:force_save] ) rescue Draft::OutOfSequence diff --git a/app/controllers/extra_locales_controller.rb b/app/controllers/extra_locales_controller.rb index e20c81ea42..314e432900 100644 --- a/app/controllers/extra_locales_controller.rb +++ b/app/controllers/extra_locales_controller.rb @@ -45,7 +45,7 @@ class ExtraLocalesController < ApplicationController end def self.url(bundle) - "#{Discourse.base_uri}/extra-locales/#{bundle}?v=#{bundle_js_hash(bundle)}" + "#{Discourse.base_path}/extra-locales/#{bundle}?v=#{bundle_js_hash(bundle)}" end def self.client_overrides_exist? diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 273fe25c2b..75be316516 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -537,6 +537,7 @@ class GroupsController < ApplicationController def search groups = Group.visible_groups(current_user) .where("groups.id <> ?", Group::AUTO_GROUPS[:everyone]) + .includes(:flair_upload) .order(:name) if (term = params[:term]).present? diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index ed8255e239..530f9e7db8 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -18,23 +18,23 @@ class InvitesController < ApplicationController expires_now invite = Invite.find_by(invite_key: params[:id]) + if invite.present? && !invite.expired? && !invite.redeemed? + store_preloaded("invite_info", MultiJson.dump( + invited_by: UserNameSerializer.new(invite.invited_by, scope: guardian, root: false), + email: invite.email, + username: UserNameSuggester.suggest(invite.email), + is_invite_link: invite.is_invite_link?) + ) - if invite.present? && !invite.expired? - if !invite.redeemed? - store_preloaded("invite_info", MultiJson.dump( - invited_by: UserNameSerializer.new(invite.invited_by, scope: guardian, root: false), - email: invite.email, - username: UserNameSuggester.suggest(invite.email), - is_invite_link: invite.is_invite_link?) - ) - - render layout: 'application' - else - flash.now[:error] = I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url) - render layout: 'no_ember' - end + render layout: 'application' else - flash.now[:error] = I18n.t('invite.not_found', base_url: Discourse.base_url) + flash.now[:error] = if invite.present? && invite.expired? + I18n.t('invite.expired', base_url: Discourse.base_url) + elsif invite.present? && invite.redeemed? + I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url) + else + I18n.t('invite.not_found', base_url: Discourse.base_url) + end render layout: 'no_ember' end end diff --git a/app/controllers/metadata_controller.rb b/app/controllers/metadata_controller.rb index 33d19f2956..594fcab99a 100644 --- a/app/controllers/metadata_controller.rb +++ b/app/controllers/metadata_controller.rb @@ -45,13 +45,13 @@ class MetadataController < ApplicationController name: SiteSetting.title, short_name: SiteSetting.short_title.presence || SiteSetting.title.truncate(12, separator: ' ', omission: ''), display: display, - start_url: Discourse.base_uri.present? ? "#{Discourse.base_uri}/" : '.', + start_url: Discourse.base_path.present? ? "#{Discourse.base_path}/" : '.', background_color: "##{ColorScheme.hex_for_name('secondary', scheme_id)}", theme_color: "##{ColorScheme.hex_for_name('header_background', scheme_id)}", icons: [ ], share_target: { - action: "/new-topic", + action: "#{Discourse.base_path}/new-topic", method: "GET", enctype: "application/x-www-form-urlencoded", params: { @@ -63,7 +63,7 @@ class MetadataController < ApplicationController { name: I18n.t('js.topic.create_long'), short_name: I18n.t('js.topic.create'), - url: "/new-topic", + url: "#{Discourse.base_path}/new-topic", icons: [ { src: "#{icon_url_base}/plus.svg", @@ -75,7 +75,7 @@ class MetadataController < ApplicationController { name: I18n.t('js.user.messages.inbox'), short_name: I18n.t('js.user.messages.inbox'), - url: "/my/messages", + url: "#{Discourse.base_path}/my/messages", icons: [ { src: "#{icon_url_base}/envelope.svg", @@ -87,7 +87,7 @@ class MetadataController < ApplicationController { name: I18n.t('js.user.bookmarks'), short_name: I18n.t('js.user.bookmarks'), - url: "/my/bookmarks", + url: "#{Discourse.base_path}/my/bookmarks", icons: [ { src: "#{icon_url_base}/bookmark.svg", @@ -99,7 +99,7 @@ class MetadataController < ApplicationController { name: I18n.t('js.filters.top.title'), short_name: I18n.t('js.filters.top.title'), - url: "/top", + url: "#{Discourse.base_path}/top", icons: [ { src: "#{icon_url_base}/signal.svg", diff --git a/app/controllers/robots_txt_controller.rb b/app/controllers/robots_txt_controller.rb index 90fdb286d0..7de07939a0 100644 --- a/app/controllers/robots_txt_controller.rb +++ b/app/controllers/robots_txt_controller.rb @@ -45,8 +45,8 @@ class RobotsTxtController < ApplicationController end def self.fetch_default_robots_info - deny_paths = DISALLOWED_PATHS.map { |p| Discourse.base_uri + p } - deny_all = [ "#{Discourse.base_uri}/" ] + deny_paths = DISALLOWED_PATHS.map { |p| Discourse.base_path + p } + deny_all = [ "#{Discourse.base_path}/" ] result = { header: "# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file", diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index 024d746aeb..01bd6df4bf 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -451,6 +451,17 @@ class SessionController < ApplicationController end end + def get_honeypot_value + secure_session.set(HONEYPOT_KEY, honeypot_value, expires: 1.hour) + secure_session.set(CHALLENGE_KEY, challenge_value, expires: 1.hour) + + render json: { + value: honeypot_value, + challenge: challenge_value, + expires_in: SecureSession.expiry + } + end + protected def check_local_login_allowed(user: nil, check_login_via_email: false) diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb index f1ea98d34d..c2df1759ce 100644 --- a/app/controllers/site_controller.rb +++ b/app/controllers/site_controller.rb @@ -25,16 +25,6 @@ class SiteController < ApplicationController render json: custom_emoji end - def selectable_avatars - avatars = if SiteSetting.selectable_avatars_enabled? - (SiteSetting.selectable_avatars.presence || "").split("\n") - else - [] - end - - render json: avatars, root: false - end - def basic_info results = { logo_url: UrlHelper.absolute(SiteSetting.site_logo_url), diff --git a/app/controllers/tag_groups_controller.rb b/app/controllers/tag_groups_controller.rb index 6b87032fa7..58a8f98ba1 100644 --- a/app/controllers/tag_groups_controller.rb +++ b/app/controllers/tag_groups_controller.rb @@ -82,19 +82,13 @@ class TagGroupsController < ApplicationController tag_group = params.delete(:tag_group) params.merge!(tag_group.permit!) if tag_group - if permissions = params[:permissions] - permissions.each do |k, v| - permissions[k] = v.to_i - end - end - result = params.permit( :id, :name, :one_per_topic, tag_names: [], parent_tag_name: [], - permissions: permissions&.keys, + permissions: {} ) result[:tag_names] ||= [] diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 2dfa10647c..c6e5810a4e 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -478,7 +478,7 @@ class TagsController < ::ApplicationController permalink = Permalink.find_by_url(url) if permalink.present? && permalink.category_id - redirect_to "#{Discourse::base_uri}/tags#{permalink.target_url}/#{params[:tag_id]}", status: :moved_permanently + redirect_to "#{Discourse.base_path}/tags#{permalink.target_url}/#{params[:tag_id]}", status: :moved_permanently else # redirect to 404 raise Discourse::NotFound diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 08a031630f..07a2a6b039 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -846,6 +846,7 @@ class TopicsController < ApplicationController elsif params[:filter] == 'unread' tq = TopicQuery.new(current_user) topics = TopicQuery.unread_filter(tq.joined_topic_user, current_user.id, staff: guardian.is_staff?).listable_topics + topics = TopicQuery.tracked_filter(topics, current_user.id) if params[:tracked].to_s == "true" if params[:category_id] if params[:include_subcategories] @@ -893,8 +894,14 @@ class TopicsController < ApplicationController TopicTrackingState.publish_dismiss_new(current_user.id, category_id) end else - current_user.user_stat.update_column(:new_since, Time.zone.now) - TopicTrackingState.publish_dismiss_new(current_user.id) + if params[:tracked].to_s == "true" + topics = TopicQuery.tracked_filter(TopicQuery.new(current_user).new_results, current_user.id) + topic_users = topics.map { |topic| { topic_id: topic.id, user_id: current_user.id, last_read_post_number: 0 } } + TopicUser.insert_all(topic_users) if !topic_users.empty? + else + current_user.user_stat.update_column(:new_since, Time.zone.now) + TopicTrackingState.publish_dismiss_new(current_user.id) + end end render body: nil end diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 366c06548a..814b65ac39 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -111,7 +111,7 @@ class UploadsController < ApplicationController if Discourse.store.internal? send_file_local_upload(upload) else - redirect_to Discourse.store.url_for(upload, force_download: params[:dl] == "1") + redirect_to Discourse.store.url_for(upload, force_download: force_download?) end else render_404 @@ -160,11 +160,13 @@ class UploadsController < ApplicationController # url_for figures out the full URL, handling multisite DBs, # and will return a presigned URL for the upload if path_with_ext.blank? - return redirect_to Discourse.store.url_for(upload) + return redirect_to Discourse.store.url_for(upload, force_download: force_download?) end redirect_to Discourse.store.signed_url_for_path( - path_with_ext, expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS + path_with_ext, + expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS, + force_download: force_download? ) end @@ -183,6 +185,10 @@ class UploadsController < ApplicationController protected + def force_download? + params[:dl] == "1" + end + def xhr_not_allowed raise Discourse::InvalidParameters.new("XHR not allowed") end diff --git a/app/controllers/user_api_keys_controller.rb b/app/controllers/user_api_keys_controller.rb index 6de32f04af..b8609190e2 100644 --- a/app/controllers/user_api_keys_controller.rb +++ b/app/controllers/user_api_keys_controller.rb @@ -71,7 +71,7 @@ class UserApiKeysController < ApplicationController client_id: params[:client_id], user_id: current_user.id, push_url: params[:push_url], - scopes: scopes + scopes: scopes.map { |name| UserApiKeyScope.new(name: name) } ) # we keep the payload short so it encrypts easily with public key @@ -147,9 +147,6 @@ class UserApiKeysController < ApplicationController if current_key = request.env['HTTP_USER_API_KEY'] request_key = UserApiKey.with_key(current_key).first revoke_key ||= request_key - if request_key && request_key.id != revoke_key.id && !request_key.scopes.include?("write") - raise Discourse::InvalidAccess - end end raise Discourse::NotFound unless revoke_key diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 674c39bbc7..854acb4cc5 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -32,7 +32,7 @@ class Users::OmniauthCallbacksController < ApplicationController # Save to redis, with a secret token, then redirect to confirmation screen token = SecureRandom.hex Discourse.redis.setex "#{Users::AssociateAccountsController::REDIS_PREFIX}_#{current_user.id}_#{token}", 10.minutes, auth.to_json - return redirect_to "#{Discourse.base_uri}/associate/#{token}" + return redirect_to "#{Discourse.base_path}/associate/#{token}" else @auth_result = authenticator.after_authenticate(auth) DiscourseEvent.trigger(:after_auth, authenticator, @auth_result) @@ -55,14 +55,14 @@ class Users::OmniauthCallbacksController < ApplicationController if parsed && # Valid (parsed.host == nil || parsed.host == Discourse.current_hostname) && # Local - !parsed.path.starts_with?("#{Discourse.base_uri}/auth/") # Not /auth URL + !parsed.path.starts_with?("#{Discourse.base_path}/auth/") # Not /auth URL @origin = +"#{parsed.path}" @origin << "?#{parsed.query}" if parsed.query end end if @origin.blank? - @origin = Discourse.base_uri("/") + @origin = Discourse.base_path("/") end @auth_result.destination_url = @origin @@ -76,7 +76,7 @@ class Users::OmniauthCallbacksController < ApplicationController cookies['_bypass_cache'] = true cookies[:authentication_data] = { value: @auth_result.to_client_hash.to_json, - path: Discourse.base_uri("/") + path: Discourse.base_path("/") } redirect_to @origin end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index e2f2e7c208..b3fdf66926 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -11,7 +11,7 @@ class UsersController < ApplicationController :update_second_factor, :create_second_factor_backup, :select_avatar, :notification_level, :revoke_auth_token, :register_second_factor_security_key, :create_second_factor_security_key, :feature_topic, :clear_featured_topic, - :bookmarks, :invited + :bookmarks, :invited, :invite_links ] skip_before_action :check_xhr, only: [ @@ -35,7 +35,6 @@ class UsersController < ApplicationController skip_before_action :verify_authenticity_token, only: [:create] skip_before_action :redirect_to_login_if_required, only: [:check_username, :create, - :get_honeypot_value, :account_created, :activate_account, :perform_account_activation, @@ -370,57 +369,87 @@ class UsersController < ApplicationController end def invited - guardian.ensure_can_invite_to_forum! + if guardian.can_invite_to_forum? + offset = params[:offset].to_i || 0 + filter_by = params[:filter] || "redeemed" + inviter = fetch_user_from_params(include_inactive: current_user.staff? || SiteSetting.show_inactive_accounts) - offset = params[:offset].to_i || 0 - filter_by = params[:filter] || "redeemed" - inviter = fetch_user_from_params(include_inactive: current_user.staff? || SiteSetting.show_inactive_accounts) + invites = if guardian.can_see_invite_details?(inviter) && filter_by == "pending" + Invite.find_pending_invites_from(inviter, offset) + elsif filter_by == "redeemed" + Invite.find_redeemed_invites_from(inviter, offset) + else + [] + end - invites = if guardian.can_see_invite_details?(inviter) && filter_by == "pending" - Invite.find_pending_invites_from(inviter, offset) - elsif filter_by == "redeemed" - Invite.find_redeemed_invites_from(inviter, offset) + show_emails = guardian.can_see_invite_emails?(inviter) + if params[:search].present? && invites.present? + filter_sql = '(LOWER(users.username) LIKE :filter)' + filter_sql = '(LOWER(invites.email) LIKE :filter) or (LOWER(users.username) LIKE :filter)' if show_emails + invites = invites.where(filter_sql, filter: "%#{params[:search].downcase}%") + end + + render json: MultiJson.dump(InvitedSerializer.new( + OpenStruct.new(invite_list: invites.to_a, show_emails: show_emails, inviter: inviter, type: filter_by), + scope: guardian, + root: false + )) else - [] - end + if current_user&.staff? + message = if SiteSetting.enable_sso + I18n.t("invite.disabled_errors.sso_enabled") + elsif !SiteSetting.enable_local_logins + I18n.t("invite.disabled_errors.local_logins_disabled") + end - show_emails = guardian.can_see_invite_emails?(inviter) - if params[:search].present? && invites.present? - filter_sql = '(LOWER(users.username) LIKE :filter)' - filter_sql = '(LOWER(invites.email) LIKE :filter) or (LOWER(users.username) LIKE :filter)' if show_emails - invites = invites.where(filter_sql, filter: "%#{params[:search].downcase}%") + render_invite_error(message) + else + render_json_error(I18n.t("invite.disabled_errors.invalid_access")) + end end - - render json: MultiJson.dump(InvitedSerializer.new( - OpenStruct.new(invite_list: invites.to_a, show_emails: show_emails, inviter: inviter, type: filter_by), - scope: guardian, - root: false - )) end def invite_links - guardian.ensure_can_invite_to_forum! + if guardian.can_invite_to_forum? + inviter = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) + guardian.ensure_can_see_invite_details!(inviter) - inviter = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) - guardian.ensure_can_see_invite_details!(inviter) + offset = params[:offset].to_i || 0 + invites = Invite.find_links_invites_from(inviter, offset) - offset = params[:offset].to_i || 0 - invites = Invite.find_links_invites_from(inviter, offset) + render json: MultiJson.dump(invites: serialize_data(invites.to_a, InviteLinkSerializer), can_see_invite_details: guardian.can_see_invite_details?(inviter)) + else + if current_user&.staff? + message = if SiteSetting.enable_sso + I18n.t("invite.disabled_errors.sso_enabled") + elsif !SiteSetting.enable_local_logins + I18n.t("invite.disabled_errors.local_logins_disabled") + end - render json: MultiJson.dump(invites: serialize_data(invites.to_a, InviteLinkSerializer), can_see_invite_details: guardian.can_see_invite_details?(inviter)) + render_invite_error(message) + else + render_json_error(I18n.t("invite.disabled_errors.invalid_access")) + end + end end def invited_count - guardian.ensure_can_invite_to_forum! + if guardian.can_invite_to_forum? + inviter = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) - inviter = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) + pending_count = Invite.find_pending_invites_count(inviter) + redeemed_count = Invite.find_redeemed_invites_count(inviter) + links_count = Invite.find_links_invites_count(inviter) - pending_count = Invite.find_pending_invites_count(inviter) - redeemed_count = Invite.find_redeemed_invites_count(inviter) - links_count = Invite.find_links_invites_count(inviter) - - render json: { counts: { pending: pending_count, redeemed: redeemed_count, links: links_count, - total: (pending_count.to_i + redeemed_count.to_i) } } + render json: { counts: { pending: pending_count, redeemed: redeemed_count, links: links_count, + total: (pending_count.to_i + redeemed_count.to_i) } } + else + if current_user&.staff? + render json: { counts: 0 } + else + render_json_error(I18n.t("invite.disabled_errors.invalid_access")) + end + end end def is_local_username @@ -643,17 +672,6 @@ class UsersController < ApplicationController } end - def get_honeypot_value - secure_session.set(HONEYPOT_KEY, honeypot_value, expires: 1.hour) - secure_session.set(CHALLENGE_KEY, challenge_value, expires: 1.hour) - - render json: { - value: honeypot_value, - challenge: challenge_value, - expires_in: SecureSession.expiry - } - end - def password_reset_show expires_now token = params[:token] @@ -1138,7 +1156,7 @@ class UsersController < ApplicationController return render json: failed_json, status: 422 end - unless SiteSetting.selectable_avatars[upload.sha1] + unless SiteSetting.selectable_avatars.include?(upload) return render json: failed_json, status: 422 end @@ -1522,19 +1540,6 @@ class UsersController < ApplicationController end end - HONEYPOT_KEY ||= 'HONEYPOT_KEY' - CHALLENGE_KEY ||= 'CHALLENGE_KEY' - - protected - - def honeypot_value - secure_session[HONEYPOT_KEY] ||= SecureRandom.hex - end - - def challenge_value - secure_session[CHALLENGE_KEY] ||= SecureRandom.hex - end - private def password_reset_find_user(token, committing_change:) @@ -1664,4 +1669,12 @@ class UsersController < ApplicationController def summary_cache_key(user) "user_summary:#{user.id}:#{current_user ? current_user.id : 0}" end + + def render_invite_error(message) + render json: { + invites: [], + can_see_invite_details: false, + error: message + } + end end diff --git a/app/controllers/users_email_controller.rb b/app/controllers/users_email_controller.rb index 898ddd6897..05a0a55857 100644 --- a/app/controllers/users_email_controller.rb +++ b/app/controllers/users_email_controller.rb @@ -13,16 +13,12 @@ class UsersEmailController < ApplicationController skip_before_action :redirect_to_login_if_required, only: [ :confirm_old_email, - :show_confirm_old_email, - :confirm_new_email, - :show_confirm_new_email + :show_confirm_old_email ] before_action :require_login, only: [ :confirm_old_email, - :show_confirm_old_email, - :confirm_new_email, - :show_confirm_new_email + :show_confirm_old_email ] def index @@ -218,7 +214,7 @@ class UsersEmailController < ApplicationController @error = I18n.t("change_email.already_done") end - if current_user.id != @user&.id + if current_user && current_user.id != @user&.id @error = I18n.t 'change_email.wrong_account_error' end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bd160babd2..84a2b28554 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -300,7 +300,7 @@ module ApplicationHelper end def login_path - "#{Discourse::base_uri}/login" + "#{Discourse.base_path}/login" end def mobile_view? @@ -488,7 +488,7 @@ module ApplicationHelper setup_data = { cdn: Rails.configuration.action_controller.asset_host, base_url: Discourse.base_url, - base_uri: Discourse::base_uri, + base_uri: Discourse.base_path, environment: Rails.env, letter_avatar_version: LetterAvatar.version, markdown_it_url: script_asset_path('markdown-it-bundle'), diff --git a/app/helpers/user_notifications_helper.rb b/app/helpers/user_notifications_helper.rb index ffbf7352c2..e2f3f05906 100644 --- a/app/helpers/user_notifications_helper.rb +++ b/app/helpers/user_notifications_helper.rb @@ -98,11 +98,11 @@ module UserNotificationsHelper end def email_image_url(basename) - UrlHelper.absolute("#{Discourse.base_uri}/images/emails/#{basename}") + UrlHelper.absolute("#{Discourse.base_path}/images/emails/#{basename}") end def url_for_email(href) - URI(href).host.present? ? href : UrlHelper.absolute("#{Discourse.base_uri}#{href}") + URI(href).host.present? ? href : UrlHelper.absolute("#{Discourse.base_path}#{href}") rescue URI::Error href end diff --git a/app/jobs/regular/export_user_archive.rb b/app/jobs/regular/export_user_archive.rb index f2e77b0088..fe5d0ea7c0 100644 --- a/app/jobs/regular/export_user_archive.rb +++ b/app/jobs/regular/export_user_archive.rb @@ -12,7 +12,9 @@ module Jobs COMPONENTS ||= %w( user_archive - user_archive_profile + preferences + auth_tokens + auth_token_logs badges bookmarks category_preferences @@ -22,6 +24,8 @@ module Jobs HEADER_ATTRS_FOR ||= HashWithIndifferentAccess.new( user_archive: ['topic_title', 'categories', 'is_pm', 'post', 'like_count', 'reply_count', 'url', 'created_at'], user_archive_profile: ['location', 'website', 'bio', 'views'], + auth_tokens: ['id', 'auth_token_hash', 'prev_auth_token_hash', 'auth_token_seen', 'client_ip', 'user_agent', 'seen_at', 'rotated_at', 'created_at', 'updated_at'], + auth_token_logs: ['id', 'action', 'user_auth_token_id', 'client_ip', 'auth_token_hash', 'created_at', 'path', 'user_agent'], badges: ['badge_id', 'badge_name', 'granted_at', 'post_id', 'seq', 'granted_manually', 'notification_id', 'featured_rank'], bookmarks: ['post_id', 'topic_id', 'post_number', 'link', 'name', 'created_at', 'updated_at', 'reminder_type', 'reminder_at', 'reminder_last_sent_at', 'reminder_set_at', 'auto_delete_preference'], category_preferences: ['category_id', 'category_names', 'notification_level', 'dismiss_new_timestamp'], @@ -38,12 +42,15 @@ module Jobs COMPONENTS.each do |name| h = { name: name, method: :"#{name}_export" } h[:filetype] = :csv - filename_method = :"#{name}_filename" - if respond_to? filename_method - h[:filename] = public_send(filename_method) - else - h[:filename] = name + filetype_method = :"#{name}_filetype" + if respond_to? filetype_method + h[:filetype] = public_send(filetype_method) end + condition_method = :"include_#{name}?" + if respond_to? condition_method + h[:skip] = !public_send(condition_method) + end + h[:filename] = name components.push(h) end @@ -61,12 +68,17 @@ module Jobs zip_filename = nil begin components.each do |component| + next if component[:skip] case component[:filetype] when :csv CSV.open("#{dirname}/#{component[:filename]}.csv", "w") do |csv| csv << get_header(component[:name]) public_send(component[:method]) { |d| csv << d } end + when :json + File.open("#{dirname}/#{component[:filename]}.json", "w") do |file| + file.write MultiJson.dump(public_send(component[:method]), indent: 4) + end else raise 'unknown export filetype' end @@ -132,6 +144,59 @@ module Jobs end end + def preferences_export + UserSerializer.new(@current_user, scope: guardian) + end + + def preferences_filetype + :json + end + + def auth_tokens_export + return enum_for(:auth_tokens) unless block_given? + + UserAuthToken + .where(user_id: @current_user.id) + .each do |token| + yield [ + token.id, + token.auth_token.to_s[0..4] + "...", # hashed and truncated + token.prev_auth_token[0..4] + "...", + token.auth_token_seen, + token.client_ip, + token.user_agent, + token.seen_at, + token.rotated_at, + token.created_at, + token.updated_at, + ] + end + end + + def include_auth_token_logs? + # SiteSetting.verbose_auth_token_logging + UserAuthTokenLog.where(user_id: @current_user.id).exists? + end + + def auth_token_logs_export + return enum_for(:auth_token_logs) unless block_given? + + UserAuthTokenLog + .where(user_id: @current_user.id) + .each do |log| + yield [ + log.id, + log.action, + log.user_auth_token_id, + log.client_ip, + log.auth_token.to_s[0..4] + "...", # hashed and truncated + log.created_at, + log.path, + log.user_agent, + ] + end + end + def badges_export return enum_for(:badges_export) unless block_given? diff --git a/app/jobs/regular/user_email.rb b/app/jobs/regular/user_email.rb index d39e02ce4f..4c1e3c3ccb 100644 --- a/app/jobs/regular/user_email.rb +++ b/app/jobs/regular/user_email.rb @@ -22,6 +22,15 @@ module Jobs # of extra work when emails are disabled. return if quit_email_early? + send_user_email(args) + + if args[:user_id].present? && args[:type].to_s == "digest" + # Record every attempt at sending a digest email, even if it was skipped + UserStat.where(user_id: args[:user_id]).update_all(digest_attempted_at: Time.zone.now) + end + end + + def send_user_email(args) post = nil notification = nil type = args[:type] @@ -153,7 +162,18 @@ module Jobs # Make sure that mailer exists raise Discourse::InvalidParameters.new("type=#{type}") unless UserNotifications.respond_to?(type) - email_args[:email_token] = email_token if email_token.present? + if email_token.present? + email_args[:email_token] = email_token + + if type.to_s == "confirm_new_email" + change_req = EmailChangeRequest.find_by_new_token(email_token) + + if change_req + email_args[:requested_by_admin] = change_req.requested_by_admin? + end + end + end + email_args[:new_email] = args[:new_email] || user.email if type.to_s == "notify_old_email" || type.to_s == "notify_old_email_add" if args[:client_ip] && args[:user_agent] diff --git a/app/jobs/scheduled/clean_up_uploads.rb b/app/jobs/scheduled/clean_up_uploads.rb index 75e91a8df3..2b735f61a5 100644 --- a/app/jobs/scheduled/clean_up_uploads.rb +++ b/app/jobs/scheduled/clean_up_uploads.rb @@ -25,23 +25,6 @@ module Jobs s3_hostname = URI.parse(base_url).hostname s3_cdn_hostname = URI.parse(SiteSetting.Upload.s3_cdn_url || "").hostname - # Any URLs in site settings are fair game - ignore_urls = [ - *SiteSetting.selectable_avatars.split("\n"), - ].flatten.map do |url| - if url.present? - url = url.dup - - if s3_cdn_hostname.present? && s3_hostname.present? - url.gsub!(s3_cdn_hostname, s3_hostname) - end - - url[base_url] && url[url.index(base_url)..-1] - else - nil - end - end.compact.uniq - result = Upload.by_users .where("uploads.retain_hours IS NULL OR uploads.created_at < current_timestamp - interval '1 hour' * uploads.retain_hours") .where("uploads.created_at < ?", grace_period.hour.ago) @@ -71,7 +54,9 @@ module Jobs .where("g.flair_upload_id IS NULL") .where("ss.value IS NULL") - result = result.where("uploads.url NOT IN (?)", ignore_urls) if ignore_urls.present? + if SiteSetting.selectable_avatars.present? + result = result.where.not(id: SiteSetting.selectable_avatars.map(&:id)) + end result.find_each do |upload| if upload.sha1.present? diff --git a/app/jobs/scheduled/enqueue_digest_emails.rb b/app/jobs/scheduled/enqueue_digest_emails.rb index b126dc1553..4496f2548b 100644 --- a/app/jobs/scheduled/enqueue_digest_emails.rb +++ b/app/jobs/scheduled/enqueue_digest_emails.rb @@ -6,8 +6,10 @@ module Jobs every 30.minutes def execute(args) - return if SiteSetting.disable_digest_emails? || SiteSetting.private_email? - target_user_ids.each do |user_id| + return if SiteSetting.disable_digest_emails? || SiteSetting.private_email? || SiteSetting.disable_emails == 'yes' + users = target_user_ids + + users.each do |user_id| ::Jobs.enqueue(:user_email, type: :digest, user_id: user_id) end end @@ -24,12 +26,16 @@ module Jobs .where("user_stats.bounce_score < #{SiteSetting.bounce_score_threshold}") .where("user_emails.primary") .where("COALESCE(last_emailed_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)") + .where("COALESCE(user_stats.digest_attempted_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)") .where("COALESCE(last_seen_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)") .where("COALESCE(last_seen_at, '2010-01-01') >= CURRENT_TIMESTAMP - ('1 DAY'::INTERVAL * #{SiteSetting.suppress_digest_email_after_days})") + .order("user_stats.digest_attempted_at ASC NULLS FIRST") # If the site requires approval, make sure the user is approved query = query.where("approved OR moderator OR admin") if SiteSetting.must_approve_users? + query = query.limit(GlobalSetting.max_digests_enqueued_per_30_mins_per_site) + query.pluck(:id) end diff --git a/app/jobs/scheduled/ensure_db_consistency.rb b/app/jobs/scheduled/ensure_db_consistency.rb index c687cbec34..6ecb928d07 100644 --- a/app/jobs/scheduled/ensure_db_consistency.rb +++ b/app/jobs/scheduled/ensure_db_consistency.rb @@ -45,7 +45,7 @@ module Jobs private def format_measure - result = +"EnsureDbConsitency Times\n" + result = +"EnsureDbConsistency Times\n" result << @measure_times.map do |name, duration| " #{name}: #{duration}" end.join("\n") diff --git a/app/jobs/scheduled/weekly.rb b/app/jobs/scheduled/weekly.rb index c71ac7042d..a020fa11f1 100644 --- a/app/jobs/scheduled/weekly.rb +++ b/app/jobs/scheduled/weekly.rb @@ -14,6 +14,7 @@ module Jobs UserAuthToken.cleanup! Email::Cleaner.delete_rejected! Notification.purge_old! + Bookmark.cleanup! end end end diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index 6e8b69aa3d..93ee5e3d88 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -88,7 +88,7 @@ class UserNotifications < ActionMailer::Base def confirm_new_email(user, opts = {}) build_user_email_token_by_template( - "user_notifications.confirm_new_email", + opts[:requested_by_admin] ? "user_notifications.confirm_new_email_via_admin" : "user_notifications.confirm_new_email", user, opts[:email_token] ) diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index 889be79bd9..79fe3feac6 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -200,7 +200,7 @@ class AdminDashboardData end def subfolder_ends_in_slash_check - I18n.t('dashboard.subfolder_ends_in_slash') if Discourse.base_uri =~ /\/$/ + I18n.t('dashboard.subfolder_ends_in_slash') if Discourse.base_path =~ /\/$/ end def pop3_polling_configuration diff --git a/app/models/badge.rb b/app/models/badge.rb index 69d06ded61..92e02a50a9 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -263,7 +263,7 @@ class Badge < ActiveRecord::Base def long_description key = "badges.#{i18n_name}.long_description" - I18n.t(key, default: self[:long_description] || '', base_uri: Discourse.base_uri, max_likes_per_day: SiteSetting.max_likes_per_day) + I18n.t(key, default: self[:long_description] || '', base_uri: Discourse.base_path, max_likes_per_day: SiteSetting.max_likes_per_day) end def long_description=(val) @@ -273,7 +273,7 @@ class Badge < ActiveRecord::Base def description key = "badges.#{i18n_name}.description" - I18n.t(key, default: self[:description] || '', base_uri: Discourse.base_uri, max_likes_per_day: SiteSetting.max_likes_per_day) + I18n.t(key, default: self[:description] || '', base_uri: Discourse.base_path, max_likes_per_day: SiteSetting.max_likes_per_day) end def description=(val) diff --git a/app/models/bookmark.rb b/app/models/bookmark.rb index a8f7a37f5a..1dc75828f7 100644 --- a/app/models/bookmark.rb +++ b/app/models/bookmark.rb @@ -103,6 +103,20 @@ class Bookmark < ActiveRecord::Base .order('date(bookmarks.created_at)') .count end + + ## + # Deletes bookmarks that are attached to posts/topics that were deleted + # more than X days ago. We don't delete bookmarks instantly when a post/topic + # is deleted so that there is a grace period to un-delete. + def self.cleanup! + grace_time = 3.days.ago + DB.exec(<<~SQL, grace_time: grace_time) + DELETE FROM bookmarks b + USING topics t, posts p + WHERE (b.topic_id = t.id AND b.post_id = p.id) + AND (t.deleted_at < :grace_time OR p.deleted_at < :grace_time) + SQL + end end # == Schema Information diff --git a/app/models/category.rb b/app/models/category.rb index 7c2a8ea51e..48715f4d4e 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -49,7 +49,7 @@ class Category < ActiveRecord::Base validates :user_id, presence: true - validates :name, if: Proc.new { |c| c.new_record? || c.will_save_change_to_name? }, + validates :name, if: Proc.new { |c| c.new_record? || c.will_save_change_to_name? || c.will_save_change_to_parent_category_id? }, presence: true, uniqueness: { scope: :parent_category_id, case_sensitive: false }, length: { in: 1..50 } @@ -724,7 +724,7 @@ class Category < ActiveRecord::Base end def full_slug(separator = "-") - start_idx = "#{Discourse.base_uri}/c/".size + start_idx = "#{Discourse.base_path}/c/".size url[start_idx..-1].gsub("/", separator) end @@ -735,7 +735,7 @@ class Category < ActiveRecord::Base end def url - @@url_cache[self.id] ||= "#{Discourse.base_uri}/c/#{slug_path.join('/')}/#{self.id}" + @@url_cache[self.id] ||= "#{Discourse.base_path}/c/#{slug_path.join('/')}/#{self.id}" end def url_with_id @@ -756,7 +756,7 @@ class Category < ActiveRecord::Base def create_category_permalink old_slug = saved_changes.transform_values(&:first)["slug"] - url = +"#{Discourse.base_uri}/c" + url = +"#{Discourse.base_path}/c" url << "/#{parent_category.slug_path.join('/')}" if parent_category_id url << "/#{old_slug}/#{id}" url = Permalink.normalize_url(url) diff --git a/app/models/color_scheme.rb b/app/models/color_scheme.rb index ab82c117a9..66328721f4 100644 --- a/app/models/color_scheme.rb +++ b/app/models/color_scheme.rb @@ -196,6 +196,7 @@ class ColorScheme < ActiveRecord::Base new_color_scheme = new(name: params[:name]) new_color_scheme.via_wizard = true if params[:via_wizard] new_color_scheme.base_scheme_id = params[:base_scheme_id] + new_color_scheme.user_selectable = true if params[:user_selectable] colors = CUSTOM_SCHEMES[params[:base_scheme_id].to_sym]&.map do |name, hex| { name: name, hex: hex } diff --git a/app/models/concerns/has_url.rb b/app/models/concerns/has_url.rb index 300dafc11c..dc6c58cee1 100644 --- a/app/models/concerns/has_url.rb +++ b/app/models/concerns/has_url.rb @@ -44,5 +44,29 @@ module HasUrl result || self.find_by("url LIKE ?", "%#{data[1]}") end + + def get_from_urls(upload_urls) + urls = [] + sha1s = [] + + upload_urls.each do |url| + next if url.blank? + + uri = begin + URI(UrlHelper.unencode(url)) + rescue URI::Error + end + + next if uri&.path.blank? + urls << uri.path + + if data = extract_url(uri.path).presence + urls << data[1] + sha1s << data[2] if self.name == "Upload" + end + end + + self.where(url: urls).or(self.where(sha1: sha1s)) + end end end diff --git a/app/models/draft.rb b/app/models/draft.rb index 2165a0ad8a..6d34463bee 100644 --- a/app/models/draft.rb +++ b/app/models/draft.rb @@ -9,8 +9,9 @@ class Draft < ActiveRecord::Base class OutOfSequence < StandardError; end - def self.set(user, key, sequence, data, owner = nil) + def self.set(user, key, sequence, data, owner = nil, force_save: false) return 0 if !User.human_user_id?(user.id) + force_save = force_save.to_s == "true" if SiteSetting.backup_drafts_to_pm_length > 0 && SiteSetting.backup_drafts_to_pm_length < data.length backup_draft(user, key, sequence, data) @@ -41,10 +42,11 @@ class Draft < ActiveRecord::Base current_sequence ||= 0 if draft_id - if current_sequence != sequence + if !force_save && (current_sequence != sequence) raise Draft::OutOfSequence end + sequence = current_sequence if force_save sequence += 1 # we need to keep upping our sequence on every save diff --git a/app/models/email_change_request.rb b/app/models/email_change_request.rb index 4307cdb457..be1448ff92 100644 --- a/app/models/email_change_request.rb +++ b/app/models/email_change_request.rb @@ -4,6 +4,7 @@ class EmailChangeRequest < ActiveRecord::Base belongs_to :old_email_token, class_name: 'EmailToken' belongs_to :new_email_token, class_name: 'EmailToken' belongs_to :user + belongs_to :requested_by, class_name: "User", foreign_key: :requested_by_user_id validates :new_email, presence: true, format: { with: EmailValidator.email_regex } @@ -11,23 +12,38 @@ class EmailChangeRequest < ActiveRecord::Base @states ||= Enum.new(authorizing_old: 1, authorizing_new: 2, complete: 3) end + def requested_by_admin? + self.requested_by.admin? && !self.requested_by_self? + end + + def requested_by_self? + self.requested_by_user_id == self.user_id + end + + def self.find_by_new_token(token) + joins( + "INNER JOIN email_tokens ON email_tokens.id = email_change_requests.new_email_token_id" + ).where("email_tokens.token = ?", token).last + end end # == Schema Information # # Table name: email_change_requests # -# id :integer not null, primary key -# user_id :integer not null -# old_email :string -# new_email :string not null -# old_email_token_id :integer -# new_email_token_id :integer -# change_state :integer not null -# created_at :datetime not null -# updated_at :datetime not null +# id :integer not null, primary key +# user_id :integer not null +# old_email :string +# new_email :string not null +# old_email_token_id :integer +# new_email_token_id :integer +# change_state :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# requested_by_user_id :integer # # Indexes # -# index_email_change_requests_on_user_id (user_id) +# idx_email_change_requests_on_requested_by (requested_by_user_id) +# index_email_change_requests_on_user_id (user_id) # diff --git a/app/models/emoji.rb b/app/models/emoji.rb index 7222cafebc..f7c7b70c90 100644 --- a/app/models/emoji.rb +++ b/app/models/emoji.rb @@ -73,7 +73,7 @@ class Emoji def self.url_for(name) name = name.delete_prefix(':').delete_suffix(':').gsub(/(.+):t([1-6])/, '\1/\2') - "#{Discourse.base_uri}/images/emoji/#{SiteSetting.emoji_set}/#{name}.png?v=#{EMOJI_VERSION}" + "#{Discourse.base_path}/images/emoji/#{SiteSetting.emoji_set}/#{name}.png?v=#{EMOJI_VERSION}" end def self.cache_key(name) @@ -115,7 +115,7 @@ class Emoji emojis.each do |name, url| result << Emoji.new.tap do |e| e.name = name - url = (Discourse.base_uri + url) if url[/^\/[^\/]/] + url = (Discourse.base_path + url) if url[/^\/[^\/]/] e.url = url e.group = group || DEFAULT_GROUP end @@ -135,7 +135,7 @@ class Emoji def self.base_url db = RailsMultisite::ConnectionManagement.current_db - "#{Discourse.base_uri}/uploads/#{db}/_emoji" + "#{Discourse.base_path}/uploads/#{db}/_emoji" end def self.replacement_code(code) diff --git a/app/models/javascript_cache.rb b/app/models/javascript_cache.rb index 832a60f7e6..20f8b63a84 100644 --- a/app/models/javascript_cache.rb +++ b/app/models/javascript_cache.rb @@ -8,7 +8,7 @@ class JavascriptCache < ActiveRecord::Base before_save :update_digest def url - "#{GlobalSetting.cdn_url}#{Discourse.base_uri}/theme-javascripts/#{digest}.js?__ws=#{Discourse.current_hostname}" + "#{GlobalSetting.cdn_url}#{Discourse.base_path}/theme-javascripts/#{digest}.js?__ws=#{Discourse.current_hostname}" end private diff --git a/app/models/permalink.rb b/app/models/permalink.rb index ea317839e5..844678923d 100644 --- a/app/models/permalink.rb +++ b/app/models/permalink.rb @@ -78,7 +78,7 @@ class Permalink < ActiveRecord::Base def target_url return external_url if external_url - return "#{Discourse::base_uri}#{post.url}" if post + return "#{Discourse.base_path}#{post.url}" if post return topic.relative_url if topic return category.url if category return tag.full_url if tag diff --git a/app/models/remote_theme.rb b/app/models/remote_theme.rb index 292ba79d7e..2ec39d6a01 100644 --- a/app/models/remote_theme.rb +++ b/app/models/remote_theme.rb @@ -104,6 +104,7 @@ class RemoteTheme < ActiveRecord::Base def self.out_of_date_themes self.joined_remotes.where("commits_behind > 0 OR remote_version <> local_version") + .where(themes: { enabled: true }) .pluck("themes.name", "themes.id") end diff --git a/app/models/topic.rb b/app/models/topic.rb index 67a4ab2f2c..558c15417e 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -1230,7 +1230,7 @@ class Topic < ActiveRecord::Base # NOTE: These are probably better off somewhere else. # Having a model know about URLs seems a bit strange. def last_post_url - "#{Discourse.base_uri}/t/#{slug}/#{id}/#{posts_count}" + "#{Discourse.base_path}/t/#{slug}/#{id}/#{posts_count}" end def self.url(id, slug, post_number = nil) @@ -1244,7 +1244,7 @@ class Topic < ActiveRecord::Base end def self.relative_url(id, slug, post_number = nil) - url = +"#{Discourse.base_uri}/t/" + url = +"#{Discourse.base_path}/t/" url << "#{slug}/" if slug.present? url << id.to_s url << "/#{post_number}" if post_number.to_i > 1 diff --git a/app/models/topic_link_click.rb b/app/models/topic_link_click.rb index 91d22a1ee0..63166cfb80 100644 --- a/app/models/topic_link_click.rb +++ b/app/models/topic_link_click.rb @@ -9,7 +9,9 @@ class TopicLinkClick < ActiveRecord::Base validates_presence_of :topic_link_id - WHITELISTED_REDIRECT_HOSTNAMES = Set.new(%W{www.youtube.com youtu.be}) + ALLOWED_REDIRECT_HOSTNAMES = Set.new(%W{www.youtube.com youtu.be}) + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + deprecate_constant 'WHITELISTED_REDIRECT_HOSTNAMES', 'TopicLinkClick::ALLOWED_REDIRECT_HOSTNAMES' # Create a click from a URL and post_id def self.create_from(args = {}) @@ -93,7 +95,7 @@ class TopicLinkClick < ActiveRecord::Base return nil unless uri # Only redirect to allowlisted hostnames - return url if WHITELISTED_REDIRECT_HOSTNAMES.include?(uri.hostname) || is_cdn_link + return url if ALLOWED_REDIRECT_HOSTNAMES.include?(uri.hostname) || is_cdn_link return nil end diff --git a/app/models/translation_override.rb b/app/models/translation_override.rb index a09797f33c..1f217e9f84 100644 --- a/app/models/translation_override.rb +++ b/app/models/translation_override.rb @@ -4,13 +4,15 @@ require "i18n/i18n_interpolation_keys_finder" class TranslationOverride < ActiveRecord::Base # Allowlist i18n interpolation keys that can be included when customizing translations - CUSTOM_INTERPOLATION_KEYS_WHITELIST = { + ALLOWED_CUSTOM_INTERPOLATION_KEYS = { "user_notifications.user_" => %w{ topic_title_url_encoded site_title_url_encoded context } } + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + deprecate_constant 'CUSTOM_INTERPOLATION_KEYS_WHITELIST', 'TranslationOverride::ALLOWED_CUSTOM_INTERPOLATION_KEYS' validates_uniqueness_of :translation_key, scope: :locale validates_presence_of :locale, :translation_key, :value @@ -98,7 +100,7 @@ class TranslationOverride < ActiveRecord::Base custom_interpolation_keys = [] - CUSTOM_INTERPOLATION_KEYS_WHITELIST.select do |key, value| + ALLOWED_CUSTOM_INTERPOLATION_KEYS.select do |key, value| if transformed_key.start_with?(key) custom_interpolation_keys = value end diff --git a/app/models/upload.rb b/app/models/upload.rb index 1656fdb5ec..36fc6da0a2 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -167,6 +167,7 @@ class Upload < ActiveRecord::Base # we do not want to exclude topic links that for whatever reason # have secure-media-uploads in the URL e.g. /t/secure-media-uploads-are-cool/223452 route = UrlHelper.rails_route_from_url(url) + return false if route.blank? route[:action] == "show_secure" && route[:controller] == "uploads" && FileHelper.is_supported_media?(url) rescue ActionController::RoutingError false diff --git a/app/models/user.rb b/app/models/user.rb index 75d076944b..72933dfdc7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -826,14 +826,14 @@ class User < ActiveRecord::Base # TODO it may be worth caching this in a distributed cache, should be benched if SiteSetting.external_system_avatars_enabled url = SiteSetting.external_system_avatars_url.dup - url = +"#{Discourse::base_uri}#{url}" unless url =~ /^https?:\/\// + url = +"#{Discourse.base_path}#{url}" unless url =~ /^https?:\/\// url.gsub! "{color}", letter_avatar_color(normalized_username) url.gsub! "{username}", UrlHelper.encode_component(username) url.gsub! "{first_letter}", UrlHelper.encode_component(normalized_username.grapheme_clusters.first) url.gsub! "{hostname}", Discourse.current_hostname url else - "#{Discourse.base_uri}/letter_avatar/#{normalized_username}/{size}/#{LetterAvatar.version}.png" + "#{Discourse.base_path}/letter_avatar/#{normalized_username}/{size}/#{LetterAvatar.version}.png" end end @@ -1193,13 +1193,10 @@ class User < ActiveRecord::Base end def set_random_avatar - if SiteSetting.selectable_avatars_enabled? && SiteSetting.selectable_avatars.present? - urls = SiteSetting.selectable_avatars.split("\n") - if urls.present? - if upload = Upload.get_from_url(urls.sample) - update_column(:uploaded_avatar_id, upload.id) - UserAvatar.create!(user_id: id, custom_upload_id: upload.id) - end + if SiteSetting.selectable_avatars_enabled? + if upload = SiteSetting.selectable_avatars.sample + update_column(:uploaded_avatar_id, upload.id) + UserAvatar.create!(user_id: id, custom_upload_id: upload.id) end end end diff --git a/app/models/user_api_key.rb b/app/models/user_api_key.rb index c705541a73..951add6d7a 100644 --- a/app/models/user_api_key.rb +++ b/app/models/user_api_key.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true class UserApiKey < ActiveRecord::Base + self.ignored_columns = [ + "scopes" # TODO(2020-12-18): remove + ] SCOPES = { read: [:get], @@ -19,6 +22,7 @@ class UserApiKey < ActiveRecord::Base } belongs_to :user + has_many :scopes, class_name: "UserApiKeyScope", dependent: :destroy scope :active, -> { where(revoked_at: nil) } scope :with_key, ->(key) { where(key_hash: ApiKey.hash_key(key)) } @@ -41,6 +45,7 @@ class UserApiKey < ActiveRecord::Base @key.present? end + # Scopes allowed to be requested by external services def self.allowed_scopes Set.new(SiteSetting.allow_user_api_key_scopes.split("|")) end @@ -78,13 +83,15 @@ class UserApiKey < ActiveRecord::Base end def has_push? - (scopes.include?("push") || scopes.include?("notifications")) && push_url.present? && SiteSetting.allowed_user_api_push_urls.include?(push_url) + scopes.any? { |s| s.name == "push" || s.name == "notifications" } && + push_url.present? && + SiteSetting.allowed_user_api_push_urls.include?(push_url) end def allow?(env) - scopes.any? do |name| - UserApiKey.allow_scope?(name, env) - end + scopes.any? do |s| + UserApiKey.allow_scope?(s.name, env) + end || is_revoke_self_request?(env) end def self.invalid_auth_redirect?(auth_redirect) @@ -92,6 +99,12 @@ class UserApiKey < ActiveRecord::Base .split('|') .none? { |u| WildcardUrlChecker.check_url(u, auth_redirect) } end + + private + + def is_revoke_self_request?(env) + UserApiKey.allow_permission?([:post, 'user_api_keys#revoke'], env) && (env[:id].nil? || env[:id].to_i == id) + end end # == Schema Information diff --git a/app/models/user_api_key_scope.rb b/app/models/user_api_key_scope.rb new file mode 100644 index 0000000000..bae6a92800 --- /dev/null +++ b/app/models/user_api_key_scope.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class UserApiKeyScope < ActiveRecord::Base +end + +# == Schema Information +# +# Table name: user_api_key_scopes +# +# id :bigint not null, primary key +# user_api_key_id :integer not null +# name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_user_api_key_scopes_on_user_api_key_id (user_api_key_id) +# diff --git a/app/models/user_avatar.rb b/app/models/user_avatar.rb index bd2751d6b4..731dee5004 100644 --- a/app/models/user_avatar.rb +++ b/app/models/user_avatar.rb @@ -78,7 +78,7 @@ class UserAvatar < ActiveRecord::Base def self.local_avatar_template(hostname, username, upload_id) version = self.version(upload_id) - "#{Discourse.base_uri}/user_avatar/#{hostname}/#{username}/{size}/#{version}.png" + "#{Discourse.base_path}/user_avatar/#{hostname}/#{username}/{size}/#{version}.png" end def self.external_avatar_url(user_id, upload_id, size) diff --git a/app/models/user_stat.rb b/app/models/user_stat.rb index bc2a9d251c..364b4a2618 100644 --- a/app/models/user_stat.rb +++ b/app/models/user_stat.rb @@ -290,4 +290,5 @@ end # flags_ignored :integer default(0), not null # first_unread_at :datetime not null # distinct_badge_count :integer default(0), not null +# digest_attempted_at :datetime # diff --git a/app/serializers/concerns/topic_tags_mixin.rb b/app/serializers/concerns/topic_tags_mixin.rb index 58841188b0..e5552afe53 100644 --- a/app/serializers/concerns/topic_tags_mixin.rb +++ b/app/serializers/concerns/topic_tags_mixin.rb @@ -10,9 +10,8 @@ module TopicTagsMixin end def tags - # Calling method `pluck` along with `includes` causing N+1 queries - tags = topic.tags.map(&:name) - + # Calling method `pluck` or `order` along with `includes` causing N+1 queries + tags = (SiteSetting.tags_sort_alphabetically ? topic.tags.sort_by(&:name) : topic.tags.sort_by(&:topic_count).reverse).map(&:name) if scope.is_staff? tags else diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 594b3fbf46..3935ce2052 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -131,7 +131,7 @@ class UserSerializer < UserCardSerializer { id: k.id, application_name: k.application_name, - scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s}") }, + scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s.name}") }, created_at: k.created_at, last_used_at: k.last_used_at, } diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb index ad9d310577..127eca9d54 100644 --- a/app/services/badge_granter.rb +++ b/app/services/badge_granter.rb @@ -70,17 +70,15 @@ class BadgeGranter post_id: @post_id, seq: seq) - return unless SiteSetting.enable_badges + return unless SiteSetting.enable_badges + if @granted_by != Discourse.system_user StaffActionLogger.new(@granted_by).log_badge_grant(user_badge) end - if SiteSetting.enable_badges? - unless @badge.badge_type_id == BadgeType::Bronze && user_badge.granted_at < 2.days.ago - notification = self.class.send_notification(@user.id, @user.username, @user.effective_locale, @badge) - - user_badge.update notification_id: notification.id - end + unless @badge.badge_type_id == BadgeType::Bronze && user_badge.granted_at < 2.days.ago + notification = self.class.send_notification(@user.id, @user.username, @user.effective_locale, @badge) + user_badge.update!(notification_id: notification.id) end end end diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb index 837bbff376..9817abebd1 100644 --- a/app/services/post_alerter.rb +++ b/app/services/post_alerter.rb @@ -470,7 +470,8 @@ class PostAlerter if SiteSetting.allow_user_api_key_scopes.split("|").include?("push") && SiteSetting.allowed_user_api_push_urls.present? clients = user.user_api_keys - .where("('push' = ANY(scopes) OR 'notifications' = ANY(scopes))") + .joins(:scopes) + .where("user_api_key_scopes.name IN ('push', 'notifications')") .where("push_url IS NOT NULL AND push_url <> ''") .where("position(push_url IN ?) > 0", SiteSetting.allowed_user_api_push_urls) .where("revoked_at IS NULL") diff --git a/app/views/exceptions/_not_found_topics.html.erb b/app/views/exceptions/_not_found_topics.html.erb index 2e6c107e3c..836122931f 100644 --- a/app/views/exceptions/_not_found_topics.html.erb +++ b/app/views/exceptions/_not_found_topics.html.erb @@ -1,20 +1,24 @@
- -
-

<%= t 'page_not_found.recent_topics' %>

- <% @recent.each do |t| %> -
- <%= link_to t.title, t.relative_url %><%= category_badge(t.category) %> -
- <% end %> - " class="btn btn-default"><%= t 'page_not_found.see_more' %>… -
+ <% if @top_viewed.count > 0 %> + + <% end %> + <% if @recent.count > 0 %> +
+

<%= t 'page_not_found.recent_topics' %>

+ <% @recent.each do |t| %> +
+ <%= link_to t.title, t.relative_url %><%= category_badge(t.category) %> +
+ <% end %> + " class="btn btn-default"><%= t 'page_not_found.see_more' %>… +
+ <% end %>
diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 9eba75d413..12db631ba8 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -8,8 +8,8 @@ <%- end %> -<%- if Discourse.base_uri.present? %> - +<%- if Discourse.base_path.present? %> + <% end %> <%= canonical_link_tag %> <%= render_sitelinks_search_tag %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 9cb5efcce9..b79c327d5b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -45,7 +45,7 @@ <%= render_google_tag_manager_head_code %> <%= render_google_universal_analytics_code %> - + <%- if include_ios_native_app_banner? %> @@ -59,7 +59,7 @@ <%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %> - <%- if !current_user && (data = cookies.delete(:authentication_data, path: Discourse.base_uri("/"))) %> + <%- if !current_user && (data = cookies.delete(:authentication_data, path: Discourse.base_path("/"))) %> <%- end %> diff --git a/app/views/layouts/finish_installation.html.erb b/app/views/layouts/finish_installation.html.erb index 91df807798..3958fb1179 100644 --- a/app/views/layouts/finish_installation.html.erb +++ b/app/views/layouts/finish_installation.html.erb @@ -1,6 +1,8 @@ <%= discourse_stylesheet_link_tag 'wizard', theme_ids: nil %> + <%= discourse_color_scheme_stylesheets %> + <%= preload_script 'ember_jquery' %> <%= preload_script 'wizard-vendor' %> <%= render partial: "layouts/head" %> diff --git a/app/views/list/list.erb b/app/views/list/list.erb index 2338d39c6a..4b7ee6b772 100644 --- a/app/views/list/list.erb +++ b/app/views/list/list.erb @@ -102,7 +102,7 @@ <% end %> - '><%= t.posts_count %> + '><%= t.posts_count - 1 %> '><%= t.views %> diff --git a/app/views/robots_txt/index.erb b/app/views/robots_txt/index.erb index 71ca94baa7..f1a8db2c48 100644 --- a/app/views/robots_txt/index.erb +++ b/app/views/robots_txt/index.erb @@ -1,5 +1,5 @@ <%= @robots_info[:header] %> -<% if Discourse.base_uri.present? %> +<% if Discourse.base_path.present? %> # This robots.txt file is not used. Please append the content below in the robots.txt file located at the root <% end %> # diff --git a/app/views/robots_txt/no_index.erb b/app/views/robots_txt/no_index.erb index a02cc1b1f5..88d46ae9ed 100644 --- a/app/views/robots_txt/no_index.erb +++ b/app/views/robots_txt/no_index.erb @@ -5,9 +5,9 @@ # we return the X-Robots-Tag with noindex, nofollow which will ensure # indexing is minimized and nothing shows up in Google search results User-agent: googlebot -Allow: <%= Discourse.base_uri + "/" %> -Disallow: <%= Discourse.base_uri + "/uploads/*" %> +Allow: <%= Discourse.base_path + "/" %> +Disallow: <%= Discourse.base_path + "/uploads/*" %> User-agent: * -Disallow: <%= Discourse.base_uri + "/" %> +Disallow: <%= Discourse.base_path + "/" %> diff --git a/app/views/users/activate_account.html.erb b/app/views/users/activate_account.html.erb index 85d99fa536..18a6220283 100644 --- a/app/views/users/activate_account.html.erb +++ b/app/views/users/activate_account.html.erb @@ -13,7 +13,7 @@ <%= preload_script "ember_jquery" %> <%= preload_script "vendor" %> <%= render_google_universal_analytics_code %> - <%= tag.meta id: 'data-activate-account', data: { path: path('/u/hp') } %> + <%= tag.meta id: 'data-activate-account', data: { path: path('/session/hp') } %> <%- end %> <%= preload_script "activate-account" %> diff --git a/app/views/wizard/index.html.erb b/app/views/wizard/index.html.erb index 85a57bc80c..7b828df909 100644 --- a/app/views/wizard/index.html.erb +++ b/app/views/wizard/index.html.erb @@ -1,6 +1,7 @@ <%= discourse_stylesheet_link_tag :wizard, theme_ids: nil %> + <%= discourse_color_scheme_stylesheets %> <%= preload_script "locales/#{I18n.locale}" %> <%- if ExtraLocalesController.client_overrides_exist? %> <%= preload_script_url ExtraLocalesController.url('overrides') %> diff --git a/bin/notify_file_change b/bin/notify_file_change index e408e6e408..8680b97966 100755 --- a/bin/notify_file_change +++ b/bin/notify_file_change @@ -6,7 +6,11 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd ../tmp && pwd )" SOCKET="$DIR"/file_change.sock if [[ -e "$SOCKET" ]]; then - echo "$1 $2" | socat - UNIX-CONNECT:$SOCKET >/dev/null 2>/dev/null + if command -v socat; then + echo "$1 $2" | socat - UNIX-CONNECT:$SOCKET >/dev/null 2>/dev/null + else + echo "$1 $2" | nc -U $SOCKET >/dev/null 2>/dev/null + fi if [ $? != 0 ]; then rm $SOCKET fi diff --git a/config/application.rb b/config/application.rb index d84148df11..a55a24e27a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -134,8 +134,7 @@ module Discourse config.assets.paths += %W(#{config.root}/config/locales #{config.root}/public/javascripts) if Rails.env == "development" || Rails.env == "test" - config.assets.paths << "#{config.root}/test/javascripts" - config.assets.paths << "#{config.root}/test/stylesheets" + config.assets.paths << "#{config.root}/app/assets/javascripts/discourse/tests" config.assets.paths << "#{config.root}/node_modules" end @@ -356,7 +355,7 @@ module Discourse %w{qunit.js qunit.css test_helper.css - test_helper.js + discourse/tests/test_helper.js wizard/test/test_helper.js }.include?(logical_path) || logical_path =~ /\/node_modules/ || diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf index c7250354b4..84d9c84e34 100644 --- a/config/discourse_defaults.conf +++ b/config/discourse_defaults.conf @@ -233,6 +233,11 @@ force_anonymous_min_queue_seconds = 1 # only trigger anon if we see more than N requests for this path in last 10 seconds force_anonymous_min_per_10_seconds = 3 +# Any requests with the headers Discourse-Background = true will not be allowed to queue +# longer than this amount of time. +# Discourse will rate limit and ask client to try again later. +background_requests_max_queue_length = 0.5 + # if a message bus request queues for 100ms or longer, we will reject it and ask consumer # to back off reject_message_bus_queue_seconds = 0.1 @@ -309,3 +314,8 @@ allowed_theme_repos = # We want this off by default so the process is not started when it does not # need to be (e.g. development, test, certain hosting tiers) enable_email_sync_demon = false + +# we never want to queue more than 10000 digests per 30 minute block +# this can easily lead to blocking sidekiq +# on multisites we recommend a far lower number +max_digests_enqueued_per_30_mins_per_site = 10000 diff --git a/config/environments/test.rb b/config/environments/test.rb index bbb7614e80..0d753f4ed8 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -13,7 +13,7 @@ Discourse::Application.configure do config.public_file_server.enabled = true # don't consider reqs local so we can properly handle exceptions like we do in prd - config.consider_all_requests_local = false + config.consider_all_requests_local = false # disable caching config.action_controller.perform_caching = false diff --git a/config/initializers/014-track-setting-changes.rb b/config/initializers/014-track-setting-changes.rb index 9e0b13c44b..fed99a618c 100644 --- a/config/initializers/014-track-setting-changes.rb +++ b/config/initializers/014-track-setting-changes.rb @@ -27,7 +27,7 @@ DiscourseEvent.on(:site_setting_changed) do |name, old_value, new_value| end end - Stylesheet::Manager.clear_core_cache!(["desktop", "mobile"]) if name == :base_font + Stylesheet::Manager.clear_core_cache!(["desktop", "mobile"]) if [:base_font, :heading_font].include?(name) Report.clear_cache(:storage_stats) if [:backup_location, :s3_backup_bucket].include?(name) diff --git a/config/initializers/100-sidekiq.rb b/config/initializers/100-sidekiq.rb index 56588effb4..9311b1633f 100644 --- a/config/initializers/100-sidekiq.rb +++ b/config/initializers/100-sidekiq.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Ensure that scheduled jobs are loaded before mini_scheduler is configured. -if Rails.env == "development" && Sidekiq.server? +if Rails.env == "development" require "jobs/base" Dir.glob("#{Rails.root}/app/jobs/scheduled/*.rb") do |f| diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index dcc7415467..fa7e23cc17 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -205,6 +205,10 @@ ar: topic_html: 'الموضوع: %{topicTitle}' post: "المنشور رقم %{postNumber}" close: "أغلق" + twitter: "شارِك على تويتر" + facebook: "شارِك على فيسبوك" + email: "شارِك عبر البريد الإلكتروني" + url: "انسخ العنوان وشارِكه" action_codes: public_topic: "أجعل هذا الموضوع عامًّا %{when}" private_topic: "تحويل هذا الموضوع إلى رسالة شخصية %{when}" @@ -351,12 +355,25 @@ ar: help: bookmark: "انقر لوضع علامة مرجعية علي أوّل منشور في هذا الموضوع" unbookmark: "انقر لإزالة كلّ العلامات المرجعية في هذا الموضوع" + unbookmark_with_reminder: "انقر لإزالة كلّ العلامات والتذكيرات في هذا الموضوع. ضبطت تذكيرًا %{reminder_at} لهذا الموضوع." bookmarks: + created: "وضعت علامة على هذه المشاركة. %{name}" not_bookmarked: "أشر هذا المكتوب" remove: "أزل العلامة المرجعية" + delete: "احذف العلامة" + confirm_delete: "أمتأكّد من حذف هذه العلامة؟ سيُحذف التذكير أيضًا." confirm_clear: "هل أنت متأكد من مسح جميع الاشعارات المرجعية من هذا الموضوع؟" save: "احفظ" no_timezone: 'لم تحدد منظقتك الزمنية بعد. لن تتمكن من تفعيل التذكيرات. حددها في ملفك الشخصي.' + invalid_custom_datetime: "التاريخ والوقت الذين كتبتهما غير صالحين، من فضلك أعِد المحاولة." + list_permission_denied: "لا تملك صلاحية عرض علامات هذا المستخدم." + no_user_bookmarks: "ما من مشاركات عليها علامة. تتيح لك العلامات الرجوع إلى المشاركات التي تريد بسرعة." + auto_delete_preference: + label: "احذفها تلقائيًا" + never: "(لا تحذفها)" + when_reminder_sent: "ما إن أضبط التذكير" + on_owner_reply: "بعد أن أردّ على هذه المشاركة" + search_placeholder: "ابحث في العلامات حسب الاسم أو عنوان الموضوع أو محتوى المشاركة" search: "البحث" reminders: later_today: "في وقت لاحق اليوم" @@ -370,6 +387,7 @@ ar: new_topic: "مسودة موضوع جديد" topic_reply: "مسودة الرد" abandon: + confirm: "فتحت مسودّة أخرى في هذا الموضوع فعلًا. أمتأكّد من تركها؟" yes_value: "نعم، لا أريده" no_value: "لا, إبقاء" preview: "معاينة" @@ -379,6 +397,8 @@ ar: saved: "حُفظت!" upload: "ارفع" uploading: "يرفع..." + uploading_filename: "يرفع: %{filename}..." + clipboard: "الحافظة" uploaded: "رُفع!" pasting: "جاري النسخ" enable: "فعّل" @@ -392,14 +412,24 @@ ar: banner: close: "تجاهل هذا الإعلان." edit: "عدل هذا الإعلان >>" + pwa: + install_banner: "أتريد تثبيت %{title} على هذا الجهاز؟" choose_topic: none_found: "لم نجد اي موضوعات." + title: + search: "ابحث عن موضوع" + placeholder: "اكتب هنا اسم الموضوع أو عنوانه أو معرّفه" choose_message: none_found: "لم يتم العثور على أي رسالة" + title: + search: "ابحث عن رسالة" review: + order_by: "افرز حسب" in_reply_to: "ردا على" explain: total: "مجموع" + claim_help: + claimed_by_other: "فقط %{username} من يمكنه مراجعة هذا العنصر." awaiting_approval: "بأنتضار موافقة" delete: "أحذف" settings: @@ -408,27 +438,44 @@ ar: title: "إعدادات" moderation_history: "تاريخ الادارة" view_all: "اظهار الكل" + none: "ما من عناصر لمراجعتها." + view_pending: "اعرض المُرجأ" topic: "الموضوع:" filtered_user: "مستخدم" + show_all_topics: "اعرض كلّ المواضيع" user: username: "اسم المستخدم" email: "البريد الإلكتروني" name: "الإسم" topics: topic: "موضوع" + deleted: "[حُذف الموضوع]" + original: "(الموضوع الأصلي)" details: "التفاصيل" edit: "عدّل" save: "احفظ" cancel: "ألغِ" + new_topic: "بالموافقة على هذا العنصر سيُنشأ موضوع جديد" filters: all_categories: "(جميع الأقسام)" type: title: "النوع" + all: "(كلّ الأنواع)" refresh: "تحديث" + status: "الحالة" category: "تصنيف" + orders: + created_at: "تاريخ الإنشاء" + created_at_asc: "تاريخ الإنشاء (بالعكس)" + priority: + medium: "متوسّطة" + high: "عالية" + conversation: + view_full: "اعرض المحادثة كاملةً" scores: date: "التاريخ" type: "النوع" + reviewed_by: "راجَعها" statuses: pending: title: "قيد الانتظار" @@ -491,6 +538,8 @@ ar: remove_user_as_group_owner: "سحب صلاحية المالك" groups: member_added: "تم الإضافة" + add_members: + input_placeholder: "أسماء المستخدمين أو عناوين البريد" requests: reason: "سبب" accepted: "مقبول" @@ -509,11 +558,24 @@ ar: email: title: "البريد الإلكتروني" credentials: + title: "بيانات الولوج" + smtp_server: "خادوم SMTP" + smtp_port: "منفذ SMTP" + smtp_ssl: "استعمل SSL لِ‍ SMTP" + imap_server: "خادوم IMAP" + imap_port: "منفذ IMAP" + imap_ssl: "استعمل SSL لِ‍ IMAP" username: "اسم المستخدم" password: "كلمة المرور" + mailboxes: + none_found: "لم توجد صناديق بريد في حساب البريد الإلكتروني هذا." membership: title: العضوية access: صلاحية + categories: + title: الفئات + tags: + watching_first_post_tags_instructions: "سيُرسل إخطار للمستخدمين بأولّ مشاركة في كلّ موضوع يحمل هذه الوسوم." logs: title: "السّجلّات" when: "متى" @@ -524,11 +586,16 @@ ar: details: "تفاصيل" from: "من" to: "إلى" + permissions: + title: "التصاريح" + none: "ما من فئات مرتبطة بهذه المجموعة." + description: "يمكن لأعضاء هذه المجموعة الوصول إلى هذه الفئات" public_admission: "السماح للاعضاء بالانضمام إلى المجموعة بحرية (يتطلب أن تكون المجموعة مرئية للجميع )" public_exit: "السماح للأعضاء بمغادرة المجموعة بحرية" empty: posts: "لا منشورات من أعضاء هذه المجموعة." members: "لا أعضاء في هذه المجموعة." + requests: "ما من طلبات اشتراك لهذه المجموعة." mentions: "لم يُشِر أحد إلى هذه المجموعة." messages: "لا رسائل لهذه المجموعة." topics: "لا موضوعات من أعضاء هذه المجموعة." @@ -538,6 +605,8 @@ ar: leave: "غادر" request: "طلب" message: "رسالة" + confirm_leave: "أمتأكّد من ترك هذه المجموعة؟" + allow_membership_requests: "اسمح للمستخدمين بإرسال طلبات الاشتراك إلى مالكي المجموعة (يطلب أن تكون المجموعة ظاهرة للعموم)" membership_request_template: "لوحة مخصص تظهر للأعضاء عندما يرسلون طلب عضوية" membership_request: submit: "ارسل الطلب" @@ -545,6 +614,7 @@ ar: reason: "دع مدراء المجموعة يعرفون لماذا انت تنتمي لهذه المجموعة" membership: "العضوية" name: "الاسم" + group_name: "اسم المجموعة" user_count: "الأعضاء" bio: "عن المجموعة" selector_placeholder: "أدخل اسم المستخدم" @@ -553,7 +623,9 @@ ar: title: "المجموعات" all: "كل المجموعات" empty: "لا توجد مجموعات ظاهرة" + filter: "رشّح حسب اسم المجموعة" owner_groups: "مجموعاتي" + close_groups: "المجموعات المُغلقة" automatic: "تلقائي" closed: "مغلق" automatic_group: مجموعة تلقائية @@ -571,10 +643,13 @@ ar: activity: "النشاط" members: title: "الأعضاء" + filter_placeholder_admin: "اسم المستخدم أو البريد الإلكتروني" filter_placeholder: "اسم المستخدم" remove_member: "حذف عضو" + remove_member_description: "أزِل %{username} من هذه المجموعة" remove_owner: "حذف كمالك" owner: "المالك" + forbidden: "ليس مسموحًا لك عرض الأعضاء." topics: "المواضيع" posts: "المنشورات" mentions: "الإشارات" @@ -609,6 +684,9 @@ ar: flair_color_placeholder: "(إختياري) اللون بترميز Hexadecimal" flair_preview_icon: "معاينة الأيقونة" flair_preview_image: "معاينة الصورة" + flair_type: + icon: "اختر أيقونةً" + image: "ارفع صورةً" user_action_groups: "1": "الإعجابات المعطاة" "2": "الإعجابات المتلقاة" @@ -622,7 +700,7 @@ ar: "12": "العناصر المرسلة" "13": "البريد الوارد" "14": "قيد الانتظار" - "15": "Drafts" + "15": "المسودّات" categories: all: "كل الأقسام" all_subcategories: "الكل" @@ -688,6 +766,7 @@ ar: ignore_duration_save: "تجاهل" mute_option: "مكتومة" normal_option: "عادي" + normal_option_title: "سنُرسل إليك إخطارًا لو ردّ هذا المستخدم عليك أو اقتبس كلامك أو أشار إليك." activity_stream: "النّشاط" preferences: " التّفضيلات" feature_topic_on_profile: @@ -718,6 +797,13 @@ ar: dismiss_notifications: "تجاهل الكل" dismiss_notifications_tooltip: "اجعل كل الإشعارات مقروءة" first_notification: "إشعارك الأول! قم بالضغط عليه للبدء." + color_schemes: + dark_instructions: "يمكنك معاينة مخطّط الوضع الداكن اللوني بتفعيل الوضع الداكن من جهازك." + undo: "صفّر" + regular: "العادي" + dark: "الوضع الداكن" + default_dark_scheme: "(مبدئيّات الموقع)" + dark_mode: "الوضع الداكن" external_links_in_new_tab: "فتح الروابط الخارجية في تبويب جديد" enable_quoting: "فعل خاصية إقتباس النصوص المظللة" change: "غيّر" @@ -756,10 +842,12 @@ ar: watched_first_post_tags: "مراقبة أول منشور" watched_first_post_tags_instructions: "سيصلك إشعار بأول منشور في كل موضوع يستخدم هذة الأوسمة." muted_categories: "مكتوم" + regular_categories_instructions: "سترى هذه الفئات في قوائم المواضيع ”الحديثة“ و”النشيطة“." no_category_access: "كمشرف لديك صلاحيات وصول محدودة للأقسام, الحفظ معطل" delete_account: "أحذف الحسابي" delete_account_confirm: "أمتأكّد من حذف حسابك للأبد؟ هذا إجراء لا عودة فيه!" deleted_yourself: "حُذف حسابك بنجاح." + delete_yourself_not_allowed: "من فضلك راسِل أحد أعضاء الطاقم إن أردت حذف حسابك." unread_message_count: "الرسائل" admin_delete: "أحذف" users: "الأعضاء" @@ -818,12 +906,15 @@ ar: second_factor: name: "الإسم" label: "كود" + enable_description: | + امسح رمز QR ضوئيًا في أحد التطبيقات المدعومة (أندرويد وآي‌أو‌إس) وأدخِل رمز الاستيثاق. disable_description: "يرجى ادخال رمز التوثيق من التطبيق الخاص بك" show_key_description: "أضف يدويا" short_description: | قم بحماية الحساب الخاص بك مع رمز الامان ذو الاستخدام الواحد. oauth_enabled_warning: "يرجى الملاحظة ان تسجيل الدخول عن طريق حسابات مواقع التواصل الاجتماعي سيتم تعطيلها بمجرد تفعيل خاصية التوثيق بعاملين الحساب" disable: "عطل" + disable_confirm: "أمتأكّد من تعطيل كلّ طرائق الاستيثاق بخطوتين؟" save: "احفظ" edit: "عدّل" security_key: @@ -833,6 +924,7 @@ ar: error: "حدث عطل أثناء تغيير هذه القيمة." change_username: title: "تغيير اسم المستخدم" + confirm: "أمتأكّد كلّ التأكيد من تغيير اسم المستخدم؟" taken: "عذرا، اسم المستخدم غير متاح." invalid: "اسم المستخدم غير صالح. يمكنه احتواء احرف و ارقام انجليزية فحسب" add_email: @@ -978,6 +1070,7 @@ ar: expired: "الدعوة انتهت صلاحيتها " rescind: "حذف" rescinded: "الدعوة حذفت" + rescind_all_confirm: "أمتأكّد من إزالة كلّ الدعوات المنضية؟" reinvite: "اعادة ارسال الدعوة" reinvite_all: "أعد إرسال كل الدعوات" reinvite_all_confirm: "هل انت متأكد من رغبتك في اعادة ارسال كل الدعوات؟" @@ -996,7 +1089,7 @@ ar: invite_link: success: "وُلّد رابط الدعوة بنجاح!" bulk_invite: - none: "لم تدعُ أحدًا إلى هنا بعد. أرسل الدّعوات إمّا فرديّة، أو إلى مجموعة أشخاص عبر رفع ملفّ CSV." + none: "لم تدعُ أحدًا إلى هنا بعد. أرسِل الدعوات فردية أو جماعية عبر رفع ملف CSV." success: "رُفع الملف بنجاح. سيصلك إشعارا عبر رسالة عند اكتمال العملية." error: "عذرا، يجب أن يكون الملفّ بنسق CSV." password: @@ -1114,6 +1207,10 @@ ar: enabled: "هذا الموقع في وضع القراءة فقط. نأمل أن تتابع تصفحه، ولكن الرد، والإعجاب وغيرها من الصلاحيات معطلة حاليا." login_disabled: "تسجيل الدخول معطل في حال كان الموقع في وضع القراءة فقط." logout_disabled: "تسجيل الخروج معطّل في حال كان الموقع في وضع القراءة فقط." + too_few_topics_notice_MF: >- + هيًا بنا لنبدأ نقاشًا! هناك حاليًا {currentTopics, plural, zero {} one {موضوع واحد} two {موضوعين اثنين} few {# مواضيع} many {# موضوعًا} other {# موضوع}}. يحتاج الزوّار إلى أكثر لقراءته والردّ عليه، ولذلك ننصح بوجود {requiredTopics, plural, one {موضوع واحد} two {موضوعين اثنين} few {# مواضيع} many {# موضوعًا} other {# موضوع}} على الأقل. يرى طاقم الموقع فقط هذه الرسالة. + too_few_posts_notice_MF: >- + هيًا بنا لنبدأ نقاشًا! هناك حاليًا {currentPosts, plural, zero {} one {مشاركة واحدة} two {مشاركتين اثنتين} few {# مشاركات} many {# مشاركةً} other {# مشاركة}}. يحتاج الزوّار إلى أكثر لقراءتها والردّ عليها، ولذلك ننصح بوجود {requiredPosts, plural, one {مشاركة واحدة} two {مشاركتين اثنتين} few {# مشاركات} many {# مشاركةً} other {# مشاركة}} على الأقل. يرى طاقم الموقع فقط هذه الرسالة. learn_more: "اطّلع على المزيد..." all_time: "المجموع" all_time_desc: "عدد المواضيع المنشأة" @@ -1142,6 +1239,7 @@ ar: hide_session: "ذكرني غدا" hide_forever: "لا شكرا" hidden_for_session: "لا بأس، سأسلك غدًا. يمكنك دوما استخدام 'تسجيل الدخول' لإنشاء حساب ايضا." + value_prop: "حين تسجّل حسابًا نتذكّر بالضبك ما كنت تقرأه، فلا تخشَ من أن تعود ولا ترى ما كان أمامك البتّة. كما وستستلم الإخطارات (هنا وعبر البريد) متى ما ردّ أحدهم عليك. وطبعًا، يمكنك إبداء إعجابك بالمشاركات ومشاركة الجميع الشعور بالرفق. :heartpulse:" summary: enabled_description: "أنت تطالع ملخّصًا لهذا الموضوع، أي أكثر المنشورات الجديرة بالاهتمام حسب نظرة المجتمع." description: "هناك %{replyCount} من الردود." @@ -1176,7 +1274,7 @@ ar: complete_email: "إن تطابق حساب ما مع %{email}، يفترض أن تستلم قريبًا بريدًا به إرشادات إعادة تعيين كلمة المرور." complete_username_not_found: "لا يوجد حساب يطابق اسم المستخدم %{username}" complete_email_not_found: "لا حساب يطابق %{email}" - help: "لم يصل البريد؟ تفحص مجلد السبام

لست متأكداً اي عنوان بريد الكتروني قمت بأستخدامة؟ قم بأدخال عنوان البريد الالكتروني و سنعلمك ان كان موجوداً هنا

ان لم يعد بأمكانك الوصول الى البريد الالكتروني في حسابك, رجاء اتصل بـ طاقم الدعم الخاص بنا

" + help: "ألم يصل البريد بعد؟ تفحّص مجلد السخام أولًا.

ألست متأكّدًا من العنوان الذي استعملته؟ أدخِل عنوان البريد الإلكتروني وسنُعلمك لو كان موجودًا هنا.

إن فقدت إمكانية الدخول على عنوان البريد المرتبط بحسابك، فمن فضلك راسِل طاقمنا الجاهز دومًا لمساعدتك.

" button_ok: "حسنا" button_help: "مساعدة" email_login: @@ -1234,7 +1332,6 @@ ar: accept_invite: "قُبول الدعوة " success: "تم إنشاء حسابك و تسجيل دخولك للموقع" name_label: "الإسم" - password_label: "حدد كلمة المرور" optional_description: "(إختياري)" password_reset: continue: "تابع نحو %{site_name}" @@ -1275,6 +1372,11 @@ ar: medium_dark_tone: لون البشرة اسمر قاتم dark_tone: لون البشرة داكن default: الرموز التعبيرية المخصصة + shared_drafts: + title: "المسودّات المشتركة" + notice: "لا يظهر هذا الموضوع إلّا لمن يرى الفئة %{category}." + confirm_publish: "أمتأكّد من نشر هذه المسودّة؟" + publishing: "ينشر الموضوع..." composer: emoji: "الرموز التعبيرية :)" more_emoji: "أكثر..." @@ -1289,6 +1391,7 @@ ar: saved_local_draft_tip: "حُفظ محليا" similar_topics: "موضوعك يشابه..." drafts_offline: "مسودات محفوظة " + group_mentioned_limit: "تحذير! لقد أشرت إلى %{group} ولكن عدد الأعضاء في هذه المجموعة يفوق الحدّ الأقصى الذي ضبطه المدير للإشارات (وهو %{max} من المستخدمين). لن يصل الإخطار لأحد." group_mentioned: zero: "الإشارة إلى %{group} تعني عدم إخطار أحد. أمتأكّد؟" one: "الإشارة إلى %{group} تعني إخطار شخص واحد. أمتأكّد؟" @@ -1299,12 +1402,14 @@ ar: cannot_see_mention: category: "لقد قمت بالإشارة إلي %{username} لكن لن يتم إشعارهم لأن ليس لديهم صلاحية الوصول لهذا القسم. عليك ان تقوم باضافتهم لمجموعة لديها حق الوصول لهذا القسم." private: "لقد قمت بالإشارة إلي %{username} لكن لن يتم إشعارهم لأن ليس لديهم صلاحية الوصول لهذه الرسالة الخاصة. عليك ان تقوم باضافتهم إلي هذة الرسالة." - duplicate_link: "يبدوا ان هذا الرابط %{domain} تم ذكرة فعلا في الموضوع من قبل @%{username} في رد منذ %{ago} – هل انت متاكد انك تريد نشرة مرة اخري؟" + duplicate_link: "يظهر بأن الرابط الذي يُشير إلى %{domain} نشره @%{username} في الموضوع فعلًا في ردّ بتاريخ %{ago}. أمتأكّد من نشره ثانيةً؟" error: title_missing: "العنوان مطلوب" title_too_short: "العنوان يجب أن يكون علي الاقل %{min} حرف" title_too_long: "العنوان يجب أن لا يزيد عن %{max} حرف" + post_missing: "لا يمكن أن تكون المشاركة فارغة" post_length: "المنشور يجب أن يكون علي الاقل %{min} حرف" + try_like: "هل جرّبت نقر زر %{heart}؟" category_missing: "يجب عليك إختيار احد الأقسام" save_edit: "أحفظ التعديل" reply_original: "التعليق على الموضوع الأصلي" @@ -1321,9 +1426,12 @@ ar: topic_featured_link_placeholder: "ضع رابطاً يظهر مع العنوان" remove_featured_link: "حذف الرابط من الموضوع" reply_placeholder: "اكتب ما تريد هنا. استخدم Markdown، أو BBCode، أو HTML للتنسيق. اسحب الصور أو ألصقها." + reply_placeholder_no_images: "أدخِل ما تريد هنا. استعمل مارك‌داون أو BBCode أو HTML لتنسيق النص." + reply_placeholder_choose_category: "اختر فئةً قبل الكتابة هنا." view_new_post: "شاهد منشورك الجديد." saving: "يحفظ" saved: "حُفظ!" + saved_draft: "كنتَ تكتب مسودّة لمشاركة. انقر لمواصلتها." uploading: "يرفع..." show_preview: "أظهر المعاينة »" hide_preview: "« أخفِ المعاينة" @@ -1338,6 +1446,7 @@ ar: link_description: "ادخل وصف الرابط هنا " link_dialog_title: "اضف الرابط" link_optional_text: "عنوان اختياري" + link_url_placeholder: "ألصِق عنوانًا أو اكتب للبحث في المواضيع" quote_title: "اقتباس فقرة" quote_text: "اقتباس فقرة" code_title: "المحافظة على التنسيق" @@ -1348,7 +1457,10 @@ ar: olist_title: "قائمة مرقمة" ulist_title: "قائمة نقطية" list_item: "قائمة العناصر" + toggle_direction: "بدّل الاتجاه" help: "مساعدة في رموز التنسيق" + collapse: "صغّر لوحة الكتابة" + open: "افتح لوحة الكتابة" modal_ok: "حسنا" modal_cancel: "إلغاء" cant_send_pm: "نأسف، لا يمكنك إرسال الرسائل إلى %{username}." @@ -1432,10 +1544,12 @@ ar: clear_all: "إلغ إختيار الكل" too_short: "عبارة البحث قصيرة جدًّا." title: "أبحث في الموضوعات أو المنشورات أو الأعضاء أو الأقسام" + full_page_title: "ابحث في المواضيع أو المشاركات" no_results: "لا يوجد نتائج." no_more_results: "لا يوجد نتائج أخرى." searching: "يبحث..." post_format: "#%{post_number} كتبها %{username}" + results_page: "نتائج البحث عن ”%{term}“" more_results: "يوجد عدد كبير من النتائج, يرجى تضييق نطاق البحث." cant_find: "لا تستطيع ايجاد ما تبحث عنة؟" start_new_topic: "افتح موضوع جديد." @@ -1459,8 +1573,11 @@ ar: with_tags: label: موسوم filters: + label: أرجِع المواضيع/المشاركات التي... + title: تتطابق في العنوان فقط likes: أعجبتني posted: نشرت بها + created: أنشأتها watching: أراقبها tracking: أتابعها bookmarks: وضعت علية علامة مرجعية @@ -1477,8 +1594,6 @@ ar: noreplies: ليس فيها أيّ ردّ single_user: تحتوي عضوا واحدا post: - count: - label: أدنى عدد للمنشورات time: label: المُرسَلة before: قبل @@ -1527,13 +1642,12 @@ ar: new: "ليست هناك مواضيع جديدة." read: "لم تقرأ أيّ موضوع بعد." posted: "لم تشارك في أيّ موضوع بعد." - latest: "لا مواضيع حديثة. يا للأسف." bookmarks: "لا مواضيع معلّمة بعد." category: "لا يوجد موضوعات في قسم ’%{category}‘." top: "لا يوجد موضوعات مشاهدة بكثرة" educate: new: '

سوف تظهر موضوعاتك هنا.

بشكل افتراضي تعتبر الموضوعات جديدة و يظهر بجانبها كلمه جديد إذا لم يمضي علي نشرها اكثر من يومين.

زور صفحة التفضيلات لتغيير هذا السلوك

' - unread: '

الموضوعات الغير مقروءة تظهر هنا.

بشكل افتراضي تعتبر الموضوعات غير مقروءة و يظهر بجانبها عداد 1 في حال:

او قمت بضبط مستوي اشعارات الموضوع إلي مراقب او متابع.

زور صفحة التفضيلات لتغيير هذا السلوك.

' + unread: '

تظهر المواضيع غير المقروءة هنا.

تكون المواضيع (مبدئيًا) غير مقروءة وتعرض أعداد 1 غير مقروءة إن:

أو أنّك ضبطت الموضوع ليُراقب أو يُتابع عبر زرّ التحكّم بالإخطارات أسفل كلّ موضوع.

إن أردت تعديل هذا السلوك، فزُر تفضيلاتك.

' bottom: latest: "لا يوجد موضوعات حديثة أخرى." posted: "لا يوجد مواضيع منشورة أخرى." @@ -1553,6 +1667,7 @@ ar: other: "%{count} منشور في الموضوع" create: "موضوع جديد" create_long: "كتابة موضوع جديد" + open_draft: "افتح المسودّة" private_message: "أرسل رسالة خاصة" archive_message: help: "انقل الرسالة للأرشيف لديك" @@ -1629,7 +1744,6 @@ ar: read_more_MF: "{UNREAD, plural, zero {} one {تبقى موضوع واحد غير مقروء} two {تبقى موضوعان غير مقروءان} few {تبقت # مواضيع غير مقروءة} many {تبقى # موضوعا غير مقروء} other {تبقى # موضوع غير مقروء}} {BOTH, select, true { و} false {} other {}}{NEW, plural, zero {} one {{UNREAD, plural, zero {تبقى } one {} two {} few {} many {} other {}}موضوع واحد جديد} two {{UNREAD, plural, zero {تبقى } one {} two {} few {} many {} other {}}موضوعان جديدان} few {{UNREAD, plural, zero {تبقت } one {} two {} few {} many {} other {}}# مواضيع جديدة} many {{UNREAD, plural, zero {تبقى } one {} two {} few {} many {} other {}}# موضوعا جديدا} other {{UNREAD, plural, zero {تبقى } one {} two {} few {} many {} other {}}# موضوع جديد}}، أو {CATEGORY, select, true {تصفّح المواضيع الأخرى في {catLink}} false {{latestLink}} other {}}" browse_all_categories: تصفّح كل الأقسام view_latest_topics: اعرض اخر الموضوعات - suggest_create_topic: لمَ لا تكتب موضوعًا؟ jump_reply_up: انتقل إلى أول رد jump_reply_down: انتقل إلى آخر رد deleted: "الموضوع محذوف" @@ -1709,7 +1823,7 @@ ar: "3_1": "ستصلك إشعارات لأنك أنشأت هذا الموضوع." "3": "ستصلك إشعارات لأنك تراقب هذا الموضوع." "2_8": "سوف تشاهد عداد للردود الجديدة لانك تتابع هذا القسم." - "2_4": "سوف تشاهد عداد للردود الجديدة لانك قمت بالمشاركة برد في هذا الموضوع." + "2_4": "سترى عدّادًا بالردود الجديدة لأنّك نشرت ردًا في هذا الموضوع." "2_2": "سوف تشاهد عداد للردود الجديدة لانك تتابع هذا الموضوع." "2": 'You will see a count of new replies because you read this topic.' "1_2": "سيصلك إشعار إن أشار أحد إلى @اسمك أو رد عليك." @@ -1837,7 +1951,7 @@ ar: to_topic_username: "لقد أدخلت اسم مستخدم. سنرسل إشعارًا يحتوي رابطًا يدعوهم إلى هذا الموضوع." to_username: "أدخل اسم المستخدم للشخص الذي تريد دعوته. سنرسل إشعارًا يحتوي رابطًا يدعوهم إلى هذا الموضوع." email_placeholder: "name@example.com" - success_email: "قمنا بإرسال دعوة بالبريد لـ %{emailOrUsername} . سيتم تنبيهك عند قبول الدعوة , تحقق من تبويب الدعوات في صفحتك الشخصية لمتابعة دعوتك." + success_email: "أرسلنا دعوة إلى %{emailOrUsername} بالبريد. سنُرسل إليك إخطارًا متى قبل الدعوة. طالِع لسان الدعوات في صفحة المستخدم عندك لتتابع دعواتك المُرسلة." success_username: "دعونا هذا العضو للمشاركة في هذا الموضوع." error: "عذرا, لا يمكنك دعوة هذا الشخص, ربما لأن تم دعوتة مسبقا؟ (الدعوات محدودة)" success_existing_email: "العضو ذو عنوان البريد الإلكتروني %{emailOrUsername} مسجل بالفعل. لقد قمنا بدعوتة للمشاركة بهذا الموضوع." @@ -1963,12 +2077,14 @@ ar: create: "عذرا، حدثت مشكلة اثناء إنشاء منشورك. من فضلك حاول مجددا." edit: "عذرا، حدثت مشكلة اثناء تعديل منشورك. من فضلك حاول مجددا." upload: "عذرا، حدثت مشكلة اثناء رفع الملف. من فضلك حاول مجددا." + file_too_large: "معذرةً ولكن الملف كبير جدًا (أقصى حجم هو %{max_size_kb} ك.بايت). لماذا لا ترفع الملف الكبير هذا إلى خدمة مشاركة سحابية وتلصق الرابط؟" too_many_uploads: "عذرا، يمكنك فقط رفع ملف واحد كل مرة." upload_not_authorized: "عذرا, نوع الملف الذي تحاول رفعة محذور ( الانواع المسموح بها: %{authorized_extensions})." image_upload_not_allowed_for_new_user: "عذرا، لا يمكن للأعضاء الجدد رفع الصور." attachment_upload_not_allowed_for_new_user: "عذرا، لا يمكن للأعضاء الجدد رفع المرفقات." attachment_download_requires_login: "عذرا، عليك تسجيل الدخول لتحميل المرفقات." abandon_edit: + confirm: "أمتأكّد من إهمال تعديلاتك؟" no_value: "لا, إبقاء" abandon: confirm: "أمتأكد من التخلي عن المنشور؟" @@ -2388,6 +2504,7 @@ ar: this_month: "شهر" this_week: "أسبوع" today: "يوم" + browser_update: 'للأسف فمتصفّح الإنترنت الذي تستعمل قديم جدًا ليعمل عليه هذا الموقع. من فضلك حدّث متصفّحك لترى المحتوى الغني والولوج والردّ.' permission_types: full: "انشاء / رد / مشاهدة" create_post: "رد / مشاهدة" @@ -2604,6 +2721,7 @@ ar: general_tab: "عام" security_tab: "الأمن" report_filter_any: "أي" + too_many_requests: لقد نفّذت هذا الإجراء مرّات كثيرة جدًا. من فضلك انتظر قليلًا قبل معاودة المحاولة. reports: today: "اليوم" yesterday: "امس" @@ -2834,6 +2952,7 @@ ar: new: "جديد" new_style: "واجهة جديدة" delete: "أحذف" + delete_confirm: 'أمتأكّد من حذف ”%{theme_name}“؟' color: "لون" opacity: "الشفافية" copy: "نسخ" @@ -2866,6 +2985,8 @@ ar: color_scheme_select: "حدد الألوان لاستخدامها في الواجهة" custom_sections: "أقسام مخصصة:" theme_components: "مكونات المواجهة" + convert_component_alert: "أمتأكّد من تحويل هذا المكوّن إلى سمة؟ سيُزال من %{relatives} باعتباره مكوّن." + convert_theme_alert: "أمتأكّد من تحويل هذه السمة إلى مكوّ،؟ ستُزال من %{relatives} باعتبارها أبًا." collapse: إخفاء uploads: "الملفات المساعدة" no_uploads: "يمكنك رفع ملفات خاصة بواجهة المستخدم الخاصة بك مثل الخطوط و الصور" @@ -2889,18 +3010,30 @@ ar: updating: "تحديث..." up_to_date: "الواجة محدثة, اخر مره تم التحقق من التحديث:" add: "اضافة" + theme_settings: "إعدادت السمة" + no_settings: "ليس لهذه السمة أيّ إعدادات." + theme_translations: "ترجمات السمة" + empty: "ما من عناصر" + compare_commits: "(طالِع الإيداعات الجديدة)" + repo_unreachable: "تعذّر الاتصال بمستودع غِت لهذه السمة. رسالة العُطل:" + imported_from_archive: "استُوردت هذه السمة من ملف ‎.zip" scss: text: "CSS" + title: "أدخِل CSS صالح، نقبل كلّ أنماط CSS وSCSS الصالحة" header: text: "Header" title: "ادخل الـ HTML لعرضه فوق هيدر الموقع" after_header: text: "بعد الـ Header" + title: "أدخِل HTML لعرضه في كلّ الصفحات بعد الترويسة" footer: text: "تذييل " title: "ادخل نص الـ HTML ليتم عرضه في ذيل الصفحة (الفوتر)" embedded_scss: text: "CSS مضمن" + color_definitions: + text: "تعريفات الألوان" + title: "أدخِل تعريفات الألوان المخصّصة (للمستخدمين المتقدّمين فقط)" head_tag: text: "" title: "رقْم HTML الذي سيُدرج قبل وسم ‎‎" @@ -2908,11 +3041,20 @@ ar: text: "" title: "رقْم HTML الذي سيُدرج قبل وسم ‎‎" colors: + select_base: + title: "اختر مخطّط الألوان الأساس" + description: "المخطّط الأساس:" title: "الألوان" + edit: "حرّر مخطّطات الألوان" + long_title: "مخطّطات الألوان" + about: "عدّل الألوان التي تستعملها السمات. أنشِئ مخطّط ألوان جديد لتبدأ." + new_name: "مخطّط ألوان جديد" copy_name_prefix: "نسخة من" + delete_confirm: "أنحذف مخطّط الألوان هذا؟" undo: "تراجع" undo_title: "التراجع عن تغيير اللن الى اللون السابق" revert: "تراجع" + revert_title: "صفّر هذا اللون إلى مخطّط ألوان دِسكورس المبدئي." primary: name: "الأساسي" description: "أغلب النّصوص، والأيقونات والحدود." @@ -2943,13 +3085,22 @@ ar: love: name: "إعجاب" description: "لون زر الإعجاب." + robots: + title: "بدّل ملف robots.txt في الموقع:" email_style: + title: "نمط الرسائل الإلكترونية" + heading: "خصّص نمط الرسائل الإلكترونية" + html: "قالب HTML" css: "CSS" + reset: "صفّر إلى المبدئي" + save_error_with_reason: "لم تُحفظ التعديلات. %{error}" email: title: "البريد الالكتروني" settings: "إعدادات" templates: "القوالب" preview_digest: "ملخص المعاينة." + advanced_test: + email: "الرسالة الأصلية" sending_test: "يرسل بريدا إلكترونيا اختباريا..." error: "خطأ - %{server_error}" test_error: "حدث خطأ أثناء إرسال رسالة تجريبية. الرجاء فحص إعدادات البريد الإلكتروني و التأكد من أن الاستضافة لا تمنع مرور البريد الإلكتروني والمحاولة مرة أخرى." diff --git a/config/locales/client.be.yml b/config/locales/client.be.yml index 8ffbb17aec..99730e4215 100644 --- a/config/locales/client.be.yml +++ b/config/locales/client.be.yml @@ -737,7 +737,6 @@ be: invites: welcome_to: "Сардэчна запрашаем да сайта %{site_name}!" name_label: "Назва" - password_label: "ўсталяваць пароль" password_reset: continue: "Працягваюць% {site_name}" emoji_set: @@ -892,7 +891,6 @@ be: new: "У вас няма новых тэм." read: "Вы яшчэ не прачыталі ніводнай тэмы." posted: "Вы яшчэ не дапісвалі ў адну тэму." - latest: "Апошніх тэм няма. Шкада." bookmarks: "У вас пакуль няма тэмаў у закладках." category: "У катэгорыі %{category} няма тэм." top: "There are no top topics." @@ -936,7 +934,6 @@ be: read_more: "Хочаце пачытаць яшчэ? %{CatLink} або %{latestLink}." browse_all_categories: Паглядзець усе катэгорыі view_latest_topics: Праглядзіце апошнія тэмы - suggest_create_topic: Чаму б не стварыць тэму? jump_reply_up: перайсці да больш ранняй адказы jump_reply_down: перайсці да больш позняй адказы deleted: "Тэма была выдаленая" diff --git a/config/locales/client.bg.yml b/config/locales/client.bg.yml index 9555b4e201..100f54c95e 100644 --- a/config/locales/client.bg.yml +++ b/config/locales/client.bg.yml @@ -1184,7 +1184,6 @@ bg: accept_invite: "Приеми поканата." success: "Профилът ви е създаден и вече сте влезли в него." name_label: "Име" - password_label: "Задайте парола" password_reset: continue: "Продължете към %{site_name}" emoji_set: @@ -1393,7 +1392,6 @@ bg: new: "Нямате нови теми." read: "Все още не сте прочели нито една тема." posted: "Все още не сте публикували нито една тема." - latest: "Няма повече теми в Последни. Това е тъжно." bookmarks: "Все още нямате теми в Отметки." category: "Няма теми в категория %{category}." top: "Няма топ теми." @@ -1465,7 +1463,6 @@ bg: read_more_MF: "Има { UNREAD, plural, =0 {} one { 1 непрочетено } other { # непрочетени } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 нова тема} other { {BOTH, select, true{and } false {are } other{}} # теми } } или {CATEGORY, select, true {потърси други теми в {catLink}} false {{latestLink}} other {}}" browse_all_categories: Прегледай всички категории view_latest_topics: виж последните теми - suggest_create_topic: Защо не създадете тема ? jump_reply_up: към по ранен отговор jump_reply_down: към по-късен отговор deleted: "Темата беше изтрита" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index a9f9a48280..de9e0c8a70 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -1609,7 +1609,6 @@ bs_BA: accept_invite: "Prihvati pozivnicu" success: "Vaš račun je napravljen i sada ste prijavljeni." name_label: "Ime" - password_label: "Postavi šifru" optional_description: "(opciono)" password_reset: continue: "Nastavi ka %{site_name}" @@ -1969,8 +1968,6 @@ bs_BA: noreplies: imaju nula odgovora single_user: sadrži jednog korisnika post: - count: - label: Minimalan broj objava time: label: Objavljeno before: prije @@ -2016,7 +2013,6 @@ bs_BA: new: "Nemate novih tema." read: "Niste pročitali nijednu temu." posted: "Niste odgovorili ni na jednu temu." - latest: "Nema posljednjih tema. To je tužno." bookmarks: "Još nemate zabilježenih tema." category: "Nema tema u %{category}." top: "Nema top tema." @@ -2100,7 +2096,6 @@ bs_BA: read_more_MF: "Postoj{ UNREAD, plural, =0 {} one {i 1 nepročitana } few {e # nepročitane } other {i # nepročitanih } } { NEW, plural, =0 {} one { {BOTH, select, true{i } false {i } other{}} 1 nova tema} few {} other { {BOTH, select, true{i } false {e } other{}} # novih tema} } preostalih za čitati, ili {CATEGORY, select, true {pogledajte ostale teme u {catLink}} false {{latestLink}} other {}}" browse_all_categories: Pregledaj sve kategorije view_latest_topics: pogledaj najnovije teme - suggest_create_topic: Zašto ne kreirati novu temu? jump_reply_up: jump to earlier reply jump_reply_down: jump to later reply deleted: "Ova tema je obrisana" diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml index 13ee0c6e58..c0aef17e88 100644 --- a/config/locales/client.ca.yml +++ b/config/locales/client.ca.yml @@ -1473,7 +1473,6 @@ ca: accept_invite: "Accepta la invitació" success: "S'ha creat el compte i ara heu iniciat la sessió." name_label: "Nom" - password_label: "Estableix la contrasenya" optional_description: "(opcional)" password_reset: continue: "Continua a %{site_name}" @@ -1825,8 +1824,6 @@ ca: noreplies: tenen zero respostes single_user: contenen un únic usuari post: - count: - label: Recompte mínim de publicacions time: label: Publicat before: abans de @@ -1872,7 +1869,6 @@ ca: new: "No teniu cap tema nou." read: "Encara no heu llegit cap tema." posted: "Encara no heu publicat cap tema." - latest: "No hi ha temes més recents. És una llàstima." bookmarks: "Encara no heu marcat temes com a preferits." category: "No hi ha temes de %{category}." top: "No hi ha temes principals." @@ -1950,7 +1946,6 @@ ca: read_more_MF: "Hi ha { UNREAD, plural, =0 {} one { 1 no llegit } other { are # no llegits } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 nou tema} other { {BOTH, select, true{and } false {are } other{}} # nous temes} } restants, or {CATEGORY, select, true {navegueu per altres temes en {catLink}} false {{latestLink}} other {}}" browse_all_categories: Navega per totes les categories view_latest_topics: mira els temes més recents - suggest_create_topic: Per què no creeu un tema? jump_reply_up: salta a la resposta anterior jump_reply_down: salta a la resposta posterior deleted: "El tema ha estat suprimit" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 38fdcc7af6..b7dd7162d5 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -1239,7 +1239,6 @@ cs: accept_invite: "Přijmout pozvání" success: "Váš účet byl vytvořen a nyní jste přihlášeni." name_label: "Jméno" - password_label: "Nastavit heslo" optional_description: "(volitelné)" password_reset: continue: "Pokračovat na %{site_name}" @@ -1558,8 +1557,6 @@ cs: noreplies: jsou bez odpovědi single_user: obsahují jen jednoho uživatele post: - count: - label: Minimální počet příspěvků time: label: Přidáno before: před @@ -1606,7 +1603,6 @@ cs: new: "Nemáte žádná nová témata ke čtení." read: "Zatím jste nečetli žádná témata." posted: "Zatím jste nepřispěli do žádného tématu." - latest: "Nejsou tu žádná témata z poslední doby. To je docela smutné." bookmarks: "V tématech nemáte žádné záložky." category: "V kategorii %{category} nejsou žádná témata." top: "Nejsou tu žádná populární témata." @@ -1696,7 +1692,6 @@ cs: read_more_MF: "{ UNREAD, plural, =0 {} one { Zbývá 1 nepřečtené téma } other { Je tu # nepřečtených témat } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 nové téma} other { {BOTH, select, true{and } false {are } other{}} # nových témat} }, nebo {CATEGORY, select, true {zobrazit další témata v kategorii {catLink}} false {{latestLink}} other {}}" browse_all_categories: Projděte všechny kategorie view_latest_topics: zobrazte si populární témata - suggest_create_topic: Co takhle založit nové téma? jump_reply_up: přejít na předchozí odpověď jump_reply_down: přejít na následující odpověď deleted: "Téma bylo smazáno" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 5d5d2158be..ced02c7216 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -1383,7 +1383,6 @@ da: accept_invite: "Accepter Invitation" success: "Din konto er blevet oprettet, og du er nu logget ind." name_label: "Navn" - password_label: "Sæt Kodeord" optional_description: "(valgfri)" password_reset: continue: "Fortsæt til %{site_name}" @@ -1729,8 +1728,6 @@ da: noreplies: har ingen svar single_user: indeholder en enkelt bruger post: - count: - label: Minimum for antal indlæg time: label: Sendt before: før @@ -1776,7 +1773,6 @@ da: new: "Du har ingen nye emner." read: "Du har ikke læst nogen emner endnu." posted: "Du har ikke skrevet nogen indlæg endnu." - latest: "Der er ikke nogen nye emner. Det er sørgeligt." bookmarks: "Du har ingen bogmærkede emner endnu." category: "Der er ingen emner i kategorien %{category}." top: "Der er ingen top emner" @@ -1853,7 +1849,6 @@ da: read_more_MF: "Der { UNREAD, plural, =0 {} one { er 1 ulæst } other { er # ulæste } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 new topic} other { {BOTH, select, true{and } false {are } other{}} # new topics} } tilbage, eller {CATEGORY, select, true {kig på andre emner i {catLink}} false {{latestLink}} other {}}" browse_all_categories: Vis alle kategorier view_latest_topics: vis seneste emner - suggest_create_topic: Hvorfor ikke oprette et emne? jump_reply_up: hop til tidligere svar jump_reply_down: hop til senere svar deleted: "Emnet er blevet slettet" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index d7f3778559..bafb6da9db 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -1580,7 +1580,6 @@ de: accept_invite: "Einladung annehmen" success: "Dein Konto wurde erstellt und du bist jetzt angemeldet." name_label: "Name" - password_label: "Wähle ein Passwort" optional_description: "(optional)" password_reset: continue: "Weiter zu %{site_name}" @@ -1599,29 +1598,6 @@ de: categories_and_top_topics: "Kategorien und angesagte Themen" categories_boxes: "Boxen mit Unterkategorien" categories_boxes_with_topics: "Spalten mit hervorgehobenen Themen" - base_font_setting: - helvetica: "Helvetica / Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "RobotoCondensed" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "Ubuntu" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" shortcut_modifier_key: shift: "Umschalt" ctrl: "Strg" @@ -1978,7 +1954,7 @@ de: single_user: Themen mit nur einem Benutzer post: count: - label: Themen mit … oder mehr Beiträgen + label: Beiträge time: label: 'Zeitraum einschränken:' before: Beiträge vor dem @@ -2024,7 +2000,6 @@ de: new: "Es gibt für dich keine neuen Themen." read: "Du hast noch keine Themen gelesen." posted: "Du hast noch keine Beiträge verfasst." - latest: "Es gibt keine aktuellen Themen. Das ist schade." bookmarks: "Du hast noch keine Themen mit einem Lesezeichen versehen." category: "Es gibt keine Themen in %{category}." top: "Es gibt keine Top-Themen." @@ -2110,7 +2085,6 @@ de: bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}" browse_all_categories: Alle Kategorien durchsehen view_latest_topics: aktuelle Themen anzeigen - suggest_create_topic: Möchtest du ein neues Thema erstellen? jump_reply_up: zur vorherigen Antwort springen jump_reply_down: zur nachfolgenden Antwort springen deleted: "Das Thema wurde gelöscht" @@ -2206,7 +2180,7 @@ de: "2_8": "Du wirst eine Anzahl neuer Antworten sehen, weil du diese Kategorie verfolgst." "2_4": "Du wirst eine Anzahl neuer Antworten sehen, weil du einen Beitrag in diesem Thema geschrieben hast." "2_2": "Du wirst eine Anzahl neuer Antworten sehen, weil du dieses Thema verfolgst." - "2": 'You will see a count of new replies because you read this topic.' + "2": 'Du wirst eine Anzahl neuer Antworten sehen, weil du dieses Thema gelesen hast.' "1_2": "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet." "1": "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet." "0_7": "Du ignorierst alle Benachrichtigungen dieser Kategorie." diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml index 179e21ad4c..79ffe89ff2 100644 --- a/config/locales/client.el.yml +++ b/config/locales/client.el.yml @@ -1573,7 +1573,6 @@ el: accept_invite: "Αποδοχή Πρόσκλησης" success: "Ο λογαριασμός σας έχει δημιουργηθεί και έχετε συνδεθεί. " name_label: "Όνομα" - password_label: "Ορισμός Κωδικού Πρόσβασης" optional_description: "(προεραιτικό)" password_reset: continue: "Συνεχίστε στην %{site_name}" @@ -1592,29 +1591,6 @@ el: categories_and_top_topics: "Κατηγορίες και κορυφαία θέματα" categories_boxes: "Κουτιά με υποκατηγορίες" categories_boxes_with_topics: "Κουτιά με προτεινόμενα θέματα" - base_font_setting: - helvetica: "Helvetica/Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "RobotoCondensed" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "Ubuntu" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" shortcut_modifier_key: shift: " Shift" ctrl: "Ctrl" @@ -1970,8 +1946,6 @@ el: noreplies: έχουν μηδέν απαντήσεις single_user: περιέχουν ένα μόνο χρήστη post: - count: - label: Ελάχιστος αριθμός αναρτήσεων time: label: Αναρτήθηκε before: πριν @@ -2017,7 +1991,6 @@ el: new: "Δεν υπάρχουν νέα νήματα." read: "Δεν έχεις διαβάσει κανένα νήμα ακόμη." posted: "Δεν έχεις αναρτήσει σε κάποιο νήμα ακόμη." - latest: "Δεν υπάρχουν νέα νήματα. Αυτό είναι λυπηρό." bookmarks: "Δεν έχεις βάλει σελιδοδείκτη σε κανένα νήμα." category: "Δεν υπάρχουν νήματα στην κατηγορία %{category}." top: "Δεν υπάρχουν κορυφαία νήματα." @@ -2105,7 +2078,6 @@ el: browse_all_categories: Περιήγηση σε όλες τις κατηγορίες browse_all_tags: Περιηγηθείτε σε όλες τις ετικέτες view_latest_topics: δες τα πρόσφατα νήματα - suggest_create_topic: Γιατί δεν φτιάχνεις ένα νέο νήμα; jump_reply_up: μετάβαση στην απάντηση που προηγείται jump_reply_down: μετάβαση στην απάντηση που ακολουθεί deleted: "Το νήμα έχει διαγραφεί " diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index eed794b1fe..95cad87875 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1148,6 +1148,7 @@ en: taken: "Sorry, that email is not available." error: "There was an error changing your email. Perhaps that address is already in use?" success: "We've sent an email to that address. Please follow the confirmation instructions." + success_via_admin: "We've sent an email to that address. The user will need to follow the confirmation instructions in the email." success_staff: "We've sent an email to your current address. Please follow the confirmation instructions." change_avatar: @@ -1190,6 +1191,7 @@ en: sso_override_instructions: "Email can be updated from SSO provider." no_secondary: "No secondary emails" instructions: "Never shown to the public." + admin_note: "Note: An admin user changing another non-admin user's email indicates the user has lost access to their original email account, so a reset password email will be sent to their new address. The user's email will not change until they complete the reset password process." ok: "We will email you to confirm" required: "Please enter an email address" invalid: "Please enter a valid email address" @@ -1714,7 +1716,7 @@ en: accept_invite: "Accept Invitation" success: "Your account has been created and you're now logged in." name_label: "Name" - password_label: "Set Password" + password_label: "Password" optional_description: "(optional)" password_reset: @@ -1940,6 +1942,9 @@ en: label: "Toggle topic bump" desc: "Reply without changing latest reply date" + reload: "Reload" + ignore: "Ignore" + notifications: tooltip: regular: @@ -2119,15 +2124,21 @@ en: single_user: contain a single user post: count: - label: Minimum Post Count + label: Posts + min: + placeholder: minimum + max: + placeholder: maximum time: label: Posted before: before after: after + views: + label: Views min_views: - label: Minimum Views + placeholder: minimum max_views: - label: Maximum Views + placeholder: maximum hamburger_menu: "go to another topic list or category" new_item: "new" @@ -2166,6 +2177,7 @@ en: choose_new_tags: "Choose new tags for these topics:" choose_append_tags: "Choose new tags to append for these topics:" changed_tags: "The tags of those topics were changed." + remove_tags: "Remove Tags" none: unread: "You have no unread topics." @@ -2178,7 +2190,7 @@ en: category: "There are no %{category} topics." top: "There are no top topics." educate: - new: '

Your new topics appear here.

By default, topics are considered new and will show a new indicator if they were created in the last 2 days.

Visit your preferences to change this.

' + new: '

Your new topics will appear here. By default, topics are considered new and will show a indicator if they were created in the last 2 days.

Visit your preferences to change this.

' unread: '

Your unread topics appear here.

By default, topics are considered unread and will show unread counts 1 if you:

Or if you have explicitly set the topic to Tracked or Watched via the notification control at the bottom of each topic.

Visit your preferences to change this.

' bottom: latest: "There are no more latest topics." @@ -3397,8 +3409,8 @@ en: delete: "Delete" confirm_delete: "Are you sure you want to delete this tag group?" everyone_can_use: "Tags can be used by everyone" - usable_only_by_staff: "Tags are visible to everyone, but only staff can use them" - visible_only_to_staff: "Tags are visible only to staff" + usable_only_by_groups: "Tags are visible to everyone, but only the following groups can use them" + visible_only_to_groups: "Tags are visible only to the following groups" topics: none: diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index 6650de3fd0..6d4c51e2d6 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -122,6 +122,7 @@ es: twitter: "Compartir en Twitter" facebook: "Compartir en Facebook" email: "Enviar por correo electrónico" + url: "Copias y compartir URL" action_codes: public_topic: "hizo este tema público %{when}" private_topic: "hizo este tema un mensaje personal %{when}" @@ -263,7 +264,9 @@ es: unbookmark: "Haz clic para eliminar todos los marcadores en este tema" unbookmark_with_reminder: "Haz clic para quitar todos los marcadores y recordatorios en este tema. Tienes un recordatorio puesto %{reminder_at} para este tema." bookmarks: + created: "Has guardado esta publicación en marcadores. %{name}" not_bookmarked: "marcar esta publicación" + created_with_reminder: "Has marcado esta publicación con un recordatorio %{date}. %{name}" remove: "Eliminar marcador" delete: "Borrar marcador" confirm_delete: "¿Seguro que quieres borrar este marcados? El recordatorio también se borrará." @@ -272,6 +275,7 @@ es: no_timezone: 'No has establecido una zona horaria todavía. No podrás establecer recordatorios. Puedes elegir una en tu perfil.' invalid_custom_datetime: "La fecha y hora que suministraste es inválida. Por favor, intenta de nuevo." list_permission_denied: "No tienes permiso para ver los marcadores de este usuario." + no_user_bookmarks: "No tiene publicaciones marcadas; los marcadores le permiten referirse rápidamente a publicaciones específicas." auto_delete_preference: label: "Borrar automáticamente" never: "Nunca" @@ -294,6 +298,7 @@ es: today_with_time: "hoy a las %{time}" tomorrow_with_time: "mañana a las %{time}" at_time: "a las %{date_time}" + existing_reminder: "Tiene un recordatorio establecido para este marcador que se enviará %{at_date_time}" copy_codeblock: copied: "¡copiado!" drafts: @@ -517,6 +522,7 @@ es: sent_by_user: "Enviado por %{user}" sent_by_you: "Enviado por ti" directory: + username: "Nombre de usuario" filter_name: "filtrar por nombre de usuario" title: "Usuarios" likes_given: "Dados" @@ -549,9 +555,9 @@ es: member_requested: "Solicitado el" add_members: title: "Agregar miembros a %{group_name}" - description: "También puede pegar en una lista separada por comas." + description: "También puedes pegar una lista separada por comas." usernames: "Ingrese nombres de usuario o direcciones de correo electrónico" - input_placeholder: "Nombre de usuario o correo electrónico" + input_placeholder: "Nombres de usuario o correos electrónicos" notify_users: "Notificaciones a usuarios" requests: title: "Solicitudes" @@ -597,7 +603,16 @@ es: categories: title: Categorías long_title: "Notificaciones predeterminadas de categoría" + description: "Cuando se agregan usuarios a este grupo, la configuración de notificaciones de categorías será establecida a estos valores predeterminados. Después, pueden cambiarla." + watched_categories_instructions: "Vigilar automáticamente todos los temas en estas categorías. Los miembros del grupo serán notificados de cada nueva respuesta y tema, y un contador de respuestas nuevas al lado del tema." + tracked_categories_instructions: "Seguir automáticamente todos los temas en estas categorías. Aparecerá un contador de nuevas respuestas al lado del tema." + watching_first_post_categories_instructions: "Los usuarios serán notificados de la primera respuesta en cada tema nuevo en estas categorías." regular_categories_instructions: "Si estas categorías están silenciadas, no estarán silenciadas para los miembros del grupo. Se les notificará a los usuarios si son mencionados o si alguien les responde." + muted_categories_instructions: "Los usuarios no serán notificados acerca de temas nuevos en estas categorías, y no aparecerán en las categorías o páginas de temas recientes." + tags: + title: Etiquetas + long_title: "Etiqueta notificaciones predeterminadas" + description: "Cuando se agregan usuarios a este grupo, su configuración de notificaciones de etiquetas será establecida a estos valores predeterminados. Después, pueden cambiarla." logs: title: "Registros" when: "Cuándo" @@ -608,6 +623,10 @@ es: details: "Detalles" from: "Desde" to: "Hasta" + permissions: + title: "Permisos" + none: "No hay categorías asociadas a este grupo." + description: "Los miembros de este grupo pueden acceder a estas categorías" public_admission: "Permitir que los usuarios se unan al grupo libremente (Se requiere que el grupo sea publicamente visible)" public_exit: "Permitir a los usuarios abandonar el grupo libremente" empty: @@ -845,7 +864,21 @@ es: dismiss_notifications_tooltip: "Marcar todas las notificaciones no leídas como leídas" first_notification: "¡Tu primera notificación! Selecciónala para comenzar." dynamic_favicon: "Mostrar número en el icono del navegador" + skip_new_user_tips: + description: "Omitir consejos de bienvenida" + not_first_time: "¿No es tu primera vez?" + skip_link: "Saltarse estos consejos" theme_default_on_all_devices: "Hacer que este sea el tema por defecto en todos mis dispositivos" + color_scheme_default_on_all_devices: "Establecer la combinación de colores como predeterminadas en todos mis dispositivos" + color_scheme: "Combinación de colores" + color_schemes: + default_description: "Predeterminado" + disable_dark_scheme: "Igual que regular" + undo: "Restablecer" + dark: "Modo oscuro" + default_dark_scheme: "(valor predeterminado por el sitio)" + dark_mode: "Modo oscuro" + dark_mode_enable: "Activar modo oscuro automático" text_size_default_on_all_devices: "Hacer que este sea el tamaño por defecto en todos mis dispositivos" allow_private_messages: "Permitir que otros usuarios me envíen mensajes privados" external_links_in_new_tab: "Abrir todos los enlaces externos en una nueva pestaña" @@ -976,6 +1009,7 @@ es: second_factor: title: "Autenticación en dos pasos" enable: "Gestionar autenticación en dos pasos" + disable_all: "Desactivar todo" forgot_password: "¿Olvidaste tu contraseña?" confirm_password_description: "Por favor, confirma tu contraseña para continuar" name: "Nombre" @@ -1068,6 +1102,7 @@ es: sso_override_instructions: "El correo electrónico puede actualizarse desde el proveedor de SSO." no_secondary: "Sin correos electrónicos secundarios" instructions: "Nunca se mostrará al público." + admin_note: "Nota: Un usuario administrador que cambia el correo electrónico de otro usuario no administrador indica que el usuario ha perdido el acceso a su cuenta de correo electrónico original, por lo que se enviará un correo electrónico para restablecer la contraseña a su nueva dirección. El correo electrónico del usuario no cambiará hasta que complete el proceso de restablecimiento de contraseña." ok: "Te enviaremos un correo electrónico para confirmar" required: "Por favor, introduce un correo electrónico" invalid: "Por favor, ingresa una dirección de correo electrónico válida" @@ -1422,6 +1457,8 @@ es: title: "Mensaje" invite: "Invitar a otros..." edit: "Añadir o quitar..." + remove: "Eliminar ..." + add: "Añadir ..." leave_message: "¿Estás seguro de que quieres abandonar este mensaje?" remove_allowed_user: "¿Estás seguro de que quieres eliminar a %{name} de este mensaje?" remove_allowed_group: "¿Estás seguro de que quieres eliminar a %{name} de este mensaje?" @@ -1539,7 +1576,7 @@ es: accept_invite: "Aceptar invitación" success: "Tu cuenta ha sido creada y has iniciado sesión." name_label: "Nombre" - password_label: "Establecer contraseña" + password_label: "Contraseña" optional_description: "(opcional)" password_reset: continue: "Continuar a %{site_name}" @@ -1580,6 +1617,9 @@ es: one: "Seleccionar al menos %{count} item." other: "Selecciona al menos %{count} elementos." invalid_selection_length: "La selección debe tener al menos %{count} caracteres." + components: + categories_admin_dropdown: + title: "Gestionar categorías" date_time_picker: from: Desde to: Hasta @@ -1702,6 +1742,8 @@ es: abandon: "cerrar el editor y descartar borrador" enter_fullscreen: "ingresar al editor en pantalla completa" exit_fullscreen: "salir del editor en pantalla completa" + show_toolbar: "mostrar la barra de herramientas del editor" + hide_toolbar: "ocultar la barra de herramientas del editor" modal_ok: "OK" modal_cancel: "Cancelar" cant_send_pm: "Lo sentimos, no puedes enviar un mensaje a %{username}." @@ -1714,6 +1756,7 @@ es: draft: Borrador edit: Editar reply_to_post: + label: Responder a una publicación de %{postUsername} desc: Responder a una publicación específica reply_as_new_topic: label: Responder como tema enlazado @@ -1739,6 +1782,8 @@ es: toggle_topic_bump: label: "Alternar bump del tema" desc: "Responder sin alterar la fecha de última respuesta" + reload: "Recargar" + ignore: "Ignorar" notifications: tooltip: regular: @@ -1820,6 +1865,8 @@ es: liked_consolidated: "nuevos me gusta" post_approved: "publicación aprobada" membership_request_consolidated: "nuevas solicitudes de membresía" + reaction: "nueva reacción" + votes_released: "Voto liberado" upload_selector: title: "Agregar imagen" title_with_attachments: "Agregar una imagen o archivo" @@ -1906,11 +1953,21 @@ es: single_user: contienen un solo usuario post: count: - label: Número mínimo de publicaciones + label: Publicaciones + min: + placeholder: mínimo + max: + placeholder: máximo time: label: Fecha de publicación before: antes de after: después de + views: + label: Visitas + min_views: + placeholder: mínimo + max_views: + placeholder: máximo hamburger_menu: "ir a otra lista de temas o categoría" new_item: "nuevo" go_back: "volver" @@ -1952,7 +2009,8 @@ es: new: "No tienes temas nuevos." read: "Todavía no has leído ningún tema." posted: "Todavía no has publicado en ningún tema." - latest: "No hay temas recientes. Qué pena." + ready_to_create: "Listo para " + latest: "¡Estás al día!" bookmarks: "Todavía no tienes temas guardados en marcadores." category: "No hay temas con la categoría %{category}." top: "No hay temas destacados." @@ -2037,8 +2095,9 @@ es: read_more_MF: "Hay { UNREAD, plural, =0 {} one { 1 no leído } other { # no leídos } } { NEW, plural, =0 {} one { {BOTH, select, true{y } false { } other{}} 1 nuevo tema} other { {BOTH, select, true{y } false { } other{}} # nuevos temas} } restantes, o {CATEGORY, select, true {explora otros temas en {catLink}} false {{latestLink}} other {}}" bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}" browse_all_categories: Ver todas las categorías + browse_all_tags: Ver todas las etiquetas view_latest_topics: ver los temas recientes - suggest_create_topic: '¿Por qué no creas un tema?' + suggest_create_topic: '¿iniciar una nueva conversación?' jump_reply_up: saltar a la primera respuesta jump_reply_down: saltar a la última respuesta deleted: "El tema ha sido eliminado" @@ -2244,6 +2303,7 @@ es: success: "Hemos invitado a ese usuario a participar en este mensaje." success_group: "Hemos invitado a ese grupo a participar en este mensaje." error: "Lo sentimos, se produjo un error al invitar a ese usuario." + not_allowed: "Lo sentimos, no se puede invitar a ese usuario." group_name: "nombre del grupo" controls: "Controles del tema" invite_reply: @@ -2894,6 +2954,8 @@ es: profile: "%{shortcut} Perfil" messages: "%{shortcut} Mensajes" drafts: "%{shortcut} Borradores" + next: "%{shortcut} Tema siguiente" + previous: "%{shortcut} Tema anterior" navigation: title: "Navegación" jump: "%{shortcut} Ir a la publicación #" @@ -3042,6 +3104,7 @@ es: delete_unused_confirmation_more_tags: one: "%{tags} y %{count} más" other: "%{tags} y %{count} más" + delete_no_unused_tags: "No hay etiquetas sin usar." delete_unused: "Eliminar etiquetas sin usar" delete_unused_description: "Eliminar todas las etiquetas que no estén asociadas a ningún tema o mensaje personal" cancel_delete_unused: "Cancelar" @@ -3184,6 +3247,9 @@ es: view_table: "tabla" view_graph: "gráfico" refresh_report: "Actualizar reporte" + daily: Día + monthly: Mes + weekly: Semana dates: "Fechas (UTC)" groups: "Todos los grupos" disabled: "Este reporte está inhabilitado" @@ -3233,6 +3299,7 @@ es: title: "¿Quién puede ver este grupo?" public: "Todos" logged_on_users: "Usuarios que hayan iniciado sesión" + staff: "Propietarios y moderadores de grupos" owners: "Propietarios del grupo" description: "Los administradores pueden ver todos los grupos." members_visibility_levels: @@ -3296,6 +3363,7 @@ es: save: Guardar new_key: Nueva clave de API revoked: Revocada + delete: Eliminar permanentemente not_shown_again: Esta clave no se volverá a mostrar. Asegúrate de copiarla correctamente antes de continuar. continue: Continuar use_global_key: Clave global (permite todas las acciones) @@ -3306,6 +3374,15 @@ es: allowed_parameters: Parámetros permitidos optional_allowed_parameters: Parámetros permitidos (opcional) any_parameter: (cualquier parámetro) + allowed_urls: Direcciones URL permitidas + descriptions: + topics: + read: Leer un tema o una publicación específica del mismo. También usando RSS. + write: Crear un nuevo tema o publicar en uno existente. + users: + sync_sso: Sincronizar usuario usando SSO. + show: Obtener información sobre un usuario. + check_emails: Obtener una lista de los correos electrónicos del usuario. web_hooks: title: "Webhooks" none: "Ahora mismo no hay webhooks." @@ -3545,6 +3622,7 @@ es: is_default: "El tema está habilitado por defecto" user_selectable: "El tema puede ser seleccionado por usuarios" color_scheme: "Paleta de color" + default_light_scheme: "Claro (predeterminado)" color_scheme_select: "Selecciona colores para usar en el tema" custom_sections: "Secciones personalizadas:" theme_components: "Componentes del tema" @@ -4431,6 +4509,7 @@ es: title: "Insertado" host: "Hosts permitidos" class_name: "Nombre de clase" + allowed_paths: "Lista de rutas permitidas" edit: "editar" category: "Publicar en categoría" add_host: "Añadir host" diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml index d7e921f800..6f4f2c82f7 100644 --- a/config/locales/client.et.yml +++ b/config/locales/client.et.yml @@ -1099,7 +1099,6 @@ et: accept_invite: "Võta kutse vastu" success: "Teie konto on loodud ning olete nüüd sisse logitud." name_label: "Nimi" - password_label: "Määra parool" optional_description: "(valikuline)" password_reset: continue: "Edasi saidile %{site_name}" @@ -1346,8 +1345,6 @@ et: noreplies: ilma vastusteta single_user: on ainult ühe kasutaja postitustega post: - count: - label: Minimaalne postituste arv time: label: Postitatud before: enne @@ -1391,7 +1388,6 @@ et: new: "Sul ei ole uusi teemasid." read: "Sa ei ole veel ühtegi teemat lugenud." posted: "Sa ei ole veel ühtegi teemasse postitanud." - latest: "Ühtegi värsket teemat pole. Nukker." bookmarks: "Sul ei ole veel ühtegi järjehoidjaga teemal." category: "Foorumis %{category} teemad puuduvad." top: "Tippteemad puuduvad." @@ -1466,7 +1462,6 @@ et: read_more_MF: "Sul on { UNREAD, plural, =0 {} one { is 1 lugemata } other { are # lugemata } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 uus teema} other { {BOTH, select, true{and } false {are } other{}} # uut teemat} } jäänud või {CATEGORY, select, true {loe teisi teemasid {catLink} alafoorumis} false {{latestLink}} other {}}" browse_all_categories: Vaata kõiki foorumeid view_latest_topics: vaata värskeid teemasid - suggest_create_topic: Teeks äkki teema? jump_reply_up: hüppa varasema vastuse juurde jump_reply_down: hüppa hilisema vastuse juurde deleted: "See teema on kustutatud" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 45fb0ceeba..088896d015 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -122,6 +122,7 @@ fa_IR: twitter: "در توییتر به اشتراک بگذارید" facebook: "در فیس‌بوک به اشتراک بگذارید" email: "ارسال از طریق ایمیل" + url: "کپی و اشتراک‌گذاری نشانی وب" action_codes: public_topic: "این موضوع در %{when} عمومی شده" private_topic: "این موضوع در %{when} یک پیغام خصوصی شده" @@ -1477,7 +1478,6 @@ fa_IR: accept_invite: "پذیرفتن دعوت" success: "حساب‌کاربری شما با موفقیت ایجاد شد و می‌توانید وارد سایت شوید." name_label: "نام" - password_label: "تنظیم رمز عبور" optional_description: "(اختیاری)" password_reset: continue: "برو به %{site_name}" @@ -1818,12 +1818,16 @@ fa_IR: noreplies: پاسخی ندارند single_user: یک کاربر دارند post: - count: - label: کمترین تعداد فرسته time: label: ارسال شده before: قبل از after: بعد از + views: + label: بازدید + min_views: + placeholder: حداقل + max_views: + placeholder: حداکثر hamburger_menu: "به فهرست مبحث یا دسته بندی دیگر بروید" new_item: "تازه" go_back: "برگردید" @@ -1860,12 +1864,13 @@ fa_IR: choose_new_tags: "انتخاب برچسب‌های جدید برای این موضوعات:" choose_append_tags: "انتخاب برچسب‌های جدید برای افزودن به این موضوعات:" changed_tags: "انتخاب برچسب برای موضوعاتی که عوض شدند" + remove_tags: "حذف برچسب‌ها" none: unread: "موضوع خوانده نشده‌ای ندارید." new: "شما هیچ موضوع تازه‌ای ندارید" read: "هنوز هیچ موضوعی را نخوانده‌اید." posted: "هنوز در هیچ موضوعی نوشته نگذاشته‌اید." - latest: "هیچ موضوع تازه‌ای نیست. چه بد!" + ready_to_create: "آماده به " bookmarks: "هنوز هیچ موضوع نشانک‌داری ندارید." category: "هیچ موضوعی در %{category} نیست." top: "موضوع برتری وجود ندارد." @@ -1943,7 +1948,6 @@ fa_IR: read_more_MF: "{ UNREAD, plural, =0 {} one { یک پیام خوانده نشده } other { # پیام خوانده نشده } } { NEW, plural, =0 {} one { {BOTH, select, true{و } false { } other{}} 1 موضوع جدید } other { {BOTH, select, true{و } false { } other{}} # موضوع جدید } } وجود دارد, یا {CATEGORY, select, true {نمایش سایر موضوعات دسته‌بندی {catLink}} false {{latestLink}} other {}}" browse_all_categories: جستوجوی همه‌ی دسته‌‌بندی‌ها view_latest_topics: مشاهده آخرین موضوع - suggest_create_topic: چطور است یک موضوع بسازیم؟ jump_reply_up: رفتن به جدید ترین پاسخ jump_reply_down: رفتن به پاسخ بعدی deleted: "موضوع پاک شده است" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 68e3942492..df0cab3bcb 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -1498,7 +1498,6 @@ fi: accept_invite: "Hyväksy kutsu" success: "Tili luotiin, ja olet nyt kirjautunut sisään." name_label: "Nimi" - password_label: "Aseta salasana" optional_description: "(ei pakollinen)" password_reset: continue: "Jatka sivustolle %{site_name}" @@ -1861,8 +1860,6 @@ fi: noreplies: ei ole vastattu single_user: on kirjoittanut vain yksi käyttäjä post: - count: - label: Viestejä vähintään time: label: Kirjoitettu before: ennen @@ -1908,7 +1905,6 @@ fi: new: "Sinulla ei ole uusia ketjuja." read: "Et ole lukenut vielä yhtään yhtään ketjua." posted: "Et ole kirjoittanut vielä yhteenkään ketjuun." - latest: "Tuoreimpia ketjuja ei ole. Onpa harmi." bookmarks: "Et ole vielä merkinnyt kirjanmerkkejä." category: "Alueella %{category} ei ole ketjua." top: "Kuumia ketjuja ei ole." @@ -1994,7 +1990,6 @@ fi: bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}" browse_all_categories: Selaa keskustelualueita view_latest_topics: tuoreimpia ketjuja - suggest_create_topic: Jospa aloittaisit uuden ketjun? jump_reply_up: hyppää aiempaan vastaukseen jump_reply_down: hyppää myöhempään vastaukseen deleted: "Tämä ketju on poistettu" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 175071b5ee..a51daff867 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -122,6 +122,7 @@ fr: twitter: "Partager sur Twitter" facebook: "Partager sur Facebook" email: "Envoyer par e-mail" + url: "Copier et partager l'URL" action_codes: public_topic: "a rendu ce sujet public %{when}" private_topic: "a rendu ce sujet message direct %{when}" @@ -601,8 +602,11 @@ fr: access: Accès categories: title: Catégories + long_title: "Notifications par défaut de la catégorie" + description: "Lorsque des utilisateurs sont ajoutés à ce groupe, leurs paramètres de notification de catégorie seront définis à ces valeurs par défaut. Ensuite, ils pourront les modifier." tags: title: Étiquettes + long_title: "Notifications par défaut des étiquettes" logs: title: "Journaux" when: "Date" @@ -918,6 +922,8 @@ fr: users: "Utilisateurs" muted_users: "Silencieux" muted_users_instructions: "Ignorer toutes les notifications et messages directs provenant de ces utilisateurs." + allowed_pm_users: "Autorisés" + allowed_pm_users_instructions: "Autoriser uniquement les messages directs de ces utilisateurs." ignored_users: "Ignorés" ignored_users_instructions: "Ignorer tous les messages, notifications et messages directs provenant de ces utilisateurs." tracked_topics_link: "Afficher" @@ -1552,7 +1558,6 @@ fr: accept_invite: "Accepter l'invitation" success: "Votre compte a été créé et vous êtes maintenant connecté." name_label: "Nom" - password_label: "Définir le mot de passe" optional_description: "(facultatif)" password_reset: continue: "Continuer vers %{site_name}" @@ -1571,29 +1576,6 @@ fr: categories_and_top_topics: "Catégories et meilleurs sujets" categories_boxes: "Boîtes avec sous-catégories" categories_boxes_with_topics: "Boîtes avec sujets à la une" - base_font_setting: - helvetica: "Helvetica / Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "Roboto Condensé" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "Ubuntu" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" shortcut_modifier_key: shift: "Maj" ctrl: "Ctrl" @@ -1750,6 +1732,7 @@ fr: draft: Brouillon edit: Modifier reply_to_post: + label: Répondre à un message de %{postUsername} desc: Répondre à un message spécifique reply_as_new_topic: label: Répondre via un sujet lié @@ -1941,8 +1924,6 @@ fr: noreplies: n'ont aucune réponse single_user: contiennent un unique utilisateur post: - count: - label: Nombre minimum de messages time: label: Date before: avant @@ -1983,12 +1964,12 @@ fr: choose_new_tags: "Choisissez de nouvelles étiquettes pour ces sujets :" choose_append_tags: "Choisissez de nouvelles étiquettes à ajouter à ces sujets :" changed_tags: "Les étiquettes de ces sujets ont été modifiées." + remove_tags: "Supprimer les étiquettes" none: unread: "Vous n'avez aucun sujet non lu." new: "Vous n'avez aucun nouveau sujet." read: "Vous n'avez lu aucun sujet pour le moment." posted: "Vous n'avez écrit aucun message pour le moment." - latest: "Il n'y a aucun sujet pour le moment. C'est triste…" bookmarks: "Vous n'avez pas encore mis de signets à des sujets." category: "Il n'y a pas de sujets dans %{category}." top: "Il n'y a pas de meilleurs sujets." @@ -2073,8 +2054,8 @@ fr: read_more_MF: "Il y { UNREAD, plural, =0 {} one { a 1 sujet non lu } other { a # sujets non lus } } { NEW, plural, =0 {} one { {BOTH, select, true{et } false {a } other{}} 1 nouveau sujet } other { {BOTH, select, true{et } false {a } other{}} # nouveaux sujets } } restant, ou {CATEGORY, select, true {consulter les autres sujets dans {catLink}} false {{latestLink}} other {}}" bumped_at_title_MF: "{FIRST_POST} : {CREATED_AT}\n{LAST_POST} : {BUMPED_AT}" browse_all_categories: Voir toutes les catégories + browse_all_tags: Parcourir toutes les étiquettes view_latest_topics: voir les derniers sujets - suggest_create_topic: Pourquoi ne pas créer un sujet ? jump_reply_up: aller à des réponses précédentes jump_reply_down: aller à des réponses ultérieures deleted: "Ce sujet a été supprimé" diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml index 082fe44b5d..8c6998fdbc 100644 --- a/config/locales/client.gl.yml +++ b/config/locales/client.gl.yml @@ -1414,7 +1414,6 @@ gl: accept_invite: "Aceptar convite" success: "A túa conta acaba de ser creada e tes a sesión iniciada." name_label: "Nome" - password_label: "Estabelecer o contrasinal" optional_description: "(opcional)" password_reset: continue: "Continuar a %{site_name}" @@ -1782,7 +1781,6 @@ gl: new: "Non tes novos temas." read: "Aínda non liches ningún tema." posted: "Aínda non publicaches en ningún tema." - latest: "Non hai últimos temas. Triste." bookmarks: "Aínda non marcaches este tema." category: "Non hai temas en %{category}." top: "Non hai temas destacados." @@ -1864,7 +1862,6 @@ gl: bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}" browse_all_categories: Explorar todas as categorías view_latest_topics: ver últimos temas - suggest_create_topic: Porque non crear un tema? jump_reply_up: ir a unha resposta anterior jump_reply_down: ir a unha resposta posterior deleted: "Eliminouse o tema" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index b5fe7b78b5..4fba65a701 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -166,6 +166,7 @@ he: twitter: "שיתוף בטוויטר" facebook: "שיתוף בפייסבוק" email: "שליחה בדוא״ל" + url: "העתקה ושיתוף של כתובת" action_codes: public_topic: "נושא זה הפך לציבורי ב־%{when}" private_topic: "נושא זה הפך להודעה פרטית ב־%{when}" @@ -1154,6 +1155,7 @@ he: taken: "סליחה, הכתובת הזו אינה זמינה." error: "הייתה שגיאה בשינוי כתובת הדואר האלקטרוני שלך. אולי היא תפוסה?" success: "שלחנו דואר אלקטרוני לכתובת הדואר הזו. בבקשה עיקבו אחרי הוראות האישור שם." + success_via_admin: "שלחנו הודעה לכתובת הדוא״ל הזו. על המשתמש יהיה לעקוב אחר הוראות האישור שבהודעה." success_staff: "שלחנו דואר אלקטרוני לכתובת הדואר הזו. אנא עיקבו אחרי הוראות האישור." change_avatar: title: "שינוי תמונת הפרופיל" @@ -1191,6 +1193,7 @@ he: sso_override_instructions: "ניתן לעדכן את כתובת הדוא״ל דרך ספק ה־SSO." no_secondary: "אין כתובות דוא״ל משניות" instructions: "לעולם לא מוצג לציבור." + admin_note: "הערה: משתמש ניהולי שמשנה כתובת דוא״ל למשתמש שאינו ניהולי מציינת שהגישה של המשתמש לכתובת הדוא״ל המקורית אבדה, לכן תישלח הודעת איפוס ססמה לכתובת החדשה בדוא״ל. כתובת הדוא״ל של המשתמש לא תוחלף עד לביצוע תהליך איפוס הססמה." ok: "נשלח אליכם דואר אלקטרוני לאישור" required: "נא למלא כתובת דוא״ל" invalid: "בבקשה הכניסו כתובת דואר אלקטרוני תקינה" @@ -1686,7 +1689,7 @@ he: accept_invite: "קבלת הזמנה" success: "החשבון נוצר ונכנסת אליו." name_label: "שם" - password_label: "הגדרת ססמה" + password_label: "ססמה" optional_description: "(רשות)" password_reset: continue: "המשיכו ל-%{site_name}" @@ -1705,29 +1708,6 @@ he: categories_and_top_topics: "קטגוריות ונושאים מובילים" categories_boxes: "תיבות עם תתי קטגוריות" categories_boxes_with_topics: "תיבות עם נושאים מומלצים" - base_font_setting: - helvetica: "Helvetica/Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "RobotoCondensed" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "Ubuntu" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" shortcut_modifier_key: shift: "Shift" ctrl: "Ctrl" @@ -1895,6 +1875,7 @@ he: draft: טיוטה edit: עריכה reply_to_post: + label: תגובה לפוסט מאת %{postUsername} desc: תגובה לפוסט ספיציפי reply_as_new_topic: label: תגובה כנושא מקושר @@ -1920,6 +1901,8 @@ he: toggle_topic_bump: label: "החלפת מצב הקפצת נושא" desc: "הגב מבלי לשנות את תאריך התגובה האחרונה" + reload: "רענון" + ignore: "התעלמות" notifications: tooltip: regular: @@ -2106,11 +2089,21 @@ he: single_user: מכילים משתמש/ת יחידים post: count: - label: מספר פוסטים מינימלי + label: פוסטים + min: + placeholder: מזערי + max: + placeholder: מרבי time: label: פורסמו before: לפני after: אחרי + views: + label: צפיות + min_views: + placeholder: מזערי + max_views: + placeholder: מרבי hamburger_menu: "עיברו לרשימת נושאים אחרת או קטגוריה" new_item: "חדש" go_back: "חזור אחורה" @@ -2149,12 +2142,14 @@ he: choose_new_tags: "בחירה בתגיות חדשות לנושאים אלו:" choose_append_tags: "בחירה בתגיות חדשות שיתווספו לנושאים אלו:" changed_tags: "התגיות של נושאים אלו השתנו." + remove_tags: "הסרת תגיות" none: unread: "אין לך נושאים שלא נקראו." new: "אין לך נושאים חדשים." read: "עדיין לא קראת אף נושא." posted: "עדיין לא פרסמתם באף נושא." - latest: "אין נושאים אחרונים. זה עצוב." + ready_to_create: "הסתיימו ההכנות לקראת " + latest: "התעדכנת בהכול!" bookmarks: "אין לך עדיין סימניות לנושאים." category: "אין נושאים בקטגוריה %{category}." top: "אין נושאים מובילים." @@ -2256,7 +2251,7 @@ he: browse_all_categories: עיינו בכל הקטגוריות browse_all_tags: עיון בכל התגיות view_latest_topics: הצגת נושאים אחרונים - suggest_create_topic: למה לא ליצור נושא חדש? + suggest_create_topic: לפתוח דיון חדש? jump_reply_up: קפיצה לתגובה קודמת jump_reply_down: קפיצה לתגובה מאוחרת deleted: "הנושא הזה נמחק" @@ -2354,7 +2349,7 @@ he: "2_8": "תראו ספירה של תגובות חדשות כיוון שאתם עוקבים אחר קטגוריה זו." "2_4": "תראו ספירה של תגובות חדשות כיוון שפרסמתם תגובה לנושא זה." "2_2": "תראו ספירה של תגובות חדשות כיוון שאתם עוקבים אחר נושא זה:" - "2": 'You will see a count of new replies because you read this topic.' + "2": 'יופיע עיטור עם מספר התגובות החדשות מכיוון שקראת את הנושא הזה.' "1_2": "תישלח התראה אם מישהו יזכיר את @שם_המשתמש שלך או ישיב לך." "1": "תישלח התראה אם מישהו יזכיר את @שם_המשתמש שלך או ישיב לך." "0_7": "אתם מתעלמים מכל ההתראות בקטגוריה זו." diff --git a/config/locales/client.hu.yml b/config/locales/client.hu.yml index 42ecdddf67..368a14f10c 100644 --- a/config/locales/client.hu.yml +++ b/config/locales/client.hu.yml @@ -122,6 +122,7 @@ hu: twitter: "Megosztás Twitteren" facebook: "Megosztás Facebookon" email: "Küldés e-mailben" + url: "URL másolása és megosztása" action_codes: public_topic: "téma nyilvánossá téve: %{when}" private_topic: "téma privát üzenetté alakítva: %{when}" @@ -494,7 +495,7 @@ hu: days_visited: "Látogatás" days_visited_long: "Látogatott nap" posts_read: "Olvasott" - posts_read_long: "Elolvasott bejegyzések" + posts_read_long: "Olvasott bejegyzések" last_updated: "Legutóbb frissítve:" total_rows: one: "%{count} felhasználó" @@ -1346,7 +1347,6 @@ hu: accept_invite: "Meghívás elfogadása" success: "A fiók elkészült és mostmár be vagy jelentkezve" name_label: "Név" - password_label: "Állíts be jelszót" optional_description: "(opcionális)" password_reset: continue: "Tovább a%{site_name}" @@ -1634,7 +1634,6 @@ hu: new: "Nincsenek új témakörök." read: "Még egy témakört sem olvastál el." posted: "Még egy témakörhöz sem szóltál hozzá." - latest: "Szomorú, de nem állnak rendelkezésre friss témakörök." bookmarks: "Még nem adtál hozzá témakört a könyvjelzőidhez." category: "Nincsenek témakörök a %{category} kategóriában." top: "Nincsenek top témák" @@ -1700,7 +1699,6 @@ hu: read_more_MF: "{ UNREAD, plural, =0 {} one { is 1 unread } other { are # unread } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 new topic} other { {BOTH, select, true{and } false {are } other{}} # new topics} } remaining, or {CATEGORY, select, true {browse other topics in {catLink}} false {{latestLink}} other {}}" browse_all_categories: Böngéssz a kategóriák között view_latest_topics: legújabb témák megtekintése - suggest_create_topic: Miért nem hozol létre egy témakört? 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" diff --git a/config/locales/client.hy.yml b/config/locales/client.hy.yml index c60dc56a77..f543abaade 100644 --- a/config/locales/client.hy.yml +++ b/config/locales/client.hy.yml @@ -1425,7 +1425,6 @@ hy: accept_invite: "Ընդունել Հրավերը" success: "Ձեր հաշիվը ստեղծված է, և այժմ Դուք մուտք եք գործել:" name_label: "Անուն" - password_label: "Առաջադրել Գաղտնաբառ" optional_description: "(ընտրովի)" password_reset: continue: "Շարունակել դեպի %{site_name}" @@ -1785,8 +1784,6 @@ hy: noreplies: պատասխաններ չունեն single_user: պարունակում են մեկ օգտատեր post: - count: - label: Գրառումների Նվազագույն Քանակը time: label: Հրապարակվել է before: մինչև @@ -1832,7 +1829,6 @@ hy: new: "Դուք չունեք նոր թեմաներ:" read: "Դուք դեռևս չեք կարդացել ոչ մի թեմա:" posted: "Դուք դեռևս գրառում չեք կատարել ոչ մի թեմայում:" - latest: "Վերջերս կատարված հրապարակումներ չկան: Տխուր է:" bookmarks: "Դուք դեռևս չունեք էջանշված թեմաներ:" category: " %{category}-ում թեմաներ չկան:" top: "Թոփ թեմաներ չկան:" @@ -1918,7 +1914,6 @@ hy: bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}" browse_all_categories: Դիտել բոլոր կատեգորիաները view_latest_topics: դիտեք վերջին թեմաները - suggest_create_topic: Միգուցե՞ չստեղծել նոր թեմա jump_reply_up: ցատկել դեպի ավելի վաղ պատասխան jump_reply_down: ցատկել դեպի ավելի հին պատասխան deleted: "Թեման ջնջվել է" diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index e80eae9587..9d0e7fabda 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -1146,7 +1146,6 @@ id: invited_by: "Anda telah diundang oleh:" accept_invite: "Terima Undangan" name_label: "Nama" - password_label: "Atur Kata Sandi" optional_description: "(opsional)" password_reset: continue: "Lanjut ke %{site_name}" @@ -1344,7 +1343,6 @@ id: show_links: "tampilkan pranala dalam topik ini" browse_all_categories: Lihat semua kategori view_latest_topics: lihat topik terbaru - suggest_create_topic: Buat topik baru? jump_reply_up: lompat ke balasan sebelumnya progress: jump_prompt_or: "atau" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 772dfa603f..cd58bc6bea 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -122,6 +122,7 @@ it: twitter: "Condividi su Twitter" facebook: "Condividi su Facebook" email: "Invia per email" + url: "Copia e condividi l'URL" action_codes: public_topic: "ha reso questo argomento pubblico %{when}" private_topic: "ha reso questo argomento un messaggio personale %{when}" @@ -263,7 +264,9 @@ it: unbookmark: "Clicca per rimuovere tutti i segnalibri a questo argomento" unbookmark_with_reminder: "Fare clic per rimuovere tutti i segnalibri e i promemoria in questo argomento. Hai un promemoria impostato %{reminder_at} per questo argomento." bookmarks: + created: "Hai aggiunto questo messaggio ai segnalibri %{name}" not_bookmarked: "aggiungi ai segnalibri" + created_with_reminder: "Hai aggiunto ai segnalibri questo messaggio con un promemoria %{date}. %{name}" remove: "Rimuovi Segnalibro" delete: "Cancella Segnalibro" confirm_delete: "Sicuro di voler eliminare questo segnalibro? Anche il promemoria verrà eliminato." @@ -272,6 +275,7 @@ it: no_timezone: 'Non hai ancora impostato un fuso orario. Non sarai in grado di impostare promemoria. Creane uno nel tuo profilo .' invalid_custom_datetime: "La data e l'ora fornite non sono valide, riprova." list_permission_denied: "Non sei autorizzato a visualizzare i segnalibri di questo utente." + no_user_bookmarks: "Non hai nessun messaggio nei Segnalibri. I Segnalibri ti permettono di fare riferimento rapidamente a specifici messaggi." auto_delete_preference: label: "Elimina automaticamente" never: "Mai" @@ -294,6 +298,7 @@ it: today_with_time: "oggi alle %{time}" tomorrow_with_time: "domani alle %{time}" at_time: "a %{date_time}" + existing_reminder: "Hai un promemoria impostato per questo segnalibro, che verrà inviato il %{at_date_time}" copy_codeblock: copied: "copiato!" drafts: @@ -517,6 +522,7 @@ it: sent_by_user: "Inviato da %{user}" sent_by_you: "Inviato da te" directory: + username: "Nome utente" filter_name: "filtra per nome utente" title: "Utenti" likes_given: "Dati" @@ -547,6 +553,12 @@ it: groups: member_added: "Aggiunto" member_requested: "Richiesto alle" + add_members: + title: "Aggiungi membri a %{group_name}" + description: "Puoi anche incollare in un elenco separato da virgole." + usernames: "Inserisci nomi utente o indirizzi email" + input_placeholder: "Nomi utente o email" + notify_users: "Notifica gli utenti" requests: title: "Richieste" reason: "Motivo" @@ -588,6 +600,24 @@ it: membership: title: Iscrizione access: Accesso + categories: + title: Categorie + long_title: "Notifiche predefinite di categoria" + description: "Quando gli utenti vengono aggiunti a questo gruppo, le loro impostazioni di notifica di categoria verranno impostate su questi valori predefiniti. Potranno essere cambiati in un secondo momento." + watched_categories_instructions: "Osserva automaticamente tutti gli argomenti in queste categorie. I membri del Gruppo riceveranno notifica di tutti i nuovi messaggi e argomenti e, accanto all'argomento, apparirà il conteggio dei nuovi messaggi." + tracked_categories_instructions: "Seguirai automaticamente tutti gli argomenti appartenenti a queste categorie. Accanto all'argomento comparirà il conteggio dei nuovi messaggi." + watching_first_post_categories_instructions: "Gli utenti riceveranno una notifica alla pubblicazione del primo messaggio in ogni nuovo argomento in queste categorie." + regular_categories_instructions: "Se queste categorie saranno silenziate, saranno silenziate per tutti i membri del gruppo. Gli utenti saranno avvisati se menzionati o se qualcuno risponde ai loro messaggi." + muted_categories_instructions: "Gli utenti non riceveranno alcuna notifica su nuovi argomenti in queste categorie, né verranno visualizzati nella pagina delle Categorie o degli argomenti Recenti." + tags: + title: Etichette + long_title: "Notifiche predefinite delle Etichette" + description: "Quando vengono aggiunti utenti a questo gruppo, le loro impostazioni di notifica delle etichette verranno impostate su questi valori predefiniti, che potranno essere cambiati in seguito." + watched_tags_instructions: "Osserva automaticamente tutti gli argomenti con queste etichette. Ai membri del gruppo saranno notificati tutti i nuovi messaggi e argomenti e, accanto all'argomento, apparirà anche il numero di nuovi messaggi." + tracked_tags_instructions: "Segui automaticamente tutti gli argomenti con queste etichette. Accanto all'argomento apparirà il numero di nuovi messaggi." + watching_first_post_tags_instructions: "Gli utenti riceveranno una notifica alla pubblicazione del primo messaggio in ogni nuovo argomento con queste etichette." + regular_tags_instructions: "Se queste etichette vengono silenziate, ciò non si applicherà ai membri del gruppo. Agli utenti arriverà una notifica quando verranno menzionati o riceveranno una risposta." + muted_tags_instructions: "Gli utenti non riceveranno notifiche su nessun nuovo argomento con queste etichette, e non appariranno in Recenti." logs: title: "Log" when: "Quando" @@ -598,6 +628,10 @@ it: details: "Dettagli" from: "Da" to: "A" + permissions: + title: "Autorizzazioni" + none: "Non ci sono categorie associate a questo gruppo." + description: "I membri di questo gruppo possono accedere a queste categorie" public_admission: "Consenti agli utenti di unirsi al gruppo liberamente (richiede che il gruppo abbia visibilità pubblica)" public_exit: "Consenti agli utenti di lasciare il gruppo liberamente" empty: @@ -735,6 +769,7 @@ it: latest_by: "i più recenti di" toggle_ordering: "inverti l'ordinamento" subcategories: "Sottocategorie" + muted: "Categorie silenziate" topic_sentence: one: "%{count} argomento" other: "%{count} argomenti" @@ -834,7 +869,22 @@ it: dismiss_notifications_tooltip: "Imposta tutte le notifiche non lette come lette " first_notification: "La tua prima notifica! Selezionala per iniziare." dynamic_favicon: "Mostra il contatore sull'icona del browser" + skip_new_user_tips: + not_first_time: "Non è la tua prima volta?" + skip_link: "Ignora questi consigli" theme_default_on_all_devices: "Rendi questo il tema di default su tutti i miei dispositivi." + color_scheme_default_on_all_devices: "Imposta questa combinazione (o combinazioni) di colori come predefinita/e su tutti i miei dispositivi" + color_scheme: "Combinazione di colori" + color_schemes: + default_description: "Predefinito" + disable_dark_scheme: "Come di norma" + dark_instructions: "Puoi provare i colori del tema scuro attivando il tema scuro sul tuo dispositivo." + undo: "Reimposta" + regular: "Abituale" + dark: "Modo scuro" + default_dark_scheme: "(predefinito del sito)" + dark_mode: "Modo scuro" + dark_mode_enable: "Attiva lo schema di colori automatico del modo scuro" text_size_default_on_all_devices: "Rendi questa dimensione del testo di default su tutti i miei dispositivi" allow_private_messages: "Consenti agli altri utenti di inviarmi messaggi personali" external_links_in_new_tab: "Apri tutti i link esterni in nuove schede" @@ -881,6 +931,8 @@ it: muted_categories: "Silenziate" muted_categories_instructions: "Non riceverai notifiche riguardanti i contenuti di queste categorie, e non appariranno nelle pagine delle Categorie o dei Recenti." muted_categories_instructions_dont_hide: "Non riceverai alcuna notifica relativa a nuovi argomenti in queste categorie." + regular_categories: "Abituale" + regular_categories_instructions: "Vedrai queste categorie nelle liste di argomenti \"Recenti\" e \"Popolari\"." no_category_access: "Come moderatore hai accesso limitato alla categoria, il salvataggio è disabilitato." delete_account: "Cancella il mio account" delete_account_confirm: "Sei sicuro di voler cancellare il tuo account in modo permanente? Questa azione non può essere annullata!" @@ -965,6 +1017,7 @@ it: second_factor: title: "Autenticazione a Due Fattori" enable: "Gestione autenticazione a due fattori" + disable_all: "Disattiva tutto" forgot_password: "Ha dimenticato la password?" confirm_password_description: "Per favore conferma la tua password per continuare" name: "Nome" @@ -982,6 +1035,7 @@ it: use: "Usa l'app Authenticator" enforced_notice: "E' obbligatorio abilitare l'autenticazione a due fattori per accedere a questo sito." disable: "Disabilita" + disable_confirm: "Vuoi davvero disabilitare tutti i metodi a due fattori?" save: "Salva" edit: "Modifica" edit_title: "Modifica secondo fattore" @@ -1057,6 +1111,7 @@ it: sso_override_instructions: "L'email può essere aggiornata dal provider SSO." no_secondary: "Nessuna email secondaria" instructions: "Mai mostrato pubblicamente" + admin_note: "Nota: un utente amministratore che modifica l'email di un utente non amministratore implica che l'utente abbia perso l'accesso al proprio account email originale, e quindi l'email di reimpostazione della password verrà inviata al nuovo indirizzo. L'email dell'utente non cambierà finché non completerà il processo di reimpostazione della password." ok: "Ti invieremo una email di conferma" required: "Inserisci un indirizzo email" invalid: "Inserisci un indirizzo email valido" @@ -1361,6 +1416,8 @@ it: Cominciamo la discussione! {currentTopics, plural, one {C'è # argomento} other {Ci sono# argomenti}}. I visitatori hanno bisogno di più per leggere e rispondere: – consigliamo almeno {requiredTopics, plural, one {# argomento} other {# argomenti}}. Solo lo staff può vedere questo messaggio. too_few_posts_notice_MF: >- Cominciamo la discussione! {currentPosts, plural, one {C'è # messaggio} other {Ci sono # messaggi}}. I visitatori hanno bisogno di più per leggere e rispondere: – consigliamo almeno {requiredPosts, plural, one {# messaggio} other {# messaggi}}. Solo lo staff può vedere questo messaggio. + logs_error_rate_notice: + reached_hour_MF: "{relativeAge}{rate, plural, one {# errore/ora} other {# errori/ore}} ha raggiunto il limite di {limit, plural, one {# error/hour} other {# errors/hour}} impostato dal sito." learn_more: "per saperne di più..." all_time: "totale" all_time_desc: "totale argomenti creati" @@ -1406,6 +1463,8 @@ it: title: "Messaggio" invite: "Invita altri..." edit: "Aggiungi o rimuovi..." + remove: "Rimuovi..." + add: "Aggiungi..." leave_message: "Vuoi veramente abbandonare questo messaggio?" remove_allowed_user: "Davvero vuoi rimuovere %{name} da questo messaggio?" remove_allowed_group: "Vuoi veramente rimuovere %{name} da questo messaggio?" @@ -1523,7 +1582,7 @@ it: accept_invite: "Accetta Invito" success: "Il tuo account è stato creato e ora sei connesso." name_label: "Nome" - password_label: "Imposta Password" + password_label: "Password" optional_description: "(opzionale)" password_reset: continue: "Procedi su %{site_name}" @@ -1564,6 +1623,9 @@ it: one: "Seleziona almeno %{count} elemento." other: "Seleziona almeno %{count} elementi." invalid_selection_length: "La selezione deve contenere almeno %{count} caratteri." + components: + categories_admin_dropdown: + title: "Gestisci le categorie" date_time_picker: from: Da to: A @@ -1686,6 +1748,8 @@ it: abandon: "chiudi il composer e scarta la bozza" enter_fullscreen: "Espandi il composer a tutto schermo" exit_fullscreen: "Lascia il composer a tutto schermo" + show_toolbar: "mostra la barra degli strumenti del Composer" + hide_toolbar: "nascondi la barra degli strumenti del Composer" modal_ok: "OK" modal_cancel: "Annulla" cant_send_pm: "Spiacenti, non puoi inviare un messaggio a %{username}." @@ -1698,6 +1762,7 @@ it: draft: Bozza edit: Modifica reply_to_post: + label: Rispondi al messaggio di %{postUsername} desc: Rispondi a uno specifico messaggio reply_as_new_topic: label: Rispondi con un argomento correlato @@ -1723,6 +1788,8 @@ it: toggle_topic_bump: label: "Commuta riproposizione argomenti" desc: "Rispondi senza cambiare la data dell'ultima risposta" + reload: "Ricarica" + ignore: "Ignora" notifications: tooltip: regular: @@ -1766,6 +1833,9 @@ it: watching_first_post: "Nuovo Argomento %{description}" membership_request_accepted: "Iscrizione accettata in "%{group_name}"" membership_request_consolidated: "%{count} richieste di iscrizione aperte per '%{group_name}'" + reaction: "%{username} %{description}" + reaction_2: "%{username}, %{username2} %{description}" + votes_released: "%{description} - completato" group_message_summary: one: "%{count} messaggi in arrivo nella casella %{group_name}" other: "%{count} messaggi in arrivo nella casella %{group_name}" @@ -1804,6 +1874,8 @@ it: liked_consolidated: "nuovi \"Mi piace\"" post_approved: "messaggio approvato" membership_request_consolidated: "nuove richieste di iscrizione" + reaction: "nuova reazione" + votes_released: "Il voto è stato sbloccato" upload_selector: title: "Aggiungi un'immagine" title_with_attachments: "Aggiungi un'immagine o un file" @@ -1890,11 +1962,21 @@ it: single_user: contengono un singolo utente post: count: - label: Conteggio Messaggi Minimo + label: Messaggi + min: + placeholder: minimo + max: + placeholder: massimo time: label: Pubblicato before: prima del after: dopo il + views: + label: Visualizzazioni + min_views: + placeholder: minimo + max_views: + placeholder: massimo hamburger_menu: "vai ad un'altra lista di argomenti o categoria" new_item: "nuovo" go_back: "indietro" @@ -1931,12 +2013,14 @@ it: choose_new_tags: "Scegli nuove etichette per i seguenti argomenti:" choose_append_tags: "Scegli nuove etichette da aggiungere a questi argomenti:" changed_tags: "Le etichette per quegli argomenti sono state cambiate." + remove_tags: "Rimuovi etichette" none: unread: "Non ci sono argomenti non letti." new: "Non ci sono nuovi argomenti." read: "Non hai ancora letto nessun argomento." posted: "Non hai ancora scritto in nessun argomento." - latest: "Non ci sono argomenti più recenti. Ciò è triste." + ready_to_create: "Pronto a " + latest: "Non hai nuovi messaggi da leggere!" bookmarks: "Non hai ancora argomenti nei segnalibri." category: "Non ci sono argomenti in %{category}." top: "Non ci sono argomenti di punta." @@ -1950,6 +2034,7 @@ it: new: "Non ci sono altri argomenti nuovi." unread: "Non ci sono altri argomenti non letti" category: "Non ci sono altri argomenti nella categoria %{category}." + tag: "Non ci sono altri argomenti con l'etichetta %{tag}." top: "Non ci sono altri argomenti di punta." bookmarks: "Non ci sono ulteriori argomenti nei segnalibri." topic: @@ -2021,8 +2106,9 @@ it: read_more_MF: "{ UNREAD, plural, =0 {} one { C'è 1 argomento non letto } other { Ci sono # argomenti non letti } } { NEW, plural, =0 {} one { {BOTH, select, true{e } false {è } other{}} 1 nuovo argomento} other { {BOTH, select, true{e } false {Ci sono } other{}} # nuovi argomenti} } restanti, o {CATEGORY, select, true {visualizza altri argomenti in {catLink}} false {{latestLink}} other {}}" bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}" browse_all_categories: Scorri tutte le categorie + browse_all_tags: Sfoglia tutte le etichette view_latest_topics: visualizza gli argomenti più recenti - suggest_create_topic: Perché non crei un argomento? + suggest_create_topic: iniziare una nuova conversazione? jump_reply_up: passa a una risposta precedente jump_reply_down: passa a una risposta successiva deleted: "L'argomento è stato eliminato" @@ -2118,7 +2204,7 @@ it: "2_8": "Vedrai un conteggio delle nuove risposte perché stai seguendo questa categoria." "2_4": "Vedrai un conteggio delle nuove risposte perché hai pubblicato una risposta in questo argomento." "2_2": "Vedrai un conteggio delle nuove risposte perché stai seguendo questo argomento." - "2": 'You will see a count of new replies because you read this topic.' + "2": 'Vedrai un conteggio delle nuove risposte perché hai letto questo argomento.' "1_2": "Riceverai notifiche se qualcuno menziona il tuo @nome o ti risponde." "1": "Riceverai notifiche se qualcuno menziona il tuo @nome o ti risponde." "0_7": "Stai ignorando tutte le notifiche di questa categoria." @@ -2228,6 +2314,7 @@ it: success: "Abbiamo invitato l'utente a partecipare a questo messaggio." success_group: "Abbiamo invitato il gruppo a partecipare a questo messaggio." error: "Spiacenti, si è verificato un errore durante l'invito dell'utente." + not_allowed: "Spiacenti, l'utente non può essere invitato." group_name: "nome gruppo" controls: "Impostazioni Argomento" invite_reply: @@ -2874,6 +2961,8 @@ it: profile: "%{shortcut} Profilo" messages: "%{shortcut} Messaggi" drafts: "%{shortcut} Bozze" + next: "%{shortcut} Argomento successivo" + previous: "%{shortcut} Argomento precedente" navigation: title: "Navigazione" jump: "%{shortcut} Vai al messaggio n°" @@ -3022,6 +3111,7 @@ it: delete_unused_confirmation_more_tags: one: "%{tags} e %{count} altra" other: "%{tags} e %{count} altre" + delete_no_unused_tags: "Non ci sono etichette inutilizzate." delete_unused: "Elimina Etichette Inutilizzate" delete_unused_description: "Elimina tutte le etichette che non sono usate in alcun argomento o messaggio personale" cancel_delete_unused: "Annulla" @@ -3164,6 +3254,9 @@ it: view_table: "tabella" view_graph: "grafico" refresh_report: "Aggiorna Rapporto" + daily: Ogni giorno + monthly: Mensile + weekly: Settimanale dates: "Date (UTC)" groups: "Tutti i gruppi" disabled: "Il rapporto è disabilitato" @@ -3213,6 +3306,8 @@ it: title: "Chi può vedere questo Gruppo?" public: "Chiunque" logged_on_users: "Utenti connessi" + members: "Proprietari, membri e moderatori del gruppo" + staff: "Proprietari e moderatori del gruppo" owners: "Proprietari del gruppo" description: "Gli amministratori possono vedere tutti i gruppi." members_visibility_levels: @@ -3276,6 +3371,7 @@ it: save: Salva new_key: Nuova Chiave API revoked: Revocata + delete: Elimina definitivamente not_shown_again: Questa chiave non verrà più visualizzata. Assicurati di prenderne una copia prima di continuare. continue: Continua use_global_key: Chiave globale (consente tutte le azioni) @@ -3283,6 +3379,11 @@ it: allowed_parameters: Parametri consentiti optional_allowed_parameters: Parametri Consentiti (Opzionali) any_parameter: (qualsiasi parametro) + allowed_urls: URL consentiti + descriptions: + topics: + read: Leggi un argomento o un messaggio specifico in esso. È supportato anche RSS. + write: Crea un nuovo argomento o pubblica su uno esistente. web_hooks: title: "Webhook" none: "Non ci sono webhook disponibili adesso." @@ -3522,6 +3623,7 @@ it: is_default: "Il tema è abilitato di default" user_selectable: "Il tema può essere selezionato dagli utenti" color_scheme: "Tavolozza dei colori" + default_light_scheme: "Light (predefinito)" color_scheme_select: "Seleziona i colori da usare nel tema" custom_sections: "Sezioni personalizzate:" theme_components: "Componenti Tema" @@ -3619,6 +3721,9 @@ it: embedded_scss: text: "CSS incorporato" title: "Inserisci CSS personalizzato da inviare con la versione incorporata dei commenti" + color_definitions: + text: "Definizioni dei colori" + title: "Inserisci definizioni di colore personalizzate (solo utenti avanzati)" head_tag: text: "" title: "HTML che sarà inserito prima del tag " @@ -4204,6 +4309,7 @@ it: external_name: "Nome" external_email: "Email" external_avatar_url: "URL dell'Immagine Profilo" + delete_sso_record: "Elimina record SSO" user_fields: title: "Campi Utente" help: "Tutti i campi che i tuoi utenti possono riempire." @@ -4406,6 +4512,7 @@ it: title: "Incorporo" host: "Host Abilitati" class_name: "Nome Classe" + allowed_paths: "Lista path permessi" edit: "modifica" category: "Pubblica nella Categoria" add_host: "Aggiungi Host" @@ -4470,5 +4577,6 @@ it: regular: "Utente Normale" previews: topic_title: "Argomento di discussione" + font_title: "Font %{font}" share_button: "Condividi" reply_button: "Rispondi" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index b41838634c..73c8deaf7e 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -1216,7 +1216,6 @@ ja: accept_invite: "招待に応じる" success: "あなたのアカウントは作成されて、ログインされました。" name_label: "氏名" - password_label: "パスワードを設定" optional_description: "(任意)" password_reset: continue: "%{site_name} に進む" @@ -1477,8 +1476,6 @@ ja: noreplies: 返信がまったくない single_user: 一人しか参加者がいない post: - count: - label: 投稿数の下限 time: label: 投稿されたタイミング before: より前 @@ -1522,7 +1519,6 @@ ja: new: "新しいトピックはありません。" read: "まだトピックを一つも読んでいません。" posted: "トピックは一つもありません。" - latest: "最新のトピックはありません。" bookmarks: "ブックマークしたトピックはありません。" category: "%{category} トピックはありません。" top: "トップトピックはありません。" @@ -1591,7 +1587,6 @@ ja: read_more_MF: "{ UNREAD, plural, =0 {} one { 1つの未読 } other { are # つの未読 } } { NEW, plural, =0 {} one { {BOTH, select, true{と } false {} other{}} 1 つの新着 トピック} other { {BOTH, select, true{と } false {} other{}} # つの新着 トピック} } が残っています、あるいは {CATEGORY, select, true { {catLink}の他のトピックを見てみてください。} false {{latestLink}} other {}}" browse_all_categories: 全てのカテゴリを閲覧する view_latest_topics: 最新のトピックを見る - suggest_create_topic: 新しいトピックを作成しませんか? jump_reply_up: 以前の返信へジャンプ jump_reply_down: 以後の返信へジャンプ deleted: "トピックは削除されました" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 792d7a3469..7e717d325a 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -24,11 +24,11 @@ ko: thousands: "%{number}천" millions: "%{number}백만" dates: - time: "a h:mm" - time_with_zone: "HH : mm (z)" - time_short_day: "ddd, HH : mm" + time: "HH:mm" + time_with_zone: "HH:mm (z)" + time_short_day: "ddd, HH:mm" timeline_date: "YYYY MMM" - long_no_year: "D MMM, HH : mm" + long_no_year: "D MMM, HH:mm" long_no_year_no_time: "MMM D" full_no_year_no_time: "MMMM Do" long_with_year: "YYYY MMM D a h:mm" @@ -45,13 +45,13 @@ ko: less_than_x_seconds: other: "< %{count}초" x_seconds: - other: "%{count}초전" + other: "%{count}초" less_than_x_minutes: other: "< %{count}분" x_minutes: - other: "%{count}분전" + other: "%{count}분" about_x_hours: - other: "%{count}시간 전" + other: "%{count}시간" x_days: other: "%{count}일 전" x_months: @@ -100,6 +100,7 @@ ko: twitter: "트위터에 공유" facebook: "페이스북에 공유" email: "이메일로 공유" + url: "URL 복사 및 공유" action_codes: public_topic: "이 글을 %{when}에 공개" private_topic: "이 글을 개인 메시지로 설정 %{when}" @@ -109,7 +110,7 @@ ko: user_left: "%{who}님이 %{when}에 이 메시지에서 자신을 제거 했습니다" removed_user: "%{when}에 %{who}님이 삭제됨" removed_group: "%{when}에 %{who}님이 삭제됨" - autobumped: "%{when}에 자동으로 끌어올려짐" + autobumped: "%{when}에 자동으로 끌어 올려짐" autoclosed: enabled: "%{when}에 닫힘" disabled: "%{when}에 열림" @@ -126,55 +127,55 @@ ko: enabled: "%{when}에 전체적으로 고정됨" disabled: "%{when}에 고정 해제됨" visible: - enabled: "%{when}에 목록에 게시됨" + enabled: "%{when}에 목록에 게시" disabled: "%{when}에 목록에서 감춤" banner: enabled: "%{when}에 배너를 만들었습니다. 사용자가 닫을 때까지 모든 페이지의 상단에 나타납니다." - disabled: "이 배너를 제거했습니다 %{when}. 더 이상 모든 페이지의 상단에 표시되지 않습니다." - forwarded: "위 이메일을 전달했습니다" - topic_admin_menu: "글 처리" - wizard_required: "새로운 Discourse에 오신것을 환영합니다! 설치 마법사 로 시작해봅시다✨" - emails_are_disabled: "관리자가 이메일 송신을 전체 비활성화 했습니다. 어떤 종류의 이메일 알림도 보내지지 않습니다." - bootstrap_mode_enabled: "쉬운 시작을 위해 부트스트랩 모드로 구동 되었습니다. 모든 새로운 사용자에게 신뢰 수준 1이 부여되고 매일 이메일 다이제스트가 보내집니다. 이 기능은 총 사용자 수가 %{min_users}를 초과 할 때 자동으로 꺼집니다." + disabled: "%{when}에 이 배너를 제거했습니다. 더 이상 모든 페이지의 상단에 표시되지 않습니다." + forwarded: "위의 이메일을 전달했습니다." + topic_admin_menu: "글 관리" + wizard_required: "새로운 Discourse에 오신것을 환영합니다! 설치 마법사로 시작합니다 ✨" + emails_are_disabled: "관리자에 의해 모든 이메일의 발신이 비활성화 되었습니다. 어떤 종류의 이메일 알림도 전송되지 않습니다." + bootstrap_mode_enabled: "쉬운 시작을 위해 부트스트랩 모드로 구동 되었습니다. 모든 새로운 사용자에게 회원 레벨 1이 부여되고 이메일로 매일 요약 전송이 활성화됩니다. %{min_users}명의 사용자가 가입하면 자동으로 해제됩니다." bootstrap_mode_disabled: "Bootstrap 모드가 24시간 이내에 해제됩니다." themes: - default_description: "기본값" - broken_theme_alert: "사이트의 테마 혹은 컴포넌트 (%{theme}) 오류로 정상적으로 작동하지 않을 수 있습니다. %{path}에서 비활성화 해주세요." + default_description: "기본" + broken_theme_alert: "테마 / %{theme} 구성 요소에 오류가있어 사이트가 작동하지 않을 수 있습니다. %{path}에서 비활성화 하십시오." s3: regions: - ap_northeast_1: "아시아 태평양 (토쿄)" + ap_northeast_1: "아시아 태평양 (도쿄)" ap_northeast_2: "아시아 태평양 (서울)" ap_south_1: "아시아 태평양 (뭄바이)" ap_southeast_1: "아시아 태평양 (싱가폴)" ap_southeast_2: "아시아 태평양 (시드니)" ca_central_1: "캐나다 (중부)" - cn_north_1: "중국 (북경)" + cn_north_1: "중국 (베이징)" cn_northwest_1: "중국 (닝샤)" - eu_central_1: "유럽연합 (프랑크푸르트)" - eu_north_1: "유럽연합 (스톡홀름)" - eu_west_1: "유럽연합 (아일랜드)" + eu_central_1: "EU (프랑크푸르트)" + eu_north_1: "EU (스톡홀름)" + eu_west_1: "EU (아일랜드)" eu_west_2: "EU (런던)" eu_west_3: "EU (파리)" sa_east_1: "남아메리카 (상파울루)" - us_east_1: "미국 동부 (N. 버지니아)" + us_east_1: "미국 동부 (버지니아 북부)" us_east_2: "미국 동부 (오하이오)" us_gov_east_1: "AWS GovCloud (미국 동부)" us_gov_west_1: "AWS GovCloud (미국 서부)" us_west_1: "미국 서부 (N. 캘리포니아)" - us_west_2: "미국 서부 (오레곤)" + us_west_2: "미국 서부 (오리건)" edit: "이 글의 제목과 카테고리 편집" expand: "확장" not_implemented: "죄송합니다. 아직 사용할 수 없는 기능입니다." no_value: "아니오" yes_value: "예" - submit: "제출" - generic_error: "죄송합니다. 오류가 발생하였습니다." - generic_error_with_reason: "오류가 발생하였습니다: %{error}" - go_ahead: "계속" + submit: "확인" + generic_error: "죄송합니다. 오류가 발생했습니다." + generic_error_with_reason: "오류가 발생했습니다: %{error}" + go_ahead: "계속하기" sign_up: "회원가입" log_in: "로그인" age: "나이" - joined: "가입함" + joined: "가입" admin_title: "관리자" show_more: "더 보기" show_help: "옵션" @@ -190,54 +191,54 @@ ko: conduct: "윤리 강령" mobile_view: "모바일로 보기" desktop_view: "PC로 보기" - you: "당신" + you: "사용자님" or: "또는" - now: "방금 전" + now: "방금" read_more: "더 읽기" more: "더 보기" less: "덜" never: "전혀" - every_30_minutes: "매 30분 마다" - every_hour: "매 한시간 마다" + every_30_minutes: "30분마다" + every_hour: "매 시간마다" daily: "매일" weekly: "매주" - every_month: "매월" + every_month: "매달" every_six_months: "6개월마다" max_of_count: "최대 %{count}" alternation: "또는" character_count: - other: "%{count} 자" + other: "%{count} 글자" related_messages: title: "관련 메시지" - see_all: '@ %{username} ...의 모든 메시지 보기' + see_all: '@%{username}의 모든 메시지 보기...' suggested_topics: title: "주요 글" - pm_title: "추천 메세지" + pm_title: "제안 메시지" about: simple_title: "소개" title: "소개: %{title}" stats: "사이트 통계" our_admins: "관리자" - our_moderators: "운영자" - moderators: "운영자" + our_moderators: "관리자" + moderators: "관리자" stat: all_time: "전체" - last_7_days: "최근 7일" - last_30_days: "최근 30일" + last_7_days: "지난 7일" + last_30_days: "지난 30일" like_count: "좋아요" topic_count: "글" - post_count: "게시글" + post_count: "게시물" user_count: "사용자" - active_user_count: "활성화된 사용자" - contact: "문의" - contact_info: "사이트 운영과 관련된 사항이나 요청이 있으시다면 이메일 %{contact_info}로 연락주시기 바랍니다." + active_user_count: "활성 사용자" + contact: "문의하기" + contact_info: "이 사이트에 영향을 미치는 중대한 문제 또는 긴급한 문제가 발생하는 경우 %{contact_info} 에 문의하십시오." bookmarked: title: "북마크" - clear_bookmarks: "북마크 제거" + clear_bookmarks: "북마크 지우기" help: bookmark: "이 글의 첫 번째 게시물을 북마크하려면 클릭하세요." unbookmark: "이 항목의 모든 북마크를 제거하려면 클릭하십시오." - unbookmark_with_reminder: "이 주제의 모든 책갈피 및 미리 알림을 제거하려면 클릭하십시오. 이 주제에 대한 알림 설정 %{reminder_at}이 있습니다." + unbookmark_with_reminder: "이 항목의 모든 북마크 및 미리 알림을 제거하려면 클릭하십시오. 이 글에 대해 알림이 %{reminder_at} 으로 설정되었습니다." bookmarks: created: "이 글을 북마크했습니다. %{name}" not_bookmarked: "이 글을 북마크" @@ -245,34 +246,34 @@ ko: remove: "북마크 삭제" delete: "북마크 삭제" confirm_delete: "이 북마크를 삭제 하시겠습니까? 알림도 삭제됩니다." - confirm_clear: "이 토픽의 모든 북마크를 지우시겠습니까?" + confirm_clear: "이 글의 모든 북마크를 지우시겠습니까?" save: "저장" - no_timezone: '아직 시간대를 설정하지 않았습니다. 미리 알림을 설정할 수 없습니다. 프로필에서 설정 하십시오 .' - invalid_custom_datetime: "제공 한 날짜와 시간이 잘못되었습니다. 다시 시도하십시오." + no_timezone: '아직 시간대를 설정하지 않았습니다. 미리 알림을 설정할 수 없습니다. 프로필에서 설정 하세요.' + invalid_custom_datetime: "입력한 날짜와 시간이 잘못되었습니다. 다시 시도하십시오." list_permission_denied: "이 사용자의 북마크를 볼 수있는 권한이 없습니다." no_user_bookmarks: "북마크된 게시물이 없습니다. 북마크를 사용하면 특정 게시물을 빠르게 확인 할 수 있습니다." auto_delete_preference: - label: "자동으로 삭제" + label: "자동 삭제" never: "절대" when_reminder_sent: "알림이 전송되면" on_owner_reply: "이 글에 댓글을 작성한 후" search_placeholder: "이름, 글 제목 또는 글 내용으로 북마크 검색" search: "검색" reminders: - later_today: "오늘 늦게" + later_today: "오늘 나중에" next_business_day: "다음 영업일" tomorrow: "내일" next_week: "다음 주" - later_this_week: "이번 주 후반" + later_this_week: "이번 주말" start_of_next_business_week: "월요일" - start_of_next_business_week_alt: "다음 주 월요일" + start_of_next_business_week_alt: "다음주 월요일" next_month: "다음 달" - custom: "맞춤 날짜 및 시간" + custom: "사용자 지정 날짜 및 시간" last_custom: "마지막" none: "알림이 필요하지 않습니다" today_with_time: "오늘 %{time}" tomorrow_with_time: "내일 %{time}" - at_time: "%{date_time}에서" + at_time: "%{date_time}" existing_reminder: "이 북마크는 %{at_date_time}에 미리 알림이 설정되어 있습니다" copy_codeblock: copied: "복사되었습니다!" @@ -280,88 +281,88 @@ ko: resume: "이력서" remove: "삭제" new_topic: "새 글 초안" - new_private_message: "새로운 비공개 메시지 초안" + new_private_message: "새 비공개 메시지 초안" topic_reply: "임시 댓글" abandon: confirm: "이 글의 다른 초안을 이미 열었습니다. 해당 초안을 포기하시겠습니까?" - yes_value: "예, 버립니다." - no_value: "아니요, 버리지 않습니다." + yes_value: "예, 포기합니다" + no_value: "아니요, 유지합니다" topic_count_latest: other: "%{count}개의 새글 또는 업데이트 된 글 보기" topic_count_unread: - other: "%{count}개의 읽지 않은 주제" + other: "%{count}개의 읽지 않은 글 보기" topic_count_new: other: "%{count}개의 새로운 글 보기" preview: "미리보기" cancel: "취소" save: "변경사항 저장" - saving: "저장 중..." - saved: "저장 완료!" + saving: "저장하는 중..." + saved: "저장되었습니다!" upload: "업로드" uploading: "업로드 중..." - uploading_filename: "업르도중: %{filename}..." + uploading_filename: "업르도 중: %{filename}..." clipboard: "클립보드" - uploaded: "업로드 완료!" - pasting: "붙혀넣는중..." + uploaded: "업로드 되었습니다!" + pasting: "붙여넣기 중..." enable: "활성화" disable: "비활성화" - continue: "계속" + continue: "계속하기" undo: "실행 취소" revert: "되돌리기" failed: "실패" - switch_to_anon: "익명 모드 들어가기" - switch_from_anon: "익명 모드 나가기" + switch_to_anon: "익명 모드 시작" + switch_from_anon: "익명 모드 종료" banner: - close: "배너 닫기" - edit: "이 배너 수정 >>" + close: "이 배너를 닫습니다." + edit: "이 배너 편집 >>" pwa: - install_banner: "이 장치에 %{title}설치 하시겠습니까?" + install_banner: "이 장치에 %{title} 바로가기를 만드시겠습니까?" choose_topic: none_found: "글을 찾을 수 없습니다." title: - search: "주제 검색" - placeholder: "여기에 주제 제목, URL 또는 ID를 입력하십시오" + search: "글 검색" + placeholder: "여기에 글 제목, URL 또는 ID를 입력하십시오" choose_message: - none_found: "메시지를 찾을 수 없습니다." + none_found: "메시지가 없습니다." title: search: "메시지 검색" - placeholder: "여기에 메시지 제목, url 또는 id를 입력하십시오" + placeholder: "여기에 글 제목, URL 또는 ID를 입력하십시오" review: - order_by: "주문" + order_by: "정렬" in_reply_to: "다음에 댓글" explain: - why: "이 항목이 대기열에있는 이유를 설명하십시오." + why: "이 항목이 대기열에있는 이유 설명" title: "검토 가능한 점수" formula: "공식" subtotal: "소계" - total: "총" + total: "합계" min_score_visibility: "가시성에 대한 최소 점수" score_to_hide: "게시물 숨기기 점수" take_action_bonus: - name: "행동을 취했다" - title: "직원이 조치를 취하면 깃발에 보너스가 부여됩니다." + name: "조치를 취했습니다" + title: "관리자가 신고를 처리하면 보너스가 부여됩니다." user_accuracy_bonus: name: "사용자 정확도" - title: "역사적으로 플래그가 동의 된 사용자에게는 보너스가 제공됩니다." + title: "신고가 처리된 경우 신고자 에게는 보너스가 주어집니다." trust_level_bonus: - name: "신뢰 수준" - title: "신뢰 수준이 높은 사용자가 만든 검토 가능한 항목의 점수가 높습니다." + name: "회원 레벨" + title: "레원 레벨이 놓은 사용자가 만든 검토 항목의 점수가 높습니다." type_bonus: - name: "타입 보너스" - title: "직원이 검토 가능한 특정 유형에 우선 순위를 부여하기 위해 보너스를 할당 할 수 있습니다." + name: "유형 보너스" + title: "특정한 검토 유형에는 관리자가 보너스를 할당하여 우선 순위를 높일 수 있습니다." claim_help: - optional: "다른 사람이 검토하지 못하도록이 항목을 요청할 수 있습니다." - required: "검토하기 전에 항목을 청구해야합니다." + optional: "다른 사용자가 처리하지 못하도록 이 항목을 지정 할 수 있습니다." + required: "항목을 검토하려면 먼저 항목을 검토 요청을 해야합니다." claimed_by_you: "이 항목을 요청했으며 검토 할 수 있습니다." - claimed_by_other: "이 아이템은 %{username}의해서만 리뷰 될 수 있습니다." + claimed_by_other: "이 항목은 %{username}님에 의해서만 처리 될 수 있습니다." claim: - title: "이 주제를 주장하십시오" + title: "이 글을 클레임" unclaim: - help: "이 주장을 제거" - awaiting_approval: "승인 대기 중" + help: "이 클레임 제거" + awaiting_approval: "승인 대기중" delete: "삭제" settings: - saved: "저장 완료" + saved: "저장되었습니다" save_changes: "변경사항 저장" title: "설정" priorities: @@ -370,7 +371,7 @@ ko: view_all: "모두 보기" grouped_by_topic: "주제별로 그룹화" none: "검토 할 항목이 없습니다." - view_pending: "보류중 보기" + view_pending: "보류중 항목 보기" topic_has_pending: other: "이 글에는 승인 대기중인 %{count}개의 댓글이 있습니다" title: "검토" @@ -378,18 +379,18 @@ ko: filtered_topic: "한 글에서 검토 가능한 콘텐츠로 필터링했습니다." filtered_user: "사용자" show_all_topics: "모든 글 보기" - deleted_post: "(게시물 삭제)" - deleted_user: "(사용자 삭제)" + deleted_post: "(게시물 삭제됨)" + deleted_user: "(사용자 삭제됨)" user: bio: "바이오" website: "웹 사이트" - username: "아이디" + username: "사용자 이름" email: "이메일" name: "이름" fields: "필드" user_percentage: summary: - other: "%{agreed},%{disagreed},%{ignored} (전체 %{count}개의 신고)" + other: "%{agreed}, %{disagreed}, %{ignored} (전체 %{count}개의 신고)" agreed: other: "%{count}% 동의" disagreed: @@ -399,22 +400,22 @@ ko: topics: topic: "글" reviewable_count: "수" - reported_by: "보고자" + reported_by: "신고자" deleted: "[삭제된 글]" - original: "(원래 주제)" - details: "상세" + original: "(원래 글)" + details: "세부 정보" unique_users: - other: "%{count} 사용자" + other: "회원 %{count}명" replies: other: "댓글 %{count}" - edit: "수정" + edit: "편집" save: "저장" cancel: "취소" - new_topic: "이 항목을 승인하면 새로운 주제가 생성됩니다" + new_topic: "이 항목을 승인하면 새 글이 만들어집니다." filters: - all_categories: "(전체 카테고리)" + all_categories: "(모든 카테고리)" type: - title: "형식" + title: "유형" all: "(모든 유형)" minimum_score: "최소 점수:" refresh: "새로고침" @@ -423,26 +424,26 @@ ko: orders: score: "점수" score_asc: "점수 (역순)" - created_at: "에 만든" - created_at_asc: "에서 생성 (역)" + created_at: "작성 날짜" + created_at_asc: "작성 날짜 (역순)" priority: title: "최소 우선 순위" - low: "(어떤)" + low: "(모든)" medium: "중간" high: "높음" conversation: view_full: "전체 대화 보기" scores: - about: "이 점수는보고자의 신뢰 수준, 이전 플래그의 정확성 및보고되는 항목의 우선 순위를 기반으로 계산됩니다." + about: "이 점수는 보고자의 회원 레벨, 이전 신고의 정확도 및 보고되는 항목의 우선 순위를 기준으로 계산됩니다." score: "점수" date: "날짜" - type: "형식" + type: "유형" status: "상태" submitted_by: "제출자" - reviewed_by: "검토 자" + reviewed_by: "검토자" statuses: pending: - title: "대기중" + title: "보류중" approved: title: "승인됨" rejected: @@ -452,7 +453,7 @@ ko: deleted: title: "삭제됨" reviewed: - title: "(모두 검토 됨)" + title: "(모두 검토됨)" all: title: "(모두)" types: @@ -460,13 +461,13 @@ ko: title: "신고된 글" flagged_by: "신고자" reviewable_queued_topic: - title: "대기중인 주제" + title: "대기중인 글" reviewable_queued_post: title: "대기중인 글" reviewable_user: title: "사용자" approval: - title: "게시물 승인 필요" + title: "승인이 필요한 게시물" description: "새로운 게시글이 있습니다. 그러나 이 게시글이 보여지려면 운영자의 승인이 필요합니다." pending_posts: other: "승인 대기중인 게시물이 %{count}개 있습니다." @@ -474,38 +475,38 @@ ko: user_action: user_posted_topic: "%{user} 사용자가 글 작성" you_posted_topic: "사용자님글 작성" - user_replied_to_post: "%{user}님이 %{post_number} 게시글에 답글 올림" - you_replied_to_post: "%{post_number} 게시글에 답글 올림" + user_replied_to_post: "%{user}님이 %{post_number}에 글을 남김" + you_replied_to_post: "사용자님%{post_number}에 글을 남김" user_replied_to_topic: "%{user}님이 에 댓글을 작성했습니다" you_replied_to_topic: "사용자님에 댓글을 작성했습니다" user_mentioned_user: "%{user}님이 %{another_user}를 멘션함" user_mentioned_you: "%{user}님이 를 멘션함" - you_mentioned_user: "%{another_user}님을 멘션함" - posted_by_user: "%{user}에 의해 게시됨" - posted_by_you: "가 게시함" + you_mentioned_user: "내가 %{another_user}님을 멘션함" + posted_by_user: "%{user}님이 작성" + posted_by_you: "사용자님이 작성" sent_by_user: "%{user}님이 보냄" sent_by_you: "가 보냄" directory: username: "사용자명" - filter_name: "아이디로 필터" + filter_name: "사용자명으로 필터링" title: "사용자" - likes_given: "제공한" - likes_received: "받은" + likes_given: "줌" + likes_received: "받음" topics_entered: "읽음" - topics_entered_long: "읽은 토픽 수" + topics_entered_long: "글 조회" time_read: "읽은 시간" topic_count: "글" topic_count_long: "글 작성됨" - post_count: "답글" - post_count_long: "답글" + post_count: "댓글" + post_count_long: "댓글 게시됨" no_results: "결과가 없습니다." - days_visited: "조회수" - days_visited_long: "일일 조회수" + days_visited: "방문" + days_visited_long: "방문 일수" posts_read: "읽음" - posts_read_long: "게시글 읽음" + posts_read_long: "게시물 읽음" last_updated: "마지막 업데이트 :" total_rows: - other: "%{count} 사용자" + other: "사용자 %{count}명" group_histories: actions: change_group_setting: "그룹 설정 변경" @@ -515,33 +516,33 @@ ko: remove_user_as_group_owner: "소유자 지정 취소하기" groups: member_added: "추가됨" - member_requested: "요청" + member_requested: "요청됨" add_members: - title: "사용자 추가" - description: "이 그룹의 회원 관리" - usernames: "아이디" + title: "%{group_name}에 구성원 추가" + description: "쉼표로 구분해 붙여 넣을 수도 있습니다." + usernames: "사용자 이름 또는 이메일 주소를 입력하세요." input_placeholder: "사용자 이름 또는 이메일" notify_users: "사용자에게 알림" requests: title: "요청" - reason: "사유" - accept: "승인" - accepted: "승인됨" + reason: "이유" + accept: "동의" + accepted: "수락됨" deny: "거부" denied: "거부됨" - undone: "취소 요청" - handle: "회원 요청 처리" + undone: "요청 실행 취소" + handle: "멤버십 요청 처리" manage: title: "관리" name: "이름" - full_name: "이름" - add_members: "멤버 추가" - delete_member_confirm: "'%{username}'님을 '%{group}'그룹에서 삭제 하시겠습니까?" + full_name: "전체 이름" + add_members: "회원 추가" + delete_member_confirm: "'%{group}'그룹에서 '%{username}' 사용자를 제거 하시겠습니까?" profile: title: 프로필 interaction: title: 상호 작용 - posting: 포스팅 + posting: 게시 notification: 알림 email: title: "이메일" @@ -554,7 +555,7 @@ ko: imap_server: "IMAP 서버" imap_port: "IMAP 포트" imap_ssl: "IMAP에 SSL 사용" - username: "아이디" + username: "사용자명" password: "비밀번호" mailboxes: synchronized: "동기화된 사서함" @@ -584,52 +585,52 @@ ko: logs: title: "로그" when: "언제" - action: "허용여부" + action: "처리" acting_user: "활동하는 사용자" - target_user: "타겟 사용자" + target_user: "대상 사용자" subject: "제목" - details: "세부 내용" + details: "세부 정보" from: "보내는사람" to: "받는사람" permissions: title: "권한" none: "이 그룹과 관련된 카테고리가 없습니다." description: "이 그룹의 회원은 이 카테고리에 접근 할 수 있습니다." - public_admission: "사용자가 그룹에 자유롭게 가입할 수 있도록 허용합니다. (그룹이 공개되어야 함)" - public_exit: "사용자가 그룹을 자유롭게 탈퇴할 수 있도록 허용합니다." + public_admission: "사용자가 그룹에 자유롭게 가입할 수 있도록 허용합니다. (공개 그룹이어야 함)" + public_exit: "사용자가 스스로 그룹에서 탈퇴 할 수 있도록 허용" empty: - posts: "이 그룹에는 아직 멤버들이 포스트를 작성하지 않았습니다." - members: "이 그룹에는 구성원이 없습니다." - requests: "이 그룹에 대한 회원 요청이 없습니다." - mentions: "이 그룹에서는 언급이 없습니다." - messages: "이 그룹에 대한 메시지는 없습니다." + posts: "이 그룹에는 아직 회원들이 글을 작성하지 않았습니다." + members: "이 그룹에는 회원이 없습니다." + requests: "이 그룹에 대한 멤버십 요청이 없습니다." + mentions: "이 그룹에 대한 언급이 없습니다." + messages: "이 그룹에 대한 메시지가 없습니다." topics: "이 그룹의 회원이 작성한 글이 없습니다." - logs: "이 그룹에 대한 기록이 없습니다." + logs: "이 그룹에 대한 로그가 없습니다." add: "추가" - join: "붙다" + join: "가입" leave: "나가기" request: "요청" message: "메시지" confirm_leave: "이 그룹을 탈퇴 하시겠습니까?" - allow_membership_requests: "사용자가 그룹 소유자에게 회원 요청을 보낼 수 있도록 허용 (공개적으로 공개 된 그룹 필요)" - membership_request_template: "가입 요청을 전송할 때 사용자에게 표시할 커스텀 템플릿" + allow_membership_requests: "사용자가 그룹 소유자에게 멤버십 요청을 보낼 수 있도록 허용 (공개 그룹이어야 함)" + membership_request_template: "멤버십 요청을 보낼 때 사용자에게 표시 할 사용자 지정 템플릿" membership_request: submit: "요청 보내기" title: "@%{group_name}에 가입 요청하기" reason: "그룹 소유자에게 왜 이 그룹에 속해야하는지 알립니다." - membership: "회원제" + membership: "멤버십" name: "이름" - group_name: "그룹명" + group_name: "그룹 이름" user_count: "사용자" - bio: "이 그룹에 대하여" - selector_placeholder: "아이디 입력" + bio: "그룹 소개" + selector_placeholder: "사용자 이름 입력" owner: "소유자" index: title: "그룹" all: "모든 그룹" - empty: "보이는 그룹이 없습니다." - filter: "그룹 유형별로 분류" - owner_groups: "내가 소유 한 그룹" + empty: "공개된 그룹이 없습니다." + filter: "그룹 유형으로 필터링" + owner_groups: "내가 소유한 그룹" close_groups: "닫힌 그룹" automatic_groups: "자동 그룹" automatic: "자동" @@ -641,59 +642,59 @@ ko: close_group: 그룹 닫기 my_groups: "내 그룹" group_type: "그룹 유형" - is_group_user: "준회원" + is_group_user: "회원" is_group_owner: "소유자" title: other: "그룹" activity: "활동" members: - title: "멤버" + title: "회원" filter_placeholder_admin: "아이디 혹은 이메일" - filter_placeholder: "아이디" + filter_placeholder: "사용자명" remove_member: "회원 삭제" - remove_member_description: "이 그룹에서 %{username}(을)를 삭제합니다." - make_owner: "소유자 만들기" - make_owner_description: "이 그룹에서 %{username}(을)를 소유자로 설정합니다." + remove_member_description: "이 그룹에서 %{username} 제거" + make_owner: "소유자로 만들기" + make_owner_description: "%{username} 사용자를 이 그룹의 소유자로 만들기" remove_owner: "소유자로 제거" - remove_owner_description: "이 그룹의 소유자로 %{username} 제거" + remove_owner_description: "%{username}님을 이 그룹의 소유자에서 제거" owner: "소유자" forbidden: "회원을 볼 수 없습니다." topics: "글" posts: "게시글" mentions: "멘션" messages: "메시지" - notification_level: "그룹 메시지의 기본 알림 레벨" + notification_level: "그룹 메시지에 대한 기본 알림 수준" alias_levels: - mentionable: "누가 이 그룹에 @멘션을 할 수 있나요?" - messageable: "누가 이 그룹에 메시지를 보낼 수 있나요?" + mentionable: "이 그룹은 누가 @mention 할 수 있습니까?" + messageable: "누가 이 그룹에 메시지를 보낼 수 있습니까?" nobody: "0명" only_admins: "관리자 전용" mods_and_admins: "운영자 및 관리자만" members_mods_and_admins: "그룹 멤버, 운영자, 관리자만" - owners_mods_and_admins: "그룹 소유자, 중재자 및 관리자 만" + owners_mods_and_admins: "그룹 소유자, 운영자 및 관리자만" everyone: "모두" notifications: watching: - title: "알림 : 주시 중" - description: "이 메시지에 새로운 답글이 있을 때 알림을 받게 되며 새로운 답글의 개수는 표시됩니다." + title: "주시중" + description: "모든 메시지의 모든 새 게시물에 대한 알림을 받게되며 새 댓글 개수가 표시됩니다." watching_first_post: title: "첫번째 글 보기" description: "이 그룹의 새 메시지에 대한 알림을 받지만 메시지에 회신하지는 않습니다." tracking: - title: "추적 중" - description: "누군가 당신의 @아이디 로 언급했거나 당신의 글에 답글이 달릴 때 알림을 받게 됩니다." + title: "트래킹" + description: "누군가 사용자님을 @name 형식으로 언급하거나 사용자님에게 댓글을 보내면 알림을 받게되며 새 댓글 수가 표시됩니다." regular: - title: "알림 : 일반" - description: "누군가 당신의 @아이디 로 언급했거나 당신의 글에 답글이 달릴 때 알림을 받게 됩니다." + title: "보통" + description: "누군가 사용자님을 @name 형식으로 언급하거나 사용자님에게 댓글을 보내면 알림을 받게됩니다." muted: - title: "알림 : 끔" + title: "뮤트" description: "이 그룹의 메시지에 대한 알림을받지 않습니다." flair_url: "아바타 플레어 이미지" flair_upload_description: "20 x 20픽셀보다 작은 정사각형 이미지를 사용하세요." flair_bg_color: "아바타 플레어 배경 색상" - flair_bg_color_placeholder: "(선택사항) 16진수 컬러 값" + flair_bg_color_placeholder: "(선택 사항) 16진수 색상 값" flair_color: "아바타 플레어 색상" - flair_color_placeholder: "(선택사항) 16진수 컬러 값" + flair_color_placeholder: "(선택 사항) 16진수 색상 값" flair_preview_icon: "미리보기 아이콘" flair_preview_image: "미리보기 이미지" flair_type: @@ -704,17 +705,17 @@ ko: "2": "좋아요" "3": "북마크" "4": "글" - "5": "답글" + "5": "댓글" "6": "응답" "7": "멘션" "9": "인용" "11": "편집" "12": "보낸 편지함" "13": "받은 편지함" - "14": "대기" + "14": "보류중" "15": "임시저장" categories: - all: "전체 카테고리" + all: "모든 카테고리" all_subcategories: "모두" no_subcategory: "없음" category: "카테고리" @@ -727,7 +728,7 @@ ko: position: "위치" posts: "게시글" topics: "글" - latest: "최근" + latest: "최신" latest_by: "가장 최근" toggle_ordering: "정렬 컨트롤 토글" subcategories: "하위 카테고리" @@ -738,26 +739,26 @@ ko: other: "지난주 %{count}개의 새 글이 있습니다." topic_stat_sentence_month: other: "지난달 %{count}개의 새 글이 있습니다." - n_more: "카테고리 (%{count} 더) ..." + n_more: "카테고리 (%{count}개 더보기) ..." ip_lookup: - title: IP 주소 Lookup - hostname: Hostname + title: IP 주소 조회 + hostname: 호스트 이름 location: 위치 location_not_found: (알수없음) organisation: 소속 phone: 전화 other_accounts: "현재 IP주소의 다른 계정들:" - delete_other_accounts: "삭제 %{count}" - username: "아이디" + delete_other_accounts: "%{count}개 삭제" + username: "사용자명" trust_level: "TL" read_time: "읽은 시간" - topics_entered: "입력된 제목:" - post_count: "포스트 개수" + topics_entered: "읽은 글" + post_count: "# 게시물" confirm_delete_other_accounts: "정말 이 계정들을 삭제하시겠습니까?" powered_by: "MaxMindDB 사용" copied: "복사됨" user_fields: - none: "(옵션을 선택하세요)" + none: "(옵션 선택)" required: '"%{name}"에 대한 값을 입력하십시오.' user: said: "%{username}:" @@ -765,7 +766,7 @@ ko: mute: "알림 끄기" edit: "환경 설정 편집" download_archive: - button_text: "모두 다운로드하기" + button_text: "모두 다운로드" confirm: "정말로 작성한 모든 글을 다운로드할까요?" success: "다운로드가 시작되었습니다. 다운로드 과정이 완료되면 메시지로 알려드리겠습니다." rate_limit_error: "게시글은 하루에 한번만 다운로드할 수 있습니다. 내일 다시 시도해보세요." @@ -777,62 +778,62 @@ ko: filter_by: "필터 기준" all: "모두" read: "읽기" - unread: "읽지 않은 글" + unread: "읽지 않음" ignore_duration_title: "사용자 무시" - ignore_duration_username: "아이디" - ignore_duration_when: "지속:" + ignore_duration_username: "사용자명" + ignore_duration_when: "기간:" ignore_duration_save: "무시" ignore_duration_note: "무시 기간이 만료되면 모든 무시가 자동으로 제거됩니다." - ignore_duration_time_frame_required: "시간대를 선택하십시오" + ignore_duration_time_frame_required: "기간을 선택하세요" ignore_no_users: "무시 된 사용자가 없습니다." ignore_option: "무시됨" - ignore_option_title: "이 사용자와 관련된 알림을받지 않으며 모든 주제와 답글이 숨겨집니다." - add_ignored_user: "더하다..." - mute_option: "알림끔" - mute_option_title: "이 사용자와 관련된 알림을받지 않습니다." - normal_option: "알림 : 일반" - normal_option_title: "이 사용자가 답장을 보내거나 인용하거나 언급하면 알림을받습니다." + ignore_option_title: "이 사용자와 관련된 알림은 수신되지 않으며 해당 글과 댓글이 모두 숨겨집니다." + add_ignored_user: "추가..." + mute_option: "알림 꺼짐" + mute_option_title: "이 사용자와 관련된 알림을 받지 않습니다." + normal_option: "보통" + normal_option_title: "이 사용자가 사용자님에게 댓글을 작성 하거나, 인용하거나, 멘션하면 알림이 전송됩니다." activity_stream: "활동" preferences: "환경 설정" feature_topic_on_profile: - open_search: "새로운 주제를 선택하십시오" - title: "주제를 선택하십시오" - search_label: "제목으로 주제 검색" + open_search: "새 글 선택" + title: "글 선택" + search_label: "제목으로 글 검색" save: "저장" clear: - title: "Clear" - warning: "추천 주제를 지우시겠습니까?" + title: "지우기" + warning: "추천 글을 지우시겠습니까?" use_current_timezone: "현재 시간대 사용" profile_hidden: "이 사용자의 프로필은 비공개 상태입니다." expand_profile: "확장" - collapse_profile: "무너짐" + collapse_profile: "축소" bookmarks: "북마크" bio: "내 소개" timezone: "시간대" - invited_by: "(이)가 초대했습니다." - trust_level: "회원등급" + invited_by: "초대 자" + trust_level: "회원 레벨" notifications: "알림" statistics: "통계" desktop_notifications: label: "실시간 알림" - not_supported: "안타깝게도 지금 사용하고 계시는 브라우저는 알림을 지원하지 않습니다." + not_supported: "이 브라우저에서는 알림이 지원되지 않습니다. 죄송합니다." perm_default: "알림 켜기" - perm_denied_btn: "권한 거부" + perm_denied_btn: "사용 권한 거부됨" perm_denied_expl: "알림을 허가하지 않으셨군요. 브라우저 설정을 통해서 알림을 허용해주세요." disable: "알림 비활성화" enable: "알림 활성화" - each_browser_note: "노트: 사용하시는 모든 브라우저에서 이 설정을 변경해야합니다." - consent_prompt: "포스트에 댓글이 달렸을때 실시간 알림을 받겠습니까?" + each_browser_note: "참고: 사용하는 모든 브라우저에서 이 설정을 변경해야 합니다." + consent_prompt: "내 글에 댓글이 달리면 실시간으로 알림을 받으시겠습니까?" dismiss: "해지" dismiss_notifications: "모두 해지" dismiss_notifications_tooltip: "읽지 않은 알림을 모두 읽음으로 표시" - first_notification: "당신의 첫 번째 알림입니다! 시작하려면 선택해보세요." - dynamic_favicon: "브라우저 아이콘에 카운트 표시" + first_notification: "첫 번째 알림! 시작하려면 선택하세요." + dynamic_favicon: "브라우저 아이콘에 수 표시" skip_new_user_tips: description: "새 사용자 온보딩 팁 및 배지 건너뛰기" not_first_time: "처음이 아니십니까?" skip_link: "이 팁 건너 뛰기" - theme_default_on_all_devices: "모든 장치에서 기본 테마로 설정" + theme_default_on_all_devices: "이 테마를 모든 기기에서 기본 테마로 설정" color_scheme_default_on_all_devices: "모든 장치에서 기본 색 구성표 설정" color_scheme: "색상 구성표" color_schemes: @@ -847,8 +848,8 @@ ko: dark_mode_enable: "어두운 모드 색 구성표 자동 사용" text_size_default_on_all_devices: "모든 장치에서 기본 텍스트 크기로 설정" allow_private_messages: "다른 사용자가 나에게 개인 메시지를 보내는것을 허용" - external_links_in_new_tab: "모든 외부 링크를 새 탭에 열기" - enable_quoting: "강조 표시된 텍스트에 대한 알림을 사용합니다" + external_links_in_new_tab: "새 탭에서 모든 외부 링크 열기" + enable_quoting: "강조 표시된 텍스트에 대한 알림 활성화" enable_defer: "읽지 않은 주제로 표시하도록 연기 활성화" change: "변경" featured_topic: "주요 글" @@ -858,8 +859,8 @@ ko: admin_tooltip: "이 회원은 관리자입니다." silenced_tooltip: "이 회원은 차단되었습니다" suspended_notice: "이 회원은 %{date}까지 접근 금지 되었습니다." - suspended_permanently: "이 회원은 일시정지되었습니다." - suspended_reason: "사유: " + suspended_permanently: "이 회원은 일시정지 되었습니다." + suspended_reason: "이유: " github_profile: "Github" email_activity_summary: "활동 요약" mailing_list_mode: @@ -868,40 +869,40 @@ ko: instructions: | 이 설정은 활동 요약보다 우선합니다.
뮤트된 글 및 카테고리는 이메일에 포함되지 않습니다. - individual: "모든 새로운 게시글에 대해 메일을 보내주세요." - individual_no_echo: "내가 쓴 것만 제외하고 모든 게시글에 대해 이메일을 보내주세요" - many_per_day: "모든 새 게시글에 대해 이메일을 보내주세요 (일일 약 %{dailyEmailEstimate}개)" - few_per_day: "모든 새로운 게시글에 대해 메일을 보내주세요 (하루에 약 2개)." - warning: "메일 링리스트 모드가 활성화되었습니다. 이메일 알림 설정이 무시됩니다." + individual: "모든 새 게시물에 대한 이메일 보내기" + individual_no_echo: "내 게시물을 제외한 모든 새 게시물에 대해 이메일 보내기" + many_per_day: "모든 새 게시물에 대해 이메일을 보냅니다 (하루에 약 %{dailyEmailEstimate}개)" + few_per_day: "모든 새 게시물에 대해 이메일을 보냅니다 (하루에 약 2개)." + warning: "메일링 리스트 모드가 활성화되었습니다. 이메일 알림 설정이 재설정 됩니다." tag_settings: "태그" - watched_tags: "팔로우" + watched_tags: "주시중" watched_tags_instructions: "이 태그가 있는 모든 글을 자동으로 볼 수 있습니다. 모든 새 글에 대한 알림이 표시되고 글 옆에 새 게시물 개수도 표시됩니다." - tracked_tags: "추적하기" + tracked_tags: "팔로우중" tracked_tags_instructions: "이 태그를 사용하여 모든 글을 자동으로 팔로우 합니다. 글 옆에 새 게시물 수가 표시됩니다." - muted_tags: "알람 끄기" + muted_tags: "알림 끔" muted_tags_instructions: "이 태그를 사용하면 새 글에 대한 알림을 받지 않으며 최신 항목에도 표시되지 않습니다." - watched_categories: "팔로우" + watched_categories: "주시중" watched_categories_instructions: "이 카테고리의 모든 글을 자동으로 팔로우하게 됩니다. 모든 새 글에 대한 알림을 받게되며 새 게시물 수도 글 옆에 표시됩니다." - tracked_categories: "추적하기" + tracked_categories: "팔로우중" tracked_categories_instructions: "이 카테고리의 모든 글을 자동으로 팔로우 합니다. 글 옆에 새 게시물 수가 표시됩니다." watched_first_post_categories: "첫번째 글 보기" watched_first_post_categories_instructions: "이 카테고리의 각 새 글에 대한 첫 번째 댓글 알림을 받습니다." watched_first_post_tags: "첫번째 글 보기" watched_first_post_tags_instructions: "이 태그를 사용하여 각 새 글에 대한 첫 번째 댓글 알림을 받습니다." - muted_categories: "알림 끄기" - muted_categories_instructions: "이 카테고리의 새 주제에 대한 알림을받지 않으며 카테고리 또는 최신 페이지에 나타나지 않습니다." - muted_categories_instructions_dont_hide: "이 카테고리의 새로운 주제에 대한 알림을받지 않습니다." + muted_categories: "알림 끔" + muted_categories_instructions: "이 카테고리의 새로운 글에 대한 알림은 제공되지 않으며 카테고리 또는 최근 글 페이지에 표시되지 않습니다." + muted_categories_instructions_dont_hide: "이 카테고리의 새로운 글에 대한 알림은 받지 않습니다." regular_categories: "일반" regular_categories_instructions: "“최근 글” 및 “주요 글” 목록에서 이 카테고리를 볼 수 있습니다." - no_category_access: "운영자는 이 카테고리 접근에 제약을 받습니다. 저장이 해제 됩니다." + no_category_access: "관리자로서 카테고리 접근이 제한되어 있으므로 저장이 비활성화됩니다." delete_account: "내 계정 삭제" - delete_account_confirm: "정말로 계정을 삭제할까요? 이 작업은 되돌릴 수 없습니다." - deleted_yourself: "계정이 삭제 되었습니다." - delete_yourself_not_allowed: "계정을 삭제하려면 직원에게 문의하십시오." + delete_account_confirm: "계정을 영구적으로 삭제 하시겠습니까? 이 작업은 취소 할 수 없습니다!" + deleted_yourself: "사용자님의 계정이 삭제되었습니다." + delete_yourself_not_allowed: "계정 삭제를 원하시면 관리자에게 문의하시기 바랍니다." unread_message_count: "메시지" admin_delete: "삭제" - users: "회원" - muted_users: "알람 끄기" + users: "사용자" + muted_users: "알림 끔" muted_users_instructions: "이 사용자의 모든 알림 및 개인 메시지를 표시하지 않습니다." allowed_pm_users: "허용됨" allowed_pm_users_instructions: "이 사용자의 개인 메시지만 허용합니다." @@ -911,10 +912,10 @@ ko: tracked_topics_link: "보이기" automatically_unpin_topics: "글 끝에 도달하면 글을 자동으로 고정 해제합니다." apps: "앱" - revoke_access: "접근권한 회수" - undo_revoke_access: "접근권환 회수 취소" + revoke_access: "접근 권한을 취소" + undo_revoke_access: "접근 권한 해지 취소" api_approved: "승인됨:" - api_last_used_at: "마지막에 사용한 :" + api_last_used_at: "마지막 사용 :" theme: "테마" save_to_change_theme: '"%{save_text}"을 클릭하면 테마가 업데이트됩니다.' home: "기본 홈페이지" @@ -923,19 +924,19 @@ ko: flags_given: "유용한 신고" flagged_posts: "신고된 글" deleted_posts: "삭제된 글" - suspensions: "정지시킨 계정" + suspensions: "차단" warnings_received: "경고" - rejected_posts: "거부 된 게시물" + rejected_posts: "거부된 게시물" messages: - all: "전체" - inbox: "수신함" - sent: "보냄" - archive: "저장됨" + all: "모두" + inbox: "받은 편지함" + sent: "보낸 편지함" + archive: "저장함" groups: "내 그룹" bulk_select: "메시지 선택" - move_to_inbox: "수신함으로 이동" - move_to_archive: "보관하기" - failed_to_move: "선택한 메시지를 이동할 수 없습니다 (아마도 네트워크가 다운됨)" + move_to_inbox: "받은 편지함으로 이동" + move_to_archive: "저장함" + failed_to_move: "선택한 메시지를 이동하지 못했습니다. (네트워크가 다운되었을 수 있음)" select_all: "모두 선택" tags: "태그" preferences_nav: @@ -953,51 +954,51 @@ ko: in_progress: "(이메일 전송 중)" error: "(오류)" emoji: "이모티콘 잠금" - action: "비밀번호 재설정 메일 보내기" + action: "비밀번호 재설정 이메일 보내기" set_password: "비밀번호 설정" - choose_new: "새로운 비밀번호를 적어주세요" - choose: "비밀번호를 적어주세요" + choose_new: "새 비밀번호를 입력하세요" + choose: "비밀번호를 입력하세요" second_factor_backup: title: "2 단계 백업 코드" regenerate: "재생성" - disable: "해제" - enable: "설정" + disable: "비활성" + enable: "활성화" enable_long: "백업 코드 사용" - manage: "백업 코드를 관리하십시오. 남아있는 %{count} 백업 코드가 있습니다." - copy_to_clipboard: "클립보드에 복사하기" - copy_to_clipboard_error: "데이터를 클립보드에 복사하는데 오류가 발생했습니다." + manage: "백업 코드를 관리합니다. %{count}개의 백업 코드가 남아 있습니다." + copy_to_clipboard: "클립 보드에 복사" + copy_to_clipboard_error: "데이터를 클립보드로 복사하는 중 오류 발생" copied_to_clipboard: "클립보드에 복사됨" download_backup_codes: "백업 코드 다운로드" - remaining_codes: "남아있는 %{count} 백업 코드가 있습니다." + remaining_codes: "%{count}개의 백업 코드가 남아 있습니다." use: "백업 코드 사용" enable_prerequisites: "백업 코드를 생성하기 전에 기본 두 번째 요소를 활성화해야합니다." codes: - title: "백업 코드가 재생성됨" - description: "이러한 각 백업 코드는 한 번만 사용할 수 있습니다. 안전하지만 접근 가능한 곳에 보관하십시오." + title: "생성된 백업 코드" + description: "이 백업 코드는 한 번만 사용할 수 있습니다. 안전한 곳에 보관하십시오." second_factor: - title: "이중 인증" - enable: "이중 인증 관리" + title: "2단계 인증" + enable: "2단계 인증 관리" disable_all: "모두 비활성화" - forgot_password: "비밀번호를 잊으 셨나요?" - confirm_password_description: "비밀번호를 확인해주세요." + forgot_password: "비밀번호를 잊으셨습니까?" + confirm_password_description: "계속하려면 비밀번호를 입력하세요" name: "이름" label: "코드" - rate_limit: "다른 인증 코드를 시도하기 전에 기다리십시오." + rate_limit: "다른 인증 코드를 시도하기 전에 잠시 기다려 주십시오." enable_description: | 지원되는 앱 (AndroidiOS)에서 이 QR코드를 스캔하고 인증 코드를 입력하세요. - disable_description: "앱에서 인증 코드를 입력하십시오" + disable_description: "앱의 인증 코드를 입력하세요" show_key_description: "수동으로 입력" short_description: | 일회용 보안 코드로 계정을 보호하십시오. extended_description: | 2단계 인증은 비밀번호 외에 일회용 토큰을 요구하여 계정에 추가 보안을 추가합니다. 토큰은 AndroidiOS 장치에서 생성 할 수 있습니다. - oauth_enabled_warning: "계정에서 2 단계 인증이 활성화되면 소셜 로그인이 비활성화됩니다." - use: "인증 기 앱 사용" - enforced_notice: "이 사이트에 액세스하기 전에 2 단계 인증을 활성화해야합니다." + oauth_enabled_warning: "계정에서 2단계 인증이 활성화되면 소셜 로그인이 비활성화됩니다." + use: "Authenticator 앱 사용" + enforced_notice: "이 사이트에 액세스하기 전에 2단계 인증을 활성화해야 합니다." disable: "해제" - disable_confirm: "두 번째 요소를 모두 비활성화 하시겠습니까?" + disable_confirm: "두 번째 요소 방법을 모두 비활성화 하시겠습니까?" save: "저장" - edit: "수정" + edit: "편집" edit_title: "두 번째 요소 편집" edit_description: "두 번째 요인 이름" enable_security_key_description: | @@ -1006,7 +1007,7 @@ ko: title: "토큰 기반 인증 자" add: "인증자 추가" default_name: "내 인증 자" - name_and_code_required_error: "인증 자 앱에서 이름과 코드를 제공해야합니다." + name_and_code_required_error: "인증 앱의 이름과 코드를 제공해야합니다." security_key: register: "등록하기" title: "보안 키" @@ -1017,15 +1018,15 @@ ko: edit: "보안 키 편집" save: "저장" edit_description: "보안 키 이름" - name_required_error: "보안 키의 이름을 제공해야합니다." + name_required_error: "보안 키의 이름을 제공해야 합니다." change_about: title: "내 소개 변경" - error: "값을 바꾸는 중 에러가 발생했습니다." + error: "이 값을 변경하는 중에 오류가 발생했습니다." change_username: - title: "아이디 변경" - confirm: "정말로 아이디를 변경 하시겠습니까?" - taken: "죄송합니다. 이미 사용 중인 아이디입니다." - invalid: "아이디가 잘못되었습니다. 숫자와 문자를 포함해야합니다." + title: "사용자명 변경" + confirm: "사용자명을 변경 하시겠습니까?" + taken: "죄송합니다. 사용중인 사용자명 입니다." + invalid: "해당 사용자명이 잘못되었습니다. 숫자와 문자만 포함해야 합니다." add_email: title: "이메일 추가" add: "추가" @@ -1034,32 +1035,33 @@ ko: taken: "죄송합니다. 해당 이메일은 사용 할 수 없습니다." error: "이메일 변경 중 오류가 발생했습니다. 이미 사용 중인 이메일인지 확인해주세요." success: "이메일 발송이 완료되었습니다. 확인하신 후 절차에 따라주세요." + success_via_admin: "해당 주소로 이메일을 보냈습니다. 이메일에 있는 확인 지침을 따라야 합니다." success_staff: "현재 주소로 이메일을 보냈습니다. 확인 절차에 따라 진행해 주세요." change_avatar: title: "프로필 사진 변경" gravatar: "%{gravatarName} 기반" gravatar_title: "%{gravatarName}의 웹 사이트에서 아바타 변경" gravatar_failed: "해당 이메일 주소로 %{gravatarName}을 찾을 수 없습니다." - refresh_gravatar_title: "당신의 %{gravatarName}를 상쾌하게하십시오" + refresh_gravatar_title: "%{gravatarName} 새로 고침" letter_based: "자동 생성된 아바타" - uploaded_avatar: "커스텀 사진" - uploaded_avatar_empty: "커스텀 사진 추가" - upload_title: "프로필 사진 업로드" + uploaded_avatar: "사용자 지정 사진" + uploaded_avatar_empty: "사용자 지정 사진 추가" + upload_title: "사진 업로드" image_is_not_a_square: "경고: 정사각형 이미지가 아니기 때문에 사진을 수정하였습니다." change_profile_background: title: "프로필 헤더" instructions: "프로필 헤더는 중앙에 위치하며 기본 너비는 1110px입니다." change_card_background: title: "사용자 카드 배경" - instructions: "배경 이미지는 가운데를 기준으로 표시되며 590px이 기본 가로 사이즈 입니다." + instructions: "배경 이미지는 중앙에 배치되며 기본 너비는 590px 입니다." change_featured_topic: - title: "특집 주제" - instructions: "이 주제에 대한 링크는 사용자 카드 및 프로필에 있습니다." + title: "주요 글" + instructions: "이 글에 대한 링크는 사용자 카드 및 프로필에 있습니다." email: title: "이메일" primary: "기본 이메일" secondary: "보조 이메일" - primary_label: "주요" + primary_label: "기본" unconfirmed_label: "미확인" resend_label: "활성화 이메일 재전송" resending_label: "보내는 중..." @@ -1067,96 +1069,96 @@ ko: update_email: "이메일 변경" set_primary: "기본 이메일 설정" destroy: "이메일 제거" - add_email: "대체 이메일 추가" + add_email: "보조 이메일 추가" sso_override_instructions: "SSO 제공 업체에서 이메일을 업데이트 할 수 있습니다." no_secondary: "보조 이메일이 없습니다" - instructions: "절대로 공개되지 않습니다." - ok: "내 이메일로 확인 메일이 전송됩니다." + instructions: "다른 사용자에게 공개되지 않습니다." + ok: "확인을 위해 이메일을 보내드립니다." required: "이메일 주소를 입력하십시오" - invalid: "유효한 이메일 주소를 입력해주세요." - authenticated: "내 이메일이 %{provider}에 의해 인증되었습니다." + invalid: "유효한 이메일 주소를 입력하십시오." + authenticated: "사용자님의 이메일은 %{provider}에 의해 인증되었습니다." frequency_immediately: "만약 전송된 메일을 읽지 않았을 경우, 즉시 메일을 다시 보내드립니다." frequency: - other: "최근 %{count}분 동안접속하지 않을 경우에만 메일이 전송됩니다." + other: "최근 %{count}분 동안 접속하지 않을 경우에만 메일이 전송됩니다." associated_accounts: - title: "관련 계정" + title: "연결된 계정" connect: "연결" - revoke: "회수" + revoke: "취소" cancel: "취소" not_connected: "(연결되지 않음)" confirm_modal_title: "%{provider} 계정 연결" confirm_description: - account_specific: "귀하의 %{provider} 계정 '%{account_description}'이 인증에 사용됩니다." + account_specific: "사용자님의 %{provider} 계정 '%{account_description}'이 인증에 사용됩니다." generic: "%{provider} 계정이 인증에 사용됩니다." name: title: "이름" - instructions: "이름 (선택사항)" - instructions_required: "이름" - required: "사용자 이름을 입력 해주세요" + instructions: "전체 이름 (선택 사항)" + instructions_required: "전체 이름" + required: "이름을 입력하세요" too_short: "이름이 너무 짧습니다." ok: "사용 가능한 이름입니다." username: - title: "아이디" + title: "사용자명" instructions: "공백없이, 짧고 특이하게" - short_instructions: "@%{username}으로 멘션이 가능합니다." - available: "아이디로 사용가능합니다." - not_available: "사용할 수 없는 아이디입니다. 다시 시도해보세요. %{suggestion}" - not_available_no_suggestion: "불가합니다" - too_short: "아이디가 너무 짧습니다" - too_long: "아이디가 너무 깁니다." - checking: "사용가능한지 확인 중..." - prefilled: "이메일이 등록된 아이디와 연결되어 있습니다." - required: "사용자 이름을 입력 해주세요" + short_instructions: "다른 사용자가 사용자님을 @%{username} 으로 멘션 할 수 있습니다." + available: "사용자명으로 사용할 수 있습니다." + not_available: "사용할 수 없습니다. %{suggestion}는 어떠세요?" + not_available_no_suggestion: "사용할 수 없음" + too_short: "사용자명 너무 짧습니다" + too_long: "사용자명이 너무 깁니다." + checking: "사용자명 사용 가능 여부 확인 중..." + prefilled: "이메일이 등록된 사용자명과 일치합니다." + required: "사용자명을 입력 해주세요" locale: title: "인터페이스 언어" - instructions: "UI 언어. 변경 후 새로 고침하면 반영됩니다." + instructions: "사용자 인터페이스 언어를 변경 후 페이지 새로 고침하면 반영됩니다." default: "(기본)" - any: "무관" + any: "모든" password_confirmation: - title: "비밀번호를 재입력해주세요." + title: "비밀번호 다시 입력" invite_code: - title: "코드 초대" - instructions: "계정 등록에는 초대 코드가 필요합니다" + title: "초대 코드" + instructions: "계정을 등록하려면 초대 코드가 필요합니다." auth_tokens: - title: "최근에 사용한 기기" + title: "최근에 사용한 장치" ip: "IP" - details: "세부 내용" - log_out_all: "모두 로그 아웃" + details: "세부 정보" + log_out_all: "모두 로그아웃" active: "지금 사용" not_you: "사용자님이 아닌가요?" - show_all: "모두 표시 (%{count})" - show_few: "더 적게 표시" - was_this_you: "이게 너야?" - was_this_you_description: "그렇지 않은 경우 비밀번호를 변경하고 어디에서나 로그 아웃하는 것이 좋습니다." + show_all: "모두 보기 (%{count})" + show_few: "간략히 보기" + was_this_you: "사용자님 이었나요?" + was_this_you_description: "본인이 아닌 경우 비밀번호를 변경하고 모든 곳에서 로그아웃하는 것이 좋습니다." browser_and_device: "%{device}의 %{browser}" secure_account: "내 계정 보안" - latest_post: "마지막으로 게시했습니다…" - last_posted: "마지막글" + latest_post: "마지막 작성…" + last_posted: "마지막 글" last_emailed: "마지막 이메일" last_seen: "마지막 접속" - created: "생성일" + created: "가입" log_out: "로그아웃" location: "위치" - website: "웹사이트" + website: "웹 사이트" email_settings: "이메일" hide_profile_and_presence: "내 공개 프로필 및 현재 상태 기능 숨기기" enable_physical_keyboard: "iPad에서 실제 키보드 지원 활성화" text_size: - title: "문자 크기" + title: "글자 크기" smallest: "가장 작음" - smaller: "더 작은" - normal: "알림 : 일반" - larger: "더 큰" - largest: "가장 큰" + smaller: "더 작음" + normal: "보통" + larger: "큼" + largest: "가장 큼" title_count_mode: - title: "백그라운드 페이지 제목은 다음 개수를 표시합니다." + title: "배경 페이지 제목에 다음 개수가 표시됩니다:" notifications: "새로운 알림" contextual: "새 페이지 내용" like_notification_frequency: title: "좋아요를 받았을 때 알림받기" - always: "항상 알림받기" - first_time_and_daily: "포스트가 첫 좋아요를 받았을 때부터 매일 알림받기" - first_time: "포스트가 첫 좋아요를 받았을 때 알림받기" + always: "항상" + first_time_and_daily: "글이 첫 좋아요를 받았을 때부터 매일 알림받기" + first_time: "게시물이 처음 좋아요를 받았을때" never: "알림 받지 않기" email_previous_replies: title: "이메일 하단에 예전에 읽은 댓글도 포함하기" @@ -1165,31 +1167,31 @@ ko: never: "알림 받지 않기" email_digests: title: "여기를 방문하지 않을 때 인기 주제와 답글을 이메일로 보내주세요" - every_30_minutes: "매 30분 마다" - every_hour: "매 시간" + every_30_minutes: "30분 마다" + every_hour: "매시간" daily: "매일" weekly: "매주" - every_month: "매월" + every_month: "매달" every_six_months: "6개월마다" email_level: title: "누군가가 나를 인용하거나, 내 게시물에 댓글을 달거나, 내 @username 을 언급하거나, 글에 나를 초대 할 때 이메일을 보냅니다." always: "항상 알림 받기" - only_when_away: "멀리있을 때만" - never: "하지않음" - email_messages_level: "메시지가 왔을 때 이메일 받기" + only_when_away: "방문이 없을때 알림 받기" + never: "알림 받지 않음" + email_messages_level: "누군가 나에게 메시지를 보내면 나에게 이메일 보내기" include_tl0_in_digests: "신규 사용자가 작성한 내용도 요약 메일에 포함시키기" email_in_reply_to: "이메일에 댓글 내용을 발췌해서 포함" - other_settings: "추가 사항" + other_settings: "기타" categories_settings: "카테고리" new_topic_duration: - label: "아래 조건에 해당하면 새로운 토픽으로 간주" - not_viewed: "아직 읽지 않은 토픽" - last_here: "마지막 방문이후 작성된 토픽" - after_1_day: "지난 하루간 생성된 토픽" - after_2_days: "지난 2일간 생성된 토픽" - after_1_week: "최근 일주일간 생성된 토픽" - after_2_weeks: "지난 2주간 생성된 토픽" - auto_track_topics: "내가 들어간 토픽 자동으로 추적" + label: "아래 조건에 해당하면 새로운 글로 간주" + not_viewed: "아직 읽어 보지 못했어요" + last_here: "마지막 방문 이후 작성된 글" + after_1_day: "지난 하루간 작성된 글" + after_2_days: "지난 2일 동안 작성된 글" + after_1_week: "지난주에 작성된 글" + after_2_weeks: "지난 2주 동안 작성된 글" + auto_track_topics: "내가 작성한 글 자동 팔로우" auto_track_options: never: "하지않음" immediately: "즉시" @@ -1200,51 +1202,51 @@ ko: after_4_minutes: "4분 후" after_5_minutes: "5분 후" after_10_minutes: "10분 후" - notification_level_when_replying: "토픽에 포스트를 쓰면 그 토픽을 다음으로 설정" + notification_level_when_replying: "글에 댓글을 쓰면 그 글을 다음으로 설정" invited: - search: "검색" + search: "초대를 검색하려면 입력..." title: "초대" - user: "사용자 초대" - sent: "마지막으로 보낸" + user: "초대된 사용자" + sent: "마지막으로 보냄" none: "표시 할 초대가 없습니다." truncated: - other: "앞 %{count}개의 초대를 보여줍니다." - redeemed: "초대를 받았습니다." - redeemed_tab: "Redeemed" - redeemed_tab_with_count: "교환된 (%{count})" - redeemed_at: "에 초대되었습니다." - pending: "초대를 보류합니다." - pending_tab: "보류" - pending_tab_with_count: "지연 (%{count})" - topics_entered: "읽은 토픽" - posts_read_count: "글 읽기" + other: "처음 %{count}개의 초대를 표시합니다." + redeemed: "사용된 초대" + redeemed_tab: "사용됨" + redeemed_tab_with_count: "사용됨 (%{count})" + redeemed_at: "사용됨" + pending: "보류 중인 초대" + pending_tab: "보류중" + pending_tab_with_count: "보류중 (%{count})" + topics_entered: "읽은 글" + posts_read_count: "읽은 댓글" expired: "이 초대장의 기한이 만료되었습니다." rescind: "삭제" rescinded: "초대가 제거되었습니다." rescind_all: "만료된 초대 제거" rescinded_all: "모든 만료 된 초대가 제거되었습니다!" rescind_all_confirm: "만료 된 초대를 모두 제거 하시겠습니까?" - reinvite: "초대 메일 재전송" - reinvite_all: "모든 초대장 다시 보내기" - reinvite_all_confirm: "정말로 모든 초대장을 다시 보낼까요?" + reinvite: "초대 다시 보내기" + reinvite_all: "모든 초대 다시 보내기" + reinvite_all_confirm: "정말로 모든 초대를 다시 보내시겠습니까?" reinvited: "초대 메일 재전송 됨" reinvited_all: "모든 초대장이 다시 발송되었습니다!" time_read: "읽은 시간" - days_visited: "일일 방문" - account_age_days: "일일 계정 나이" + days_visited: "방문 일수" + account_age_days: "계정 사용 기간 (일)" source: "다음을 통해 초대" links_tab: "링크" links_tab_with_count: "링크 (%{count})" link_url: "링크" - link_created_at: "생성일자" + link_created_at: "작성됨" link_redemption_stats: "회수" link_groups: 그룹 link_expires_at: 만료 - create: "초대장 보내기" + create: "초대 보내기" copy_link: "링크 복사" generate_link: "초대 링크 복사" link_generated: "초대 링크가 성공적으로 생성되었습니다!" - valid_for: "이 초대링크는 이메일 주소 '%{email}'에 한해서만 유효합니다." + valid_for: "초대 링크는 다음 이메일 주소에만 유효합니다: %{email}" single_user: "단일 사용자" multiple_user: "여러 사용자" invite_link: @@ -1256,17 +1258,17 @@ ko: bulk_invite: none: "아직 아무도 초대하지 않았습니다. 개별로 초대장을 보내거나 CSV 파일을 업로드하여 한 번에 여러 사람을 초대 할 수 있습니다." text: "일괄 초대" - success: "파일이 성공적으로 업로드되었습니다. 완료되면 메시지로 알려드리겠습니다." - error: "죄송합니다. CSV형식의 파일만 올릴 수 있습니다." - confirmation_message: "업로드 한 파일의 모든 사람에게 초대장을 이메일로 보내려고합니다." + success: "파일이 성공적으로 업로드되었습니다. 처리가 완료되면 메시지를 통해 알림을 받게됩니다." + error: "죄송합니다. 파일은 CSV 형식이어야 합니다." + confirmation_message: "업로드된 파일의 모든 사람에게 초대를 메일로 보내려 합니다." password: title: "비밀번호" - too_short: "암호가 너무 짧습니다." - common: "That password is too common." - same_as_username: "비밀번호가 아이디와 동일합니다." + too_short: "비밀번호가 너무 짧습니다." + common: "이 비밀번호는 너무 평범합니다." + same_as_username: "비밀번호가 사용자명과 동일합니다." same_as_email: "비밀번호가 이메일과 동일합니다." - ok: "적절한 암호입니다." - instructions: "최소 %{count} 자" + ok: "적절한 비밀번호 입니다." + instructions: "%{count}자 이상" required: "비밀번호를 입력하세요" summary: title: "요약" @@ -1282,169 +1284,174 @@ ko: likes_received: other: "받음" days_visited: - other: "방문일수" + other: "방문 일수" topics_entered: other: "읽은 글" posts_read: other: "읽은 댓글" bookmark_count: - other: "북마크들" - top_replies: "인기 댓글" - no_replies: "아직 답글이 없습니다." - more_replies: "답글 더 보기" + other: "북마크" + top_replies: "주요 댓글" + no_replies: "아직 댓글이 없습니다." + more_replies: "댓글 더 보기" top_topics: "주요 글" - no_topics: "아직 주제가 없습니다." - more_topics: "주제 더 보기" - top_badges: "인기 배지" + no_topics: "아직 글이 없습니다." + more_topics: "더 많은 글" + top_badges: "주요 배지" no_badges: "아직 배지가 없습니다." more_badges: "배지 더 보기" - top_links: "인기 링크" + top_links: "상위 링크" no_links: "아직 링크가 없습니다." - most_liked_by: "가장 많이 좋아요를 한 사용자" + most_liked_by: "가장 많은 좋아요를 받은 사용자" most_liked_users: "가장 많이 좋아요를 받은" most_replied_to_users: "댓글을 가장 많이 단 사람" no_likes: "아직 좋아요가 없습니다." top_categories: "상위 카테고리" - topics: "주제글" + topics: "글" replies: "댓글" ip_address: title: "마지막 IP 주소" registration_ip_address: - title: "IP Address 등록" + title: "등록 IP 주소" avatar: title: "프로필 사진" - header_title: "프로필, 메시지, 북마크 그리고 설정" + header_title: "프로필, 메시지, 북마크 및 환경설정" title: - title: "호칭" + title: "제목" none: "(없음)" primary_group: - title: "주 그룹" + title: "기본 그룹" none: "(없음)" filters: - all: "전체" + all: "모두" stream: - posted_by: "에 의해 작성되었습니다" - sent_by: "에 의해 전송되었습니다" + posted_by: "게시자 :" + sent_by: "보낸 사람" private_message: "메시지" - the_topic: "주제" - loading: "로딩 중..." + the_topic: "글" + loading: "로드 중..." errors: - prev_page: "로드하는 중" + prev_page: "로드하는 동안" reasons: - network: "네트워크 에러" - server: "서버 에러" + network: "네트워크 오류" + server: "서버 오류" forbidden: "접근 거부됨" - unknown: "에러" + unknown: "오류" not_found: "페이지를 찾을 수 없습니다" desc: - network: "접속상태를 확인해주세요." + network: "연결 상태를 확인하십시오." network_fixed: "문제가 해결된 것으로 보입니다." - server: "에러 코드: %{status}" - forbidden: "볼 수 있도록 허용되지 않았습니다." - not_found: "에구, 어플리케이션이 없는 URL를 가져오려고 시도했습니다." + server: "오류 코드: %{status}" + forbidden: "사용자님은 볼 수 없습니다." + not_found: "죄송합니다. 애플리케이션이 존재하지 않는 URL을 로드하려고 했습니다." unknown: "문제가 발생했습니다." buttons: - back: "뒤로가기" - again: "다시시도" + back: "뒤로 가기" + again: "다시 시도" fixed: "페이지 열기" modal: close: "닫기" - dismiss_error: "해고 오류" + dismiss_error: "오류 무시" close: "닫기" - assets_changed_confirm: "사이트가 업데이트 되었습니다. 새로고침하시겠습니까?" + assets_changed_confirm: "이 사이트는 방금 업데이트되었습니다. 지금 최신 버전으로 새로 고침 하시겠습니까?" logout: "로그아웃 되었습니다." - refresh: "새로고침" + refresh: "새로 고침" home: "홈" read_only_mode: - enabled: "이 사이트는 현재 읽기전용 모드입니다. 브라우징은 가능하지만, 댓글달기, 좋아요 등 다른 행위들은 현재 비활성화 되어있습니다." - login_disabled: "사이트가 읽기 전용모드로 되면서 로그인은 비활성화되었습니다." - logout_disabled: "사이트가 읽기 전용모드일 때 로그아웃은 비활성화됩니다." + enabled: "이 사이트는 현재 읽기전용 모드입니다. 브라우징은 가능하지만, 댓글 달기, 좋아요 및 기타 작업을 사용할 수 없습니다." + login_disabled: "사이트가 읽기 전용 모드인 동안에는 로그인이 비활성화됩니다." + logout_disabled: "사이트가 읽기 전용 모드인 동안에는 로그아웃이 비활성화됩니다." too_few_topics_notice_MF: >- 토론을 시작 합시다 ! {currentTopics, plural, one {is # topic} other {are # topics}}가 있습니다. 방문자는 더 읽고 답장해야합니다. 적어도 {requiredTopics, plural, one { # topic} other { # topics}}을 (를) 권장합니다. 직원 만이 메시지를 볼 수 있습니다. too_few_posts_notice_MF: >- 토론을 시작 합시다 ! {currentPosts, plural, one {is # post} other {are # posts}}가 있습니다. 방문자는 더 읽고 답장해야합니다. – {requiredPosts, plural, one { # post} other { # posts}} 이상을 권장합니다. 직원 만이 메시지를 볼 수 있습니다. - learn_more: "더 배우기" - all_time: "총" - all_time_desc: "총 토픽" + logs_error_rate_notice: + reached_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} other {# errors/hour}} 사이트 설정 한계에 도달 {limit, plural, one {# error/hour} other {# errors/hour}}." + reached_minute_MF: "{relativeAge}{rate, plural, one {# error/minute} other {# errors/minute}} 사이트 설정 한계에 도달 {limit, plural, one {# error/minute} other {# errors/minute}}." + exceeded_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} other {# errors/hour}} 사이트 설정 제한 초과 {limit, plural, one {# error/hour} other {# errors/hour}}" + exceeded_minute_MF: "{relativeAge}{rate, plural, one {# error/minute} other {# errors/minute}} 사이트 설정 제한 초과 {limit, plural, one {# error/minute} other {# errors/minute}}" + learn_more: "더 알아보기..." + all_time: "합계" + all_time_desc: "생성 된 총 글" year: "년" - year_desc: "지난 365일간 생성된 주제" + year_desc: "지난 365일 동안 작성된 글" month: "월" - month_desc: "지난 30일간 생성된 주제" + month_desc: "지난 30일 동안 작성된 글" week: "주" - week_desc: "지난 7일간 생성된 주제" + week_desc: "지난 7일 동안 작성된 글" day: "일" first_post: 첫 번째 글 - mute: 음소거 - unmute: 음소거 해제 - last_post: 게시날짜 + mute: 알림끔 + unmute: 알림끔 해제 + last_post: 게시됨 local_time: "현지 시각" time_read: 읽음 - time_read_recently: "최근에 %{time_read}" + time_read_recently: "최근 %{time_read}" time_read_tooltip: "읽은 총 시간 %{time_read}" time_read_recently_tooltip: "%{time_read} 총 읽기 시간 (지난 60 일 동안 %{recent_time_read})" - last_reply_lowercase: 마지막 답글 + last_reply_lowercase: 마지막 댓글 replies_lowercase: - other: 답글 + other: 댓글 signup_cta: sign_up: "회원가입" - hide_session: "내일 다시 알려주기" + hide_session: "내일 알려주세요" hide_forever: "사양합니다." hidden_for_session: "알겠습니다. 내일 다시 물어볼께요. 언제든지 '로그인'을 통해서도 계정을 만들 수 있습니다." - intro: "여보세요! 토론을 즐기고 있지만 아직 계정에 가입하지 않은 것 같습니다." + intro: "사이트에 관심이 있지만 아직 계정을 만들지 않은 것 같습니다." value_prop: "계정을 만들면 사이트내 읽은 글과 읽지 않은 글을 기억해 보다 편한 이용을 도와 줍니다. 또한 누군가가 댓글을 달거나 질문을 할 경우 이메일을 통해 알림을 받을 수 있습니다. :heartpulse:" summary: - enabled_description: "현재 커뮤니티에서 가장 인기있는 주제의 요약본을 보고 있습니다:" - description: "댓글이 %{replyCount}개 있습니다." - description_time: "댓글이 %{replyCount}개 있고 다 읽는데 %{readingTime} 분이 걸립니다." - enable: "이 주제를 요약" - disable: "모든 포스트 보기" + enabled_description: "이 글에 대한 요약: 현재 커뮤니티의 주요 글 요약본을 보고 있습니다" + description: "%{replyCount}개의 댓글이 있습니다." + description_time: "댓글이 %{replyCount}개 있으며 예상 읽기 시간은 %{readingTime}분 입니다." + enable: "이 글 요약" + disable: "모든 글 표시" deleted_filter: - enabled_description: "이 주제는 삭제된 글들을 포함하고 있습니다. 삭제된 글을 보이지 않습니다." - disabled_description: "삭제된 글들을 표시하고 있습니다." - enable: "삭제된 글 숨김" - disable: "삭제된 글 보기" + enabled_description: "이 글에는 숨겨진 삭제 된 게시물이 있습니다." + disabled_description: "글에 삭제 된 게시물이 표시됩니다." + enable: "삭제된 게시물 숨기기" + disable: "삭제된 게시물 표시" private_message_info: title: "메시지" invite: "다른 사람 초대 ..." - edit: "추가 또는 삭제 ..." + edit: "추가 또는 제거 ..." remove: "제거 ..." add: "추가 ..." - leave_message: "정말로 이 메시지를 남길까요?" + leave_message: "정말 이 메시지를 남기시겠습니까?" remove_allowed_user: "%{name}에게서 온 메시지를 삭제할까요?" remove_allowed_group: "%{name}에게서 온 메시지를 삭제할까요?" email: "이메일" - username: "아이디" + username: "사용자명" last_seen: "마지막 접속" - created: "생성" - created_lowercase: "최초 글" - trust_level: "회원등급" - search_hint: "아이디, 이메일 혹은 IP 주소" + created: "작성됨" + created_lowercase: "작성됨" + trust_level: "회원 레벨" + search_hint: "사용자명, 이메일 또는 IP 주소" create_account: disclaimer: "등록하면 개인 정보 보호 정책서비스 약관에 동의하게됩니다." - title: "회원 가입" - failed: "뭔가 잘못되었습니다. 이 메일은 등록이 되어있습니다. 비밀번호를 잊으셨다면 비밀번호 찾기를 눌러주세요." + title: "새 계정 만들기" + failed: "문제가 발생했습니다. 이 이메일이 이미 등록되어있을 수 있습니다. 비밀번호 찾기 링크를 시도해보세요." forgot_password: title: "비밀번호 재설정" action: "비밀번호를 잊어버렸습니다." - invite: "사용자 이름 또는 이메일 주소를 입력하시면 비밀번호 재설정 이메일을 보내드립니다." - reset: "암호 재설정" - complete_username: "자신의 아이디가 %{username}이라면, 곧 비밀번호 초기화 방법과 관련된 안내 메일을 받게 됩니다." - complete_email: "만약 계정이 %{email}과 일치한다면, 비밀번호를 재설정하는 방법에 대한 이메일을 곧 받게 됩니다." - complete_username_found: "사용자 이름 %{username} 와 일치하는 계정을 찾았 습니다 . 비밀번호 재설정 방법에 대한 지침이 담긴 이메일이 발송됩니다." - complete_email_found: "%{email} 와 일치하는 계정을 찾았 습니다 . 비밀번호 재설정 방법에 대한 지침이 담긴 이메일이 발송됩니다." + invite: "사용자명 또는 이메일 주소를 입력하면 비밀번호 재설정 이메일을 보내드립니다." + reset: "비밀번호 재설정" + complete_username: "계정의 사용자명이 %{username}와 일치하면 곧 비밀번호 재설정 방법에 대한 지침이 포함 된 이메일을 받게됩니다." + complete_email: "%{email}이 계정의 이메일과 일치하면, 비밀번호를 재설정하는 방법에 대한 지침이 포함 된 이메일을 받게됩니다." + complete_username_found: "사용자명 %{username}와 일치하는 계정을 찾았습니다. 곧 비밀번호 재설정 방법에 대한 지침이 포함 된 이메일을 받게됩니다." + complete_email_found: "%{email}와 일치하는 계정을 찾았습니다. 곧 비밀번호 재설정 방법에 대한 지침이 포함 된 이메일을 받게됩니다." complete_username_not_found: "%{username}과 일치하는 계정이 없습니다." complete_email_not_found: "%{email}과 일치하는 계정이 없습니다." - help: "이메일을 못받으셨나요? 일단 스팸 폴더부터 체크해보세요.

어떤 이메일 주소를 입력했는지 확실치 않나요? 이메일 주소를 여기에 입력하시면 기록이 있는지 봐드리겠습니다.

만약 더 이상 그 이메일 주소로 접근할 수 없다면, 친절한 운영진에게 도움을 요청하세요.

" + help: "이메일이 도착하지 않았습니까? 먼저 스팸 폴더를 확인해보세요.

어떤 이메일 주소를 사용했는지 잘 모르시겠습니까? 이메일 주소를 여기에 입력하면 기록이 있는지 확인해 드리겠습니다.

만약 더 이상 그 이메일 주소로 접근할 수 없다면, 사이트 관리자에게 도움을 요청하세요.

" button_ok: "확인" button_help: "도움말" email_login: link_label: "로그인 링크를 이메일로 보내기" - button_label: "이메일로" + button_label: "이메일 사용" emoji: "이모티콘 잠금" complete_username: "계정이 사용자 이름 %{username} 과 일치하면 곧 로그인 링크가 포함 된 이메일을 받게됩니다." complete_email: "계정이 %{email} 과 일치하면 곧 로그인 링크가 포함 된 이메일을 받게됩니다." - complete_username_found: "사용자 이름이 %{username} 와 일치하는 계정을 찾았습니다. 곧 로그인 링크가 포함 된 이메일을 받으실 것입니다." - complete_email_found: "%{email} 와 (과) 일치하는 계정을 찾았습니다. 곧 로그인 링크가 포함 된 이메일을 받게됩니다." + complete_username_found: "사용자명이 %{username}와 일치하는 계정을 찾았습니다. 곧 로그인 링크가 포함 된 이메일을 받게됩니다." + complete_email_found: "%{email} 와 일치하는 계정을 찾았습니다. 곧 로그인 링크가 포함 된 이메일을 받게됩니다." complete_username_not_found: "%{username}과 일치하는 계정이 없습니다." complete_email_not_found: "%{email}과 일치하는 계정이 없습니다." confirm_title: '%{site_name}으로 가기' @@ -1452,12 +1459,12 @@ ko: confirm_button: 로그인 완료 login: title: "로그인" - username: "아이디" + username: "사용자" password: "비밀번호" - second_factor_title: "이중 인증" + second_factor_title: "2단계 인증" second_factor_description: "앱에서 인증 코드를 입력하십시오 :" second_factor_backup: "백업 코드를 사용하여 로그인" - second_factor_backup_title: "이중 요인 백업" + second_factor_backup_title: "2단계 백업" second_factor_backup_description: "백업 코드 중 하나를 입력하세요:" second_factor: "OTP 앱을 사용하여 로그인" security_key_description: "실제 보안 키가 준비되면 아래의 보안 키로 인증 버튼을 누릅니다." @@ -1466,128 +1473,105 @@ ko: security_key_not_allowed_error: "보안 키 인증 프로세스가 시간 초과되었거나 취소되었습니다." security_key_no_matching_credential_error: "제공된 보안 키에서 일치하는 자격 증명을 찾을 수 없습니다." security_key_support_missing_error: "현재 장치 또는 브라우저가 보안 키 사용을 지원하지 않습니다. 다른 방법을 사용하십시오." - email_placeholder: "이메일 주소 또는 아이디" + email_placeholder: "이메일 또는 사용자명" caps_lock_warning: "Caps Lock 켜짐" error: "알 수없는 오류" - cookies_error: "브라우저에서 쿠키를 사용하지 않는 것으로 보입니다. 먼저 활성화하지 않으면 로그인하지 못할 수 있습니다." - rate_limit: "다시 로그인 하기전에 잠시만 기다려주세요." - blank_username: "이메일 또는 사용자 이름을 입력하십시오." - blank_username_or_password: "이메일 또는 아이디, 비밀번호를 입력해 주세요." - reset_password: "암호 재설정" + cookies_error: "브라우저의 쿠키가 비활성화 된 것 같습니다. 먼저 활성화하지 않으면 로그인하지 못할 수 있습니다." + rate_limit: "다시 로그인을 시도하기 전에 잠시 기다려주십시오." + blank_username: "이메일 또는 사용자명을 입력하십시오." + blank_username_or_password: "이메일 또는 사용자명, 비밀번호를 입력하십시오." + reset_password: "비밀번호 재설정" logging_in: "로그인 중.." or: "또는" authenticating: "인증 중..." awaiting_activation: "계정이 아직 미활성 상태입니다. 활성화 메일을 보내려면 비밀번호 찾기 링크를 사용하세요." - awaiting_approval: "스태프가 아직 내 계정을 승인하지 않았습니다. 승인되면 이메일을 받게됩니다." - requires_invite: "죄송합니다. 초대를 받은 사람만 이용하실 수 있습니다." - not_activated: "아직 로그인 할 수 없습니다. 계정을 만들었을때 %{sentTo} 주소로 인증 이메일을 보냈습니다. 계정을 활성화하려면 해당 이메일의 지침을 따르십시오." - not_allowed_from_ip_address: "이 IP 주소에서 로그인 할 수 없습니다." - admin_not_allowed_from_ip_address: "You can't log in as admin from that IP address." - resend_activation_email: "다시 인증 이메일을 보내려면 여기를 클릭하세요." - omniauth_disallow_totp: "귀하의 계정에는 2 단계 인증이 활성화되어 있습니다. 비밀번호로 로그인하십시오." + awaiting_approval: "사용자님의 계정은 아직 관리자의 승인이 처리되지 않았습니다. 승인되면 이메일이 전송됩니다." + requires_invite: "죄송합니다. 이 포럼에 대한 접근은 초대를 통해서만 가능합니다." + not_activated: "아직 로그인 할 수 없습니다. 이전에 활성화 이메일을 %{sentTo}로 보냈습니다. 해당 이메일의 지침에 따라 계정을 활성화하십시오." + not_allowed_from_ip_address: "해당 IP 주소에서는 로그인 할 수 없습니다." + admin_not_allowed_from_ip_address: "해당 IP 주소에서는 관리자로 로그인 할 수 없습니다." + resend_activation_email: "활성화 이메일을 다시 보내려면 여기를 클릭하십시오." + omniauth_disallow_totp: "계정에 2단계 인증이 활성화되어 있습니다. 비밀번호로 로그인 하십시오." resend_title: "활성화 이메일 다시 보내기" change_email: "이메일 주소 변경" - provide_new_email: "새로운 주소를 적어주시면 확인 메일을 재전송해드리겠습니다." - submit_new_email: "이메일 주소 변경" - sent_activation_email_again: " %{currentEmail} 주소로 인증 이메일을 보냈습니다. 이메일이 도착하기까지 몇 분 정도 걸릴 수 있습니다. 또한 스팸 메일을 확인하십시오." + provide_new_email: "새 주소를 입력하면 확인 이메일이 다시 전송됩니다." + submit_new_email: "이메일 주소 업데이트" + sent_activation_email_again: "%{currentEmail} 주소로 다른 계정 활성화 이메일을 보냈습니다. 도착하는데 몇 분 정도 걸릴 수 있습니다. 스팸 폴더를 확인하십시오." sent_activation_email_again_generic: "다른 활성화 이메일을 보냈습니다. 도착하는 데 몇 분이 걸릴 수 있습니다. 스팸 폴더를 확인하십시오." to_continue: "로그인 해주세요" - preferences: "사용자 환경을 변경하려면 로그인이 필요합니다." - forgot: "내 계정의 상세내역 기억하지 않는다." - not_approved: "당신의 계정은 아직 활성화되지 않았습니다. 이메일을 확인하시고 로그인 해주세요." + preferences: "사용자 기본 설정을 변경하려면 로그인해야 합니다." + forgot: "내 계정 정보가 기억 나지 않습니다." + not_approved: "계정이 아직 승인되지 않았습니다. 승인 되면 이메일로 알림을 받게 됩니다." google_oauth2: name: "구글" - title: "with Google" + title: "구글 사용" twitter: name: "트위터" - title: "with Twitter" + title: "트위터 사용" instagram: name: "인스타그램" - title: "인스타그램" + title: "인스타그램 사용" facebook: name: "페이스북" - title: "with Facebook" + title: "페이스북 사용" github: name: "GitHub" - title: "GitHub" + title: "GitHub 사용" discord: - name: "불일치" - title: "불화" + name: "디스코드" + title: "디스코드 사용" second_factor_toggle: totp: "대신 인증 자 앱을 사용하십시오." backup_code: "대신 백업 코드 사용" invites: - accept_title: "초대장" + accept_title: "초대" emoji: "봉투 이모티콘" - welcome_to: "%{site_name}에 오신것을 환영합니다." - invited_by: "당신을 초청한 사람:" - social_login_available: "해당 이메일 주소를 사용하는 다른 소셜 로그인으로 접속하는 것도 가능합니다." - your_email: "당신의 계정 이메일은 %{email}입니다." - accept_invite: "초청 수락하기" - success: "계정 생성이 생성되었습니다. 현재 로그인 상태입니다." + welcome_to: "%{site_name}에 오신 것을 환영합니다!" + invited_by: "사용자님을 초대한 사람 :" + social_login_available: "또한 해당 이메일을 사용하여 소셜 로그인으로 로그인 할 수 있습니다." + your_email: "사용자님의 계정 이메일 주소는 %{email}입니다." + accept_invite: "초대 수락" + success: "사용자님의 계정이 생성되었으며 이제 로그인 되었습니다." name_label: "이름" - password_label: "비밀번호 설정" - optional_description: "(선택사항)" + password_label: "비밀번호" + optional_description: "(선택 사항)" password_reset: continue: "%{site_name}으로 가기" emoji_set: - apple_international: "Apple/International" + apple_international: "애플/인터내셔널" google: "구글" twitter: "트위터" emoji_one: "JoyPixels (이전의 EmojiOne)" - win10: "Win10" - google_classic: "Google 클래식" - facebook_messenger: "Facebook 메신저" + win10: "윈10" + google_classic: "구글 클래식" + facebook_messenger: "페이스북 메신저" category_page_style: categories_only: "카테고리만" - categories_with_featured_topics: "주요 토픽이 있는 카테고리" - categories_and_latest_topics: "카테고리와 최신 토픽" - categories_and_top_topics: "카테고리 및 주요 주제" - categories_boxes: "하위 범주가있는 상자" - categories_boxes_with_topics: "주요 주제가있는 상자" - base_font_setting: - helvetica: "Helvetica/Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "RobotoCondensed" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "우분투" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" + categories_with_featured_topics: "주요 글이 있는 카테고리" + categories_and_latest_topics: "카테고리와 최신 글" + categories_and_top_topics: "카테고리 및 주요 글" + categories_boxes: "하위 카테고리가 있는 상자" + categories_boxes_with_topics: "주요 글이 있는 상자" shortcut_modifier_key: shift: "Shift" ctrl: "Ctrl" alt: "Alt" enter: "시작하다" conditional_loading_section: - loading: 로드중... + loading: 로드 중... category_row: - topic_count: "이 카테고리의 주제 %{count}" + topic_count: "이 카테고리에는 %{count}개의 글이 있습니다" select_kit: default_header_text: 선택... - no_content: 일치하는 결과가 없습니다 + no_content: 일치하는 항목을 찾을 수 없음 filter_placeholder: 검색... - filter_placeholder_with_any: 검색 또는 생성 ... - create: "만들기 : '%{content}'" + filter_placeholder_with_any: 검색 또는 생성... + create: "만들기: '%{content}'" max_content_reached: other: "%{count}개 항목만 선택할 수 있습니다." min_content_not_reached: other: "항목을 %{count}개 이상 선택하세요." - invalid_selection_length: "선택은 %{count} 자 이상이어야합니다." + invalid_selection_length: "선택은 %{count}자 이상이어야 합니다." components: categories_admin_dropdown: title: "카테고리 관리" @@ -1595,92 +1579,92 @@ ko: from: 보내는사람 to: 받는사람 errors: - to_before_from: "날짜는 날짜보다 늦어 야합니다." + to_before_from: "종료 날짜는 시작 날짜보다 이후여야 합니다." emoji_picker: filter_placeholder: 이모티콘 검색 - smileys_&_emotion: 스마일과 감정 + smileys_&_emotion: 웃는 얼굴과 감정 people_&_body: 사람과 몸 animals_&_nature: 동물과 자연 food_&_drink: 음식과 음료 - travel_&_places: 여행과 장소 + travel_&_places: 여행 및 장소 activities: 활동 objects: 사물 symbols: 기호 - flags: 신고들 + flags: 신고 recent: 최근 사용 - default_tone: 피부톤 없음 + default_tone: 피부색 없음 light_tone: 밝은 피부색 - medium_light_tone: 약간 밝은 피부색 + medium_light_tone: 중간 밝기 피부색 medium_tone: 중간 피부색 - medium_dark_tone: 약간 어두운 피부색 + medium_dark_tone: 중간 정도의 어두운 피부색 dark_tone: 어두운 피부색 - default: 커스텀 emoji + default: 사용자 정의 이모티콘 shared_drafts: - title: "초안 공유" - notice: "이 주제는 %{category} 카테고리를 볼 수있는 사람들에게만 표시됩니다." + title: "공유 초안" + notice: "이 항목은 %{category} 카테고리를 볼 수 있는 사용자만 볼 수 있습니다." destination_category: "대상 카테고리" publish: "공유 초안 게시" confirm_publish: "이 초안을 게시 하시겠습니까?" - publishing: "주제 게시 ..." + publishing: "글 게시 중..." composer: emoji: "이모티콘:)" more_emoji: "더보기..." options: "옵션" whisper: "귓속말" unlist: "목록에서 제외됨" - blockquote_text: "Blockquote" + blockquote_text: "인용구" add_warning: "공식적인 경고입니다." toggle_whisper: "귀속말 켜고 끄기" toggle_unlisted: "목록제외 켜고 끄기" - posting_not_on_topic: "어떤 주제에 답글을 작성하시겠습니까?" - saved_local_draft_tip: "로컬로 저장됩니다." - similar_topics: "작성하려는 내용과 비슷한 주제들..." - drafts_offline: "초안" - edit_conflict: "충돌을 편집하다" + posting_not_on_topic: "어떤 글에 댓글을 작성하시겠습니까?" + saved_local_draft_tip: "로컬에 저장" + similar_topics: "작성하려는 내용과 비슷한 글들..." + drafts_offline: "오프라인 초안" + edit_conflict: "충돌 편집" group_mentioned_limit: "경고! %{group}을 언급했지만이 그룹에는 관리자가 구성한 언급 한도 %{max} 사용자보다 많은 구성원이 있습니다. 아무도 알림을받지 않습니다." group_mentioned: - other: "%{group}을 언급하면, %{count} 명의 회원에게 알림이 갑니다. 그렇게 할까요?" + other: "%{group}을 언급하면, %{count}명의 회원에게 알림이 전송됩니다. 그렇게 하시겠습니까?" cannot_see_mention: category: "%{username}에게 멘션을 썼지만, 해당 사용자가 이 카테고리에 접근할 수 없기 때문에 알림이 가지 않습니다. 이 카테고리에 접근할 수 있는 그룹에 해당 멤버가 추가되어야 합니다." private: "%{username}에게 멘션을 썼지만, 해당 사용자가 개인 메시지를 볼 수 없기 때문에 알림이 가지 않습니다. 이 개인 메시지에 해당 사용자를 초대해야 합니다." - duplicate_link: "이 토픽에는 이미%{domain}의 링크가 @%{username}님이 %{ago}전에 쓴 게시글에 게시되어 있습니다. 그래도 다시 게시할까요?" + duplicate_link: "이 글에는 이미%{domain}의 링크가 @%{username}님이 %{ago}에 쓴 게시글에 포함되어 있습니다. 그래도 다시 작성하시겠습니까?" reference_topic_title: "RE : %{title}" error: title_missing: "제목은 필수 항목입니다" - title_too_short: "제목은 최소 %{min} 글자 이상이어야 합니다." - title_too_long: "제목은 %{max} 글자 이상일 수 없습니다." + title_too_short: "제목은 %{min}자 이상 이어야 합니다." + title_too_long: "제목은 %{max}자를 초과 할 수 없습니다." post_missing: "게시물은 비워 둘 수 없습니다" - post_length: "글은 최소 %{min} 글자 이상이어야 합니다." + post_length: "글은 최소 %{min}글자 이상이어야 합니다." try_like: "%{heart} 버튼을 사용해 보셨습니까?" category_missing: "카테고리를 선택해주세요." - tags_missing: "최소한 %{count} 태그를 선택해야합니다" - topic_template_not_modified: "토픽 템플릿을 편집하여 토픽에 세부 사항 및 세부 사항을 추가하십시오." + tags_missing: "최소한 %{count}개의 태그를 선택해야합니다." + topic_template_not_modified: "글 템플릿을 편집하여 글에 세부 정보와 세부 사항을 추가하십시오." save_edit: "편집 저장" overwrite_edit: "덮어 쓰기 편집" - reply_original: "기존 주제에 대해 답글을 작성합니다." - reply_here: "여기에 답글을 작성하세요." - reply: "답글 전송" + reply_original: "기존 글에 대한 댓글 작성" + reply_here: "여기에 댓글을 작성하세요." + reply: "댓글" cancel: "취소" create_topic: "새글 작성" create_pm: "메시지" - create_whisper: "속삭임" + create_whisper: "귓속말" create_shared_draft: "공유 초안 만들기" edit_shared_draft: "공유 초안 편집" title: "혹은 Ctrl + Enter 누름" users_placeholder: "사용자 추가" title_placeholder: "이야기 나누고자 하는 내용을 한문장으로 적는다면?" - title_or_link_placeholder: "제목을 입력하거나, 링크를 붙여넣으세요" - edit_reason_placeholder: "why are you editing?" - topic_featured_link_placeholder: "타이틀과 함께 표시될 링크를 입력하세요." - remove_featured_link: "주제에서 링크를 제거하십시오." - reply_placeholder: "여기에 타이핑 하세요. 마크다운 또는 BBCode, HTML 포맷을 이용하세요. 이미지를 끌어오거나 붙여넣기 하세요." - reply_placeholder_no_images: "여기에 입력하십시오. Markdown, BBCode 또는 HTML을 사용하여 형식을 지정하십시오." + title_or_link_placeholder: "여기에 제목을 입력하거나 링크를 붙여 넣으세요." + edit_reason_placeholder: "왜 편집 중입니까?" + topic_featured_link_placeholder: "제목과 함께 표시된 링크를 입력하십시오." + remove_featured_link: "글에서 링크를 제거하십시오." + reply_placeholder: "여기에 입력하세요. Markdown, BBCode 또는 HTML을 사용하여 입력 할 수 있습니다. 이미지를 드래그하거나 붙여 넣을 수 있습니다." + reply_placeholder_no_images: "여기에 입력하세요. Markdown, BBCode 또는 HTML을 사용하여 작성합니다." reply_placeholder_choose_category: "여기에 입력하기 전에 카테고리를 선택하십시오." view_new_post: "새로운 글을 볼 수 있습니다." - saving: "저장 중..." - saved: "저장 완료!" - saved_draft: "초안이 진행 중입니다. 다시 시작하려면 누릅니다." - uploading: "업로딩 중..." + saving: "저장하는 중" + saved: "저장되었습니다!" + saved_draft: "초안 게시가 진행 중입니다. 다시 시작하려면 탭하세요." + uploading: "업로드 중..." show_preview: "미리보기 열기 »" hide_preview: "« 미리보기 숨김" quote_post_title: "전체 글을 인용" @@ -1688,25 +1672,25 @@ ko: bold_title: "굵게" bold_text: "굵게하기" italic_label: "I" - italic_title: "강조" + italic_title: "기울이기 적용" italic_text: "강조하기" link_title: "하이퍼링크" - link_description: "링크 설명을 입력" + link_description: "여기에 링크 설명을 입력하십시오." link_dialog_title: "하이퍼링크 삽입" - link_optional_text: "옵션 제목" - link_url_placeholder: "주제를 검색하기 위해 URL 또는 유형 붙여 넣기" + link_optional_text: "선택적 제목" + link_url_placeholder: "글을 검색하려면 URL을 붙여 넣거나 입력하세요." quote_title: "인용구" quote_text: "인용구" - code_title: "코드 샘플" - code_text: "미리 지정된 양식 사용은 4개의 띄어쓰기로 들여쓰세요." - paste_code_text: "여기에 코드를 붙여넣거나 입력하세요" + code_title: "코드" + code_text: "미리 서식이 지정된 텍스트를 4칸 들여쓰기" + paste_code_text: "여기에 코드를 입력하거나 붙여 넣습니다." upload_title: "업로드" - upload_description: "업로드 설명을 입력" + upload_description: "여기에 업로드 설명을 입력하십시오." olist_title: "번호 매기기 목록" ulist_title: "글 머리 기호 목록" - list_item: "주제" + list_item: "목록 항목" toggle_direction: "방향 전환" - help: "마크다운 편집 도움말" + help: "Markdown 편집 도움말" collapse: "글쓰기 화면 최소화" open: "글쓰기 화면을 엽니다" abandon: "글쓰기 화면을 닫고 초안을 삭제합니다." @@ -1718,19 +1702,19 @@ ko: modal_cancel: "취소" cant_send_pm: "죄송합니다. %{username}님에게 메시지를 보낼 수 없습니다." yourself_confirm: - title: "수신자 추가를 잊으셨나요?" - body: "현재 이 메시지는 당신에게만 전송됩니다." - admin_options_title: "이 주제에 대한 옵션 설정" + title: "받는 사람 추가를 잊으셨나요?" + body: "지금 이 메시지는 자신에게만 전송됩니다!" + admin_options_title: "이 글에 대한 옵션 설정" composer_actions: reply: 댓글 draft: 임시저장 - edit: 수정 + edit: 편집 reply_to_post: desc: 특정 게시물에 답장 reply_as_new_topic: - label: 링크 된 주제로 답장 - desc: 이 주제에 링크 된 새로운 주제를 만듭니다 - confirm: 새로운 토픽 초안이 저장되었으며 링크 된 토픽을 작성하면 덮어 씁니다. + label: 링크 된 글로 답장 + desc: 이 주제에 링크 된 새로운 글을 만듭니다 + confirm: 새 글 초안이 저장되어 있으며 링크된 글을 만들면 덮어 쓰게됩니다. reply_as_new_group_message: label: 새 그룹 메시지로 답장 desc: 받는 사람이 같은 새 비공개 메시지 만들기 @@ -1738,19 +1722,21 @@ ko: label: 새 메시지 desc: 새 개인 메시지 쓰기 reply_to_topic: - label: 주제에 대한 답변 - desc: 특정 게시물이 아닌 주제에 대한 답변 + label: 글에 대한 답변 + desc: 특정 게시물이 아닌 글에 대한 답변 toggle_whisper: - label: 속삭임 토글 - desc: 속삭임은 직원 만 볼 수 있습니다 + label: 귀속말 켜고 끄기 + desc: 귓속말은 관리자만 볼 수 있습니다. create_topic: - label: "새 주제글" + label: "새 글" shared_draft: - label: "초안 공유" - desc: "직원 만 볼 수있는 주제 초안" + label: "공유 초안" + desc: "관리자만 볼 수있는 글 초안 작성" toggle_topic_bump: - label: "토픽 범프 전환" + label: "글 범프 전환" desc: "최신 회신 날짜를 변경하지 않고 회신" + reload: "새로 고침" + ignore: "무시" notifications: tooltip: regular: @@ -1759,10 +1745,10 @@ ko: other: "%{count}개의 읽지않은 메시지가 있습니다" high_priority: other: "읽지 않은 높은 우선 순위의 알림 %{count}개" - title: "@name 언급, 글과 주제에 대한 답글, 개인 메시지 등에 대한 알림" + title: "@name 멘션 알림, 글에 대한 댓글, 개인 메시지 등에 대한 알림" none: "현재 알림을 불러올 수 없습니다." empty: "알림이 없습니다." - post_approved: "귀하의 게시물이 승인되었습니다" + post_approved: "사용자님의 게시물이 승인되었습니다" reviewable_items: "검토가 필요한 항목" mentioned: "%{username} %{description}" group_mentioned: "%{username} %{description}" @@ -1784,123 +1770,126 @@ ko: invitee_accepted: "%{username} 님이 초대를 수락했습니다" moved_post: "%{username} 님이 %{description} (을)를 이동했습니다" linked: "%{username} %{description}" - granted_badge: "'%{description}' 를 받았습니다" + granted_badge: "'%{description}' 획득" topic_reminder: "%{username} %{description}" - watching_first_post: "새 토픽 %{description}" - membership_request_accepted: "'%{group_name}'에 회원 가입" - membership_request_consolidated: "%{count} '%{group_name}'에 대한 오픈 멤버쉽 요청" + watching_first_post: "새 글 %{description}" + membership_request_accepted: "'%{group_name}'에 회원 가입" + membership_request_consolidated: "'%{group_name}'에 대한 %{count}건의 회원 가입 요청" reaction: "%{username} %{description}" reaction_2: "%{username}, %{username2} %{description}" votes_released: "%{description} - 완료됨" group_message_summary: - other: " %{group_name} 사서함에 %{count} 개의 메시지가 있습니다" + other: " %{group_name} 사서함에 %{count}개의 메시지가 있습니다" popup: - mentioned: '"%{topic}" - %{site_title}에서 %{username} 님이 나를 멘션했습니다' - group_mentioned: '"%{topic}" - %{site_title}에서 %{username} 님이 당신을 언급했습니다' - quoted: '"%{topic}" - %{site_title}에서 %{username} 님이 나를 인용했습니다' - replied: '"%{topic}" - %{site_title}에서 %{username} 님이 내게 답글을 달았습니다' - posted: '"%{topic}" - %{site_title}에서 %{username}님이 글을 게시하였습니다' - private_message: '%{username}에서 "%{topic}"에 개인 메시지를 보냈습니다-%{site_title}' - linked: '%{username}님이 "%{topic}" - %{site_title}에 내 글을 링크했습니다' - watching_first_post: '%{username}가 새로운 주제 "%{topic}"-%{site_title}을 작성했습니다.' - confirm_title: "알림 활성 - %{site_title}" + mentioned: '"%{topic}"에서 %{username}님이 나를 멘션했습니다 - %{site_title}' + group_mentioned: '"%{topic}"에서 %{username}님이 사용자님을 언급했습니다 - %{site_title}' + quoted: '"%{topic}"에서 %{username}님이 사용자님을 인용했습니다 - %{site_title}' + replied: '"%{topic}"에서 %{username}님이 사용자님에게 댓글을 달았습니다 - %{site_title}' + posted: '"%{topic}"에서 %{username}님이 글을 게시하였습니다 - %{site_title}' + private_message: '%{username}에서 "%{topic}"에 개인 메시지를 보냈습니다-%{site_title}' + linked: '%{username}님이 "%{topic}" 글에서 사용자님을 링크했습니다 - %{site_title}' + watching_first_post: '%{username}님이 새 글 "%{topic}"을 만들었습니다 - %{site_title}' + confirm_title: "알림 사용 - %{site_title}" confirm_body: "완료! 알림이 활성화되었습니다." - custom: "%{site_title}의 %{username}에서 알림" + custom: "%{site_title}의 %{username}님의 알림" titles: - mentioned: "말하는" - replied: "새로운 답장" - quoted: "인용" - edited: "편집" - liked: "새로운 것" - private_message: "새로운 개인 메시지" - invited_to_private_message: "개인 메시지에 초대" + mentioned: "멘션" + replied: "새 댓글" + quoted: "인용됨" + edited: "편집됨" + liked: "새로운 좋아요" + private_message: "새 개인 메시지" + invited_to_private_message: "비공개 메시지에 초대됨" invitee_accepted: "초대 수락" - posted: "새로운 게시물" - moved_post: "소식이 이동 됨" + posted: "새 글" + moved_post: "게시물 이동됨" linked: "연결됨" bookmark_reminder: "북마크 알림" - bookmark_reminder_with_name: "북마크 알림-%{name}" - granted_badge: "배지 부여" - invited_to_topic: "주제에 초대" + bookmark_reminder_with_name: "북마크 알림 - %{name}" + granted_badge: "부여된 배지" + invited_to_topic: "글에 초대" group_mentioned: "언급 된 그룹" - group_message_summary: "새로운 그룹 메시지" - watching_first_post: "새로운 주제" - topic_reminder: "주제 알림" - liked_consolidated: "새로운 좋아하는" + group_message_summary: "새 그룹 메시지" + watching_first_post: "새 글" + topic_reminder: "글 알림" + liked_consolidated: "새로운 좋아요" post_approved: "게시물 승인됨" - membership_request_consolidated: "새로운 회원 요청" + membership_request_consolidated: "신규 멤버십 요청" reaction: "새로운 반응" + votes_released: "투표가 발표되었습니다" upload_selector: - title: "이미지 추가하기" - title_with_attachments: "이미지 또는 파일 추가하기" - from_my_computer: "컴퓨터에서 가져오기" - from_the_web: "인터넷에서 가져오기" + title: "이미지 추가" + title_with_attachments: "이미지 또는 파일 추가" + from_my_computer: "내 기기에서" + from_the_web: "웹에서" remote_tip: "이미지 링크" - remote_tip_with_attachments: "이미니자 파일 링크 %{authorized_extensions}" + remote_tip_with_attachments: "이미지 또는 파일의 링크 %{authorized_extensions}" local_tip: "기기에서 이미지 선택" - local_tip_with_attachments: "디바이스에서 이미지나 파일을 선택하세요 %{authorized_extensions}" - hint: "(드래그&드랍으로 업로드 가능)" - hint_for_supported_browsers: "편집창에 이미지를 끌어다 놓거나 붙여넣기 할 수도 있습니다" - uploading: "업로드 중입니다..." + local_tip_with_attachments: "기기에서 이미지 또는 파일 선택 %{authorized_extensions}" + hint: "(편집기로 드래그 앤 드롭하여 업로드 할 수도 있습니다)" + hint_for_supported_browsers: "편집기로 이미지를 끌어다 놓거나 붙여 넣을 수도 있습니다." + uploading: "업로드 중" select_file: "파일 선택" default_image_alt_text: 이미지 search: - sort_by: "다음으로 정렬" + sort_by: "정렬 기준" relevance: "관련성" - latest_post: "가장 최근 글" - latest_topic: "최신 토픽" - most_viewed: "가장 많이 본" - most_liked: "가장 많이 좋아요를 받은" + latest_post: "최신 글" + latest_topic: "최신 글" + most_viewed: "가장 많이 봄" + most_liked: "가장 좋아함" select_all: "모두 선택" - clear_all: "다 지우기" - too_short: "검색 단어가 너무 짧습니다." - title: "주제, 글, 사용자, 카테고리 검색" + clear_all: "모두 지우기" + too_short: "검색어가 너무 짧습니다." + result_count: + other: "%{term}에 대한 %{count}%{plus}개의 검색 결과" + title: "글, 사용자 또는 카테고리 검색" full_page_title: "글 또는 댓글 검색" no_results: "검색 결과가 없습니다" no_more_results: "더 이상 결과가 없습니다." - searching: "검색중..." - post_format: "#%{post_number} by %{username}" - results_page: "'%{term}'의 검색 결과" + searching: "검색 중..." + post_format: "%{username}님의 글 #%{post_number}" + results_page: "'%{term}'에 대한 검색 결과" more_results: "검색 결과가 많습니다. 검색 조건을 좁혀보세요." - cant_find: "원하는 걸 찾을 수 없으신가요?" - start_new_topic: "새 토픽을 만들어볼까요?" + cant_find: "원하는 것을 찾을 수 없습니까?" + start_new_topic: "새 글을 만들어볼까요?" or_search_google: "혹은 구글에서 검색해볼 수도 있습니다." search_google: "대신 구글에서 검색해보세요." search_google_button: "구글" - search_google_title: "이 사이트 검색" + search_google_title: "이 사이트에서 검색" context: - user: "@%{username}의 글 검색" + user: "@%{username}님의 글 검색" category: "#%{category} 카테고리에서 검색" - tag: "# %{tag} 태그 검색" - topic: "이 주제를 검색" + tag: "#%{tag} 태그 검색" + topic: "이 글에서 검색" private_messages: "메시지 검색" advanced: - title: 고급 검색 + title: 상세 검색 posted_by: - label: 글쓴이 + label: '글쓴이:' in_category: - label: 분류 + label: 분류됨 in_group: - label: 그룹에 속한 + label: 그룹 내 with_badge: - label: 배지가 있는 + label: 배지 포함 with_tags: - label: 태그 + label: 태그 됨 filters: - label: 주제 / 게시 만 반환 ... - title: 제목 만 일치 - likes: 내가 좋아요 누른 - posted: 내가 게시글을 쓴 - created: 내가 만들었다 - watching: 내가 주시하는 - tracking: 내가 추적하는 - private: 내 메시지에서 - bookmarks: 나는 북마크했다 - first: 가 가장 첫 포스트입니다. + label: 항목/게시물 만 반환... + title: 제목에서만 일치 + likes: 내가 좋아요 누름 + posted: 내가 쓴 글 + created: 내가 작성함 + watching: 내가 주시중 + tracking: 내가 팔로우중 + private: 내 메시지 + bookmarks: 내 북마크 + first: 첫 번째 게시물입니다 pinned: 고정됨 - seen: 읽었다 - unseen: 읽지 않은 것 - wiki: 은(는) 위키입니다. + seen: 읽음 + unseen: 읽지 않음 + wiki: 위키입니다 images: 이미지 포함 all_tags: 위의 모든 태그 statuses: @@ -1908,76 +1897,88 @@ ko: open: 가 열렸습니다 closed: 가 닫혔습니다 public: 공개 - archived: 가 보관되었습니다 - noreplies: 답글이 없습니다 + archived: 보관 됨 + noreplies: 댓글이 없습니다 single_user: 1명의 사용자를 포함합니다 post: count: - label: 최소 게시글 수 + label: 글 + min: + placeholder: 최소 + max: + placeholder: 최대 time: - label: 게시날짜 + label: 게시 됨 before: 이전 after: 이후 + views: + label: 조회 + min_views: + placeholder: 최소 + max_views: + placeholder: 최대 hamburger_menu: "다른 글 목록 또는 카테고리로 이동" new_item: "새 항목" go_back: "돌아가기" - not_logged_in_user: "user page with summary of current activity and preferences" + not_logged_in_user: "현재 활동 및 기본 설정에 대한 요약이 포함된 사용자 페이지" current_user: "사용자 페이지로 이동" view_all: "모두 보기" topics: - new_messages_marker: "마지막 조회시간" + new_messages_marker: "마지막 방문" bulk: select_all: "모두 선택" clear_all: "모두 지우기" - unlist_topics: "주제 내리기" - relist_topics: "토픽 재정렬하기" + unlist_topics: "목록에서 글 숨기기" + relist_topics: "목록에서 글 다시 보이기" reset_read: "읽기 초기화" - delete: "주제 삭제" + delete: "글 삭제" dismiss: "해지" - dismiss_read: "읽지않음 전부 해지" + dismiss_read: "읽지 않은 모든 항목 닫기" dismiss_button: "해지..." - dismiss_tooltip: "새 글을 무시하거나 주제 추적 멈추기" - also_dismiss_topics: "이 주제를 더 이상 추적하지 않고 읽지 않은 글에서 표시하지 않음" + dismiss_tooltip: "새 게시물만 닫거나 글 팔로우 중지" + also_dismiss_topics: "이 글 팔로우를 중지하여 다시 읽지 않은 것으로 표시되지 않도록합니다." dismiss_new: "새글 제거" - toggle: "주제 복수 선택" + toggle: "글 일괄 선택 전환" actions: "일괄 적용" - change_category: "카테고리 설정하기" - close_topics: "주제 닫기" - archive_topics: "주제 보관하기" + change_category: "카테고리 설정" + close_topics: "글 닫기" + archive_topics: "글 보관" notification_level: "알림" - choose_new_category: "주제의 새로운 카테고리를 선택" + choose_new_category: "글에 대한 새 카테고리 선택:" selected: - other: "%{count}개의 주제가 선택되었습니다." - change_tags: "태그 교체" - append_tags: "태그 덧붙이기" - choose_new_tags: "이 토픽의 태그를 입력하세요:" - choose_append_tags: "이 토픽에 추가할 태그를 입력하세요:" - changed_tags: "토픽의 태그가 변경되었습니다." + other: "사용자님은 %{count}개의 글을 선택했습니다." + change_tags: "태그 바꾸기" + append_tags: "태그 추가" + choose_new_tags: "다음 글에 대한 새 태그를 선택하십시오:" + choose_append_tags: "다음 글에 추가 할 새 태그를 선택하십시오:" + changed_tags: "해당 글의 태그가 변경되었습니다." + remove_tags: "태그 제거" none: - unread: "읽지 않은 주제가 없습니다." - new: "읽을 새로운 주제가 없습니다." - read: "아직 어떠한 주제도 읽지 않았습니다." - posted: "아직 어떠한 주제도 작성되지 않았습니다." - latest: "최근 글이 없습니다." - bookmarks: "아직 북마크한 주제가 없습니다." - category: "%{category}에 주제가 없습니다." - top: "Top 주제가 없습니다." + unread: "읽지 않은 글이 없습니다." + new: "새로운 글이 없습니다." + read: "아직 읽은 글이 없습니다." + posted: "아직 어떤 글도 게시하지 않았습니다." + ready_to_create: "지금 " + latest: "모두 확인했습니다!" + bookmarks: "아직 북마크된 글이 없습니다." + category: "%{category}에 글이 없습니다." + top: "주요 글이 없습니다." educate: - new: '

회원님의 주제는 여기에 나타납니다.

기본적으로 생긴 지 이틀 안된 주제는 새것으로 간주하고 new 표시가 뜹니다.

바꾸고 싶으면 환경설정으로 가보세요.

' - unread: '

회원님이 읽지 않은 주제는 여기에 나타납니다.

기본적으로 주제는 읽지 않은 것으로 간주하고 다음과 같은 조건 중 하나를 만족하면 읽지 않은 글갯수 1 을 표시합니다:

또는 주제를 추적하거나 지켜보기 위해 각 주제의 밑부분에 달린 알림제어판에서 설정하는 경우도 포합됩니다.

설정을 바꾸려면 환경설정 페이지로 가세요.

' + new: '

새로운 글은 여기에 나타납니다.

기본적으로 지난 2일 이내에 생성 된 경우 새 글로 간주되며 new 로 표시됩니다.

이를 변경하려면 환경 설정 에서 변경 하십시오.

' + unread: '

읽지 않은 글은 여기에 표시됩니다.

기본적으로 글은 읽지 않은 것으로 간주하고 다음과 같은 조건 중 하나를 만족하면 읽지 않은 글갯수 1 을 표시합니다:

또는 글을 팔로우 하거나 지켜보기 위해 각 글의 밑부분에 달린 알림 제어판에서 설정하는 경우도 포합됩니다.

설정을 바꾸려면 환경설정 페이지로 가세요.

' bottom: latest: "더 이상 최근 글이 없습니다." posted: "더 이상 작성한 글이 없습니다." - read: "더 이상 읽을 주제가 없습니다" - new: "더 이상 읽을 새로운 주제가 없습니다." - unread: "더 이상 읽지 않은 주제가 없습니다" - category: "더 이상 %{category}에 주제가 없습니다" + read: "더 이상 읽을 글이 없습니다." + new: "더 이상 새로운 글이 없습니다." + unread: "더 이상 읽지 않은 글이 없습니다." + category: "더 이상 %{category}에 글이 없습니다." tag: "더 이상 %{tag}의 글이 없습니다." - top: "더 이상 인기 주제가 없습니다." - bookmarks: "더이상 북마크한 주제가 없습니다." + top: "더 이상 주요 글이 없습니다." + bookmarks: "더이상 북마크한 글이 없습니다." topic: filter_to: - other: "이 토픽에 %{count}개 게시글" + other: "이 글에 %{count}개의 게시글" create: "새글 쓰기" create_long: "새글 쓰기" open_draft: "초안 열기" @@ -1986,218 +1987,218 @@ ko: help: "저장함으로 이동" title: "저장됨" move_to_inbox: - title: "수신함으로 이동" - help: "메시지를 편지함으로 되돌리기" + title: "받은 편지함으로 이동" + help: "메시지를 받은 편지함으로 다시 이동" edit_message: help: "메시지의 첫 번째 게시물 수정" - title: "수정" + title: "편집" defer: help: "읽지 않은 상태로 표시" title: "연기" feature_on_profile: - help: "사용자 카드 및 프로필에이 주제에 대한 링크 추가" + help: "사용자 카드 및 프로필에 이 글에 대한 링크 추가" title: "프로필 기능" remove_from_profile: - warning: "프로필에 이미 추천 주제가 있습니다. 계속하면이 주제가 기존 주제를 대체합니다." - help: "사용자 프로필에서이 주제에 대한 링크를 제거하십시오." + warning: "프로필에 이미 추천 글이 있습니다. 계속하면 이 글이 기존 글을 대체합니다." + help: "사용자 프로필에서 이 항목에 대한 링크 제거" title: "프로필에서 제거" - list: "주제 목록" - new: "새로운 주제" - unread: "읽지 않은" + list: "글" + new: "새 글" + unread: "읽지 않음" new_topics: - other: "%{count}개의 새로운 주제" + other: "%{count}개의 새로운 글" unread_topics: - other: "%{count}개의 읽지 않은 주제" - title: "주제" + other: "%{count}개의 읽지 않은 글" + title: "글" invalid_access: - title: "이 주제는 비공개입니다" - description: "죄송합니다. 그 주제에 접근 할 수 없습니다!" - login_required: "해당 주제를 보려면 로그인이 필요합니다." + title: "비공개 글입니다." + description: "죄송합니다. 해당 글에 접근 할 수 없습니다!" + login_required: "해당 글을 보려면 로그인이 필요합니다." server_error: - title: "주제를 불러오지 못했습니다" - description: "죄송합니다. 연결 문제로 인해 해당 주제를 불러올 수 없습니다. 다시 시도하십시오. 문제가 지속되면 문의해 주시기 바랍니다" + title: "글을 불러오지 못했습니다." + description: "죄송합니다. 연결 문제로 인해 해당 글을 불러올 수 없습니다. 다시 시도하십시오. 문제가 지속되면 문의해 주시기 바랍니다" not_found: - title: "주제를 찾을 수 없습니다" - description: "죄송합니다. 주제를 찾을 수 없습니다. 아마도 운영자에 의해 삭제된 것 같습니다." + title: "글을 찾을 수 없음" + description: "죄송합니다. 해당 글을 찾을 수 없습니다. 관리자가 삭제 한 것일 수 있습니다." total_unread_posts: - other: "이 주제에 %{count}개의 읽지 않을 게시 글이 있습니다." + other: "이 글에 %{count}개의 읽지 않은 게시 글이 있습니다." unread_posts: - other: "이 주제에 %{count}개의 읽지 않을 게시 글이 있습니다." + other: "이 글에 %{count}개의 읽지 않은 예전 게시물이 있습니다." new_posts: - other: "최근 읽은 이후 %{count}개 글이 이 주제에 작성되었습니다." + other: "마지막으로 읽은 이후 이 글에 %{count}개의 새로운 게시물이 있습니다." likes: - other: "이 주제에 %{count}개의 '좋아요'가 있습니다." - back_to_list: "주제 리스트로 돌아갑니다." - options: "주제 옵션" - show_links: "이 주제에서 링크를 표시합니다." + other: "이 글에 %{count}개의 좋아요가 있습니다." + back_to_list: "글 목록으로 돌아 가기" + options: "글 옵션" + show_links: "이 글의 링크 표시" toggle_information: "글의 세부 정보를 열고 닫습니다." read_more_in_category: "더 읽을거리가 필요하신가요? %{catLink} 또는 %{latestLink}를 살펴보세요." - read_more: "%{catLink} 또는 %{latestLink}에서 더 많은 토픽들을 찾으실 수 있습니다" - unread_indicator: "아직이 주제의 마지막 게시물을 읽은 회원이 없습니다." - read_more_MF: "읽지 않은 글 { UNREAD, plural, =0 {} one { is 1 개 }other { are # 개 } }, 새 글은 { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 개 } other { {BOTH, select, true{and } false {are } other{}} # 개 } } 을 확인해보세요, {CATEGORY, select, true {{catLink} 카테고리의 다른 글들도 살펴보세요.} false {{latestLink}} other {}}" - bumped_at_title_MF: "{FIRST_POST} : {CREATED_AT} {LAST_POST} : {BUMPED_AT}" + read_more: "%{catLink} 또는 %{latestLink}에서 더 많은 글을 찾으실 수 있습니다." + unread_indicator: "아직 이 글의 마지막 게시물을 읽은 회원이 없습니다." + read_more_MF: "읽지 않은 글 { UNREAD, plural, =0 {} one { 1 개 }other { # 개 } } { NEW, plural, =0 {} one { {BOTH, select, true{와 } false {를 } other{}} 새 글 1 개 } other { {BOTH, select, true{와 } false {를 } other{}} 새 글 # 개 } } 를 확인해보세요, {CATEGORY, select, true {{catLink} 카테고리의 다른 글들도 살펴보세요.} false {{latestLink}} other {}}" + bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}" browse_all_categories: 모든 카테고리 보기 - browse_all_tags: 모든 태그 찾아보기 + browse_all_tags: 모든 태그 보기 view_latest_topics: 최근 글 보기 - suggest_create_topic: 새 주제를 작성 해 보실래요? + suggest_create_topic: 새로운 대화를 시작 하시겠습니까? jump_reply_up: 이전 답글로 이동 jump_reply_down: 이후 답글로 이동 - deleted: "주제가 삭제되었습니다" + deleted: "글이 삭제되었습니다." topic_status_update: - title: "토픽 타이머" + title: "글 타이머" save: "타이머 설정" num_of_hours: "시간:" num_of_days: "일 수:" remove: "타이머 제거하기" publish_to: "게시되는 곳:" when: "게시일:" - time_frame_required: 시간대를 선택하십시오 + time_frame_required: 기간을 선택하세요. auto_update_input: none: "시간대 선택" now: "지금" - later_today: "오늘 늦게" + later_today: "오늘 나중에" tomorrow: "내일" - later_this_week: "이번 주 후반" + later_this_week: "이번 주말" this_weekend: "이번 주말" next_week: "다음 주" two_weeks: "2주" next_month: "다음 달" - two_months: "이 개월" + two_months: "2개월" three_months: "3개월" - four_months: "4 개월" + four_months: "4개월" six_months: "6개월" one_year: "1년" - forever: "영구적" - pick_date_and_time: "날짜와 시간을 " + forever: "영원히" + pick_date_and_time: "날짜 및 시간 선택" set_based_on_last_post: "마지막 게시글 기준으로 닫기" publish_to_category: - title: "발행 스케쥴링" + title: "게시 예약" temp_open: title: "임시로 열기" auto_reopen: - title: "자동으로 열린 토픽" + title: "자동으로 열린 글" temp_close: title: "임시로 닫기" auto_close: - title: "자동으로 닫힌 토픽" + title: "글 자동 잠금" label: "토픽이 자동으로 열린 후 지난 시간:" - error: "유효한 값을 입력하세요" - based_on_last_post: "적어도 주제의 마지막 글이 이만큼 오래되지 않았으면 닫지 마세요." + error: "유효한 값을 입력하십시오." + based_on_last_post: "글의 마지막 게시물이 이보다 오래 될 때까지 닫지 마십시오." auto_delete: - title: "자동 삭제 토픽" + title: "자동 삭제 글" auto_bump: - title: "자동 범프 주제" + title: "자동 끌어올림 글" reminder: - title: "다시 알림받기" + title: "알림" auto_delete_replies: title: "답글 자동 삭제" status_update_notice: - auto_open: "이 토픽은 %{timeLeft}후에 자동으로 열립니다." - auto_close: "이 토픽은 %{timeLeft}후에 자동으로 닫힙니다." - auto_publish_to_category: "이 토픽은 #%{categoryName}에 %{timeLeft}시간이 지나면 발행됩니다." - auto_close_based_on_last_post: "이 주제는 마지막 답글이 달린 %{duration} 후 닫힙니다." - auto_delete: "이 토픽은 %{timeLeft}후에 자동으로 삭제됩니다." - auto_bump: "이 주제는 %{timeLeft}에 자동으로 적용됩니다." - auto_reminder: "%{timeLeft}후에 이 토픽을 다시 알려드리겠습니다." - auto_delete_replies: "이 주제에 대한 회신은 %{duration} 이후에 자동으로 삭제됩니다." - auto_close_title: "자동으로 닫기 설정" + auto_open: "이 글은 %{timeLeft}후에 자동으로 열립니다." + auto_close: "이 글은 %{timeLeft}에 자동으로 닫힙니다." + auto_publish_to_category: "이 글은 #%{categoryName}에 %{timeLeft} 후 게시됩니다." + auto_close_based_on_last_post: "이 글은 마지막 댓글이 달린 %{duration} 후 닫힙니다." + auto_delete: "이 글은 %{timeLeft}후에 자동으로 삭제됩니다." + auto_bump: "이 글은 %{timeLeft}에 자동으로 끌어올려 집니다." + auto_reminder: "%{timeLeft}후에 이 글을 다시 알려드리겠습니다." + auto_delete_replies: "이 글에 대한 댓글은 %{duration}후에 자동으로 삭제됩니다." + auto_close_title: "자동 닫기 설정" auto_close_immediate: - other: "주제에 마지막 게시글이 올라온 지 %{hours} 시간이 지났기 때문에 이 주제는 곧 닫힐 예정입니다." + other: "글의 마지막 게시물은 이미 %{count}시간이 지났으므로 해당 글은 즉시 닫힙니다." timeline: - back: "이전" - back_description: "마지막으로 안읽은 게시글로 돌아가기" + back: "뒤로" + back_description: "읽지 않은 마지막 게시물로 돌아가기" replies_short: "%{current} / %{total}" progress: title: 진행 중인 주제 - go_top: "맨위" - go_bottom: "맨아래" + go_top: "맨 위" + go_bottom: "맨 아래" go: "이동" - jump_bottom: "최근 글로 이동" - jump_prompt: "넘어가기" - jump_prompt_of: "번째, 총 %{count} 개 포스트" - jump_prompt_long: "로 이동 ..." - jump_bottom_with_number: "jump to post %{post_number}" + jump_bottom: "마지막 게시물로 이동" + jump_prompt: "이동..." + jump_prompt_of: "번째, %{count}개 중" + jump_prompt_long: "이동..." + jump_bottom_with_number: "%{post_number}로 이동" jump_prompt_to_date: "현재까지" jump_prompt_or: "또는" - total: 총 글 + total: 총 게시물 current: 현재 글 notifications: - title: 이 토픽에 대한 알림 빈도 변경 + title: 이 글에 대한 알림을 받는 빈도 변경 reasons: - mailing_list_mode: "메일링 리스트 모드가 활성화되었기 때문에, 이 토픽의 답글을 메일을 통하여 받게 됩니다." - "3_10": "이 토픽의 태그를 주시하고 있기 때문에 알림을 받게 됩니다." - "3_6": "이 카테고리를 보고 있어서 알림을 받게 됩니다." - "3_5": "자동으로 이 글을 보고있어서 알림을 받게 됩니다." - "3_2": "이 주제를 보고있어서 알림을 받게 됩니다." - "3_1": "이 주제를 생성하여서 알림을 받게 됩니다." - "3": "이 주제를 보고있어서 알림을 받게 됩니다." - "2_8": "이 카테고리를 추적중이므로, 새로운 게시글의 수를 표시합니다." - "2_4": "이 토픽에 게시글을 남겼기 때문에, 새로운 게시글의 수를 표시합니다." - "2_2": "이 토픽을 추적중이므로, 새로운 게시글의 수를 표시합니다." - "2": 'You will see a count of new replies because you read this topic.' - "1_2": "누군가 내 @아아디 으로 멘션했거나 내 글에 답글이 달릴 때 알림을 받게 됩니다." - "1": "누군가 내 @아아디 으로 멘션했거나 내 글에 답글이 달릴 때 알림을 받게 됩니다." - "0_7": "이 주제에 관한 모든 알림을 무시하고 있습니다." - "0_2": "이 주제에 관한 모든 알림을 무시하고 있습니다." - "0": "이 주제에 관한 모든 알림을 무시하고 있습니다." + mailing_list_mode: "메일링 리스트 모드가 활성화되어 있으므로 이 글의 댓글을 메일로 받게 됩니다." + "3_10": "이 글에 대한 태그를 주시중이므로 알림을 받게됩니다." + "3_6": "이 카테고리를 주시중이므로 알림을 받게됩니다." + "3_5": "이 글을 자동으로 주시하기 시작 했으므로 알림을 받게됩니다." + "3_2": "이 글을 보고 있으므로 알림을 받게됩니다." + "3_1": "이 글을 작성 했으므로 알림을 받게됩니다." + "3": "이 글을 보고 있으므로 알림을 받게됩니다." + "2_8": "이 카테고리를 팔로우 하고 있으므로 새 댓글 수가 표시됩니다." + "2_4": "이 글에 댓글을 작성했기 때문에 새 글 수가 표시됩니다." + "2_2": "이 글을 팔로우 하고 있기 때문에 새 글 수가 표시됩니다." + "2": '이 글을 읽었으므로 새 댓글 수가 표시됩니다.' + "1_2": "누군가가 사용자님을 @name 형식으로 멘션하거나 사용자님에게 댓글을 보내면 알림을 받게됩니다." + "1": "누군가 @name 형식으로 나에게 멘션 했거나 내 글에 댓글이 달릴 때 알림을 받게 됩니다." + "0_7": "이 카테고리의 모든 알림을 무시하고 있습니다." + "0_2": "이 글에 대한 모든 알림을 무시하고 있습니다." + "0": "이 글에 대한 모든 알림을 무시하고 있습니다." watching_pm: - title: "알림 : 주시 중" - description: "이 메시지에 새로운 답글이 있을 때 알림을 받게 되며 새로운 답글의 개수는 표시됩니다." + title: "주시 중" + description: "이 메시지의 모든 새 답글에 대한 알림을 받게되며 새 답글 수가 표시됩니다." watching: title: "주시 중" - description: "이 주제에 새로운 답글이 있을 때 알림을 받게 되며 새로운 답글의 개수는 표시됩니다." + description: "이 글의 모든 새 댓글에 대한 알림을 받게되며 새 댓글 수가 표시됩니다." tracking_pm: - title: "추적 중" - description: "이 메시지의 읽지않은 응답의 수가 표시됩니다. 누군가 내 @아이디를 멘션했거나 내게 답글을 작성하면 알림을 받습니다." + title: "트래킹" + description: "이 메시지에 대한 새 답변 수가 표시됩니다. 누군가가 @name 형식으로 사용자님을 멘션하거나 사용자님에게 답글을 보내면 알림을 받게됩니다." tracking: - title: "추적 중" - description: "이 주제의 새로운 답글의 수가 표시됩니다. 누군가 내 @아이디를 멘션했거나 내게 답글을 작성하면 알림을 받습니다." + title: "트래킹" + description: "이 글에 대한 새 답글 수가 표시됩니다. 누군가가 @name 형식으로 사용자님을 멘션하거나 사용자님에게 답글을 보내면 알림을 받게됩니다." regular: - title: "알림 : 일반" - description: "누군가 내 @아아디 으로 멘션했거나 내 글에 답글이 달릴 때 알림을 받게 됩니다." + title: "일반" + description: "누군가가 @name 형식으로 사용자님에게 멘션하거나 사용자님에게 답글을 보내면 알림을 받게됩니다." regular_pm: - title: "알림 : 일반" - description: "누군가 내 @아아디 으로 멘션했거나 내 글에 답글이 달릴 때 알림을 받게 됩니다." + title: "일반" + description: "누군가 @name 형식으로 사용자님에게 멘션하거나 사용자님에게 답글을 보내면 알림을 받게됩니다." muted_pm: - title: "알림 : 끔" + title: "알림끔" description: "이 메시지에 대해 어떠한 알림도 받지 않지 않습니다." muted: - title: "알림 없음" - description: "이 주제에 대해 어떠한 알림도 받지 않고 최신글 목록에도 나타나지 않을 것입니다." + title: "알림 꺼짐" + description: "이 글에 대한 어떠한 알림도 받지 않고 최근글 목록에도 표시되지 않습니다." actions: - title: "액션" - recover: "주제 다시 복구" - delete: "주제 삭제" - open: "주제 열기" - close: "주제 닫기" - multi_select: "글 선택" - timed_update: "토픽 타이머 설정..." - pin: "주제 고정..." - unpin: "주제 고정 취소..." - unarchive: "주제 보관 취소" - archive: "주제 보관" + title: "작업" + recover: "글 삭제 취소" + delete: "글 삭제" + open: "글 열기" + close: "글 닫기" + multi_select: "게시물 선택…" + timed_update: "글 타이머 설정..." + pin: "글 고정..." + unpin: "글 고정 해제..." + unarchive: "글 보관 취소" + archive: "글 보관" invisible: "목록에서 제외하기" visible: "목록에 넣기" reset_read: "값 재설정" - make_public: "공개 토픽으로 만들기" + make_public: "공개 글로 만들기" make_private: "개인 메시지 작성" - reset_bump_date: "끌어올림 날자 리셋" + reset_bump_date: "끌어올림 날짜 초기화" feature: - pin: "주제 고정" - unpin: "주제 고정 취소" + pin: "글 고정" + unpin: "글 고정 해제" pin_globally: "전체 공지글로 설정하기" make_banner: "배너 주제" remove_banner: "배너 주제 제거" reply: - title: "답글쓰기" - help: "이 토픽 게시글 구성 시작하기" + title: "댓글쓰기" + help: "이 글에 대한 댓글을 작성합니다" clear_pin: title: "고정 취소" help: "더 이상 목록의 맨 위에 표시하지 않도록 이 주제의 고정 상태를 해제합니다." share: title: "공유하기" extended_title: "링크 공유" - help: "이 글에 대한 링크를 공유" + help: "이 글의 링크 공유" print: title: "프린트하기" help: "이 토픽을 인쇄하기 좋은 버전으로 보기" @@ -2244,7 +2245,7 @@ ko: error: "죄송합니다. 해당 사용자를 초대하는 도중 오류가 발생했습니다." not_allowed: "죄송합니다. 해당 사용자를 초대할 수 없습니다." group_name: "그룹명" - controls: "토픽 컨트롤" + controls: "글 관리" invite_reply: title: "초대하기" username_placeholder: "아이디" @@ -2252,7 +2253,7 @@ ko: help: "이메일을 통해 다른 사람을 이 주제에 초대합니다." to_forum: "친구에게 요약 이메일을 보내고 이 포럼에 가입할 수 있도록 링크를 전송합니다." sso_enabled: "이 주제에 초대하고 싶은 사람의 아이디를 입력하세요." - to_topic_blank: "이 주제에 초대하고 싶은 사람의 아이디나 이메일주소를 입력하세요." + to_topic_blank: "이 글에 초대 할 사람의 사용자명 또는 이메일 주소를 입력하십시오." to_topic_email: "이메일 주소를 입력하셨습니다. 친구들에게 이 주제에 답변 달기가 가능하도록 조치하는 초대장을 보내겠습니다." to_topic_username: "아이디를 입력하셨습니다. 이 주제에 초대하는 링크와 함께 알림을 보내겠습니다." to_username: "초대하려는 사용자의 아이디를 입력하세요. 이 주제에 초대하는 링크와 함께 알림을 보내겠습니다." @@ -2291,11 +2292,15 @@ ko: message_title: "새 메시지 제목" radio_label: "새로운 메시지" participants: "참여자" + instructions: + other: "새 메시지를 작성하고 선택한 %{count}개의 게시물로 채우려고 합니다." move_to_existing_message: title: "기존 메시지로 이동" action: "기존 메시지로 이동" radio_label: "기존 메시지" participants: "참여자" + instructions: + other: "이동하고자 하는 %{count}개의 게시물을 선택하십시오." merge_posts: title: "선택한 게시글 합치기" action: "선택한 게시글 합치기" @@ -2306,6 +2311,7 @@ ko: description: "주제가 페이지로 게시되면 해당 URL을 공유 할 수 있으며 사용자 정의 스타일과 함께 표시됩니다." slug: "강타" public: "공개" + public_description: "관련 글이 비공개인 경우에도 해당 페이지를 볼 수 있습니다." publish_url: "귀하의 페이지는 다음 위치에 게시되었습니다." topic_published: "귀하의 주제는 다음 위치에 게시되었습니다." preview_url: "귀하의 페이지는 다음 위치에 게시됩니다." @@ -2318,6 +2324,10 @@ ko: action: "작성자 바꾸기" error: "작성자를 바꾸는 중 에러가 발생하였습니다." placeholder: "새로운 작성자의 아이디" + instructions: + other: "%{count}개의 @%{old_user}님 게시물에 대한 새 소유자를 선택하십시오." + instructions_without_old_user: + other: "%{count}개의 게시물에 대한 새 소유자를 선택하십시오." change_timestamp: title: "타임스탬프 변경하기..." action: "타임스탬프 변경" @@ -2345,6 +2355,8 @@ ko: deselect_all: 전체 선택 해제 description: other: "%{count}개의 개시글을 선택하셨어요." + deleted_by_author: + other: "(작성자에 의해 취소된 글입니다. 글이 신고된 것이 아닌 한 %{count} 시간 뒤에 자동으로 삭제됩니다)" post: quote_reply: "인용하기" quote_share: "공유" @@ -2367,7 +2379,7 @@ ko: gap: other: "%{count}개의 숨겨진 답글 보기" notice: - new_user: "%{user}이 처음으로 게시되었습니다. 커뮤니티에 오신 것을 환영합니다!" + new_user: "%{user}님이 처음으로 작성한 글 입니다. 커뮤니티에 온 것을 환영해주세요!" returning_user: "우리가 %{user}을 본 지 오래되었습니다. 마지막 게시물은 %{time}입니다." unread: "읽지 않은 포스트" has_replies: @@ -2419,7 +2431,7 @@ ko: flag: "이 글을 신고하고 개인 알림을 받습니다" delete: "이 글을 삭제합니다." undelete: "이 글 삭제를 취소합니다." - share: "이 글에 대한 링크를 공유합니다." + share: "이 글의 링크 공유" more: "더" delete_replies: confirm: "이 글에 대한 댓글을 삭제 하시겠습니까?" @@ -2526,16 +2538,16 @@ ko: settings: "설정" topic_template: "주제 템플릿" tags: "태그" - tags_allowed_tags: "이 태그를이 카테고리로 제한하십시오." - tags_allowed_tag_groups: "이 태그 그룹을이 카테고리로 제한하십시오." + tags_allowed_tags: "이 태그를 현재 카테고리로 제한:" + tags_allowed_tag_groups: "이 태그 그룹을 현재 카테고리로 제한:" tags_placeholder: "(선택사항) 허용된 태그 목록" - tags_tab_description: "위에 지정된 태그 및 태그 그룹은이 범주 및 해당 범주를 지정하는 다른 범주에서만 사용할 수 있습니다. 다른 카테고리에서는 사용할 수 없습니다." + tags_tab_description: "위에 지정된 태그 및 태그 그룹은 현재 카테고리 및 태그가 지정된 카테고리에서만 사용할 수 있습니다. 그외 카테고리에서는 사용할 수 없습니다." tag_groups_placeholder: "(선택사항) 허용된 태그 그룹 목록" - manage_tag_groups_link: "여기에서 태그 그룹을 관리하십시오." + manage_tag_groups_link: "여기에서 태그 그룹을 관리합니다." allow_global_tags_label: "다른 태그도 허용" tag_group_selector_placeholder: "(선택 사항) 태그 그룹" - required_tag_group_description: "새 주제에 태그 그룹의 태그가 있어야합니다." - min_tags_from_required_group_label: "숫자 태그 :" + required_tag_group_description: "새 글은 태그 그룹의 태그를 포함:" + min_tags_from_required_group_label: "태그 수:" required_tag_group_label: "태그 그룹 :" topic_featured_link_allowed: "이 카테고리에 주요 링크 허용" delete: "카테고리 삭제" @@ -2576,7 +2588,7 @@ ko: show_subcategory_list: "하위 카테고리 목록을 토픽위에 표시하기." read_only_banner: "사용자가이 카테고리에서 글을 작성 할 수 없는 경우의 배너 텍스트 :" num_featured_topics: "이 카테고리 페이지에 표시되는 토픽의 수:" - subcategory_num_featured_topics: "부모 카테고리 페이지에 표시되는 주요 토픽의 수:" + subcategory_num_featured_topics: "상위 카테고리 페이지에 표시되는 주요 글 수:" all_topics_wiki: "기본적으로 새로운 토픽 위키 만들기" subcategory_list_style: "하위 카테고리 목록 스타일:" sort_order: "다음 기준으로 토픽 목록 정렬:" @@ -2595,7 +2607,7 @@ ko: position_disabled: "카테고리는 활동량에 따라서 표시됩니다. 목록 내의 카테고리 순서를 지정하하려면" position_disabled_click: '"카테고리 위치 고정" 설정을 활성화 시키십시요.' minimum_required_tags: "주제에 필요한 최소 태그 수 :" - parent: "부모 카테고리" + parent: "상위 카테고리" num_auto_bump_daily: "매일 자동으로 충돌하는 공개 주제 수 :" navigate_to_first_post_after_read: "주제를 읽은 후 첫 번째 게시물로 이동" notifications: @@ -2966,6 +2978,8 @@ ko: delete_confirm: other: "정말로 이 태그를 삭제하고 이 태그가 붙은 %{count} 개의 토픽에서 태그를 제거할까요?" delete_confirm_no_topics: "정말로 이 태그를 삭제할까요?" + delete_confirm_synonyms: + other: "%{count}개의 동의어도 삭제됩니다." rename_tag: "태그 이름변경" rename_instructions: "새로운 태그의 이름을 입력하세요:" sort_by: "정렬 기준:" @@ -3018,7 +3032,7 @@ ko: name_placeholder: "태그 그룹 이름" save: "저장" delete: "삭제" - confirm_delete: "정말로 이 태그 그룹을 삭제할까요?" + confirm_delete: "이 태그 그룹을 삭제 하시겠습니까?" everyone_can_use: "모든 사람이 태그를 사용할 수 있습니다" usable_only_by_staff: "태그는 모든 사람에게 표시되지만 직원 만 사용할 수 있습니다" visible_only_to_staff: "태그는 운영진 에게만 표시됩니다" @@ -3065,10 +3079,10 @@ ko: up_to_date: "최신상태입니다!" critical_available: "중요 업데이트를 사용할 수 있습니다." updates_available: "업데이트를 사용할 수 있습니다." - please_upgrade: "업그레이드하세요." + please_upgrade: "업그레이드하세요!" no_check_performed: "업데이트 확인이 수행되지 않았습니다. sidekiq가 실행 중인지 확인하십시오." stale_data: "최근 업데이트 확인이 수행되지 않았습니다. sidekiq가 실행 중인지 확인하십시오." - version_check_pending: "최근에 업데이트 되었군요! 환상적입니다!!" + version_check_pending: "최근에 업그레이드 한 것 같습니다. 환상적입니다!" installed_version: "설치됨" latest_version: "최신" problems_found: "현재 사이트 설정에 따른 몇 가지 조언" @@ -3084,7 +3098,7 @@ ko: mobile_title: "모바일" space_used: "사용 된 %{usedSize}" space_used_and_free: "%{usedSize} (%{freeSize} 무료)" - uploads: "업로드할 파일" + uploads: "업로드된 파일" backups: "백업" backup_count: other: "%{location}에 %{count}개의 백업" @@ -3117,8 +3131,8 @@ ko: last_7_days: "최근 7일" last_30_days: "최근 30일" all_time: "모든 시간" - 7_days_ago: "7일전" - 30_days_ago: "30일전" + 7_days_ago: "7일 전" + 30_days_ago: "30일 전" all: "전체" view_table: "테이블" view_graph: "그래프" @@ -3262,6 +3276,7 @@ ko: read_lists: 주요 글, 새 글, 최근 글 등과 같은 글 목록을 읽으십시오. RSS도 지원됩니다. wordpress: 워드프레스 wp-discourse 플러그인이 작동하는데 필요합니다. users: + bookmarks: 사용자 북마크를 나열합니다. ICS 형식을 사용하면 북마크 알림을 반환합니다. sync_sso: SSO를 사용하여 사용자를 동기화합니다. show: 사용자에 대한 정보를 얻습니다. check_emails: 사용자 이메일을 나열합니다. @@ -3521,7 +3536,7 @@ ko: updates_available_tooltip: "이 테마에 대한 업데이트가 있습니다" and_x_more: "그리고 %{count} 더." collapse: 축소 - uploads: "업로드할 파일" + uploads: "업로드된 파일" no_uploads: "폰트나 이미지같은 테마와 관련된 파일만 업로드 가능합니다" add_upload: "업로드할 파일 추가하기" upload_file_tip: "업로드할 파일 선택하기(png, woff2, 기타..)" @@ -3554,7 +3569,7 @@ ko: installed: "설치됨" install_popular: "인기" install_upload: "기기에서" - install_git_repo: "자식 저장소에서" + install_git_repo: "git 저장소에서" install_create: "새로 만들기" about_theme: "소개" license: "라이센스 정보" @@ -3602,6 +3617,8 @@ ko: title: "임베디드 버전 코멘트를 보내기 위한 사용자 정의 CSS를 입력하세요" color_definitions: text: "색상 정의" + title: "사용자 색상 정의 입력 (고급 사용자용)" + placeholder: "\\r\n이 스타일 시트를 사용하여 CSS 사용자 정의 속성 목록에 사용자 정의 색상을 추가합니다.\\r\n\\r\n예 : \\r\n\\r\n:root {\\r\n --mytheme-tertiary-or-quaternary: #{dark-light-choose($tertiary, $quaternary)};\\r\n}\\r\n\\r\n플러그인 또는 코어와의 충돌을 피하기 위해 속성 이름에 접두사를 붙이는 것이 좋습니다." head_tag: text: "" title: " 태그 전에 들어갈 HTML" @@ -4090,6 +4107,13 @@ ko: cancel: "취소" confirmation: title: "전송 및 삭제 @ %{username}" + description: | +

@%{username} 님의 모든 컨텐츠는 @%{targetUsername}님의 계정에 귀속됩니다. 컨텐츠 전송 후 @%{username} 계정은 삭제됩니다.

+ +

이것은 취소 할 수 없습니다!

+ +

계속하려면: %{text}

+ text: "@%{username} 에서 @%{targetUsername} 으로 옮기기" transfer_and_delete: "전송 및 삭제 @ %{username}" cancel: "취소" merging_user: "사용자 병합 중 ..." @@ -4176,6 +4200,7 @@ ko: external_name: "Name" external_email: "Email" external_avatar_url: "프로필 사진 URL" + last_payload: "마지막 페이로드" delete_sso_record: "SSO 레코드 삭제" confirm_delete: "이 단일 사인온 (SSO) 레코드를 삭제하시겠습니까?" user_fields: diff --git a/config/locales/client.lt.yml b/config/locales/client.lt.yml index 2320a6ed5f..1d5547ba55 100644 --- a/config/locales/client.lt.yml +++ b/config/locales/client.lt.yml @@ -1111,7 +1111,6 @@ lt: invited_by: "Jus pakvietė:" accept_invite: "Priimti kvietimą" name_label: "Vardas" - password_label: "Nustatykite slaptažodį" optional_description: "(pasoronktinai)" password_reset: continue: "Tėsti į %{site_name}" @@ -1411,7 +1410,6 @@ lt: new: "Jūs neturite naujų temų." read: "Jūs dar neperskaitėte jokių temų." posted: "Jūs dar nerašėte jokiose temose." - latest: "Nėra jokių paskutinių temų. Tai liūdina." bookmarks: "Jūs dar neturite pažymėtų temų." category: "Nėra jokių temų kategorijoje %{category}." top: "Nėra jokių TOP temų." @@ -1497,7 +1495,6 @@ lt: read_more_MF: "Šiuo metu { UNREAD, plural, =0 {} one { yra 1 neperskaityta } other { yra # neperskaitytos } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 nauja tema} other { {BOTH, select, true{and } false {are } other{}} # naujos temos} } remaining, or {CATEGORY, select, true {browse other topics in {catLink}} false {{latestLink}} other {}}" browse_all_categories: Žiūrėkite visas kategorijas view_latest_topics: peržiūrėkite paskutines temas - suggest_create_topic: Kodėl gi nesukūrus naujos temos? jump_reply_up: Pereiti į ankstesnį atsakymą jump_reply_down: Pereitį į sekantį atsakymą deleted: "Tema buvo ištrinta" diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml index 4a5c0d580c..49b4fae343 100644 --- a/config/locales/client.lv.yml +++ b/config/locales/client.lv.yml @@ -1062,7 +1062,6 @@ lv: accept_invite: "Pieņemt ielūgumu" success: "Jūsu konts ir izveidots un jūs esat ielogojies " name_label: "Vārds" - password_label: "Iestatīt paroli" optional_description: "(pēc izvēles)" password_reset: continue: "Turpināt %{site_name}" @@ -1254,8 +1253,6 @@ lv: noreplies: ir bez atbildēm single_user: ir rakstījis tikai viens lietotājs post: - count: - label: Minimālais ierakstu skaits time: label: Ierakstīts before: līdz @@ -1301,7 +1298,6 @@ lv: new: "Jums nav jaunu tēmu." read: "Jūs vēl neesat izlasījuši nevienu tēmu." posted: "Jūs vel neesat rakstījuši nevienā tēmā." - latest: "Nav jaunu tēmu. Neraža." bookmarks: "Jūs vēl neesat pievienojuši grāmatzīmēm nevienu tēmu." category: "Sadaļā %{category} nav tēmu." top: "Nav svarīgu tēmu." @@ -1382,7 +1378,6 @@ lv: read_more_MF: "{ UNREAD, plural, =0 {} one { Palikusi 1 nelasīta } other { Palikušas # nelasītas } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 jauna tēma} other { {BOTH, select, true{and } false {are } other{}} # jaunas tēmas} } remaining, or {CATEGORY, select, true {pārlūkot citas tēmas iekš {catLink}} false {{latestLink}} other {}}" browse_all_categories: Pārlūkot visas sadaļas view_latest_topics: Skatīt jaunākās tēmas - suggest_create_topic: Kādēļ gan neizveidot jaunu tēmu? jump_reply_up: pāriet uz agrāku atbildi jump_reply_down: pāriet uz vēlāku atbildi deleted: "Tēma ir dzēsta " diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index e2a8c55e47..4a3b62d88d 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -1209,7 +1209,6 @@ nb_NO: accept_invite: "Godta invitasjon" success: "Kontoen din har blitt opprettet og du er nå logget inn." name_label: "Navn" - password_label: "Sett passord" optional_description: "(valgfritt)" password_reset: continue: "Fortsett til %{site_name}" @@ -1507,8 +1506,6 @@ nb_NO: noreplies: har ingen svar single_user: inneholder bare én bruker post: - count: - label: Minimum antall innlegg time: label: Skrevet before: før @@ -1553,7 +1550,6 @@ nb_NO: new: "Du har ingen nye emner å lese." read: "Du har ikke lest noen emner enda." posted: "Du har ikke skrevet innlegg i noen emner enda." - latest: "Det finnes ingen siste emner. Det er sørgelig." bookmarks: "Du har ingen bokmerkede emner enda." category: "Det finnes ingen %{category}-emner." top: "Det finnes ingen populære emner." @@ -1629,7 +1625,6 @@ nb_NO: read_more_MF: "Det { UNREAD, plural, =0 {} one { er 1 ulest } other { finnes # uleste } } { NEW, plural, =0 {} one { {BOTH, select, true{og } false {finnes } other{}} 1 nytt emne} other { {BOTH, select, true{og } false {er } other{}} # nye emner} } igjen, eller {CATEGORY, select, true {se andre emner i {catLink}} false {{latestLink}} other {}}" browse_all_categories: Se alle kategorier view_latest_topics: se siste emner - suggest_create_topic: Kanskje du kan lage et nytt emne? jump_reply_up: hopp til tidligere svar jump_reply_down: hopp til senere svar deleted: "Emnet ble slettet" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 3afb7ef158..d49ed8cd5c 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -122,6 +122,7 @@ nl: twitter: "Delen op Twitter" facebook: "Delen op Facebook" email: "Verzenden via e-mail" + url: "URL kopiëren en delen" action_codes: public_topic: "heeft dit topic openbaar gemaakt op %{when}" private_topic: "heeft dit topic een privébericht gemaakt op %{when}" @@ -1074,6 +1075,7 @@ nl: taken: "Sorry, dat e-mailadres is niet beschikbaar." error: "Er is een fout opgetreden bij het wijzigen van uw e-mailadres. Misschien is dat adres al in gebruik?" success: "We hebben een e-mail naar dat adres gestuurd. Volg de instructies voor bevestiging." + success_via_admin: "We hebben een e-mail naar dat adres gestuurd. Volg de instructies voor bevestiging in de e-mail." success_staff: "Er is een e-mail naar uw huidige adres verzonden. Volg de bevestigingsinstructies." change_avatar: title: "Uw profielafbeelding wijzigen" @@ -1111,6 +1113,7 @@ nl: sso_override_instructions: "E-mailadres kan vanaf SSO-provider worden bijgewerkt." no_secondary: "Geen extra e-mailadressen" instructions: "Nooit openbaar zichtbaar." + admin_note: "Opmerking: een beheerder die het e-mailadres van een andere niet-beheerder wijzigt, geeft aan dat de gebruiker geen toegang meer heeft tot zijn of haar e-mailaccount, dus er wordt een e-mail voor opnieuw instellen van het wachtwoord naar zijn of haar nieuwe adres gestuurd. Het e-mailadres van de gebruiker wordt pas gewijzigd nadat hij of zij het proces voor opnieuw instellen van het wachtwoord heeft voltooid." ok: "We sturen een e-mail ter bevestiging" required: "Voer een e-mailadres in" invalid: "Voer een geldig e-mailadres in" @@ -1584,7 +1587,7 @@ nl: accept_invite: "Uitnodiging accepteren" success: "Uw account is gemaakt en u bent nu aangemeld." name_label: "Naam" - password_label: "Wachtwoord instellen" + password_label: "Wachtwoord" optional_description: "(optioneel)" password_reset: continue: "Doorgaan naar %{site_name}" @@ -1603,29 +1606,6 @@ nl: categories_and_top_topics: "Categorieën en toptopics" categories_boxes: "Vakken met subcategorieën" categories_boxes_with_topics: "Vakken met aanbevolen topics" - base_font_setting: - helvetica: "Helvetica/Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "RobotoCondensed" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "Ubuntu" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" shortcut_modifier_key: shift: "Shift" ctrl: "Ctrl" @@ -1787,6 +1767,7 @@ nl: draft: Concept edit: Bewerken reply_to_post: + label: Antwoorden op een bericht van %{postUsername} desc: Antwoorden op een bepaald bericht reply_as_new_topic: label: Antwoorden als gekoppeld topic @@ -1812,6 +1793,8 @@ nl: toggle_topic_bump: label: "Topicbump in-/uitschakelen" desc: "Antwoorden zonder datum van laatste antwoord te wijzigen" + reload: "Opnieuw laden" + ignore: "Negeren" notifications: tooltip: regular: @@ -1984,11 +1967,21 @@ nl: single_user: maar één gebruiker bevatten post: count: - label: Minimaal berichtaantal + label: Berichten + min: + placeholder: minimum + max: + placeholder: maximum time: label: Geplaatst before: voor after: na + views: + label: Weergaven + min_views: + placeholder: minimaal + max_views: + placeholder: maximaal hamburger_menu: "naar een andere topiclijst of categorie" new_item: "nieuw" go_back: "terug" @@ -2025,12 +2018,14 @@ nl: choose_new_tags: "Kies nieuwe tags voor deze topics:" choose_append_tags: "Kies nieuwe tags om voor deze topics toe te voegen:" changed_tags: "De tags van deze topics zijn gewijzigd." + remove_tags: "Tags verwijderen" none: unread: "U hebt geen ongelezen topics." new: "U hebt geen nieuwe topics." read: "U hebt nog geen topics gelezen." posted: "U hebt nog niet in een topic gereageerd." - latest: "Er zijn geen nieuwste topics. Dat is jammer." + ready_to_create: "Klaar om " + latest: "U bent helemaal bij!" bookmarks: "U hebt nog geen bladwijzers voor topics gemaakt." category: "Er zijn geen topics in %{category}." top: "Er zijn geen toptopics." @@ -2118,7 +2113,7 @@ nl: browse_all_categories: Alle categorieën bekijken browse_all_tags: Alle tags bekijken view_latest_topics: nieuwste topics bekijken - suggest_create_topic: Waarom maakt u geen topic aan? + suggest_create_topic: een nieuwe conversatie te starten? jump_reply_up: naar eerder antwoord springen jump_reply_down: naar later antwoord springen deleted: "Het topic is verwijderd" diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index 1efd50eb36..f81acc5e18 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -912,6 +912,8 @@ pl_PL: theme_default_on_all_devices: "Ustaw to jako domyślny motyw na wszystkich urządzeniach" color_schemes: default_description: "Domyślny" + dark: "Tryb ciemny" + default_dark_scheme: "(domyślny na stronie)" text_size_default_on_all_devices: "Ustaw ten domyślny rozmiar tekstu na wszystkich urządzeniach" allow_private_messages: "Pozwól innym użytkownikom wysyłać do mnie prywatne wiadomości" external_links_in_new_tab: "Otwieraj wszystkie zewnętrzne odnośniki w nowej karcie" @@ -957,6 +959,8 @@ pl_PL: muted_categories: "Wyciszone" muted_categories_instructions: "Nie będziesz powiadamiany o nowych tematach w tych kategoriach. Nie pojawią się na liście nieprzeczytanych." muted_categories_instructions_dont_hide: "Nie otrzymasz powiadomień o nowych tematach w tych kategoriach. " + regular_categories: "Stały bywalec" + regular_categories_instructions: "Te kategorie będą widoczne na listach tematów „Najnowsze” i „Najpopularniejsze”." no_category_access: "Jako moderator masz limitowany dostęp do kategorii, możliwość zapisu jest wyłączona." delete_account: "Usuń moje konto" delete_account_confirm: "Czy na pewno chcesz usunąć swoje konto? To nieodwracalne!" @@ -1012,6 +1016,7 @@ pl_PL: success: "(email wysłany)" in_progress: "(email wysyłany)" error: "(błąd)" + emoji: "zablokuj emoji" action: "Wyślij wiadomość email resetującą hasło" set_password: "Ustaw hasło" choose_new: "Wyberz nowe hasło" @@ -1035,6 +1040,7 @@ pl_PL: second_factor: title: "Dwuskładnikowe uwierzytelnianie" enable: "Zarządzaj autentykacją dwuetapową" + disable_all: "Wyłącz wszystkie" forgot_password: "Zapomniałeś hasła?" confirm_password_description: "Potwierdź swoje hasło, aby kontynuować" name: "Imię" @@ -1056,11 +1062,13 @@ pl_PL: edit_description: "Nazwa drugiego czynnika" totp: title: "Uwierzytelnianie oparte na tokenach" + add: "Dodaj Authenticator" default_name: "Mój Authenticator" name_and_code_required_error: "Musisz podać nazwę i kod z aplikacji uwierzytelniającej." security_key: register: "Zarejestruj" title: "Klucze bezpieczeństwa" + add: "Dodaj klucz bezpieczeństwa" default_name: "Główny klucz bezpieczeństwa" not_allowed_error: "Upłynął limit czasu procesu rejestracji klucza bezpieczeństwa lub został on anulowany." already_added_error: "Ten klucz bezpieczeństwa został już zarejestrowany. Nie musisz go ponownie rejestrować." @@ -1077,6 +1085,7 @@ pl_PL: taken: "Przykro nam, ale ta nazwa jest zajęta." invalid: "Ta nazwa jest niepoprawna. Powinna zawierać jedynie liczby i litery." add_email: + title: "Dodaj e-mail" add: "dodaj" change_email: title: "Zmień adres email" @@ -1110,14 +1119,17 @@ pl_PL: secondary: "Drugorzędne adresy email" primary_label: "podstawowy" unconfirmed_label: "niepotwierdzony" + resend_label: "Wyślij ponownie email aktywacyjny" resending_label: "wysyłanie..." resent_label: "email wysłany" update_email: "Zmień adres email" destroy: "Usuń e-mail" + add_email: "Dodaj alternatywny adres email" sso_override_instructions: "E-mail można zaktualizować od dostawcy SSO." no_secondary: "Brak drugorzędnych adresów email" instructions: "Nie będzie publicznie widoczny." ok: "Otrzymasz potwierdzenie emailem" + required: "Podaj adres email" invalid: "Podaj poprawny adres email" authenticated: "Twój email został potwierdzony przez %{provider}" frequency_immediately: "Wyślemy powiadomienie jeśli wskazana rzecz nie została jeszcze przez Ciebie przeczytana." @@ -1154,6 +1166,7 @@ pl_PL: too_long: "Nazwa użytkownika jest zbyt długa" checking: "Sprawdzanie, czy nazwa jest dostępna…" prefilled: "Email zgadza się z zarejestrowaną nazwą użytkownika" + required: "Wpisz nazwę użytkownika" locale: title: "Język interfejsu" instructions: "Język interfejsu użytkownika. Zmieni się, gdy odświeżysz stronę." @@ -1190,6 +1203,7 @@ pl_PL: enable_physical_keyboard: "Włącz obsługę klawiatury fizycznej na iPadzie" text_size: title: "Rozmiar tekstu" + smallest: "Najmniejszy" smaller: "Mniejszy" normal: "Normalny" larger: "Większy" @@ -1294,11 +1308,13 @@ pl_PL: link_generated: "Link z zaproszenie został poprawnie wygenerowany!" valid_for: "Link do zaproszenia jest ważny tylko dla tego adresu: %{email}" single_user: "Pojedynczy użytkownik" + multiple_user: "Wielu użytkowników" invite_link: title: "Odnośnik z zaproszeniem" success: "Link z zaproszenie został poprawnie wygenerowany!" bulk_invite: none: "Jeszcze nikogo nie zaprosiłeś. Możesz wysłać pojedyncze zaproszenia lub zaprosić wiele osób na raz poprzez wysłanie pliku CSV." + text: "Zaproszenie zbiorcze" success: "Plik został przesłany pomyślnie: otrzymasz prywatną wiadomość, gdy proces zostanie zakończony." error: "Przykro nam, ale wymagany format pliku to CSV." confirmation_message: "Za chwilę wyślesz zaproszenia do wszystkich w przesłanym pliku." @@ -1594,7 +1610,6 @@ pl_PL: accept_invite: "Akceptuj zaproszenie" success: "Twje konto zostało utworzone i zostałeś zalogowany." name_label: "Nazwa" - password_label: "Ustaw hasło" optional_description: "(opcjonalne)" password_reset: continue: "Przejdź do %{site_name}" @@ -1974,8 +1989,6 @@ pl_PL: noreplies: ma zero odpowiedzi single_user: zawierają użytkownika post: - count: - label: Minimalna liczba wpisów time: label: Opublikowano before: przed @@ -2023,7 +2036,6 @@ pl_PL: new: "Nie masz nowych tematów." read: "You haven't read any topics yet." posted: "Jeszcze nie zamieściłeś wpisu w żadnym z tematów." - latest: "Nie ma najnowszych tematów. Smutne." bookmarks: "Nie posiadasz tematów dodanych do zakładek." category: "Nie ma tematów w kategorii %{category}." top: "Brak najlepszych tematów." @@ -2123,7 +2135,6 @@ pl_PL: bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT} {LAST_POST}: {BUMPED_AT}" browse_all_categories: Przeglądaj wszystkie kategorie view_latest_topics: pokaż aktualne tematy - suggest_create_topic: Może rozpoczniesz temat? jump_reply_up: przeskocz do wcześniejszej odpowiedzi jump_reply_down: przeskocz do późniejszej odpowiedzi deleted: "Temat został usunięty" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 52b8d272e3..7132a9f98b 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -1213,7 +1213,6 @@ pt: accept_invite: "Aceitar Convite" success: "A sua conta foi criada e está agora com a sessão iniciada." name_label: "Nome" - password_label: "Definir Palavra-passe" optional_description: "(opcional)" password_reset: continue: "Continuar para %{site_name}" @@ -1532,8 +1531,6 @@ pt: noreplies: têm zero respostas single_user: contêm um único utilizador post: - count: - label: Número Mínimo de Publicações time: label: Publicada before: antes @@ -1578,7 +1575,6 @@ pt: new: "Não tem novos tópicos." read: "Ainda não leu nenhum tópico." posted: "Ainda não publicou em nenhum tópico." - latest: "Não há tópicos recentes." bookmarks: "Ainda não marcou nenhum tópico." category: "Não há tópicos na categoria %{category}." top: "Não existem tópicos recentes." @@ -1654,7 +1650,6 @@ pt: read_more_MF: "{ UNREAD, plural, =0 {} one { Existe 1 não lido } other { Existem # não lidos } } { NEW, plural, =0 {} one { {BOTH, select, true{e } false {existe } other{}} 1 novo tópico} other { {BOTH, select, true{e } false {existem } other{}} # novos tópicos} } restantes, ou {CATEGORY, select, true {pesquise outros tópicos em {catLink}} false {{latestLink}} other {}}" browse_all_categories: Pesquisar em todas as categorias view_latest_topics: ver os tópicos mais recentes - suggest_create_topic: Porque não começar um tópico? jump_reply_up: avançar para resposta mais recente jump_reply_down: avançar para resposta mais antiga deleted: "Este tópico foi eliminado" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index ebb61e1c6b..2fb8eee7f7 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -119,6 +119,10 @@ pt_BR: topic_html: 'Tópico: %{topicTitle}' post: "postagem #%{postNumber}" close: "fechar" + twitter: "Compartilhar no Twitter" + facebook: "Compartilhar no Facebook" + email: "Enviar por e-mail" + url: "Copiar e compartilhar a URL" action_codes: public_topic: "tornou este tópico público em %{when}" private_topic: "tornou este tópico em uma mensagem pessoal %{when}" @@ -260,7 +264,9 @@ pt_BR: unbookmark: "Clique para remover todos os favoritos neste tópico" unbookmark_with_reminder: "Clique para remover todos os favoritos e lembretes deste tópico. Você tem um lembrete definido %{reminder_at} para este tópico." bookmarks: + created: "Você marcou esta mensagem como favorita. %{name}" not_bookmarked: "marcar postagem como favorita" + created_with_reminder: "Você marcou esta publicação com um lembrete %{date}. %{name}" remove: "Remover Favorito" delete: "Excluir Favorito" confirm_delete: "Tem certeza de que deseja excluir este favorito? O lembrete também será excluído." @@ -269,6 +275,13 @@ pt_BR: no_timezone: 'Você ainda não definiu um fuso horário. VOcê não poderá definir lembretes. Configure um no seu perfil.' invalid_custom_datetime: "A data e a hora que você forneceu são inválidas, por favor tente novamente." list_permission_denied: "Você não tem permissão para visualizar os favoritos deste usuário." + no_user_bookmarks: "Você não possui mensagens marcadas como favoritas; os favoritos permitem que você acesse, rapidamente, mensagens específicas." + auto_delete_preference: + label: "Excluir automaticamente" + never: "Nunca" + when_reminder_sent: "Assim que o lembrete for enviado" + on_owner_reply: "Após eu responder a este tópico" + search_placeholder: "Pesquisar os favoritos por nome, título do tópico ou conteúdo da mensagem" search: "Pesquisar" reminders: later_today: "Mais tarde hoje" @@ -276,6 +289,7 @@ pt_BR: tomorrow: "Amanhã" next_week: "Próxima semana" later_this_week: "Mais tarde nesta semana" + start_of_next_business_week: "Segunda-feira" start_of_next_business_week_alt: "Próxima segunda-feira" next_month: "Próximo mês" custom: "Data e hora personalizadas" @@ -284,6 +298,7 @@ pt_BR: today_with_time: "hoje às %{time}" tomorrow_with_time: "amanhã às %{time}" at_time: "em %{date_time}" + existing_reminder: "Você possui um lembrete definido para esta mensagem que será enviado %{at_date_time}" copy_codeblock: copied: "copiado!" drafts: @@ -507,6 +522,7 @@ pt_BR: sent_by_user: "Enviado por %{user}" sent_by_you: "Enviado por você" directory: + username: "Nome de usuário" filter_name: "filtrar por nome de usuário" title: "Usuários" likes_given: "Dados" @@ -537,6 +553,12 @@ pt_BR: groups: member_added: "Adicionado" member_requested: "Solicitado em" + add_members: + title: "Adicionar membros a %{group_name}" + description: "Você também pode colocar aqui uma lista de usuários separados por vírgulas." + usernames: "Digite nomes de usuários ou endereços de email" + input_placeholder: "Nomes de usuário ou endereços de email" + notify_users: "Notificar usuários" requests: title: "Solicitações" reason: "Motivo" @@ -560,12 +582,32 @@ pt_BR: notification: Notificação email: title: "E-mail" + status: "Sincronizado %{old_emails} / %{total_emails} dos emails via IMAP." credentials: + title: "Credenciais" + smtp_server: "Servidor SMTP" + smtp_port: "Porta SMTP" + smtp_ssl: "Utilizar SSL para SMTP" + imap_server: "IMAP Server" + imap_port: "Porta IMAP" + imap_ssl: "Usar SSL para IMAP" username: "Nome de Usuário" password: "Senha" + mailboxes: + synchronized: "Caixa de Correio Sincronizada" + none_found: "Nenhuma caixa de correio foi encontrada nesta conta de e-mail." + disabled: "desativado" membership: title: Associação access: Acesso + categories: + title: Categorias + long_title: "Notificações padrão da categoria" + description: "Quando usuários são adicionados a esse grupo, suas configurações de notificação de categoria serão definidas com esses padrões. Depois, eles poderão alterá-las." + watched_categories_instructions: "Você será inscrito automaticamente em todos os tópicos destas categorias. Você será notificado de todas as novas postagens e tópicos, e uma contagem de novas postagens também aparecerá ao lado do tópico." + tracked_categories_instructions: "Você será inscrito automaticamente em todos os tópicos nestas categorias. Uma contagem de postagens novas aparecerá ao lado do tópico." + watching_first_post_categories_instructions: "Usuários serão notificados sobre a primeira mensagem postada em cada tópico novo destas categorias." + regular_categories_instructions: "Se estas categorias forem silenciadas, elas apenas não serão silenciadas para membros do grupo. Os usuários serão notificados se forem mencionados ou se alguém responder a eles." logs: title: "Registros" when: "Quando" @@ -1493,7 +1535,6 @@ pt_BR: accept_invite: "Aceitar Convite" success: "Sua conta foi criada e você já está logado." name_label: "Nome" - password_label: "Definir Senha" optional_description: "(opcional)" password_reset: continue: "Continuar para %{site_name}" @@ -1856,8 +1897,6 @@ pt_BR: noreplies: não possuem respostas single_user: contém um único usuário post: - count: - label: Contagem de Postagem Mínima time: label: Postado before: antes @@ -1903,7 +1942,6 @@ pt_BR: new: "Você não tem novos tópicos." read: "Você ainda não leu nenhum tópico." posted: "Você ainda não postou em nenhum tópico." - latest: "Não há últimos tópicos. Isto é triste." bookmarks: "Você ainda não tem nenhum tópico favorito." category: "Não há tópicos na categoria %{category}." top: "Não há melhores tópicos." @@ -1989,7 +2027,6 @@ pt_BR: bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}" browse_all_categories: Ver todas as categorias view_latest_topics: ver últimos tópicos - suggest_create_topic: Por que não criar um tópico? jump_reply_up: pular para a primeira resposta jump_reply_down: pular para a última resposta deleted: "Este tópico foi excluído" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index 9b90750cdb..f97a4d15a3 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -27,7 +27,10 @@ ro: millions: "%{number}M" dates: time: "HH:mm" + time_with_zone: "HH:mm (z)" + time_short_day: "ddd, HH:mm" timeline_date: "MMM YYYY" + long_no_year: "D MMM, HH:mm" long_no_year_no_time: "DD MMM" full_no_year_no_time: "Do MMMM " long_with_year: "DD MMM YYYY HH:mm" @@ -133,6 +136,7 @@ ro: twitter: "Distribuie pe Twitter" facebook: "Distribuie pe Facebook" email: "Trimite prin e-mail" + url: "Copiați și partajați URL-ul" action_codes: public_topic: "a făcut acest subiect public %{when}" private_topic: "fă acest subiect un mesaj personal %{when}" @@ -142,6 +146,7 @@ ro: user_left: "%{who} s-a îndepărtat de la acest mesaj %{when}" removed_user: "a scos pe %{who} %{when}" removed_group: "scos pe %{who} %{when}" + autobumped: "impuls automat %{when}" autoclosed: enabled: "închis %{when}" disabled: "deschis %{when}" @@ -171,6 +176,7 @@ ro: bootstrap_mode_disabled: "Modul bootstrap va fi dezactivat în următoarele 24 de ore" themes: default_description: "Implicit" + broken_theme_alert: "Este posibil ca site-ul dvs. să nu funcționeze, deoarece tema / componenta %{theme} are erori. Dezactivați-l la %{path}." s3: regions: ap_northeast_1: "Asia Pacific (Tokyo)" @@ -272,16 +278,24 @@ ro: help: bookmark: "Click pentru a adăuga semn de carte la subiectul acesta" unbookmark: "Click pentru a șterge semnele de carte" + unbookmark_with_reminder: "Faceți clic pentru a elimina toate marcajele și mementourile din acest subiect. Aveți un set de memento %{reminder_at} pentru acest subiect." bookmarks: created: "Ai marcat acestă postare %{name} ca semn de carte" not_bookmarked: "marchează ca semn de carte acest post" + created_with_reminder: "Ai marcat această postare cu un memento %{date}. %{name}" remove: "Șterge semn de carte" delete: "Șterge semn de carte" confirm_delete: "Sunteţi sigur că doriţi să ştergeţi acest semn de carte? Mementoul va fi de asemenea şters." + confirm_clear: "Sigur doriți să ștergeți toate marcajele din acest subiect?" save: "Salvează" + no_timezone: 'Nu ați setat încă un fus orar. Nu veți putea seta mementouri. Configurați în profilul dvs..' + invalid_custom_datetime: "Data și ora pe care le-ai furnizat sunt invalide, te rugăm să încerci din nou." + list_permission_denied: "Nu ai permisiuni de acces la link-urile preferate ale acestui utilizator" + no_user_bookmarks: "Nu ai link-uri de postări salvate; link-urile salvate îți permit revenirea la postări specifice" auto_delete_preference: label: "Ștergere automată" never: "Niciodată" + when_reminder_sent: "Imediat ce memento-ul este trimis" on_owner_reply: "După ce răspund la acest subiect" search_placeholder: "Căutați marcaje după nume, titlul subiectului sau conținutul postării" search: "Caută" @@ -300,6 +314,7 @@ ro: today_with_time: "astăzi la %{time}" tomorrow_with_time: "mâine la %{time}" at_time: "la %{date_time}" + existing_reminder: "Ai un memento setat pentru acest link salvat care va fi trimis %{at_date_time}" copy_codeblock: copied: "copiat!" drafts: @@ -309,6 +324,7 @@ ro: new_private_message: "Ciornă mesaj privat nou" topic_reply: "Răspuns ciornă" abandon: + confirm: "Ai o altă ciornă pentru acest subiect. Sigur o abandonezi?" yes_value: "Da, abandonează." no_value: "Nu, păstrează." topic_count_latest: @@ -330,6 +346,8 @@ ro: saved: "Salvat!" upload: "Încarcă" uploading: "Se încarcă..." + uploading_filename: "Se încarcă: %{filename}..." + clipboard: "memoria temporară" uploaded: "Încărcat!" pasting: "Lipire..." enable: "Activează" @@ -343,25 +361,90 @@ ro: banner: close: "Ignoră acest banner." edit: "Editează acest banner." + pwa: + install_banner: "Vrei să instalezi %{title} pe acest dispozitiv?" choose_topic: none_found: "Nu au fost găsite subiecte noi." + title: + search: "Caută o discuție" + placeholder: "scrie titlul discuției, URL-ul sau ID-ul aici" choose_message: + none_found: "Nu sunt mesaje." title: search: "Căutați un mesaj" + placeholder: "scrie titlul mesajului, URL-ul sau id-ul aici" review: + order_by: "Sortează după" + in_reply_to: "ca răspuns pentru" explain: + why: "explică de ce acest element a ajuns în coada de așteptare" + title: "Scor ce poate fi revizuit" + formula: "Formulă" + subtotal: "Subtotal" total: "Total" + min_score_visibility: "Scorul minim pentru vizibilitate" + score_to_hide: "Scor pentru a ascunde postarea" + take_action_bonus: + name: "a luat măsuri" + title: "Când un membru al personalului alege să utilizeze steagul va primi un bonus." + user_accuracy_bonus: + name: "precizia utilizatorului" + trust_level_bonus: + name: "nivel de încredere" + title: "Elementele care pot fi revizuite create de utilizatorii cu nivel mai înalt de încredere au un scor mai mare." + claim_help: + optional: "Puteți revendica acest element pentru a împiedica alte persoane să-l revizuiască." + required: "Trebuie să revendici elementele înainte de a le putea revizui." + claimed_by_you: "Ai revendicat acest articol și îl poți revizui." + claimed_by_other: "Acest element poate fi revizuit numai de %{username}." + claim: + title: "revendică acest subiect" + unclaim: + help: "elimină această revendicare" + awaiting_approval: "În așteptarea aprobării" delete: "Șterge" settings: + saved: "Salvat" save_changes: "Salvează schimbările" title: "Setări" + priorities: + title: "Priorități care pot fi revizuite" moderation_history: "Istoric de moderare" + view_all: "Vezi tot" + grouped_by_topic: "Grupate după subiect" + none: "Nu există elemente de revizuit." + view_pending: "vezi ce este în aşteptare" + topic_has_pending: + one: "Acest subiect are o %{count} postare în așteptarea aprobării" + few: "Acest subiect are %{count} postări în așteptarea aprobării" + other: "Acest subiect are %{count} postări în așteptarea aprobării" + title: "Revizuire" topic: "Subiect:" + filtered_topic: "Ai ales conținutul care poate fi revizuit dintr-un singur subiect." filtered_user: "Utilizatori" + show_all_topics: "arată toate subiectele" + deleted_post: "(postare ștearsă)" + deleted_user: "(utilizator șters)" user: + bio: "Bio" + website: "Website" username: "Nume utilizator" email: "Email" name: "Nume" + fields: "Câmpuri" + user_percentage: + agreed: + one: "%{count}% este de acord" + few: "%{count}% sunt de acord" + other: "%{count}% sunt de acord" + disagreed: + one: "%{count}% nu este de acord" + few: "%{count}% nu sunt de acord" + other: "%{count}% nu sunt de acord" + ignored: + one: "%{count}% ignoră" + few: "%{count}% ignoră" + other: "%{count}% ignoră" topics: topic: "Discuție" details: "detalii" @@ -372,21 +455,62 @@ ro: all_categories: "(toate categoriile)" type: title: "Tip" + minimum_score: "Scor minim:" refresh: "Reîmprospătează" + status: "Status" category: "Categorie" + orders: + score: "Scor" + score_asc: "Scor (invers)" + created_at: "Creat la" + created_at_asc: "Creat la (invers)" + priority: + title: "Prioritate minimă" + low: "(oricare)" + medium: "Medie" + high: "Ridicată" + conversation: + view_full: "vezi conversația completă" scores: + about: "Acest scor este calculat pe baza nivelului de încredere al reclamantului, a acurateței notificărilor anterioare și a priorității elementului raportat." + score: "Scor" + date: "Dată" type: "Tip" + status: "Status" + submitted_by: "Trimis de" + reviewed_by: "Evaluat de" statuses: pending: title: "În așteptare" + approved: + title: "Aprobat" rejected: title: "Respinse" + ignored: + title: "Ignorat" + deleted: + title: "Șters" + reviewed: + title: "(toate revizuite)" + all: + title: "(totul)" types: + reviewable_flagged_post: + title: "Postare semnalată" + flagged_by: "Semnalat de" + reviewable_queued_topic: + title: "Subiect în așteptare" + reviewable_queued_post: + title: "Postare în așteptare" reviewable_user: title: "Utilizatori" approval: title: "Necesită aprobare" description: "Am primit noua postare dar trebuie să fie aprobată de un moderator înainte că ea să apară pe site. Te rugăm să ai răbdare." + pending_posts: + one: "Ai %{count} postare în așteptare." + few: "Ai %{count} postări în așteptare." + other: "Ai %{count} postări în așteptare." ok: "Ok" user_action: user_posted_topic: "%{user} a postat discuția" @@ -403,6 +527,7 @@ ro: sent_by_user: "Trimis de %{user}" sent_by_you: "Trimis de tine" directory: + username: "Nume de utilizator" filter_name: "Filtrează după nume utilizator" title: "Utilizatori" likes_given: "Oferite" @@ -419,6 +544,7 @@ ro: days_visited_long: "Zile de vizită" posts_read: "Citite" posts_read_long: "Postări citite" + last_updated: "Ultima actualizare:" total_rows: one: "%{count} utilizator" few: "%{count} utilizatori" @@ -431,9 +557,23 @@ ro: make_user_group_owner: "Fă proprietar" remove_user_as_group_owner: "Revocă proprietar" groups: + member_added: "Adăugat" + member_requested: "Solicitat la" + add_members: + title: "Adaugă membri la %{group_name}" + description: "De asemenea, poți insera o listă separată prin virgulă." + usernames: "Introdu nume de utilizator sau adrese de e-mail" + input_placeholder: "Nume de utilizator sau e-mailuri" + notify_users: "Notifică utilizatorii" requests: + title: "Cereri" reason: "Motiv" + accept: "Acceptă" accepted: "acceptat" + deny: "Respinge" + denied: "respinse" + undone: "cerere retrasă" + handle: "gestionează cererea de înscriere" manage: title: "Gestionează" name: "Nume" @@ -448,12 +588,34 @@ ro: notification: Notificare email: title: "Email" + status: "S-au sincronizat prin IMAP %{old_emails} e-mailuri din %{total_emails} în total." credentials: + title: "Date de conectare" + smtp_server: "Server SMTP" + smtp_port: "Port SMTP" + smtp_ssl: "Folosește SSL pentru SMTP" + imap_server: "Server IMAP" + imap_port: "Port IMAP" + imap_ssl: "Folosește SSL pentru IMAP" username: "Nume utilizator" password: "Parolă" + mailboxes: + synchronized: "Cutie poștală sincronizată" + none_found: "Nu au fost găsite cutii poștale în acest cont de e-mail." + disabled: "dezactivat" membership: title: Abonare access: Acces + categories: + title: Categorii + long_title: "Notificări implicite pentru categorii" + description: "Când utilizatorii sunt adăugați la acest grup, setările lor de notificare ale categoriei vor fi salvate cu aceste valori implicite. Ulterior, aceștia le pot schimba." + watched_categories_instructions: "Urmărește automat toate subiectele din aceste categorii. Membrii grupului vor fi notificați cu privire la toate postările și subiectele noi, iar numărul de postări noi va apărea, de asemenea, lângă subiect." + tracked_categories_instructions: "Urmărește automat toate subiectele din aceste categorii. Numărul de mesaje noi va apărea lângă subiect." + watching_first_post_categories_instructions: "Utilizatorii vor fi notificați cu privire la prima postare în fiecare subiect nou din aceste categorii." + regular_categories_instructions: "Dacă aceste categorii sunt dezactivate, vor fi deblocate pentru membrii grupului. Utilizatorii vor fi notificați dacă sunt menționați sau cineva le răspunde." + tags: + title: Etichete logs: title: "Jurnale" when: "Când" @@ -464,11 +626,16 @@ ro: details: "Detalii" from: "De la" to: "Către" + permissions: + title: "Permisiuni" + none: "Nu există categorii asociate acestui grup." + description: "Membrii acestui grup pot accesa aceste categorii" public_admission: "Permite utilizatorilor să adere la grup fără restricții (Presupune ca grupul să fie vizibil)" public_exit: "Permite utilizatorilor să părăsească grupul în mod liber." empty: posts: "Nu există postări ale membrilor acestui grup." members: "Nu există nici un membru în acest grup." + requests: "Nu există cereri de înscriere în acest grup." mentions: "Nu există nicio menționare în acest grup." messages: "Nu există nici un mesaj în acest grup." topics: "Nu există nici un subiect creat de vreun membru al acestui grup." @@ -478,6 +645,8 @@ ro: leave: "Părăsește" request: "Cerere" message: "Mesaj" + confirm_leave: "Ești sigur că vrei să părăsești acest grup?" + allow_membership_requests: "Permite utilizatorilor să trimită cereri de înscriere către proprietarii grupului (necesită ca grupul să fie vizibil public)" membership_request_template: "Șablonul personalizat pentru a fi afișat utilizatorilor atunci când trimiteți o solicitare de membru" membership_request: submit: "Trimite cerere" @@ -495,6 +664,7 @@ ro: all: "Toate grupurile" empty: "Nu există nici un grup vizibil." filter: "Filtrează după tipul grupului" + owner_groups: "Grupurile pe care le dețin" close_groups: "Grupuri închise" automatic_groups: "Grupuri automate" automatic: "Automat" @@ -524,6 +694,7 @@ ro: remove_owner: "Elimină ca deținător" remove_owner_description: "Elimină %{username} ca deținător al acestui grup" owner: "Deținător" + forbidden: "Nu ai permisiuni să vezi membrii." topics: "Subiecte" posts: "Postări" mentions: "Mențiuni" @@ -536,6 +707,7 @@ ro: only_admins: "Doar adminii" mods_and_admins: "Doar moderatorii și adminii" members_mods_and_admins: "Membrii grupului, moderatorii și adminii" + owners_mods_and_admins: "Numai proprietarii de grupuri, moderatorii și administratorii" everyone: "Toată lumea" notifications: watching: @@ -543,6 +715,7 @@ ro: description: "Veți fi notificat pentru fiecare nouă postare în fiecare mesaj, și va fi afișat un contor al noilor răspunsuri." watching_first_post: title: "Urmărind activ prima postare" + description: "Vei primi notificări despre mesajele noi din acest grup, dar nu și despre răspunsurile la mesaje." tracking: title: "Urmărind" description: "Vei fi notificat dacă cineva menționează @numele tău sau îți răspunde și va fi afișat un contor al noilor răspunsuri." @@ -551,13 +724,18 @@ ro: description: "Vei fi notificat dacă cineva îți menționează @numele sau îți va răspunde." muted: title: "Setat pe silențios" + description: "Nu veți fi informat despre nimic referitor la mesajele din acest grup." flair_url: "Imagine avatar cu element distinct" + flair_upload_description: "Folosește imagini pătrate de cel puțin 20px cu 20px." flair_bg_color: "Culoarea de fundal a avatarului cu element distinct" flair_bg_color_placeholder: "(Opțional) Valoarea Hex a culorii" flair_color: "Culoarea avatarului cu element distinct" flair_color_placeholder: "(Opțional) Valoarea Hex a culorii" flair_preview_icon: "Previzualizează iconiță" flair_preview_image: "Previzualizează imagine" + flair_type: + icon: "Alege o pictogramă" + image: "Încarcă o imagine" user_action_groups: "1": "Aprecieri date" "2": "Aprecieri primite" @@ -571,7 +749,7 @@ ro: "12": "Elemente trimise" "13": "Primite" "14": "În așteptare" - "15": "Drafts" + "15": "Ciorne" categories: all: "Toate categoriile" all_subcategories: "tot" @@ -590,6 +768,7 @@ ro: latest_by: "Cele mai recente după" toggle_ordering: "Comută criteriu de aranjare" subcategories: "Subcategorii" + muted: "Categorii dezactivate" topic_sentence: one: "Un subiect" few: "%{count} subiecte" @@ -662,6 +841,9 @@ ro: dismiss_notifications: "Elimină tot" dismiss_notifications_tooltip: "Marchează toate notificările ca citite" first_notification: "Prima notificare pe care ai primit-o! Selectează-o ca să continui. " + color_schemes: + default_description: "Implicit" + disable_dark_scheme: "La fel ca de obicei" allow_private_messages: "Permite altor utilizatori să îmi trimită mesaje private" external_links_in_new_tab: "Deschide toate adresele externe într-un tab nou" enable_quoting: "Activează citarea răspunsurilor pentru textul selectat" @@ -710,13 +892,16 @@ ro: admin_delete: "Șterge" users: "Utilizatori" muted_users: "Silențios" + ignored_users_instructions: "Suprimă toate mesajele, notificările și mesajele directe de la acești utilizatori." tracked_topics_link: "Arată" automatically_unpin_topics: "Detașare automată a subiectelor când ajung la sfârșitul paginii." apps: "Aplicații" revoke_access: "Revocă accesul" undo_revoke_access: "Anulează revocarea accesului" api_approved: "Aprobate:" + api_last_used_at: "Ultima utilizare la:" theme: "Temă" + save_to_change_theme: 'Tema va fi actualizată după ce faceți clic pe „%{save_text}”' home: "Pagina inițială implicită" staged: "Pus în scenă" staff_counters: @@ -725,6 +910,7 @@ ro: deleted_posts: "Postări șterse" suspensions: "Suspendări" warnings_received: "Avertizări" + rejected_posts: "postări respinse" messages: all: "Toate" inbox: "Primite" @@ -763,7 +949,15 @@ ro: copy_to_clipboard_error: "Eroare la copierea datelor în clipboard" copied_to_clipboard: "Copiat în clipboard" second_factor: + title: "Autentificare în doi pași" + enable: "Gestionează autentificarea în doi pași" name: "Nume" + short_description: | + Protejează-ți contul cu coduri de securitate de unică folosință. + extended_description: | + Autentificarea în doi pași adaugă o securitate suplimentară contului tău prin solicitarea unui token unic, în plus față de parola ta. Token-urile pot fi generate la Android și iOS . + oauth_enabled_warning: "Reține că autentificările cu serviciile sociale vor fi dezactivate o dată ce autentificarea în doi pași a fost activată în contul tău." + enforced_notice: "Este necesar să activezi autentificarea în doi pași înainte de a accesa acest site." disable: "Dezactivează" save: "Salvează" edit: "Editează" @@ -800,6 +994,8 @@ ro: title: "Email" primary_label: "primar" update_email: "Schimbă email" + instructions: "Niciodată arătat publicului." + admin_note: "Notă: Un utilizator de administrator care modifică adresa email a altui utilizator non-administrator indică faptul că utilizatorul a pierdut accesul la contul de email original, astfel încât un email de resetare a parolei va fi trimis la noua adresă. Adresa email a utilizatorului nu se va schimba până când acesta nu finalizează procesul de resetare a parolei." ok: "Îți vom trimite un email pentru confirmare." invalid: "Introduceți o adresă de email validă." authenticated: "Emailul a fost autentificat de către %{provider}." @@ -809,8 +1005,12 @@ ro: few: "Vom trimite un email doar dacă nu te-am văzut în ultimele %{count} minute." other: "Vom trimite un email doar dacă nu te-am văzut în ultimele %{count} de minute." associated_accounts: + title: "Conturi asociate" + connect: "Conectează-te" revoke: "Revocă" cancel: "Anulează" + not_connected: "(nu este conectat)" + confirm_modal_title: "Conectare cont %{provider}" name: title: "Nume" instructions: "Numele tău complet (opțional)" @@ -836,6 +1036,7 @@ ro: password_confirmation: title: "Confirmă parola" auth_tokens: + title: "Dispozitive folosite recent" ip: "Adresă IP" details: "Detalii" last_posted: "Ultima postare" @@ -947,6 +1148,7 @@ ro: title: "Sumar" stats: "Statistici" time_read: "Timp petrecut pe site" + recent_time_read: "timp de citire recent" topic_count: one: "un subiect creat" few: "subiecte create" @@ -1123,6 +1325,7 @@ ro: title: "Autentificare" username: "Utilizator" password: "Parolă" + second_factor_title: "Autentificare în doi pași" email_placeholder: "email sau nume de utilizator" caps_lock_warning: "Tasta Caps Lock este activă" error: "Eroare necunoscută" @@ -1140,6 +1343,7 @@ ro: not_allowed_from_ip_address: "Nu te poți conecta cu această adresă IP." admin_not_allowed_from_ip_address: "Nu te poți conecta ca administrator cu această adresă IP." resend_activation_email: "Click aici pentru a retrimite emailul de activare." + omniauth_disallow_totp: "Contul dvs. are autentificarea în doi pași activată. Vă rugăm să vă autentificați cu parola." resend_title: "Retrimite emailul de activare" change_email: "Schimbă adresa de email" provide_new_email: "Furnizează o nouă adresă de email și îți vom retrimite linkul de confirmare." @@ -1170,7 +1374,6 @@ ro: accept_invite: "Acceptă invitație" success: "Contul tău a fost creat și acum ești logat." name_label: "Nume" - password_label: "Setează parolă" optional_description: "(opțional)" password_reset: continue: "Continuă la %{site_name}" @@ -1306,6 +1509,8 @@ ro: desc: "Pregătește o ciornă a unei discuții care va fi vizibilă doar pentru personal" toggle_topic_bump: label: "Comută bump pentru subiect" + reload: "Reîncarcă" + ignore: "Ignoră" notifications: tooltip: message: @@ -1418,12 +1623,20 @@ ro: noreplies: nu au nici un răspuns single_user: au un singur utilizator post: - count: - label: Număr minim de postări + min: + placeholder: minim + max: + placeholder: maxim time: label: Postat(e) before: înainte after: după + views: + label: Afișări + min_views: + placeholder: minim + max_views: + placeholder: maxim hamburger_menu: "Du-te la o altă categorie saulistă de subiecte" new_item: "nou" go_back: "înapoi" @@ -1464,7 +1677,7 @@ ro: new: "Nu sunt subiecte noi." read: "Nu ai citit încă niciun subiect." posted: "Nu ai postat încă în niciun subiect." - latest: "Nu există niciun subiect recent. Asta-i trist." + ready_to_create: "Gata pentru " bookmarks: "Nu ai nici un semn de carte încă." category: "Nu există niciun subiect din categoria %{category}." top: "Nu exită niciun subiect de top." @@ -1545,7 +1758,6 @@ ro: read_more_MF: "{ UNREAD, plural, =0 {} one { Există un subiect necitit } other { Sunt # subiecte necitite } } { NEW, plural, =0 {} one { {BOTH, select, true{și } false {există } other{}} un subiect nou} other { {BOTH, select, true{și } false {sunt } other{}} # subiecte noi} } rămas(e), sau {CATEGORY, select, true {răsfoiți alte subiecte în {catLink}} false {{latestLink}} other {}}" browse_all_categories: Răsfoiți prin toate categoriile view_latest_topics: arată ultimele subiecte - suggest_create_topic: Ce ar fi să creezi un subiect? jump_reply_up: sări la un răspuns mai vechi jump_reply_down: sări la un răspuns mai nou deleted: "Subiectul a fost șters" @@ -2923,6 +3135,7 @@ ro: post_locked: "postare blocată" post_unlocked: "postare deblocată" check_personal_message: "verifică mesaj personal" + disabled_second_factor: "dezactivează Autentificarea în doi pași" topic_published: "discuția a fost publicată" screened_emails: title: "Email-uri filtrate" @@ -3060,6 +3273,7 @@ ro: private_topics_count: Subiecte private posts_read_count: Postări citite post_count: Postări create + second_factor_enabled: Autentificare în doi pași a fost activată topics_entered: Subiecte văzute flags_given_count: Marcaje de avertizare acordate flags_received_count: Marcaje de avertizare primite diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index d9cdae592d..7eece31b2c 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -166,6 +166,7 @@ ru: twitter: "Поделиться в Твиттере" facebook: "Поделиться в Фейсбуке" email: "Отправить по электронной почте" + url: "Копировать и поделиться ссылкой" action_codes: public_topic: "Сделал эту тему публичной %{when}" private_topic: "Сделал эту тему личным сообщением %{when}" @@ -201,7 +202,7 @@ ru: topic_admin_menu: "Действия администратора над темой" wizard_required: "Добро пожаловать в ваш новый Discourse! Начните с мастера настройки ✨" emails_are_disabled: "Все исходящие письма были глобально отключены администратором. Уведомления любого вида не будут отправляться на почту." - bootstrap_mode_enabled: "Для скорейшего развития вашего нового сайта был включен специальный режим его работы. В этом режиме всем новым пользователям при регистрации автоматически присваивается 1-й уровень доверия и включается ежедневная почтовая рассылка сводки новостей. Этот режим будет автоматически выключен , как только количество зарегистрированных пользователей достигнет %{min_users}." + bootstrap_mode_enabled: "Для скорейшего развития вашего нового сайта был включен специальный режим его работы. В этом режиме всем новым пользователям при регистрации автоматически присваивается 1-й уровень доверия и включается ежедневная почтовая рассылка сводки новостей. Этот режим будет автоматически выключен, как только количество зарегистрированных пользователей достигнет %{min_users}." bootstrap_mode_disabled: "Специальный режим будет отключён в течение 24 часов." themes: default_description: "По умолчанию" @@ -559,7 +560,7 @@ ru: title: "(все)" types: reviewable_flagged_post: - title: "Сообщение отмечено как ЖАЛОБА" + title: "На это сообщение поступила жалоба" flagged_by: "Кем отмечено" reviewable_queued_topic: title: "Тема в очереди" @@ -650,7 +651,7 @@ ru: interaction: title: Взаимодействие posting: Сообщения - notification: Уведомление + notification: Уведомления email: title: "Email" status: "Синхронизированные %{old_emails} / %{total_emails} электронные письма через IMAP." @@ -968,7 +969,7 @@ ru: text_size_default_on_all_devices: "Сделать это размер текста размером по умолчанию на всех моих устройствах" allow_private_messages: "Разрешить другим пользователям отправлять мне личные сообщения" external_links_in_new_tab: "Открывать все внешние ссылки в новой вкладке" - enable_quoting: "Позволить отвечать с цитированием выделенного текста" + enable_quoting: "Разрешить отвечать с цитированием выделенного текста" enable_defer: "Включить кнопку 'Отложить', чтобы помечать темы как непрочитанные" change: "изменить" featured_topic: "Избранная тема:" @@ -1154,6 +1155,7 @@ ru: taken: "Этот E-mail недоступен." error: "Произошла ошибка. Возможно, этот E-mail уже используется?" success: "На указанную почту отправлено письмо с инструкциями." + success_via_admin: "Мы отправили e-mail письмо на этот адрес. Пользователю необходимо будет следовать инструкциям по подтверждению в e-mail письме." success_staff: "Мы отправили письмо с инструкциями на ваш текущий адрес электронной почты." change_avatar: title: "Изменить аватар" @@ -1191,6 +1193,7 @@ ru: sso_override_instructions: "E-mail может быть переопределён от поставщика SSO." no_secondary: "Нет дополнительного адреса электронной почты" instructions: "Не будет отображаться." + admin_note: "Примечание: Администратор меняет адрес электронной почты другого пользователя, не являющегося администратором, что указывает на то, что пользователь потерял доступ к своему первоначальному электронному адресу. На новый адрес пользователя будет отправлено электронное письмо для сброса пароля. Адрес электронной почты не изменится, пока пользователь не завершит процесс сброса пароля." ok: "Мы вышлем вам письмо для подтверждения" required: "Пожалуйста, введите E-mail" invalid: "Введите действующий адрес электронной почты" @@ -1510,11 +1513,11 @@ ru: login_disabled: "Вход отключён, пока сайт находится в режиме «только для чтения»" logout_disabled: "Выход отключён, пока сайт находится в режиме «только для чтения»" too_few_topics_and_posts_notice_MF: >- - Давайте приступим к обсуждению! Есть {currentTopics, plural, one {# тема} few {# темы} other {# тем}} и {currentPosts, plural, one {# сообщение} few {# сообщения} other {# сообщений}}. Пользователи должны больше читать и отвечать – мы рекомендуем, по крайней мере {requiredTopics, plural, one {# тему} few {# темы} other {# тем}} и {requiredPosts, plural, one {# сообщение} few {# сообщения} other {# сообщений}}. Только сотрудники могут видеть это сообщение. + Давайте приступим к обсуждению! Есть {currentTopics, plural, one {# тема} few {# темы} other {# тем}} и {currentPosts, plural, one {# сообщение} few {# сообщения} other {# сообщений}}. Пользователи должны больше читать и отвечать – мы рекомендуем по крайней мере {requiredTopics, plural, one {# тему} few {# темы} other {# тем}} и {requiredPosts, plural, one {# сообщение} few {# сообщения} other {# сообщений}}. Только сотрудники могут видеть это сообщение. too_few_topics_notice_MF: >- - Давайте приступим к обсуждению! Есть {currentTopics, plural, one {# тема} few {# темы} other {# тем}}. Пользователи должны больше читать и отвечать – мы рекомендуем, по крайней мере {requiredTopics, plural, one {# тему} few {# темы} other {# тем}}. Только сотрудники могут видеть это сообщение. + Давайте приступим к обсуждению! Есть {currentTopics, plural, one {# тема} few {# темы} other {# тем}}. Пользователи должны больше читать и отвечать – мы рекомендуем по крайней мере {requiredTopics, plural, one {# тему} few {# темы} other {# тем}}. Только сотрудники могут видеть это сообщение. too_few_posts_notice_MF: >- - Давайте приступим к обсуждению! Есть {currentPosts, plural, one {# сообщение} few {# сообщения} other {# сообщений}}. Пользователи должны больше читать и отвечать – мы рекомендуем, по крайней мере {requiredPosts, plural, one {# сообщение} few {# сообщения} other {# сообщений}}. Только сотрудники могут видеть это сообщение. + Давайте приступим к обсуждению! Есть {currentPosts, plural, one {# сообщение} few {# сообщения} other {# сообщений}}. Пользователи должны больше читать и отвечать – мы рекомендуем по крайней мере {requiredPosts, plural, one {# сообщение} few {# сообщения} other {# сообщений}}. Только сотрудники могут видеть это сообщение. logs_error_rate_notice: reached_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} other {# errors/hour}} достигнут предел настройки сайта {limit, plural, one {# error/hour} other {# errors/hour}}." reached_minute_MF: "{relativeAge}{rate, plural, one {# error/minute} other {# errors/minute}} достигнут предел настройки сайта {limit, plural, one {# error/minute} other {# errors/minute}}." @@ -1686,7 +1689,7 @@ ru: accept_invite: "Принять приглашение" success: "Ваш аккаунт создан и теперь вы можете войти." name_label: "Имя" - password_label: "Установить пароль" + password_label: "Пароль" optional_description: "(опционально)" password_reset: continue: "Далее на %{site_name}" @@ -1705,29 +1708,6 @@ ru: categories_and_top_topics: "Разделы и популярные темы" categories_boxes: "Блоки с подразделами" categories_boxes_with_topics: "Блоки с избранными темами" - base_font_setting: - helvetica: "Helvetica/Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "RobotoCondensed" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "Ubuntu" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" shortcut_modifier_key: shift: "Shift" ctrl: "Ctrl" @@ -1921,6 +1901,8 @@ ru: toggle_topic_bump: label: "Не поднимать тему" desc: "Ответить без изменения даты последнего ответа" + reload: "Обновить" + ignore: "Игнорировать" notifications: tooltip: regular: @@ -2038,7 +2020,7 @@ ru: relevance: "По соответствию" latest_post: "По недавним сообщениям" latest_topic: "По недавним темам" - most_viewed: "По самым просматриваемым" + most_viewed: "По количеству просмотров" most_liked: "По количеству симпатий" select_all: "Выбрать всё" clear_all: "Сбросить всё" @@ -2107,11 +2089,21 @@ ru: single_user: С одним пользователем post: count: - label: Минимум сообщений в теме + label: Сообщений + min: + placeholder: минимум + max: + placeholder: максимум time: label: Дата before: До (включая) after: Начиная с + views: + label: Просмотров + min_views: + placeholder: минимум + max_views: + placeholder: максимум hamburger_menu: "Перейти к другому списку тем или другому разделу" new_item: "новый" go_back: "вернуться" @@ -2150,12 +2142,14 @@ ru: choose_new_tags: "Выберите новые теги для этих тем:" choose_append_tags: "Выберите теги для добавления к этим темам:" changed_tags: "Теги этих тем изменены." + remove_tags: "Удалить Теги" none: unread: "У вас нет непрочитанных тем." new: "У вас нет новых тем." read: "Вы ещё не прочитали ни одной темы." posted: "Вы ещё не принимали участие в обсуждении." - latest: "Новых тем нет." + ready_to_create: "Готовы " + latest: "Нет обновлённых или недавно созданных тем." bookmarks: "У вас пока нет тем, добавленных в закладки." category: "В разделе %{category} отсутствуют темы." top: "Нет обсуждаемых тем." @@ -2257,7 +2251,7 @@ ru: browse_all_categories: Просмотреть все разделы browse_all_tags: Просмотреть все теги view_latest_topics: Просмотреть последние темы - suggest_create_topic: Почему бы вам не создать новую тему? + suggest_create_topic: начать новое обсуждение? jump_reply_up: Перейти к более ранним ответам jump_reply_down: Перейти к более поздним ответам deleted: "Тема удалена" @@ -2310,8 +2304,8 @@ ru: auto_delete_replies: title: "Автоматическое удаление ответов" status_update_notice: - auto_open: "Эта тема автоматически откроется через %{timeLeft}." - auto_close: "Эта тема автоматически закроется через %{timeLeft}." + auto_open: "Эта тема автоматически откроется %{timeLeft}." + auto_close: "Эта тема автоматически закроется %{timeLeft}." auto_publish_to_category: "Эта тема будет опубликована в разделе #%{categoryName} через %{timeLeft}." auto_close_based_on_last_post: "Эта тема будет закрыта через %{duration} после последнего ответа." auto_delete: "Эта тема будет автоматически удалена %{timeLeft}." @@ -2814,7 +2808,7 @@ ru: updated: "Обновлено" name: "Имя" name_placeholder: "Присвоить имя закладке (необязательно)" - set_reminder: "Настроить напоминание (необязательно) " + set_reminder: "Настроить напоминание (необязательно)" actions: delete_bookmark: name: "Удалить закладку" @@ -2836,12 +2830,12 @@ ru: tags: "Теги" tags_allowed_tags: "Ограничить теги этим разделом:" tags_allowed_tag_groups: "Ограничить группы тегов этим разделом:" - tags_placeholder: "(Необязательно) Список доступных тегов" + tags_placeholder: "(Необязат.) Доступные теги" tags_tab_description: "Теги и группы тегов, указанные здесь, будут доступны только в этом разделе и других разделах, в которых они были указаны. Они не будут доступны для использования в других разделах." - tag_groups_placeholder: "(Необязательно) Список доступных групп тегов" + tag_groups_placeholder: "(Необязат.) Доступные группы тегов" manage_tag_groups_link: "Управление группами тегов." allow_global_tags_label: "Также разрешить другие теги" - tag_group_selector_placeholder: "(Необязательно) Группа тегов" + tag_group_selector_placeholder: "(Необязат.) Группа тегов" required_tag_group_description: "Требовать, чтобы новые темы имели теги из группы тегов:" min_tags_from_required_group_label: "Номер тега:" required_tag_group_label: "Группа тегов:" @@ -2851,7 +2845,7 @@ ru: create_long: "Создать новый раздел" save: "Сохранить раздел" slug: "Ссылка на раздел" - slug_placeholder: "(Необязательно) слова для URL, разделённые дефисами" + slug_placeholder: "(Необязат.) Слова для URL, разделённые дефисами" creation_error: При создании нового раздела возникла ошибка. save_error: При сохранении раздела возникла ошибка. name: "Название раздела" @@ -2873,19 +2867,19 @@ ru: security: "Безопасность" special_warning: "Внимание: данный раздел был предустановлен по умолчанию и его настройки безопасности не могут быть изменены. Если не хотите использовать этот раздел, удалите его вместо изменения." uncategorized_security_warning: "Это специальный раздел, предназначенный для хранения тем, которые не относятся к какому-либо разделу; у него не может быть настроек безопасности." - uncategorized_general_warning: 'Это специальный раздел, используемый в качестве раздела по умолчанию для новых тем, для которых не был выбран конкретный раздел. Если вы хотите предотвратить такое поведение и принудительно выбирать раздел, отключите соответствующую настройку здесь . Если вы хотите изменить название или описание раздела, сделайте это в настройке Оформление / Текстовое содержимое .' + uncategorized_general_warning: 'Это специальный раздел, используемый в качестве раздела по умолчанию для новых тем, для которых не был выбран конкретный раздел. Если вы хотите предотвратить такое поведение и принудительно выбирать раздел, отключите соответствующую настройку здесь. Если вы хотите изменить название или описание раздела, сделайте это в настройке Оформление / Текст.' pending_permission_change_alert: "Вы не добавили %{group} в этот раздел; нажмите эту кнопку для добавления." images: "Изображения" email_in: "Индивидуальный адрес входящей почты:" email_in_allow_strangers: "Принимать письма от анонимных пользователей, не имеющих учётных записей" email_in_disabled: "Создание новых тем через электронную почту отключено в настройках сайта. Чтобы разрешить создание новых тем через электронную почту," email_in_disabled_click: 'активируйте настройку "email in".' - mailinglist_mirror: "Категория отражает список рассылки" + mailinglist_mirror: "Раздел отражает список рассылки" show_subcategory_list: "Показывать список подразделов над списком тем в этом разделе." read_only_banner: "Текст баннера, когда пользователь не может создавать темы в этом разделе:" num_featured_topics: "Количество тем, отображаемых на странице раздела:" subcategory_num_featured_topics: "Количество избранных тем на странице родительского раздела:" - all_topics_wiki: "Создание новых тем в виде вики-сообщений" + all_topics_wiki: "Создавать новые темы в виде вики-сообщений" subcategory_list_style: "Стиль списка подразделов:" sort_order: "Порядок сортировки тем:" default_view: "Вид списка тем по умолчанию:" @@ -2904,8 +2898,8 @@ ru: position_disabled_click: 'включите настройку "fixed category positions".' minimum_required_tags: "Минимальное количество тегов, требуемых в теме:" parent: "Родительский раздел" - num_auto_bump_daily: "Число открытых тем для автоматического поднятия ежедневно:" - navigate_to_first_post_after_read: "Перейдите к первому сообщению после прочтения тем" + num_auto_bump_daily: "Число открытых тем для автоматического ежедневного поднятия:" + navigate_to_first_post_after_read: "Перейти к первому сообщению после прочтения тем" notifications: watching: title: "Наблюдать" @@ -3285,7 +3279,7 @@ ru: few: "выдано %{count}" many: "выдано %{count}" other: "выдано %{count}" - select_badge_for_title: Использовать награду в качестве Вашего титула + select_badge_for_title: Использовать награду в качестве титула none: "(нет)" successfully_granted: "Награда %{badge} успешно присвоена пользователю %{username}" badge_grouping: @@ -3431,15 +3425,15 @@ ru: enabled: "Включён безопасный режим, чтобы выйти из безопасного режима, закройте текущее окно браузера" image_removed: "(изображение удалено)" admin_js: - type_to_filter: "Введите текст для фильтрации..." + type_to_filter: "Фильтрация настроек..." admin: title: "Discourse Admin" moderator: "Модератор" tags: remove_muted_tags_from_latest: - always: "всегда" - only_muted: "при использовании только этого тега или с другими выключающими тегами" - never: "никогда" + always: "Всегда" + only_muted: "При использовании одного выключающего тега или вместе с другими выключающими тегами" + never: "Никогда" reports: title: "Список доступных отчётов" dashboard: @@ -3570,7 +3564,7 @@ ru: members_visibility_levels: title: "Кто может видеть участников этой группы?" description: "Администраторы могут видеть участников всех групп." - publish_read_state: "В сообщениях группы публикуют состояние чтения группы" + publish_read_state: "В сообщениях группы публиковать состояние чтения группы" membership: automatic: Автоматическая trust_levels_title: "Уровень доверия, автоматически присваиваемый участникам при их добавлении:" @@ -3606,8 +3600,8 @@ ru: user: "Пользователь" title: "API" key: "Ключ" - created: Создано - updated: Обновлено + created: Создан + updated: Обновлён last_used: Последнее использование never_used: (никогда) generate: "Сгенерировать" @@ -3620,23 +3614,23 @@ ru: description: Описание no_description: (без описания) all_api_keys: Все API-ключи - user_mode: Уровень пользователя + user_mode: Режим impersonate_all_users: Представиться как пользователь single_user: "Отдельный пользователь" - user_placeholder: Введите псевдоним + user_placeholder: '@псевдоним' description_placeholder: Для чего будет использоваться этот ключ? save: Сохранить new_key: Новый API-ключ - revoked: Отозвать + revoked: Отозван delete: Окончательно удалить - not_shown_again: Этот ключ больше не будет отображаться. Убедитесь, что вы сделали копию, прежде чем продолжить. + not_shown_again: Этот ключ больше не будет отображаться. Убедитесь, что вы сохранили его копию, прежде чем продолжить. continue: Продолжить use_global_key: Глобальный ключ (разрешает все действия) scopes: description: | Использование API с ограниченной областью действия позволяет более детально настраивать разрешения. Вы можете указать, какие параметры будут разрешены. Используйте запятые для разделения нескольких значений. - title: Области действия ключей API + title: Области действия ключа API resource: Ресурс action: Действие allowed_parameters: Разрешённые параметры @@ -3658,7 +3652,7 @@ ru: title: "Вебхуки" none: "Вебхуки отсутствуют." instruction: "Вебхуки позволяют Discourse уведомлять внешние службы, когда определённое событие происходит на вашем сайте. При срабатывании вебхука на соответствующий URL будет отправлен POST-запрос." - detailed_instruction: "При наступлении выбранного события, на соответствующий URL будет отправлен POST-запрос." + detailed_instruction: "При наступлении выбранного события на соответствующий URL будет отправлен POST-запрос." new: "Добавить вебхук" create: "Создать" save: "Сохранить" @@ -3671,22 +3665,22 @@ ru: warn_local_payload_url: "По-видимому вы пытаетесь настроить вебхук на локальный URL. Событие, отправляемое на локальный адрес, может иметь побочное действие или неожиданное поведение. Продолжить?" secret_invalid: "Ключ не должен содержать пробелов." secret_too_short: "Ключ должен быть не менее 12 символов." - secret_placeholder: "Необязательная строка, используемая для создания подписи" + secret_placeholder: "Создание подписи (необязательно)" event_type_missing: "Вам необходимо настроить по крайней мере один тип событий." content_type: "Тип содержимого" secret: "Ключ" event_chooser: "Какие события должны вызывать срабатывание этого вебхука?" - wildcard_event: "Присылать мне всё." - individual_event: "Выбрать отдельные события." - verify_certificate: "Проверять TLS сертификат ссылки для отправки данных" - active: "Активный" + wildcard_event: "Все события" + individual_event: "Выбрать отдельные события" + verify_certificate: "Проверять TLS-сертификат ссылки для отправки данных" + active: "Включить отправку данных" active_notice: "Мы будем отправлять подробности события, когда оно будет происходить." - categories_filter_instructions: "Подходящие вебхуки будут срабатывать только если событие связано с указанными разделами. Оставьте пустым, чтобы вебхук срабатывал для всех разделов." + categories_filter_instructions: "Подходящие вебхуки будут срабатывать только в том случае, если событие связано с указанными разделами. Оставьте поле пустым, чтобы вебхук срабатывал для всех разделов." categories_filter: "Только для этих разделов" tags_filter_instructions: "Соответствующие вебхуки будут активированы, только если событие связано с указанными тегами. Оставьте пустым, чтобы активировать вебхуки для всех тегов." tags_filter: "Сработавшие теги" - groups_filter_instructions: "Подходящие вебхуки будут срабатывать только если событие связано с указанными группами. Оставьте пустым, чтобы вебхук срабатывал для всех групп." - groups_filter: "Только для групп" + groups_filter_instructions: "Подходящие вебхуки будут срабатывать только в том случае, если событие связано с указанными группами. Оставьте поле пустым, чтобы вебхук срабатывал для всех групп." + groups_filter: "Только для этих групп" delete_confirm: "Удалить вебхук?" topic_event: name: "Событие темы" @@ -3718,12 +3712,12 @@ ru: delivery_status: title: "Статус передачи" inactive: "Неактивна" - failed: "Проблема" - successful: "Успех" - disabled: "Отключено" + failed: "Не удалась" + successful: "Успешно завершена" + disabled: "Отключена" events: none: "Нет связанных событий." - redeliver: "Возврат" + redeliver: "Повторить" incoming: one: "Есть новое событие." few: "Есть %{count} новых события." @@ -3745,7 +3739,7 @@ ru: go_events: "Перейти к событию" ping: "Ping" status: "Код состояния" - event_id: "Идентификатор (ID)" + event_id: "ID" timestamp: "Создано" completion: "Время завершения" actions: "Действия" @@ -3881,9 +3875,9 @@ ru: edit_confirm: "Это импортированная тема. Изменения CSS/HTML будут утеряны после очередного обновления." update_confirm: "Эти локальные изменения будут утеряны после обновления. Вы действительно хотите продолжить?" update_confirm_yes: "Да, продолжить обновление" - common: "Общее" - desktop: "Настольный" - mobile: "Мобильный" + common: "Обычная" + desktop: "Настольная" + mobile: "Мобильная" settings: "Настройки" translations: "Переводы" extra_scss: "Дополнительные SCSS" @@ -3918,7 +3912,7 @@ ru: collapse: Крах uploads: "Загрузки" no_uploads: "Вы можете загрузить различные ресурсы для темы, такие как шрифты и изображения" - add_upload: "Добавить вложение" + add_upload: "Добавить ресурс" upload_file_tip: "Выберите файл для загрузки (png, woff2 и т.д.)" variable_name: "Имя переменной SCSS:" variable_name_invalid: "Недопустимое имя переменной. Допускается только буквенно-цифровое значение. Имя должно начинаться с буквы и быть уникальным." @@ -3942,7 +3936,7 @@ ru: import_web_tip: "Репозиторий темы" import_web_advanced: "Дополнительно..." import_file_tip: "Файл с расширением .tar.gz, .zip, или .dcstyle.json, содержащий тему" - is_private: "Тема находится в частном git-репозитории" + is_private: "Тема находится в приватном git-репозитории" remote_branch: "Имя ветки (необязательно)" public_key: "Предоставьте доступ к репозиторию со следующим открытым ключом:" install: "Установить" @@ -3974,7 +3968,7 @@ ru: theme_settings: "Настройки темы" no_settings: "Эта тема не имеет настроек." theme_translations: "Перевод темы" - empty: "Не содержит элементов" + empty: "Нет компонентов" commits_behind: one: "Тема находится на %{count} коммит сзади!" few: "Тема находится на %{count} коммита сзади!" @@ -4076,7 +4070,7 @@ ru: preview_digest: "Предварительный просмотр" advanced_test: title: "Расширенный тест" - desc: "Посмотрите, как Discourse обрабатывает полученные письма. Вставьте ниже оригинальное сообщение и убедитесь, что письмо обработано правильно." + desc: "Посмотрите как Discourse обрабатывает полученные письма. Вставьте ниже оригинальное сообщение и убедитесь, что письмо обработано правильно." email: "Исходное сообщение" run: "Выполнить тест" text: "Выбранный текст сообщения" @@ -4643,11 +4637,11 @@ ru: revert: "Отменить изменения" revert_confirm: "Вы уверены что хотите отменить внесённые изменения?" go_back: "Вернуться к поиску" - recommended: "Мы рекомендуем изменить следующий текст под ваши требования:" - show_overriden: "Показывать только изменённый" + recommended: "Мы рекомендуем изменить текст этих шаблонов под ваши требования:" + show_overriden: "Показывать только изменённые значения" more_than_50_results: "Найдено более 50 результатов. Пожалуйста, уточните параметры поиска." settings: - show_overriden: "Показывать только изменённый" + show_overriden: "Показывать только изменённые значения" reset: "Сброс" none: "(нет)" site_settings: @@ -4665,7 +4659,7 @@ ru: label: "Загрузить" title: "Загрузить изображение(я)" selectable_avatars: - title: "Список аватаров, которые пользователи могут выбрать" + title: "Список доступных для выбора аватаров." categories: all_results: "Все настройки" required: "Обязательное" @@ -4681,7 +4675,7 @@ ru: seo: "Поисковая оптимизация (SEO)" spam: "Спам" rate_limits: "Ограничения" - developer: "Программистам" + developer: "Разработчикам" embedding: "Встраивание" legal: "Юридическое" api: "API" @@ -4698,9 +4692,9 @@ ru: secret_list: invalid_input: "Поля ввода не могут быть пустыми или содержать символ вертикальной черты." default_categories: - modal_description: "Хотели бы вы применить это изменение исторически? Это изменит настройки для %{count} существующих пользователей." + modal_description: "Хотели бы вы применить это изменение к уже настроенным процессам? Это изменит настройки для %{count} существующих пользователей." modal_yes: "Да" - modal_no: "Нет, только применить изменения в будущем" + modal_no: "Нет, применять изменения только в будущем" simple_list: add_item: "Добавить элемент..." badges: @@ -4734,13 +4728,13 @@ ru: no_badges: Нет наград, которые можно было бы выдать. none_selected: "Выберите награду в списке слева" allow_title: Разрешить использовать название награды в качестве титула - multiple_grant: Может быть предоставлен несколько раз + multiple_grant: Может быть предоставлена несколько раз listable: Отображать награду на публичной странице наград - enabled: Активировать использование награды + enabled: Активировать использование этой награды icon: Иконка image: Картинка - icon_help: "Введите имя значка Font Awesome (используйте префикс 'far-' для обычных значков и 'fab-' для значков брендов)" - image_help: "Введите URL-адрес изображения (переопределяет поле награды, если установлены оба)" + icon_help: "Введите имя иконки из коллекции Font Awesome (используйте префикс 'far-' для обычных значков и 'fab-' для значков брендов)" + image_help: "Введите URL-адрес изображения (отображается вместо иконки, если указаны оба параметра)" query: Выборка награды (SQL) target_posts: Выборка целевых сообщений auto_revoke: Запускать ежедневно запрос на отзыв @@ -4787,9 +4781,9 @@ ru: upload_csv: Загрузите файл в формате CSV с электронными адресами или псевдонимами пользователей aborted: Пожалуйста, загрузите файл в формате CSV, содержащий адреса электронной почты или псевдонимы пользователей. success: Файл успешно загружен, пользователи получат награды в ближайшее время. - replace_owners: Удалить значок от предыдущих владельцев + replace_owners: Удалять эту награду у предыдущих владельцев emoji: - title: "Иконки" + title: "Смайлы" help: "Добавить новые смайлики-emoji, которые будут доступны всем. (Подсказка: можно перетаскивать несколько файлов за раз)" add: "Добавить новую иконку" uploading: "Загрузка..." @@ -4853,14 +4847,14 @@ ru: wizard_js: wizard: done: "Готово" - finish: "Закончено" + finish: "Закончить" back: "Назад" next: "Далее" step: "%{current} из %{total}" upload: "Загрузить" uploading: "Загрузка..." upload_error: "К сожалению, не удалось загрузить файл. Попробуйте ещё раз." - quit: "Может быть позже" + quit: "Продолжить настройку позже" staff_count: one: "В вашем сообществе %{count} сотрудник (это вы)." few: "В вашем сообществе %{count} сотрудника, включая вас." diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml index a7d4d16468..cb1b6c192b 100644 --- a/config/locales/client.sk.yml +++ b/config/locales/client.sk.yml @@ -1188,7 +1188,6 @@ sk: accept_invite: "Prijať pozvánku" success: "Váš účet bol vytvorený a ste prihlásený." name_label: "Meno" - password_label: "Nastaviť heslo" optional_description: "(nepovinné)" password_reset: continue: "Pokračujte na %{site_name}" @@ -1363,8 +1362,6 @@ sk: noreplies: majú nula odpovedí single_user: obsahujú jediného používateľa post: - count: - label: Minimálny počet príspevkov time: label: Odoslané before: pred @@ -1406,7 +1403,6 @@ sk: new: "Nemáte žiadnu novú tému" read: "Neprečítali ste ešte žiadnu tému." posted: "Nanapísali ste ešte žiadnu tému." - latest: "Nie sú žiadne nové témy. To je smutné." bookmarks: "Nemáte zatiaľ žiadne témy v záložkách." category: "V kategórii %{category} nie je žiadna téma" top: "Nie sú žiadne populárne témy." @@ -1494,7 +1490,6 @@ sk: read_more_MF: "Máte { UNREAD, plural, =0 {} one { 1 neprečítanú } other { # neprečítané } } { NEW, plural, =0 {} one { {BOTH, select, true{a} false { } other{}} 1 novú tému} other { {BOTH, select, true{a } false {} other{}} # nových tém} } na prečítanie, prípadne {CATEGORY, select, true {si pozrite iné témy v {catLink}} false {{latestLink}} other {}}" browse_all_categories: Prezrieť všetky kategórie view_latest_topics: zobraziť najnošie témy - suggest_create_topic: Čo tak vytvoriť novú tému? jump_reply_up: prejsť na predchádzajúcu odpoveď jump_reply_down: prejsť na nasledujúcu odpoveď deleted: "Téma bola vymazaná" diff --git a/config/locales/client.sl.yml b/config/locales/client.sl.yml index 89a23c3728..227996f24a 100644 --- a/config/locales/client.sl.yml +++ b/config/locales/client.sl.yml @@ -1437,7 +1437,6 @@ sl: accept_invite: "Sprejmi povabilo" success: "Vaš račun je ustvarjen in vi ste prijavljeni." name_label: "Ime" - password_label: "Nastavi geslo" optional_description: "(neobvezno)" password_reset: continue: "Nadaljujte na %{site_name}" @@ -1803,8 +1802,6 @@ sl: noreplies: brez odgovora single_user: od samo enega uporabnika post: - count: - label: Minimalno število prispevkov time: label: Objavljeno before: pred @@ -1852,7 +1849,6 @@ sl: new: "Nimate novih tem." read: "Niste prebrali še nobene teme." posted: "Niste objavili še v nobeni temi." - latest: "Ni najnovejših tem." bookmarks: "Nimate tem z zaznamki." category: "Ni tem v kategoriji %{category} ." top: "Ni najboljših tem." @@ -1943,7 +1939,6 @@ sl: read_more_MF: "{ UNREAD, plural, =0 {} one { Je še 1 neprebrana } two { Sta še 2 neprebrani } other { Je še# neprebranih } } { NEW, plural, =0 {} one { {BOTH, select, true{in} false {je } other{}} 1 nova tema ostala} two { {BOTH, select, true{in } false {sta } other{}} 2 novi temi ostali} other { {BOTH, select, true{in } false {so } other{}} # nove teme ostale} }, ali {CATEGORY, select, true {brskaj druge teme v {catLink}} false {{latestLink}} other {}}" browse_all_categories: Brskajte po vseh kategorijah view_latest_topics: poglej najnovejše teme - suggest_create_topic: Ustvari novo temo? jump_reply_up: pojdi na prejšni odgovor jump_reply_down: pojdi na naslednji odgovor deleted: "Tema je bila izbrisana" diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index c49459d026..559c32cee4 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -946,7 +946,6 @@ sq: accept_invite: "Prano ftesën" success: "Llogaria juaj u krijua dhe ju tani mund të identifikoheni." name_label: "Emri" - password_label: "Vendos Fjalëkalim" password_reset: continue: "Vazhdo tek %{site_name}" emoji_set: @@ -1139,7 +1138,6 @@ sq: new: "Nuk ka tema të reja." read: "Nuk keni lexuar asnjë temë deri tani." posted: "Nuk keni shkruajtur tek asnjë temë deri tani." - latest: "Nuk ka tema të fundit. Oh sa keq." bookmarks: "Nuk keni ende tema të preferuara. " category: "Nuk ka tema në: %{category}." top: "Nuk ka tema popullore." @@ -1213,7 +1211,6 @@ sq: read_more_MF: "Keni { UNREAD, plural, =0 {} one { 1 shkrim të palexuar } other { # shkrime të palexuara } } { NEW, plural, =0 {} one { {BOTH, select, true{dhe } false {është } other{}} 1 shkrim të ri} other { {BOTH, select, true{dhe } false {} other{}} # shkrime të reja} } akoma, ose {CATEGORY, select, true {shiko temat e tjera në kategorinë {catLink}} false {{latestLink}} other {}}" browse_all_categories: Shfleto kategoritë view_latest_topics: shiko temat më të fundit - suggest_create_topic: Pse nuk hapni një temë të re? jump_reply_up: hidhe tek përgjigja paraardhëse jump_reply_down: hidhu tek përgjigja pasardhëse deleted: "Tema është fshirë" diff --git a/config/locales/client.sr.yml b/config/locales/client.sr.yml index 0ec6f930e9..e700e4ab70 100644 --- a/config/locales/client.sr.yml +++ b/config/locales/client.sr.yml @@ -854,7 +854,6 @@ sr: welcome_to: "Dobrodošao na %{site_name}!" success: "Tvoj nalog je kreiran i sada si ulogovan." name_label: "Ime foruma" - password_label: "Postavi Šifru" emoji_set: apple_international: "Apple/International" google: "Google" @@ -996,7 +995,6 @@ sr: new: "Nemate novih tema." read: "Niste još pročitali ni jednu temu." posted: "Niste se još objavljivali ni u jendoj temi." - latest: "Nema poslednjih tema. To je tužno." bookmarks: "Nemate markiranih tema još." category: "Nema tema u %{category}." top: "Nema glavnih tema." @@ -1065,7 +1063,6 @@ sr: read_more_MF: "There { UNREAD, plural, =0 {} one { is 1 unread } other { are # unread } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 new topic} other { {BOTH, select, true{and } false {are } other{}} # new topics} } remaining, or {CATEGORY, select, true {browse other topics in {catLink}} false {{latestLink}} other {}}" browse_all_categories: Pregledaj sve kategorije view_latest_topics: pogledaj najnovije teme - suggest_create_topic: Zašto ne otvorite temu? jump_reply_up: skoči na raniji odgovor jump_reply_down: skoči na kasniji odgovor deleted: "Tema je obrisana" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 15b050910a..811ce4276d 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -122,6 +122,7 @@ sv: twitter: "Dela på Twitter" facebook: "Dela på Facebook" email: "Skicka via e-post" + url: "Kopiera och dela URL" action_codes: public_topic: "gjorde det här ämnet publikt %{when}" private_topic: "gjorde det här ämnet till ett personligt meddelande %{when}" @@ -1074,6 +1075,7 @@ sv: taken: "Tyvärr är den e-postadressen inte tillgänglig." error: "Det uppstod ett problem under bytet av din e-postadress. Kanske används den redan?" success: "Vi har skickat e-post till den adressen. Var god följ bekräftelseinstruktionerna." + success_via_admin: "Vi har skickat e-post till den adressen. Var god följ bekräftelseinstruktionerna." success_staff: "Vi har skickat e-post till din nuvarande adress. Vänligen följ instruktionerna för konfirmering." change_avatar: title: "Ändra din profilbild" @@ -1111,6 +1113,7 @@ sv: sso_override_instructions: "E-postadressen kan uppdateras från SSO-leverantören." no_secondary: "Inga sekundära e-postadresser" instructions: "Visas aldrig offentligt." + admin_note: "Obs! En administratörsanvändare som ändrar annan icke-administratörsanvändares e-postadress anger att användaren har förlorat tillgången till sin ursprungliga adress, så ett återställnings-e-postmeddelande kommer att skickas till den nya adressen. Inget ändras förrän användaren har slutfört lösenordsåterställningsprocessen." ok: "Vi skickar e-post till dig för bekräftelse" required: "Vi ber dig ange en e-postadress" invalid: "Vi ber dig ange en giltig e-postadress" @@ -1584,7 +1587,7 @@ sv: accept_invite: "Acceptera inbjudan" success: "Ditt konto har skapats och du är nu inloggad." name_label: "Namn" - password_label: "Välj lösenord" + password_label: "Lösenord" optional_description: "(valfri)" password_reset: continue: "Fortsätt till %{site_name}" @@ -1603,29 +1606,6 @@ sv: categories_and_top_topics: "Kategorier och toppämnen" categories_boxes: "Boxar med underkategorier" categories_boxes_with_topics: "Boxar med utvalda ämnen" - base_font_setting: - helvetica: "Helvetica/Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "RobotoCondensed" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "Ubuntu" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" shortcut_modifier_key: shift: "Skift" ctrl: "Ctrl" @@ -1813,6 +1793,8 @@ sv: toggle_topic_bump: label: "Växla ämnesknuff" desc: "Svara utan att ändra senaste svarsdatum" + reload: "Ladda om" + ignore: "Ignorera" notifications: tooltip: regular: @@ -1985,11 +1967,21 @@ sv: single_user: innehåller en ensam användare post: count: - label: Minsta antalet inlägg + label: Inlägg + min: + placeholder: minst + max: + placeholder: maximalt time: label: Publicerad before: innan after: efter + views: + label: Visningar + min_views: + placeholder: minst + max_views: + placeholder: maximalt hamburger_menu: "gå till en annan ämneslista eller kategori" new_item: "ny" go_back: "gå tillbaka" @@ -2026,12 +2018,14 @@ sv: choose_new_tags: "Välj nya taggar för de här ämnena:" choose_append_tags: "Välj nya taggar att lägga till för dessa ämnen:" changed_tags: "Taggarna för de här ämnena ändrades." + remove_tags: "Ta bort taggar" none: unread: "Du har inga olästa ämnen." new: "Du har inga nya ämnen." read: "Du har inte läst några ämnen ännu." posted: "Du har inte postat i några ämnen ännu." - latest: "Det finns inga senaste ämnen, tråkigt nog." + ready_to_create: "Redo att " + latest: "Ni är alla ikapp!" bookmarks: "Du har inga bokmärkta ämnen ännu." category: "Det finns inga ämnen i %{category}." top: "Det finns inga toppämnen." @@ -2119,7 +2113,7 @@ sv: browse_all_categories: Bläddra bland alla kategorier browse_all_tags: Bläddra bland alla taggar view_latest_topics: visa senaste ämnen - suggest_create_topic: Varför inte skapa ett ämne? + suggest_create_topic: starta en ny konversation? jump_reply_up: hoppa till tidigare svar jump_reply_down: hoppa till senare svar deleted: "Ämnet har raderats" diff --git a/config/locales/client.sw.yml b/config/locales/client.sw.yml index 1826fa57a3..6055e8812e 100644 --- a/config/locales/client.sw.yml +++ b/config/locales/client.sw.yml @@ -1120,7 +1120,6 @@ sw: accept_invite: "Kubali mwaliko" success: "Akaunti yako imetengenezwa na sasa unaweza kuingia." name_label: "Jina" - password_label: "Andika Neno la siri" optional_description: "(sio muhimu)" password_reset: continue: "endelea kwenye %{site_name}" @@ -1414,8 +1413,6 @@ sw: noreplies: haina majibu single_user: ina mtumiaji mmoja post: - count: - label: Namba ya Chini ya Chapisho time: label: Chapishwa before: kabla @@ -1460,7 +1457,6 @@ sw: new: "Hauna mada mpya." read: "Haujasoma mada yoyote." posted: "Bado haujachapisha kwenye mada yoyote." - latest: "Hakuna mada mpya. Hii ni huzuni." bookmarks: "Hauja alamisha mada yoyote." category: "Hakuna %{category} mada." top: "Hakuna mada za juu." @@ -1534,7 +1530,6 @@ sw: read_more_MF: "Kuna { UNREAD, plural, =0 {} one { ni haijasomwa 1 } other { ni haijasomwa # } } { NEW, plural, =0 {} one { {BOTH, select, true{na } false {ni } other {}} madampya 1} other { {BOTH, select, true{na } false {ni } other{}} mada mpya #} } zilizobaki, au {CATEGORY, select, true {vinjari mada zingine ndani ya {catLink}} false {{latestLink}} other {}}" browse_all_categories: Vinjari kategoria zote view_latest_topics: tembelea mada mpya - suggest_create_topic: Kwa nini usitengeneze mada? jump_reply_up: fikia jibu la awali jump_reply_down: fikia jibu la baadaye deleted: "Mada imefutwa" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index 5d9c10c8d7..038bf21f4b 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -678,7 +678,6 @@ te: accept_title: "ఆహ్వానం" welcome_to: "%{site_name} కు సుస్వాగతం!" name_label: "పేరు" - password_label: "సంకేతపదం అమర్చండి" optional_description: "(ఐచ్ఛికం)" password_reset: continue: "%{site_name} కు కొనసాగండి" @@ -797,7 +796,6 @@ te: new: "మీకు కొత్త విషయాలు లేవు" read: "మీరింకా ఏ విషయాలూ చదవలేదు." posted: "మీరింకా ఏ విషయాలూ రాయలేదు." - latest: "కొత్త విషయాలు లేవు. అహో ఎంతటి విపరిణామం." bookmarks: "మీకింకా ఎట్టి పేజీక విషయాలూ లేవు." category: "ఎట్టి %{category} విషయాలూ లేవు" top: "ఎట్టి అగ్ర విషయాలూ లేవు." @@ -857,7 +855,6 @@ te: read_more: "మరిన్ని చదవాలనుకుంటున్నారా? %{catLink} లేదా %{latestLink}." browse_all_categories: అన్ని వర్గాలూ జల్లించు view_latest_topics: తాజా విషయాలు చూడు - suggest_create_topic: ఓ విషయమెందుకు సృష్టించకూడదూ? jump_reply_up: పాత జవాబుకు వెళ్లు jump_reply_down: తరువాతి జవాబుకు వెళ్లు deleted: "ఈ విషయం తొలగించబడింది" diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml index 60156cd89b..31d356e098 100644 --- a/config/locales/client.th.yml +++ b/config/locales/client.th.yml @@ -1252,7 +1252,6 @@ th: accept_invite: "ยอมรับคำเชิญ" success: "บัญชีของคุณถูกสร้าง คุณได้เข้าสู่ระบบแล้ว" name_label: "ชื่อ" - password_label: "ตั้งรหัสผ่าน" optional_description: "(ทางเลือก)" password_reset: continue: "ดำเนินการต่อไปยัง %{site_name}" @@ -1596,7 +1595,6 @@ th: new: "คุณไม่มีกระทู้ใหม่" read: "คุณยังไม่ได้อ่านกระทู้ใดๆเลย" posted: "คุณยังไม่ได้โพสต์ในกระทู้ใดๆเลย" - latest: "ยังไม่มีกระทู้ล่าสุด น่าเสียใจจริงๆ" bookmarks: "คุณยังไม่ได้บุ๊กมาร์กกระทู้ใดๆเลย" category: "ไม่มีกระทู้ในหมวด %{category}" top: "ไม่มีกระทู้ยอดนิยม" @@ -1662,7 +1660,6 @@ th: read_more: "ต้องการอ่านเพิ่มใช่ไหม %{catLink} หรือ %{latestLink}" unread_indicator: "ยังไม่มีสมาชิกอ่านโพสต์ล่าสุดของกระทู้นี้" view_latest_topics: ดูกระทู้ล่าสุด - suggest_create_topic: ทำไมไม่สร้างกระทู้ล่ะ jump_reply_up: ข้ามไปยังคำตอบก่อนหน้านี้ jump_reply_down: ข้ามไปยังคำตอบหลังจากนี้ deleted: "กระทู้ถูกลบ" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 3dfc3ff089..2069754f3a 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -1417,7 +1417,6 @@ tr_TR: accept_invite: "Daveti kabul et" success: "Hesabın oluşturuldu ve şimdi giriş yaptın." name_label: "İsim" - password_label: "Şifre Belirle" optional_description: "(isteğe bağlı)" password_reset: continue: "%{site_name} devam et" @@ -1775,8 +1774,6 @@ tr_TR: noreplies: sıfır cevabı olan single_user: tek kullanıcı içeren post: - count: - label: En az gönderi sayısı time: label: Gönderilen before: önce @@ -1822,7 +1819,6 @@ tr_TR: new: "Yeni konun yok." read: "Henüz herhangi bir konu okumadın." posted: "Henüz herhangi bir konuda gönderi yapmadın" - latest: "Güncel bir konu yok. Bu üzücü." bookmarks: "Henüz bir konu işaretlememişsin. " category: "%{category} konusu yok." top: "Popüler bir konu yok." @@ -1907,7 +1903,6 @@ tr_TR: read_more_MF: "{ UNREAD, plural, =0 {} one { 1 okunmamış mesaj } other { # okunmamış mesaj } } { NEW, plural, =0 {} one { {BOTH, select, true{ve} false {} other{}} 1 yeni konu} other { {BOTH, select, true{ve } false {} other{}} # yeni konu} } var. {CATEGORY, select, true {{catLink} kategorisine} false {{latestLink}} other {}} göz atabilirsin." browse_all_categories: Bütün kategorilere göz at view_latest_topics: Son konuları görüntüle - suggest_create_topic: Konu oluşturmaya ne dersin? jump_reply_up: Önceki cevaba geç jump_reply_down: Sonraki cevaba geç deleted: "Konu silindi " diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index 7041569610..bf5a5cce9b 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -166,6 +166,7 @@ uk: twitter: "Поділитися в Twitter" facebook: "Поділитися у Facebook" email: "Надіслати електронною поштою" + url: "Скопіювати та поширити посилання" action_codes: public_topic: "робить цю тему публічною %{when}" private_topic: "робить цю тему особистим повідомленням %{when}" @@ -1154,6 +1155,7 @@ uk: taken: "Даруйте, ця адреса не доступна." error: "При зміні адреси електронної пошти сталася помилка. Можливо, ця адреса вже використовується?" success: "Ми надіслали листа на цю скриньку. Будь ласка, виконайте інструкції щодо підтвердження." + success_via_admin: "Ми надіслали листа на цю скриньку. Будь ласка, виконайте з листа інструкції для підтвердження." success_staff: "Ми надіслали листа на вашу поточну скриньку. Будь ласка, виконайте інструкції щодо підтвердження." change_avatar: title: "Змінити своє зображення профілю" @@ -1191,6 +1193,7 @@ uk: sso_override_instructions: "Електронну пошту можна оновити через SSO-провайдера." no_secondary: "Немає другорядних електронних скриньок" instructions: "Ніколи не показується публічно." + admin_note: "Примітка: якщо адміністратор змінює електронну адресу іншого користувача, який не є адміністратором, це означає, що користувач втратив доступ до свого початкового облікового запису електронної пошти, тому електронний лист для скидання пароля буде надіслано на його нову адресу. Електронна адреса користувача не зміниться, доки він не завершить процес скидання пароля." ok: "Ми надішлемо Вам листа для підтвердження" required: "Будь ласка, введіть адресу електронної пошти" invalid: "Будь ласка, введіть вірний email" @@ -1509,6 +1512,17 @@ uk: enabled: "Сайт працює в режимі \"тільки для читання\". Зараз ви можете продовжувати переглядати сайт, але інші дії будуть недоступні. " login_disabled: "Вхід вимкнено, поки сайт перебуває в режимі лише для читання." logout_disabled: "Вихід відключений, поки сайт в режимі «тільки для читання»" + too_few_topics_and_posts_notice_MF: >- + Почнемо дискусію! Там {currentTopics, plural, one {is # тема} few {are # тем} many {are # тем} other {are # тем}} та {currentPosts, plural, one {# допис} few {# дописів} many {# дописів} other {# дописів}}. Відвідувачам потрібно більше читати та відповідати - ми рекомендуємо {requiredTopics, plural, one {# тема} few {# тем} many {# тем} other {# тем}} and {requiredPosts, plural, one {# допис} few {# дописів} many {# дописів} other {# posts}}. Це повідомлення може бачити лише персонал. + too_few_topics_notice_MF: >- + Розпочнемо старт дискусії! Там {currentTopics, plural, one {is # тема} few {are # тем} many {are # topics} other {are # тем}}. Відвідувачам потрібно більше читати та відповідати – ми рекомендуємо {requiredTopics, plural, one {# тем} few {# тем} many {# тем} other {# тем}}. Це повідомлення може бачити лише персонал. + too_few_posts_notice_MF: >- + Почнемо старт дискусії! Там {currentPosts, plural, one {is # post} few {are # допис} many {are # дописи} other {are # дописів}}. Відвідувачам потрібно більше читати та відповідати – ми рекомендуємо {requiredPosts, plural, one {# допис} few {# дописи} many {# posts} other {# дописів}}. Це повідомлення може бачити лише персонал. + logs_error_rate_notice: + reached_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} other {# errors/hour}} досягнуто обмеження налаштувань сайту {limit, plural, one {# error/hour} other {# errors/hour}}." + reached_minute_MF: "{relativeAge}{rate, plural, one {# error/minute} other {# errors/minute}} досягнуто обмеження налаштувань сайту {limit, plural, one {# error/minute} other {# errors/minute}}." + exceeded_hour_MF: "{relativeAge}{rate, plural, one {# error/hour} few {# errors/hour} many {# errors/hour} other {# errors/hour}} досягнуто обмеження налаштувань сайту {limit, plural, one {# error/hour} few {# errors/hour} many {# errors/hour} other {# errors/hour}}." + exceeded_minute_MF: "{relativeAge}{rate, plural, one {# error/minute} few {# errors/minute} many {# errors/minute} other {# errors/minute}} досягнуто обмеження налаштувань сайту {limit, plural, one {# error/minute} few {# errors/minute} many {# errors/minute} other {# errors/minute}}." learn_more: "дізнатися більше..." all_time: "всього" all_time_desc: "всього створено тем" @@ -1675,7 +1689,7 @@ uk: accept_invite: "Прийняти запрошення" success: "Ваш аккаунт створений та ви можете тепер увійти." name_label: "Ім'я" - password_label: "Встановити пароль" + password_label: "Пароль" optional_description: "(опціонально)" password_reset: continue: "Продовжити на %{site_name}" @@ -1694,29 +1708,6 @@ uk: categories_and_top_topics: "Категорії та головні теми" categories_boxes: "Коробки з підкатегорій" categories_boxes_with_topics: "Коробки з Обраними Темами" - base_font_setting: - helvetica: "Helvetica/Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "RobotoCondensed" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "Ubuntu" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" shortcut_modifier_key: shift: "Shift" ctrl: "Ctrl" @@ -1884,6 +1875,7 @@ uk: draft: Чернетка edit: Редагувати reply_to_post: + label: Відповідь на допис від %{postUsername} desc: Відповідь на конкретний пост reply_as_new_topic: label: Відповісти в новій пов’язаної темі @@ -1909,6 +1901,8 @@ uk: toggle_topic_bump: label: "Не піднімати тему" desc: "Відповісти без зміни дати останньої відповіді" + reload: "Перезавантажити" + ignore: "Ігнорувати" notifications: tooltip: regular: @@ -2095,11 +2089,21 @@ uk: single_user: зез відповідей post: count: - label: Мінімум повідомлень в темі + label: Дописи + min: + placeholder: мінімум + max: + placeholder: максимум time: label: Дата before: перед after: після + views: + label: Перегляди + min_views: + placeholder: мінімум + max_views: + placeholder: максимум hamburger_menu: "перейти до іншого переліку або категорії тем" new_item: "новий" go_back: "повернутися назад" @@ -2138,12 +2142,14 @@ uk: choose_new_tags: "Виберіть нові мітки для цих тем:" choose_append_tags: "Виберіть нові мітки, щоб додати що цих тем:" changed_tags: "Мітки цих тем було змінено." + remove_tags: "Видалити теги" none: unread: "У Вас немає непрочитаних тем." new: "У Вас немає нових тем." read: "Ви ще не прочитали жодної теми." posted: "Ви ще не дописували в жодну тему." - latest: "Останніх тем немає. Шкода." + ready_to_create: "Готовий до " + latest: "Немає оновлених чи нових тем!" bookmarks: "У вас немає обраних тем." category: "В категорії %{category} немає тем." top: "There are no top topics." @@ -2245,7 +2251,7 @@ uk: browse_all_categories: Переглянути всі категорії browse_all_tags: Переглянути всі теги view_latest_topics: перегляньте останні теми - suggest_create_topic: Чому б не створити тему? + suggest_create_topic: розпочати нову бесіду? jump_reply_up: перейти до ранішої відповіді jump_reply_down: перейти до пізнішої відповіді deleted: "Тему було видалено" @@ -2343,7 +2349,7 @@ uk: "2_8": "Ви побачите кількість нових відповідей, тому що стежте за цим розділом." "2_4": "Ви побачите кількість нових відповідей, тому що ви розміщували відповідь в цій темі." "2_2": "Ви побачите кількість нових відповідей, тому що стежте за цією темою." - "2": 'You will see a count of new replies because you read this topic.' + "2": 'Ви побачите кількість нових відповідей, оскільки ви прочитали цю тему.' "1_2": "Ви будете сповіщені, якщо хтось згадує ваше @ім'я чи відповідає вам." "1": "Ви будете сповіщені, якщо хтось згадує ваше @ім'я чи відповідає вам." "0_7": "Ви ігноруєте всі сповіщення у цій категорії." @@ -3989,6 +3995,7 @@ uk: color_definitions: text: "Визначення кольорів" title: "Введіть власні визначення кольорів (лише для досвідчених користувачів)" + placeholder: "\\r\nВикористовуйте цю таблицю стилів, щоб додати власні кольори до списку CSS властивостей.\\r\n\\r\nПриклад: \\r\n\\r\n:root {\\r\n --thetheme-terciary-or-quaternary: # {dark-light-choose ($tertiary, $quaternary)};\\r\n}\\r\n\\r\nДодавайте префікс до імен, щоб уникнути конфліктів з плагінами та / або ядром discourse." head_tag: text: "" title: "HTML-код, який буде вставлений перед міткою " @@ -4585,6 +4592,9 @@ uk: external_name: "Ім'я" external_email: "Електронна пошта" external_avatar_url: "URL фону профілю" + last_payload: "Останнє корисне навантаження" + delete_sso_record: "Видалити запис SSO" + confirm_delete: "Ви впевнені, що хочете видалити цей запис системи єдиного входу (SSO)?" user_fields: title: "Поля користувача" help: "Додати поля, які користувачі зможуть заповнювати." diff --git a/config/locales/client.ur.yml b/config/locales/client.ur.yml index 25279fa8f9..912cd0437c 100644 --- a/config/locales/client.ur.yml +++ b/config/locales/client.ur.yml @@ -1371,7 +1371,6 @@ ur: accept_invite: "دعوت قبول کریں" success: "آپ کا اکاؤنٹ بنا دیا گیا ہے اور اب آپ لاگ اِن کر سکتے ہیں۔" name_label: "نام" - password_label: "پاس ورڈ رکھیں" optional_description: "(اختیاری)" password_reset: continue: "%{site_name} پر جاری رکھیں" @@ -1719,8 +1718,6 @@ ur: noreplies: کے صفر جوابات ہوں single_user: صرف ایک صارف پر مشتمل ہوں post: - count: - label: کم از کم پوسٹ شمار time: label: پوسٹ کیا before: سے پہلے @@ -1766,7 +1763,6 @@ ur: new: "آپ کے پاس کوئی نئے ٹاپک موجود نہیں۔" read: "ابھی تک آپ نے کوئی ٹاپکس نہیں پڑھے۔" posted: "ابھی تک آپ نے کسی ٹاپک میں پوسٹ نہیں کیا۔" - latest: "کوئی تازہ ٹاپک موجود نہیں ہیں۔ یہ اداس کر دینے والی بات ہے۔" bookmarks: "ابھی تک آپ کے بُک مارک کیے ہوے کوئی ٹاپک نہیں ہیں۔" category: "کوئی %{category} کے ٹاپک موجود نہیں ہیں۔" top: "کوئی ٹاپ ٹاپک موجود نہیں ہیں۔" @@ -1844,7 +1840,6 @@ ur: read_more_MF: "{ UNREAD, plural, =0 {} one { 1 غیر پڑھا } other { # غیر پڑھے } } { NEW, plural, =0 {} one { {BOTH, select, true{اور } false {} other{}} 1 نیا ٹاپک} other { {BOTH, select, true{and } false {are } other{}} # نئے ٹاپکس} } باقی، یا {CATEGORY, select, true {میں دیگر ٹاپکس براؤز کریں {catLink}} false {{latestLink}} other {}}" browse_all_categories: تمام زمرہ جات براؤز کریں view_latest_topics: تازہ ترین ٹاپک دیکھیے - suggest_create_topic: کیوں نہ ایک ٹاپک بنائیں؟ jump_reply_up: اِس سے پرانے جواب پر جائیں jump_reply_down: اِس سے نئے جواب پر جائیں deleted: "ٹاپک حذف کردیا گیا ہے" diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml index dda75f2f2e..299e6afce2 100644 --- a/config/locales/client.vi.yml +++ b/config/locales/client.vi.yml @@ -1263,7 +1263,6 @@ vi: your_email: "Địa chỉ email của bạn là %{email}." accept_invite: "Chấp nhận lời mời" name_label: "T" - password_label: "Đặt mật kh" optional_description: "(tùy chọn)" password_reset: continue: "Tiếp tục truy cập %{site_name}" @@ -1454,8 +1453,6 @@ vi: noreplies: không có phản hồi single_user: chứa một người dùng post: - count: - label: Số bài viết tối thiểu time: label: Được gửi before: trước @@ -1493,7 +1490,6 @@ vi: new: "Bạn không có chủ đề mới nào." read: "Bạn vẫn chưa đọc bất kì chủ đề nào." posted: "Bạn vẫn chưa đăng bài trong bất kì một chủ đề nào" - latest: "Chán quá. Chẳng có chủ đề mới nào hết trơn." bookmarks: "Bạn chưa chủ đề nào được đánh dấu." category: "Không có chủ đề nào trong %{category} ." top: "Không có chủ đề top." @@ -1558,7 +1554,6 @@ vi: read_more_MF: "Có { UNREAD, plural, =0 {} one { is 1 unread } other { are # unread } } { NEW, plural, =0 {} one { {BOTH, select, true{and } false {is } other{}} 1 new topic} other { {BOTH, select, true{and } false {are } other{}} # new topics} } remaining, or {CATEGORY, select, true {browse other topics in {catLink}} false {{latestLink}} other {}}" browse_all_categories: Duyệt tất cả các hạng mục view_latest_topics: xem các chủ đề mới nhất - suggest_create_topic: Tại sao không tạo một chủ đề mới? jump_reply_up: nhảy đến những trả lời trước đó jump_reply_down: nhảy tới những trả lời sau đó deleted: "Chủ đề này đã bị xóa" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 9421469a60..263ea4b6ce 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -100,6 +100,7 @@ zh_CN: twitter: "分享到Twitter" facebook: "分享到Facebook" email: "通过电子邮件发送" + url: "复制并分享网址" action_codes: public_topic: "于%{when}将此主题设为公开" private_topic: "于%{when}将该主题转换为私信" @@ -489,8 +490,8 @@ zh_CN: username: "用户名" filter_name: "按用户名筛选" title: "用户" - likes_given: "点赞" - likes_received: "获得赞" + likes_given: "送出" + likes_received: "收到" topics_entered: "浏览" topics_entered_long: "浏览主题" time_read: "阅读时长" @@ -902,7 +903,7 @@ zh_CN: admin_delete: "删除" users: "用户" muted_users: "静音" - muted_users_instructions: "封禁来自这些用户的所有通知和私信。" + muted_users_instructions: "屏蔽来自这些用户的所有通知以及私信。" allowed_pm_users: "允许的" allowed_pm_users_instructions: "仅允许来自这些用户的私信。" allow_private_messages_from_specific_users: "仅允许特定用户向我发送私信" @@ -1071,6 +1072,7 @@ zh_CN: sso_override_instructions: "电子邮件地址可以通过SSO登录来更新。" no_secondary: "没有次邮箱" instructions: "绝不会被公开显示" + admin_note: "注意:一位管理员用户更改另一位非管理员用户的电子邮件,则表明该用户已失去了其原始电子邮件帐户的访问权限,因此重置密码的电子邮件将发送到其新邮箱。在用户完成重置密码流程之前,用户的电子邮件不会被更改。" ok: "将通过邮件验证确认" required: "请输入一个电子邮件地址" invalid: "请填写正确的邮箱地址" @@ -1153,10 +1155,10 @@ zh_CN: notifications: "新通知" contextual: "新建页面内容" like_notification_frequency: - title: "用户被赞时通知提醒" + title: "被赞时通知提醒" always: "始终" - first_time_and_daily: "每天首个被赞" - first_time: "历史首个被赞" + first_time_and_daily: "每天帖子首个被赞" + first_time: "帖子第一次被赞" never: "从不" email_previous_replies: title: "邮件底部包含历史回复" @@ -1272,7 +1274,7 @@ zh_CN: title: "概要" stats: "统计" time_read: "阅读时间" - recent_time_read: "最近阅读时间" + recent_time_read: "近期阅读时间" topic_count: other: "创建主题" post_count: @@ -1286,7 +1288,7 @@ zh_CN: topics_entered: other: "已阅主题" posts_read: - other: "阅读帖子" + other: "已读帖子" bookmark_count: other: "收藏" top_replies: "热门回复" @@ -1300,7 +1302,7 @@ zh_CN: more_badges: "更多徽章" top_links: "热门链接" no_links: "暂无链接。" - most_liked_by: "谁赞得最多" + most_liked_by: "被谁赞得最多" most_liked_users: "赞谁最多" most_replied_to_users: "最多回复至" no_likes: "暂无赞。" @@ -1356,7 +1358,7 @@ zh_CN: refresh: "刷新" home: "首页" read_only_mode: - enabled: "站点正处于只读模式。你可以继续浏览,但是回复、赞和其他操作暂时被禁用。" + enabled: "站点正处于只读模式。你可以继续浏览,但是回复、点赞和其它操作暂时被禁用。" login_disabled: "只读模式下不允许登录。" logout_disabled: "站点处于只读模式时退出登录被禁用。" too_few_topics_and_posts_notice_MF: >- @@ -1533,7 +1535,7 @@ zh_CN: accept_invite: "接受邀请" success: "已创建你的账户,你现在可以登录了。" name_label: "昵称" - password_label: "设置密码" + password_label: "密码" optional_description: "(可选)" password_reset: continue: "转入到 %{site_name}" @@ -1552,29 +1554,6 @@ zh_CN: categories_and_top_topics: "分类和最热主题" categories_boxes: "带子分类的框" categories_boxes_with_topics: "有特色主题的框" - base_font_setting: - helvetica: "Helvetica/Arial" - open_sans: "Open Sans" - oxanium: "Oxanium" - roboto: "Roboto" - lato: "Lato" - noto_sans_jp: "NotoSansJP" - montserrat: "Montserrat" - roboto_condensed: "RobotoCondensed" - source_sans_pro: "SourceSansPro" - oswald: "Oswald" - raleway: "Raleway" - roboto_mono: "RobotoMono" - poppins: "Poppins" - noto_sans: "NotoSans" - roboto_slab: "RobotoSlab" - merriweather: "Merriweather" - ubuntu: "Ubuntu" - pt_sans: "PTSans" - playfair_display: "PlayfairDisplay" - nunito: "Nunito" - lora: "Lora" - mukta: "Mukta" shortcut_modifier_key: shift: "Shift" ctrl: "Ctrl" @@ -1658,7 +1637,7 @@ zh_CN: title_too_long: "标题过长,最多 %{max} 个字" post_missing: "帖子不能为空" post_length: "帖子至少要有%{min} 个字符" - try_like: "试试%{heart}按钮?" + try_like: "你尝试过使用%{heart}按钮了吗?" category_missing: "未选择分类" tags_missing: "你必须至少选择%{count}个标签" topic_template_not_modified: "请通过编辑主题模板来为主题添加详情。" @@ -1733,6 +1712,7 @@ zh_CN: draft: 草稿 edit: 编辑 reply_to_post: + label: 以%{postUsername}的身份回复 desc: 回复特定帖子 reply_as_new_topic: label: 回复为联结主题 @@ -1756,8 +1736,10 @@ zh_CN: label: "共享草稿" desc: "起草一个只对管理人员可见的主题" toggle_topic_bump: - label: "切换主题置顶" - desc: "回复而不更改最新回复日期" + label: "切换主题顶帖" + desc: "回复但不改变最新回复的日期" + reload: "重新加载" + ignore: "忽略" notifications: tooltip: regular: @@ -1779,9 +1761,9 @@ zh_CN: posted: "%{username} %{description}" edited: "%{username} %{description}" liked: "%{username} %{description}" - liked_2: "%{username}, %{username2} %{description}" + liked_2: "%{username}、%{username2} %{description}" liked_many: - other: "%{username}, %{username2} 和其他 %{count} 人 %{description}" + other: "%{username}和%{username2}还有其余%{count}人 %{description}" liked_consolidated_description: other: "你的帖子有%{count}个赞" liked_consolidated: "%{username}%{description}" @@ -1818,7 +1800,7 @@ zh_CN: replied: "新回复" quoted: "引用" edited: "编辑" - liked: "新到赞" + liked: "新的赞" private_message: "新私信" invited_to_private_message: "邀请进行私下交流" invitee_accepted: "邀请已接受" @@ -1923,11 +1905,21 @@ zh_CN: single_user: 只有一个用户参与 post: count: - label: 最小帖子数 + label: 帖子 + min: + placeholder: 最小值 + max: + placeholder: 最大值 time: label: 发表于 before: 之前 after: 之后 + views: + label: 点阅量 + min_views: + placeholder: 最小值 + max_views: + placeholder: 最大值 hamburger_menu: "转到另一个主题列表或分类" new_item: "新" go_back: "返回" @@ -1963,12 +1955,13 @@ zh_CN: choose_new_tags: "为主题选择新标签:" choose_append_tags: "为这些主题添加新标签:" changed_tags: "主题的标签被修改" + remove_tags: "删除标签" none: unread: "你没有未读主题。" new: "你没有近期主题可读。" read: "你尚未阅读任何主题。" posted: "你尚未在任何主题中发帖。" - latest: "没有新的主题。" + ready_to_create: "准备好 " bookmarks: "你没有收藏任何主题。" category: "%{category}分类中没有主题。" top: "没有热门主题。" @@ -2036,7 +2029,7 @@ zh_CN: new_posts: other: "自你上一次阅读此主题后,又有 %{count} 个新帖子发表了" likes: - other: "这个主题收到了 %{count} 个赞" + other: "这个主题收到了%{count}个赞" back_to_list: "返回列表" options: "主题选项" show_links: "显示此主题中的链接" @@ -2049,7 +2042,7 @@ zh_CN: browse_all_categories: 浏览所有分类 browse_all_tags: 浏览所有标签 view_latest_topics: 查阅最新主题 - suggest_create_topic: 创建一个新的主题吧! + suggest_create_topic: 开始讨论一个新的话题? jump_reply_up: 转到更早的回复 jump_reply_down: 转到更新的回复 deleted: "此主题已被删除" @@ -2107,7 +2100,7 @@ zh_CN: auto_publish_to_category: "主题将在%{timeLeft}后被发布到#%{categoryName} 。" auto_close_based_on_last_post: "如在 %{duration}内没有新回复后主题将被关闭。" auto_delete: "主题在%{timeLeft}后将被自动删除。" - auto_bump: "此主题将在%{timeLeft}后自动顶起。" + auto_bump: "这个主题将在%{timeLeft}后自动顶帖。" auto_reminder: "你将在%{timeLeft}后收到该主题的提醒。" auto_delete_replies: "此主题的回复会在%{duration}后自动删除。" auto_close_title: "自动关闭设置" @@ -2144,7 +2137,7 @@ zh_CN: "2_8": "因为你追踪了该分类,所以你会看到新回复的数量。" "2_4": "你会看到新回复的数量的原因是你曾经回复过该主题。" "2_2": "你会看到新回复数量的原因是你正在追踪该主题。" - "2": 'You will see a count of new replies because you read this topic.' + "2": '你会看到新回复的计数是因为你浏览过该主题。' "1_2": "有人@你或回复你时会通知你。" "1": "如果有人@你或回复你,将通知你。" "0_7": "你将忽略关于该分类的所有通知。" @@ -2262,7 +2255,7 @@ zh_CN: help: "通过电子邮件或通知邀请其他人到该主题" to_forum: "将发送一封简洁的邮件,让你的朋友无需注册即可用链接参与讨论。" sso_enabled: "输入其用户名,邀请其人到本主题。" - to_topic_blank: "输入其用户名或者 Email 地址,邀请其人到本主题。" + to_topic_blank: "输入你要邀请到这个主题的用户名或电子邮件地址。" to_topic_email: "你输入了邮箱地址。我们将发送一封邮件邀请,让你的朋友可直接回复该主题。" to_topic_username: "你输入了用户名。我们将发送一个至该主题链接的邀请通知。" to_username: "输入你想邀请的人的用户名。我们将发送一个至该主题链接的邀请通知。" @@ -2388,17 +2381,17 @@ zh_CN: gap: other: "查看 %{count} 个隐藏回复" notice: - new_user: "这是 %{user} 发的第一个帖子 - 让我们欢迎他加入社区!" + new_user: "这是%{user}的首个发帖 - 让我们欢迎他加入社区吧!" returning_user: "从我们上一次看到 %{user} 有一阵子了 — 他上次发帖是 %{time}." unread: "未读帖子" has_replies: other: "%{count} 回复" unknown_user: "(未知或已删除的用户)" has_likes_title: - other: "%{count} 人赞了该贴" - has_likes_title_only_you: "你喜欢了这个帖子" + other: "%{count}人赞了该贴" + has_likes_title_only_you: "你赞了这个帖子" has_likes_title_you: - other: "你和其他 %{count} 人赞了该贴" + other: "你和另外的%{count}人赞了帖" errors: create: "抱歉,在创建你的帖子时发生了错误。请重试。" edit: "抱歉,在编辑你的帖子时发生了错误。请重试。" @@ -2431,7 +2424,7 @@ zh_CN: controls: reply: "开始撰写本帖的回复" like: "点赞此帖" - has_liked: "已赞" + has_liked: "你已赞了这个帖子" read_indicator: "阅读了帖子的用户" undo_like: "取消赞" edit: "编辑本帖" @@ -2476,7 +2469,7 @@ zh_CN: read: other: "看过" like_capped: - other: "和其他 %{count} 人赞了它" + other: "还有其他%{count}人赞了它" read_capped: other: "还有%{count}个其他用户看过" by_you: @@ -2621,7 +2614,7 @@ zh_CN: position_disabled_click: '启用“固定分类位置”设置。' minimum_required_tags: "在一个主题中至少含有多少个标签:" parent: "上级分类" - num_auto_bump_daily: "每天自动碰撞的主题的数量" + num_auto_bump_daily: "每天自动顶贴的主题的数量" navigate_to_first_post_after_read: "阅读主题后导航到第一个帖子" notifications: watching: @@ -2651,7 +2644,7 @@ zh_CN: sort_options: default: "默认" likes: "赞" - op_likes: "原始帖子赞" + op_likes: "原始帖子的赞" views: "阅览量" posts: "帖子" activity: "最后活跃时间" @@ -2744,10 +2737,10 @@ zh_CN: posts: "帖子" posts_long: "本主题有 %{number} 个帖子" posts_likes_MF: | - 这个主题有 {count, plural, other {# 个帖子}}{ratio, select, - low {,有很多人赞了该帖} - med {,有非常多人赞了该帖} - high {,大多数人赞了该帖} + 这个主题共有{count, plural, other {#个回复}}{ratio, select, + low {,赞/帖比较高} + med {,赞/帖比很高} + high {,赞/帖比非常之高} other {}} original_post: "原始帖" views: "浏览" @@ -2760,7 +2753,7 @@ zh_CN: likes: "赞" likes_lowercase: other: "赞" - likes_long: "本主题已有 %{number} 次赞" + likes_long: "这个主题已收到%{number}个赞" users: "用户" users_lowercase: other: "用户" @@ -2915,7 +2908,7 @@ zh_CN: reply_topic: "%{shortcut} 回复主题" reply_post: "%{shortcut} 回复帖子" quote_post: "%{shortcut} 引用帖子" - like: "%{shortcut} 赞帖子" + like: "%{shortcut} 点赞帖子" flag: "%{shortcut} 标记帖子" bookmark: "%{shortcut} 收藏帖子" edit: "%{shortcut} 编辑帖子" @@ -3506,7 +3499,7 @@ zh_CN: create: "创建" create_type: "类型" create_name: "名称" - long_title: "修改你站点的色彩、CSS 和 HTML" + long_title: "修改你的站点的色彩、CSS 和HTML内容" edit: "编辑" edit_confirm: "这是一个远程主题。如果你编辑了CSS/HTML,在下一次更新该主题后这些自定义项目将会被删除。" update_confirm: "这些本地更改将被更新删除。你确定你要继续吗?" @@ -3538,7 +3531,7 @@ zh_CN: convert_component_tooltip: "转换此组件到主题" convert_theme_alert: "你确定转换此组件到主题吗?它将作为%{relatives}的父级删除。" convert_theme_tooltip: "转换此主题到组件" - inactive_themes: "非活动主题:" + inactive_themes: "未使用的主题:" inactive_components: "未使用的组件:" broken_theme_tooltip: "此主题的CSS,HTML或YAML中存在错误" disabled_component_tooltip: "此组件已被停用" @@ -3614,21 +3607,21 @@ zh_CN: text: "CSS" title: "输入自定义 CSS,我们接受所有有效的 CSS 和 SCSS 样式" header: - text: "头部" - title: "输入显示在站点头部的 HTML" + text: "Header" + title: "输入需要显示在网站顶部的HTML" after_header: - text: "添加头部" - title: "输入显示在所有页面的头部之后的 HTML" + text: "After Header" + title: "输入需要显示在所有页面的Header下方的HTML" footer: - text: "页脚" - title: "输入显示在页面尾部后的 HTML" + text: "Footer" + title: "输入需要显示在页面底部的HTML" embedded_scss: - text: "嵌入使用的 CSS" - title: "输入用于嵌入评论的自定义 CSS " + text: "Embedded CSS" + title: "输入自定义的CSS" color_definitions: text: "颜色配置" title: "输入自定义颜色配置(仅限高级用户)" - placeholder: "\\r\n使用该样式将自定义颜色添加到CSS自定义属性列表。\\r\n\\r\n例如:\\r\n\\r\n:root {\\r\n --mytheme-tertiary-or-quaternary: #{dark-light-choose($tertiary, $quaternary)};\\r\n}\\r\n\\r\n强烈建议使用前缀为属性命名以避免与插件或主程序发生冲突。" + placeholder: "\\r\n使用该样式将自定义的颜色添加到CSS自定义属性列表。\\r\n\\r\n例如:\\r\n\\r\n:root {\\r\n --mytheme-tertiary-or-quaternary: #{dark-light-choose($tertiary, $quaternary)};\\r\n}\\r\n\\r\n强烈建议使用前缀为属性命名以避免与插件或主程序发生冲突。" head_tag: text: "" title: "将在 标签前插入的 HTML" @@ -3682,7 +3675,7 @@ zh_CN: description: "用于指示操作成功。" love: name: "赞" - description: "赞按钮的颜色。" + description: "赞同按钮的颜色。" robots: title: "覆盖你的站点的robots.txt文件:" warning: "这会永久性地覆盖所有相关的站点设置。" @@ -4046,7 +4039,7 @@ zh_CN: delete_all_posts: "删除所有帖子" delete_posts_progress: "删除帖子..." delete_posts_failed: "删除帖子时出现问题。" - penalty_post_actions: "你想对相关帖子做什么?" + penalty_post_actions: "你想对关联帖子做什么?" penalty_post_delete: "删除帖子" penalty_post_delete_replies: "删除帖子+所有回复" penalty_post_edit: "编辑帖子" @@ -4191,9 +4184,9 @@ zh_CN: flagged_posts: "被标记的帖子" flagged_by_users: "标记其的用户" likes_given: "点赞" - likes_received: "获得赞" - likes_received_days: "获得赞:独立天数" - likes_received_users: "获得赞:每用户" + likes_received: "获赞" + likes_received_days: "获赞:独立天数" + likes_received_users: "获赞:每用户" suspended: "已封禁(最近6个月)" silenced: "禁言(最近6个月)" qualifies: "符合信任等级3要求" @@ -4210,6 +4203,8 @@ zh_CN: external_name: "外部系统中的名字" external_email: "电子邮件" external_avatar_url: "头像URL" + delete_sso_record: "删除SSO记录" + confirm_delete: "你确定要删除此单点登录(SSO)记录吗?" user_fields: title: "用户属性" help: "增加用户能填写的字段。" @@ -4307,7 +4302,7 @@ zh_CN: secret_list: invalid_input: "输入字段不能为空或包含竖线字符。" default_categories: - modal_description: "你想在已存在的设置上应用此更改吗?这将更改%{count}位现有用户的首选项。" + modal_description: "你想在已存在的设置上应用此更改吗?这将更改%{count}位现有用户的自定义设置。" modal_yes: "是" modal_no: "不,仅应用以后的更改" simple_list: @@ -4405,7 +4400,7 @@ zh_CN: alt: "自定义表情符号预览" delete_confirm: "你确定要删除 :%{name}: emoji 么?" embedding: - get_started: "如果你想要将 Discourse 嵌入至其他网站,添加他们的主机地址。" + get_started: "如果你想要将 Discourse 嵌入至其他网站,请添加他们的域名。" confirm_delete: "你确定要删除这个主机吗?" sample: "使用下列 HTML 代码至你的站点创建和嵌入 Discourse 主题。把REPLACE_ME 替换成你将嵌入至的网址。" title: "嵌入" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index c97310ac98..b6d9f68c9e 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -1261,7 +1261,6 @@ zh_TW: accept_invite: "接受邀請" success: "你的帳號已被建立,且您已經登入了。" name_label: "姓名" - password_label: "設定密碼" optional_description: "(選擇性)" password_reset: continue: "繼續連接至 %{site_name}" @@ -1578,8 +1577,6 @@ zh_TW: noreplies: 沒有回覆 single_user: 只有一個使用者參與 post: - count: - label: 最少貼文數 time: label: 發表於 before: 之前 @@ -1624,7 +1621,6 @@ zh_TW: new: "沒有新的話題。" read: "你尚未閱讀任何話題。" posted: "你尚未在任何話題裡發表貼文。" - latest: "沒有最近的話題。真令人難過。" bookmarks: "您目前沒有把任何話題加入書籤。" category: "沒有 %{category} 的話題。" top: "沒有精選話題。" @@ -1693,7 +1689,6 @@ zh_TW: read_more_MF: "還有 { UNREAD, plural, =0 {} one { 1 個未讀的話題} other { # 個未讀的話題 } } { NEW, plural, =0 {} one { {BOTH, select, true{和 } false {} other{}} 1 個新的話題} other { {BOTH, select, true{和 } false {} other{}} # 個最近的話題} }可以閱讀,或者{CATEGORY, select, true {瀏覽{catLink}中的其他話題} false {{latestLink}} other {}}" browse_all_categories: 瀏覽所有分類 view_latest_topics: 檢視最近的貼文 - suggest_create_topic: 開啟一個新話題吧? jump_reply_up: 跳到更早的回覆 jump_reply_down: 跳到更晚的回覆 deleted: "此話題已被刪除" diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index 7e29253ec3..a4bec1c1c7 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -35,24 +35,50 @@ ar: am: "ص" pm: "م" <<: *datetime_formats - title: "دسكورس" - topics: "الموضوعات " - posts: "المنشورات" - loading: "يحمّل" - powered_by_html: 'مدعومة من دسكورس ، يفضّل عرضه و جافاسكربت مفعّل' - sign_up: "إنشاء حساب" + title: "دِسكورس" + topics: "الموضوعات" + posts: "المشاركات" + loading: "يُحمّل" + powered_by_html: 'تدعمه دِسكورس ، يفضّل عرضه بتفعيل جافاسكربت' + sign_up: "سجّل حسابًا" log_in: "لِج" - submit: "أرسل" - purge_reason: "حُذف آليًّا باعتباره حسابًا إمّا مهجورًا أو غير مفعّل" + submit: "أرسِل" + purge_reason: "حُذف تلقائيًا باعتباره حسابًا إمّا مهجورًا أو غير مفعّل" disable_remote_images_download_reason: "عُطّل تنزيل الصور عن بعد بسبب نفاذ مساحة القرص الحرّة." anonymous: "مجهول" remove_posts_deleted_by_author: "حذفها الكاتب" + redirect_warning: "تعذّر تأكيد أنّ الرابط الذي حدّدته نُشر في هذا المنتدى حقًا. مع ذلك إن أردت المواصلة، فحدّد الرابط أسفله." themes: - other_error: "حصل خطأ عند تحديث الواجهة" + bad_color_scheme: "تعذّر تحديث السمة، مخطّط الألوان غير صالح" + other_error: "حدث خطب ما أثناء تحديث السمة" + compile_error: + unrecognized_extension: "امتداد الملف غير مفهوم: %{extension}" + import_error: + generic: حدث عُطل أثناء استيراد هذه السمة + about_json: "عُطل أثناء الاستيراد: about.json غير موجود، أو غير صالح. أمتأكّد بأنّ هذه سمة دِسكورس حقًا؟" + about_json_values: "يحتوي about.json قيم غير صالحة: %{errors}" + modifier_values: "تحتوي مُعدّلت about.json قيم غير صالحة: %{errors}" + git: "حدث عُطل أثناء استنساخ مستودع غِت، الوصول ممنوع أو لم يُوجد المستودع" + file_too_big: "الملف غير المضغوط كبير جدًا." + unknown_file_type: "لا يظهر بأنّ الملف الذي رفعته سمة دِسكورس صالحة." + settings_errors: + invalid_yaml: "ملف YAML الذي قُدّم غير صالح." + data_type_not_a_number: "ضبط النوع ”%{name}“ غير مدعوم. الأنواع المدعومة هي: الأعداد الصحيحة ”integer“ والقيم المنطقية ”bool“ والقوائم ”list“ والتعدادات ”enum“ والرفع ”upload“" + name_too_long: "لأحد الإعدادا اسم طويل جدًا. الطول الأقصى هو 255" + enum_value_not_valid: "القيمة المحدّدة ليست إحدى خيارات التعداد enum." + number_value_not_valid: "القيمة الجديدة ليست ضمن المجال المسموح." + number_value_not_valid_min_max: "يجب أن تكون بين %{min} و%{max}." + number_value_not_valid_min: "يجب أن تكون أكبر من أو تساوي %{min}." + number_value_not_valid_max: "يجب أن تكون أصغر من أو تساوي %{max}." + string_value_not_valid: "طول القيمة الجديدة ليس ضمن المجال المسموح." + string_value_not_valid_min: "يجب أن يكون عدد المحارف على الأقل %{min}." + string_value_not_valid_max: "يجب أن يكون عدد المحارف على الأكثر %{max}." + locale_errors: + invalid_yaml: "ملف YAML الترجمي غير صالحة" emails: incoming: default_subject: "يحتاج هذا الموضوع عنوانًا" - show_trimmed_content: "اظهر محتوى أقل" + show_trimmed_content: "اعرف المحتوى المقصوص" maximum_staged_user_per_email_reached: "أقصى عدد للدعوات التي يمكن للعضو إرسالها باليوم بالبريد الالكتروني." errors: empty_email_error: "يحدث عندما يكون البريد المستَلم فارغ" @@ -982,9 +1008,11 @@ ar: email_token_valid_hours: "أمارات نسيان كلمة السّرّ/تنشيط الحساب صالحة لمدّة (n) ساعة." enable_badges: "تفعيل نظام الأوسمة." enable_whispers: "السماح للطاقم بعمل نقاشات سرية داخل الموضوعات." + hide_email_address_taken: "لا تُخبر المستخدمين بوجود حساب بنفس عنوان البريد الإلكتروني أثناء تسجيل الحسابات وفي استمارة نسيان كلمة السر." log_out_strict: "عند الخروج، اخرج من كلّ جلسات المستخدم في كلّ الأجهزة" new_version_emails: "إرسال بريد إلكتروني إلى عنوان contact_email عندما نسخة جديدة من ديسكورس هو متاح." invite_expiry_days: "مدة صلاحية مفاتيح دعوة عضو، بالأيام." + invite_only: "يجب على المستخدمين الموثوقين أو طاقم الموقع دعوة كلّ المستخدمين الجدد. التسجيل للعموم معطّل." login_required: "اطلب تسجيل دخول لقرائة محتوي هذا الموقع." min_username_length: "أدنى طول لاسم المستخدم (بالمحارف). تحذير: إن كان هناك أيّة مستخدمين أو مجموعات تملك أسماء أقصر من هذه، فسيعطب الموقع!" min_password_length: "أدنى طول لكلمة السّرّ." @@ -1092,7 +1120,6 @@ ar: faq_url: "إذا كانت لديك معلومات حول إضافة أخرى تريد استخدامها, فضلًا قم بتوفير العنوان كاملاً هنا." tos_url: "اذا كان لديك وثيقه شروط خدمه مستضيفها في مكان اخر و تريد استخدمها, وفر url الكامل هنا" privacy_policy_url: "اذا كان لديك وثيقه سياسه خصوصيه مستضيفها في مكان ما و نريد استخدمها. وفر url الكامل هنا" - staff_like_weight: "كم عدد مرات الترجيح الاضافيه لمنح اعجابات الطاقم " topic_view_duration_hours: "احسب عدد المواضيع التي تمت مشاهدتها مره واحده عبر ip/مستخدم كل N ساعات" user_profile_view_duration_hours: "احسب عدد ملفات التعريف للعضو التي تمت مشاهدتها مرة لكل IP/عضو في N ساعات." levenshtein_distance_spammer_emails: "عند ربط رسائل البريد الإلكتروني spammer، الأرقام والحروف تختلف التي ستبقى تسمح بربط غامض." @@ -1160,7 +1187,6 @@ ar: display_name_on_posts: "عرض الاسم الكامل للعضو على التعليقات بالاضافة الى @username." show_time_gap_days: "إذا تم إجراء وظيفتين في حد كثير من الأيام، عرض الفجوة الزمنية في الموضوع." short_progress_text_threshold: "بعد نعدي عدد المشاركات في الموضوع اكثر من هذا الرقم, شريط المهام فقط سوف يعرض رقم المشاركه الحاليه.اذا غيرت عرض شريط المهام, ربما تحتاج لتغير هذه القيمه" - default_code_lang: "تسليط الضوء علي تركيب جمله اللغه البرمجيه يطبق الي مكعبات اكواد GitHub (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "عندما يبدا احدهم بالرد علي الموضوع و اخر كان اقدم من هذه الايام, سوف يظهر تحذير, عطل عن طريق الضبط الي 0" autohighlight_all_code: "تطبيق تسليط الضوء الاجباري علي الاكواد لكل مكعبات الاكواد لمهياه مسبقا . عندما لا تحدد اللغه بشكل صريح" embed_truncate: "اقتطاع الوظائف المدمجة." @@ -1780,8 +1806,6 @@ ar: title: "منظمات" colors: title: "الواجهة" - themes_further_reading: - title: "الواجهات" icons: title: "أيقونة" homepage: diff --git a/config/locales/server.be.yml b/config/locales/server.be.yml index d8561abb92..1e613ad5ea 100644 --- a/config/locales/server.be.yml +++ b/config/locales/server.be.yml @@ -1166,7 +1166,6 @@ be: privacy_policy_url: "Калі ў вас ёсць дакумент Палітыка прыватнасці размяшчаецца ў іншых месцах, якія вы хочаце выкарыстоўваць, забяспечыць поўны URL тут." log_anonymizer_details: "Незалежна ад таго, каб трымаць дэталі карыстальніка ў часопісе пасля ананімнай. Пры выкананні ў GDPR вам трэба выключыць." newuser_spam_host_threshold: "Колькі разоў новы карыстальнік можа апублікаваць спасылку на той жа хост ў іх `newuser_spam_host_threshold` паведамленняў перад тым, як лічыцца спамам." - staff_like_weight: "Колькі дадатковых вагавой каэфіцыент, каб даць персанал любіць." topic_view_duration_hours: "Граф новага віду тэмы адзін раз у IP" user_profile_view_duration_hours: "Граф новы від профіляў карыстальнікаў адзін раз у IP" levenshtein_distance_spammer_emails: "Пры супастаўленні спамерскіх лістоў, колькасць знакаў розніцы, што будзе па-ранейшаму дазваляе невыразны матч." @@ -1259,7 +1258,6 @@ be: display_name_on_posts: "Паказваць поўнае імя карыстальніка на сваіх пасадах у дадатак да іх @Username." show_time_gap_days: "Калі дзве пасады зроблены гэтыя некалькі дзён адзін ад аднаго, адлюстроўваць прамежак часу ў гэтай тэме." short_progress_text_threshold: "Пасля таго, як колькасць пастоў у тэме ідзе вышэй гэтага ліку, індыкатар пакажа толькі бягучы пост нумар. Калі змяніць шырыню прагрэс бар, вы, магчыма, спатрэбіцца змяніць гэтае значэнне." - default_code_lang: "Сінтаксіс мовы праграмавання па змаўчанні, што асвятляе прымяняецца да блокаў GitHub коды (мовы-аўто, Ruby, Python і г.д.)" warn_reviving_old_topic_age: "Калі нехта пачынае адказваць на тэму, дзе апошні адказ старэйшыя за гэты шмат дзён, будзе выведзена папярэджанне. Можна адключыць, усталяваўшы 0." autohighlight_all_code: "Force прымяніць падсвятленне кода для ўсіх адфарматаваных блокаў кода, нават калі яны не відавочна паказаць мову." embed_truncate: "Абрэзаць укаранёныя паведамленні." @@ -2535,8 +2533,6 @@ be: disabled: "Паколькі мясцовыя лагіны адключаныя, гэта не ўяўляецца магчымым, каб адправіць запрашэнне каму-небудзь. Калі ласка, перайдзіце да наступнага кроку." finished: title: "Ваш Дыскурс гатова!" - description: | - <Р> Калі вы адчуваеце, як змяненне гэтых настроек, паўторна запусціць майстар у любы час < search_logs: graph_title: "пошук граф" joined: "рэгістрацыя" diff --git a/config/locales/server.bg.yml b/config/locales/server.bg.yml index 1387b53d46..7aa97be646 100644 --- a/config/locales/server.bg.yml +++ b/config/locales/server.bg.yml @@ -823,7 +823,6 @@ bg: faq_url: "Въведете пълен URL към страницата FAQ, ако искате да използвате собствена версия." tos_url: "Ако имате документ с \"Правила за ползване\" който се хоства някъде другаде и желаете да го използвате, въведете пълния път до URL адреса тук. " privacy_policy_url: "Ако имате документ \"Декларация за поверителност\", който се хоства някъде другаде и желаете да го използвате, въведете пълния път до URL адреса тук. " - staff_like_weight: "Колко допълнителна тежест да се дава за харесвания от екипа." levenshtein_distance_spammer_emails: "Колко символа разлика в съвпадението може да има, когато се проверява имейла за спам, за да има приблизително съвпадение." max_new_accounts_per_registration_ip: "Ако вече има (n) в ниво на доверие 0 акаунта от това IP (и никой не е от екипа или от по-високо ниво на доверие 2), спри да приемаш нови регистрации от този IP адрес." min_ban_entries_for_roll_up: "Когато щракнете на бутона Сливане, ще създадете нов бан на подмрежа, ако в същата има повече от (N) записа." @@ -869,7 +868,6 @@ bg: display_name_on_posts: "Покажи пълните имена на потребителите в техните публикации като допълнение към потребителските им имена @username." show_time_gap_days: "Ако двете публикации са направени през такъв дълъг период, покажи разликата във времето в темата." short_progress_text_threshold: "След достигането на определен брой публикации в темата, в лентата за напредъка ще бъде видим само броят на текущите публикации. Ако промените ширината на лентата за напредъка, може да се наложи да промените тази стойност. " - default_code_lang: "Подчертаване на езиковия синтаксис по подразбиране е приложен за GitHub code blocks (lang-auto, ruby, python etc.) " warn_reviving_old_topic_age: "Когато някой започне да пише в тема, където последният отговор е от преди много време, ще се покаже предупреждение. Изключи с 0. " autohighlight_all_code: "Оцветявай всички предварително форматирани кодови блокове, дори когато няма изрично посочен език." embed_truncate: "Премахване на вградените публикации." diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml index e1bf543f5b..0f8216e540 100644 --- a/config/locales/server.bs_BA.yml +++ b/config/locales/server.bs_BA.yml @@ -703,7 +703,6 @@ bs_BA: faq_url: "If you have a FAQ hosted elsewhere that you want to use, provide the full URL here." tos_url: "If you have a Terms of Service document hosted elsewhere that you want to use, provide the full URL here." privacy_policy_url: "If you have a Privacy Policy document hosted elsewhere that you want to use, provide the full URL here." - staff_like_weight: "How much extra weighting factor to give staff likes." levenshtein_distance_spammer_emails: "When matching spammer emails, number of characters difference that will still allow a fuzzy match." min_ban_entries_for_roll_up: "Kada kliknete tipku Roll up, stvorit ćete novi unos za subnet ban ako ima najmanje (N) unosa." reply_by_email_enabled: "Enable replying to topics via email." @@ -735,7 +734,6 @@ bs_BA: enable_names: "Allow showing user full names. Disable to hide full names." display_name_on_posts: "Show a user's full name on their posts in addition to their @username." short_progress_text_threshold: "After the number of posts in a topic goes above this number, the progress bar will only show the current post number. If you change the progress bar's width, you may need to change this value." - default_code_lang: "Default programming language syntax highlighting applied to GitHub code blocks (lang-auto, ruby, python etc.)" 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." embed_truncate: "Truncate the embedded posts." @@ -1029,8 +1027,6 @@ bs_BA: title: "Organizacija" colors: title: "Izgled" - themes_further_reading: - title: "Teme" homepage: fields: homepage_style: diff --git a/config/locales/server.ca.yml b/config/locales/server.ca.yml index f1da79edfa..fa53aa2df6 100644 --- a/config/locales/server.ca.yml +++ b/config/locales/server.ca.yml @@ -389,14 +389,6 @@ ca: Podeu editar la vostra darrera resposta per a afegir una citació. Per a fer-ho, seleccioneu text i premeu el botó de cita resposta que apareixerà. És més fàcil seguir un tema quan té poques respostes en profunditat que no pas moltes respostes curtes i individuals. - get_a_room: | - ### Anima tothom a participar en la conversa - - Heu respost %{count} vegades a @%{reply_username} en aquest tema en particular. - - Una gran discussió implica moltes veus i perspectives. ¿Podeu implicar algú més? - - I no us en descuideu: si voleu continuar la conversa amb aquest usuari en concret fora de la vista pública, [envieu-li un missatge personal] (%{base_path}/u/%{reply_username}). too_many_replies: | ### Heu assolit el límit de respostes per a aquest tema @@ -1605,7 +1597,6 @@ ca: privacy_policy_url: "Si teniu un document de política de privacitat que voleu utilitzar allotjat en un altre lloc, faciliteu l'adreça URL completa aquí." log_anonymizer_details: "Si s'han de mantenir o no els detalls d'un usuari en el registre (log) després de ser anonimitzat. Si compliu amb GDPR, heu de desactivar-ho." newuser_spam_host_threshold: "Quantes vegades un usuari nou pot publicar un enllaç al mateix amfitrió dins de les seves `newuser_spam_host_threshold` publicacions abans de ser considerat brossa." - staff_like_weight: "Factor de ponderació addicional per als 'M'agrada' de l'equip responsable." 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)." @@ -1739,7 +1730,6 @@ ca: display_name_on_posts: "Mostra el nom complet de l'usuari en les seves publicacions a més del seu @nomdusuari." show_time_gap_days: "Si es fan dues publicacions amb aquesta diferència de dies, mostra la diferència de temps en el tema." short_progress_text_threshold: "Després que el nombre de publicacions en un tema superi aquesta xifra, la barra de progrés només mostrarà el nombre actual de publicacions. Si voleu canviar l'amplada de la barra de progrés, cal modificar aquest valor." - default_code_lang: "Ressaltament de sintaxi del llenguatge de programació per defecte aplicat a blocs de codi de GitHub (lang-auto, ruby, python, etc.)." warn_reviving_old_topic_age: "Es mostra un avís quan algú comença una resposta a un tema on la darrera resposta té més dies que els indicats. Per a inhabilitar-ho, deixeu-ho a 0." autohighlight_all_code: "Obliga l'ús de codi ressaltat per a tots els blocs de codi preformatat, fins i tot quan no n'hagin especificat el llenguatge." highlighted_languages: "Regles incloses de ressaltat de sintaxi. (Advertència: incloure-hi massa llenguatges pot afectar el rendiment.) Vegeu: https://highlightjs.org/static/demo per a una demostració." @@ -2698,10 +2688,6 @@ ca: confirm_new_email: title: "Confirmeu l'adreça de correu nova" subject_template: "[%{email_prefix}] Confirmeu la vostra nova adreça de correu" - text_body_template: | - Confirmeu la vostra nova adreça electrònica per a %{site_name} fent clic en l'enllaç següent: - - %{base_url}/u/confirm-new-email/%{email_token} confirm_old_email: title: "Confirmeu l'adreça de correu antiga" subject_template: "[%{email_prefix}] Confirmeu la vostra adreça de correu actual" @@ -3193,8 +3179,6 @@ ca: placeholder: "San Francisco, Califòrnia" colors: title: "Aparença" - themes_further_reading: - title: "Aparences" logos: title: "Logos" fields: @@ -3242,8 +3226,6 @@ ca: disabled: "Atès que els inicis de sessió locals no estan habilitats, no és possible enviar invitacions a ningú. Procediu al pas següent." finished: title: "El vostre Discourse està a punt!" - description: | -

Si mai us ve de gust canviar aquests paràmetres, torneu a executar l'assistent en qualsevol moment o visiteu la secció d'administració; la trobareu vora la icona de clau anglesa en el menú del lloc web.

Divertiu-vos i bona sort construint la vostra nova comunitat!

search_logs: graph_title: "Recompte de cerques" joined: "Registrat" diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml index 4e470aa2b2..c46476b26e 100644 --- a/config/locales/server.cs.yml +++ b/config/locales/server.cs.yml @@ -869,7 +869,6 @@ cs: digest_posts: "Maximální počet populárních příspěvků zařazených do email přehledu." enable_mobile_theme: "Používat na mobilních zařízeních verzi přizpůsobenou pro mobily s možností přejít na plnou verzi. Zruště pokud chcete používat vlastní plně responzivní kaskádový styl." short_progress_text_threshold: "After the number of posts in a topic goes above this number, the progress bar will only show the current post number. If you change the progress bar's width, you may need to change this value." - default_code_lang: "Default programming language syntax highlighting applied to GitHub code blocks (lang-auto, ruby, python etc.)" tags_sort_alphabetically: "Zobrazit štítky dle abecedy. Standardně jsou setříděny dle popularity." errors: invalid_email: "Neplatná emailová adresa." @@ -1132,8 +1131,6 @@ cs: title: "Organizace" colors: title: "Téma" - themes_further_reading: - title: "Motivy" logos: title: "Loga" fields: diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml index f751964226..47e1e1ed81 100644 --- a/config/locales/server.da.yml +++ b/config/locales/server.da.yml @@ -1028,7 +1028,6 @@ da: faq_url: "Hvis du hoster en FAQ et andet sted kan du indtaste den fulde URL her." 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 ekstra vægt har hjælperteamets likes." 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 flagmarkeringer fra unikke brugere, der skal til for automatisk at pause et emne til ingriben" auto_respond_to_flag_actions: "Aktivér automatisk besvarelse, når en flagmarkering fjernes." @@ -1057,7 +1056,6 @@ da: disable_avatar_education_message: "Deaktivér hjælpetekst for at skifte sin avatar." full_name_required: "Fuldt navn er et påkrævet felt på en brugerprofil." short_progress_text_threshold: "Når antallet af indlæg overstiger dette tal viser statuslinjen kun det aktuelle indlægsnummer. Hvis du ændrer bredden af statuslinjen kan det være nødvendigt at opdatere denne værdi." - default_code_lang: "Standard syntax highlighting som bruges i GitHub kodeblokke (lang-auto, ruby, python etc.)." embed_truncate: "Afkort de indlejrede indlæg." embed_support_markdown: "Support markdown formatering af indlejrede indlæg." embed_post_limit: "Maksimum antal indlæg, der skal indlejres." @@ -1751,8 +1749,6 @@ da: title: "Organisation" colors: title: "Tema" - themes_further_reading: - title: "Temaer" logos: title: "Logoer" fields: diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index beb8547587..0ced2ca7c1 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -403,14 +403,6 @@ de: Du kannst deine letzte Antwort bearbeiten, um ein Zitat hinzuzufügen, indem du den Text auswählst und auf die erscheinende Schaltfläche Zitat klickst. Es ist für alle einfacher, Themen zu lesen, die wenige tiefgehende Antworten haben statt vielen kleinen und einzelnen Antworten. - get_a_room: | - ### Ermutige jeden, bei dieser Konversation mitzumachen. - - Du hast @%{reply_username} zu diesem Thema %{count} mal geantwortet! - - Eine großartige Diskkussion bezieht viele Stimmen und Sichtweisen ein. Kannst du noch andere einbeziehen? - - Und vergiss nicht, wenn du ein Gespräch mit dieser bestimmten Person ausserhalb der Öffentlichkeit ausführlich fortsetzen möchtest, [schicke ihr eine persönliche Nachricht](%{base_path} (/u/) %{reply_username}). too_many_replies: | ### Du hast das Antwort-Beschränkung für dieses Thema erreicht @@ -1611,7 +1603,6 @@ de: privacy_policy_url: "Die vollständige URL zu deinen extern gehosteten Datenschutzrichtlinien, sofern vorhanden." log_anonymizer_details: "Sollen die Details eines Benutzers nach der Anonymisierung im Protokoll aufbewahrt werden. Zur Einhaltung der DSGVO muss dies abgeschaltet werden." newuser_spam_host_threshold: "Wie häufig kann ein neuer Benutzer Links der gleichen Domain innerhalb ihrer `newuser_spam_host_threshold` Beiträge schreiben, ohne als Spam eingeordnet zu werden." - staff_like_weight: "Zusätzlicher Gewichtungsfaktor für Likes vom Team." topic_view_duration_hours: "Alle (n) Stunden einen neuen Themenaufruf pro IP/Benutzer zählen." user_profile_view_duration_hours: "Alle (n) Stunden einen neuen Profilaufruf pro IP/Benutzer zählen." 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." @@ -1749,7 +1740,6 @@ de: display_name_on_posts: "Zeige zusätzlich zum @Benutzernamen auch den vollen Namen des Benutzers bei seinen Beiträgen." show_time_gap_days: "Wenn zwei Beiträge eine bestimmte Anzahl an Tagen auseinander liegen, zeige die Zeitdifferenz im Beitrag an." short_progress_text_threshold: "Sobald die Anzahl an Beiträgen in einem Thema diese Nummer übersteigt, zeigt der Fortschrittsbalken nur noch die aktuelle Beitragsnummer. Dieser Wert sollte angepasst werden, falls die die Breite des Fortschrittsbalkens verändert wird." - default_code_lang: "Standard Syntax Highlighting, dass auf GitHub Code Blöcke angewendet wird. (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "Wenn jemand beginnt auf ein Thema zu antworten, dessen letzte Antwort älter als diese Anzahl an Tagen ist, wird eine Warnung angezeigt. Deaktiviere dies durch setzen auf 0." autohighlight_all_code: "Erzwinge Syntaxhervorhebung für alle Quellcode-Blöcke, auch dann wenn keine Sprache angeben wurde." highlighted_languages: "Aktivierter Regeln für das Syntax-Hervorhebung. (Warnung: Das Aktivieren zu vieler Sprachen kann die Leistung beeinträchtigen) siehe: https://highlightjs.org/static/demo für eine Demonstration" @@ -2974,10 +2964,6 @@ de: confirm_new_email: title: "E-Mail-Adresse bestätigen (an neue)" subject_template: "[%{email_prefix}] Bestätige deine neue E-Mail-Adresse" - text_body_template: | - Bestätige bitte deine neue E-Mail Adresse für %{site_name} durch einen Klick auf den folgenden Link: - - %{base_url}/u/confirm-new-email/%{email_token} confirm_old_email: title: "E-Mail-Adresse bestätigen (an alte)" subject_template: "[%{email_prefix}] Bestätige deine aktuelle E-Mail-Adresse" @@ -3858,8 +3844,6 @@ de: placeholder: "Berlin" colors: title: "Design" - themes_further_reading: - title: "Designs" logos: title: "Logos" fields: @@ -3907,9 +3891,6 @@ de: disabled: "Lokale Anmeldungen sind deaktiviert. Daher ist es nicht möglich, Einladungen zu verschicken. Bitte fahre mit dem nächsten Schritt fort." finished: title: "Dein Discourse ist bereit!" - description: | -

Wenn du diese Einstellungen jemals ändern möchtest, führe diesen Assistenten jederzeit erneut aus oder besuche deinen Administrationsbereich; du findest ihn neben dem Schraubenschüssel-Symbol im Seitenmenü..

-

Viel Spaß, und viel Glück beim Aufbauen deiner neuen Community!

search_logs: graph_title: "Anzahl Suchen" joined: "Beigetreten" diff --git a/config/locales/server.el.yml b/config/locales/server.el.yml index d37f76e3c3..37f5c11a45 100644 --- a/config/locales/server.el.yml +++ b/config/locales/server.el.yml @@ -1167,7 +1167,6 @@ el: tos_url: "Εάν έχετε ένα έγγραφο Όρων Χρήσης το οποίο φιλοξενείται αλλού και θέλετε να το χρησιμοποιήσετε, καταχωρήστε την πλήρη διεύθυνση URL εδώ." privacy_policy_url: "Εάν έχετε ένα έγγραφο πολιτικής προστασίας προσωπικών δεδομένων το οποίο φιλοξενείται αλλού και θέλετε να το χρησιμοποιήσετε, καταχωρήστε την πλήρη διεύθυνση URL εδώ." newuser_spam_host_threshold: "Πόσες φορές ένας νέος χρήστης μπορεί να αναρτήσει ένα σύνδεσμο προς την ίδια ιστοσελίδα εντός του ορίου 'newuser_spam_host_threshold' αναρτήσεων, πριν αυτές θεωρηθούν spam." - staff_like_weight: "Πόσο επιπλέον συντελεστή βαρύτητας να αποδίδεται στα likes των συνεργατών." topic_view_duration_hours: "Μετρήστε μια νέα προβολή νήματος μία φορά ανά IP/User κάθε N ώρες" user_profile_view_duration_hours: "Μετρήστε μια νέα προβολή προφίλ χρήστη μία φορά ανά IP/User κάθε N ώρες" levenshtein_distance_spammer_emails: "Στην αντιστοίχιση διευθύνσεων email των σπάμερ, η διαφορά στον αριθμό των χαρακτήρων η οποία θα επιτρέπει μια ασαφή αντιστοίχιση." @@ -1264,7 +1263,6 @@ el: display_name_on_posts: "Να εμφανίζεται το πλήρες όνομα του χρήστη στις δημοσιεύσεις μαζί με το @όνομαχρήστη του." show_time_gap_days: "Αν δύο αναρτήσεις δημιουργηθούν με τόσες πολλές ημέρες μεταξύ τους, εμφάνιζε το χρονικό κενό στο θέμα. " short_progress_text_threshold: "Όταν ο αριθμός των αναρτήσεων σε ένα θέμα ξεπεράσει αυτό τον αριθμό, η γραμμή προόδου θα δείχνει μόνο τον τωρινό αριθμό αναρτήσεων. Αν αλλάξεις το πλάτος της γραμμής προόδου, μπορεί να χρειαστεί να αλλάξεις αυτή την τιμή." - default_code_lang: "Το συντακτικό της προκαθορισμένης γλώσσας προγραμματισμού στο οποίο έχει δωθεί έμφαση θα εφαρμοστεί στα code blocks του GitHub (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "Όταν κάποιος αρχίζει να απαντά σε ένα θέμα όπου η τελευταία απάντηση είναι παλαιότερη από τόσες πολλές ημέρες, εμφανίζεται μία προειδοποίηση. Απενεργεποίησε θέτοντας το ίσο με 0. " autohighlight_all_code: "Αναγκαστική εφαρμογή έμφασης του κώδικα σε όλα τα διαμορφωμένα code blocks ακόμα και αν δεν έχουν προσδιορίσει τη γλώσσα." embed_truncate: "Περικόψτε τις ενσωματωμένες δημοσιεύσεις." @@ -2710,8 +2708,6 @@ el: title: "Οργάνωση" colors: title: "Θέμα" - themes_further_reading: - title: "Θέματα" logos: title: "Λογότυπα" fields: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index afb1e819ae..3557859c24 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -196,6 +196,7 @@ en: share_quote_facebook_requirements: "You must set a Facebook app id to enable quote sharing for Facebook." second_factor_cannot_enforce_with_socials: "You cannot enforce 2FA with social logins enabled. You must first disable login via: %{auth_provider_names}" second_factor_cannot_be_enforced_with_disabled_local_login: "You cannot enforce 2FA if local logins are disabled." + second_factor_cannot_be_enforced_with_sso_enabled: "You cannot enforce 2FA if SSO is enabled." local_login_cannot_be_disabled_if_second_factor_enforced: "You cannot disable local login if 2FA is enforced. Disable enforced 2FA before disabling local logins." cannot_enable_s3_uploads_when_s3_enabled_globally: "You cannot enable S3 uploads because S3 uploads are already globally enabled, and enabling this site-level could cause critical issues with uploads" conflicting_google_user_id: 'The Google Account ID for this account has changed; staff intervention is required for security reasons. Please contact staff and point them to
https://meta.discourse.org/t/76575' @@ -205,6 +206,7 @@ en: <<: *errors invite: + expired: "Your invite token has expired. Please contact staff." not_found: "Your invite token is invalid. Please contact staff." not_found_json: "Your invite token is invalid. Please contact staff." not_found_template: | @@ -217,6 +219,10 @@ en: user_exists: "There's no need to invite %{email}, they already have an account!" confirm_email: "

You’re almost done! We sent an activation mail to your email address. Please follow the instructions in the mail to activate your account.

If it doesn’t arrive, check your spam folder.

" cant_invite_to_group: "You are not allowed to invite users to specified group(s). Make sure you are owner of the group(s) you are trying to invite to." + disabled_errors: + sso_enabled: "Invites are disabled because SSO is enabled." + local_logins_disabled: "Invites are disabled because the 'enable local logins' setting is disabled." + invalid_access: "You are not permitted to view the requested resource." bulk_invite: file_should_be_csv: "The uploaded file should be of csv format." @@ -487,7 +493,7 @@ en: You’ve replied %{count} times to @%{reply_username} in this particular topic! - A great discussion involves many voices and perspectives. Can you get anybody else involved? + A great discussion includes many voices and perspectives. Can you get anybody else involved? And don’t forget, if you’d like to continue your conversation with this particular user at length outside of public view, [send them a personal message](%{base_path}/u/%{reply_username}). @@ -2023,6 +2029,7 @@ en: anonymous_account_duration_minutes: "To protect anonymity create a new anonymous account every N minutes for each user. Example: if set to 600, as soon as 600 minutes elapse from last post AND user switches to anon, a new anonymous account is created." hide_user_profiles_from_public: "Disable user cards, user profiles and user directory for anonymous users." + allow_users_to_hide_profile: "Allow users to hide their profile and presence" allow_featured_topic_on_user_profiles: "Allow users to feature a link to a topic on their user card and profile." @@ -2095,7 +2102,7 @@ en: display_name_on_posts: "Show a user's full name on their posts in addition to their @username." show_time_gap_days: "If two posts are made this many days apart, display the time gap in the topic." short_progress_text_threshold: "After the number of posts in a topic goes above this number, the progress bar will only show the current post number. If you change the progress bar's width, you may need to change this value." - default_code_lang: "Default programming language syntax highlighting applied to GitHub code blocks (lang-auto, ruby, python etc.)" + default_code_lang: "Default programming language syntax highlighting applied to GitHub code blocks (auto, nohighlight, ruby, python etc.)" 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" @@ -2228,7 +2235,8 @@ en: push_notifications_prompt: "Display user consent prompt." push_notifications_icon: "The badge icon that appears in the notification corner. A 96×96 monochromatic PNG with transparency is recommended." - base_font: "Font to use in most places on the site. Themes can override." + base_font: "Base font to use for most text on the site. Themes can override via the `--font-family` CSS custom property." + heading_font: "Font to use for headings on the site. Themes can override via the `--heading-font-family` CSS custom property." short_title: "The short title will be used on the user's home screen, launcher, or other places where space may be limited. It should be limited to 12 characters." @@ -3709,6 +3717,18 @@ en: %{base_url}/u/confirm-new-email/%{email_token} + If you did not request this change, please contact %{site_name} admin. + + confirm_new_email_via_admin: + title: "Confirm New Email" + subject_template: "[%{email_prefix}] Confirm your new email address" + text_body_template: | + Confirm your new email address for %{site_name} by clicking on the following link: + + %{base_url}/u/confirm-new-email/%{email_token} + + This email change was requested by a site admin. If you did not request this change, please contact %{site_name} admin. + confirm_old_email: title: "Confirm Old Email" subject_template: "[%{email_prefix}] Confirm your current email address" @@ -3903,7 +3923,7 @@ en: latte: "Latte" summer: "Summer" dark_rose: "Dark Rose" - default_theme_name: "Light" + default_theme_name: "Default" light_theme_name: "Light" dark_theme_name: "Dark" neutral_theme_name: "Neutral" @@ -4544,8 +4564,8 @@ en: tags: title: "Tags" - staff_tag_disallowed: 'The tag "%{tag}" may only be applied by staff.' - staff_tag_remove_disallowed: 'The tag "%{tag}" may only be removed by staff.' + restricted_tag_disallowed: 'You cannot apply the tag "%{tag}".' + restricted_tag_remove_disallowed: 'You cannot remove the tag "%{tag}".' minimum_required_tags: one: "You must select at least %{count} tag." other: "You must select at least %{count} tags." @@ -4689,20 +4709,17 @@ en: placeholder: "San Francisco, California" colors: - title: "Theme" - - themes_further_reading: - title: "Themes" - description: - "Looking to customize your Discourse? Take advantage of our powerful theming system: - - Popular theme components (for more, browse #theme)" + title: "Colors" fonts: title: "Fonts" + fields: + body_font: + label: "Body font" + heading_font: + label: "Heading font" + font_preview: + label: "Preview" logos: title: "Logos" @@ -4759,6 +4776,7 @@ en: title: "Your Discourse is Ready!" description: |

If you ever feel like changing these settings, re-run this wizard any time, or visit your admin section; find it next to the wrench icon in the site menu.

+

It is easy to customize your Discourse even further using our powerful theming system. For examples, check out the top themes and components on meta.discourse.org.

Have fun, and good luck building your new community!

search_logs: diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index bab44835bc..5ca8115910 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -188,6 +188,7 @@ es: cannot_enable_s3_uploads_when_s3_enabled_globally: "No puedes habilitar las cargas S3 porque las cargas S3 ya están habilitadas globalmente, y habilitar este nivel de sitio podría causar problemas críticos con las cargas." conflicting_google_user_id: 'El ID de la cuenta Google para esta cuenta ha cambiado; el staff debe intervenir por razones de seguridad. Por favor, ponte en contacto con el staff y envía esta referencia
https://meta.discourse.org/t/76575' invite: + expired: "Su token de invitación ha caducado. Por favor, póngase en contacto con el personal." not_found: "El código de invitación es inválido. Por favor, ponte en contacto con nuestro equipo." not_found_json: "El código de invitación es inválido. Por favor, ponte en contacto con nuestro equipo." not_found_template: | @@ -200,6 +201,8 @@ es: user_exists: "No hay necesidad de invitar a %{email}, ¡ya tienen una cuenta!" confirm_email: "

¡Ya casi terminas! Te enviamos un correo de activación a tu dirección de correo electrónico. Por favor, sigue las instrucciones que allí se encuentran para activar tu cuenta.

Si no llega, revisa la carpeta de spam o correo no deseado.

" cant_invite_to_group: "No tienes permisos para invitar usuarios a los grupos especificados. Asegúrate de que eres dueño en los grupos a los que estás intentando invitar." + disabled_errors: + invalid_access: "No tienes permitido ver el recurso solicitado." bulk_invite: file_should_be_csv: "El archivo subido debería estar en formato csv." max_rows: "Las primeras %{max_bulk_invites} invitaciones se han enviado. Intenta dividir el archivo en partes más pequeñas." @@ -375,6 +378,7 @@ es: email_already_used_in_category: "«%{email}» ya está siendo utilizado por la categoría «%{category_name}»." cant_allow_membership_requests: "No puedes permitir solicitudes de membresía de un grupo sin ningún propietario." already_requested_membership: "Ya has solicitado la membresía de este grupo." + adding_too_many_users: "Se pueden añadir un máximo de %{limit} usuarios a la vez" default_names: everyone: "todos" admins: "administradores" @@ -437,14 +441,6 @@ es: Este tema es claramente importante para ti – has publicado más del %{percent}% de las respuestas. Podría mejor todavía si le dieras a otras personas la oportunidad de compartir sus puntos de vista también. ¿Podrías invitarles? - get_a_room: | - ### Anima a todos a participar en la conversación - - ¡Has respondido %{count} veces a @%{reply_username} en este tema en particular! - - Una gran discusión involucra muchas voces y perspectivas. ¿Puedes involucrar a alguien más? - - Y no olvides que si deseas continuar tu conversación con este usuario en particular en privado, puedes [enviarle un mensaje privado](%{base_path}/u/%{reply_username}). too_many_replies: | ### Has alcanzado el límite de respuestas para este tema @@ -1448,11 +1444,13 @@ es: invite_code: "El usuario debe ingresar este código para que se le permita el registro de la cuenta, ignorado cuando está vacío (no distingue entre mayúsculas y minúsculas)" approve_suspect_users: "Agregar usuarios sospechosos a la lista por revisión. Los usuarios sospechosos que hayan ingresado una biografía/página web pero que no hayan registrado ninguna actividad de lectura." pending_users_reminder_delay: "Notificar a los moderadores si hay usuarios nuevos que hayan estado esperando aprobación durante más de esta cantidad de horas. Usa -1 para desactivar estas notificaciones." + persistent_sessions: "Los usuarios permanecerán con la sesión abierta aunque cierren su navegador" maximum_session_age: "El usuario permanecerá con su sesión iniciada n horas desde su última visita" ga_universal_tracking_code: "Código de seguimiento de Google Universal Analytics (analytics.js), ejemplo: UA-12345678-9; visita https://google.com/analytics" ga_universal_domain_name: "Nombre del dominio establecido en Google Universal Analytics (analytics.js), ejemplo: misitio.com; visita https://google.com/analytics" ga_universal_auto_link_domains: "Habilitar el seguimiento multidominio de Google Universal Analytics (analytics.js). Los enlaces salientes de estos dominios tendrán la ID del cliente agregados. Revisa La guía de Google para el seguimiento multidominio." enable_escaped_fragments: "Ir a la API Ajax-Crawling de Google si no se detecta ningún webcrawler. Visita https://developers.google.com/webmasters/ajax-crawling/docs/learn-more" + moderators_manage_categories_and_groups: "Permitir a los moderadores administrar categorías y grupos" cors_origins: "Orígenes permitidos para las solicitudes de origen cruzado (CORS). Cada origen debe incluir http:// or https://. La variable env DISCOURSE_ENABLE_CORS debe establecerse como verdadero para activar CORS." allowed_iframes: "Una lista de prefijos de dominios para iframe src que discourse puede permitir de forma segura en publicaciones" slow_down_crawler_user_agents: "Agentes de usuario de crawlers a los que se debe aplicar una cuota limitada en robots.txt utilizando la directiva de retardo de rastreo" @@ -1702,7 +1700,6 @@ es: privacy_policy_url: "Si tienes un documento de política de privacidad alojado en algún otro sitio y que quieras utilizar, ingresa la URL completa aquí." log_anonymizer_details: "Mantener o no los detalles de un usuario en el registro después de ser anonimizados. Para cumplir con la RGPD, deberás apagarlo." newuser_spam_host_threshold: "Cantidad de veces que un usuario nuevo puede publicar un enlace al mismo host dentro del `newuser_spam_host_threshold` de sus publicaciones antes de ser considerado spam." - staff_like_weight: "Factor de peso adicional que otorgan los me gusta dados por los miembros del staff." 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." @@ -1854,7 +1851,6 @@ es: display_name_on_posts: "Mostrar el nombre completo de un usuario en sus publicaciones, además de su @usuario." show_time_gap_days: "Si entre dos publicaciones han pasado este número de días, mostrar el lapso de tiempo en el tema." short_progress_text_threshold: "Después de que el número de publicaciones en un tema alcance esta cifra, la barra de progreso solo mostrará el número de publicaciones actual. Si cambias la anchura de la barra de progreso, deberías revisar este valor." - default_code_lang: "Lenguaje de programación por defecto para aplicar el resaltado de la sintaxis en los bloques de código de GitHub (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "Cuando alguien publica en un tema cuya última respuesta fue hace más tiempo que este número de días, se le mostrará un aviso para desalentar el hecho de revivir una antigua discusión. Establece el valor en 0 para deshabilitar." autohighlight_all_code: "Forzar el resaltado de código a los bloques de código preformateado cuando no se especifique el lenguaje del código." highlighted_languages: "Reglas de resaltado de sintaxis incluidas. (Aviso: incluir demasiados lenguajes puede afectar al rendimiento) Ver: https://highlightjs.org/static/demo para un demo." @@ -2006,6 +2002,9 @@ es: low_weight_invalid: "No puedes poner el peso a un valor superior o igual a 1 o menor que «category_search_priority_very_low_weight»." high_weight_invalid: "No puedes establecer el peso como menor o igual a 1 ni mayor a 'category_search_priority_very_high_weight'." very_high_weight_invalid: "No puedes poner el peso a un valor inferior a «category_search_priority_high_weight»." + allowed_unicode_usernames: + regex_invalid: "La expresión regular no es válida: %{error}" + leading_trailing_slash: "La expresión regular no puede empezar y terminar con una barra." unicode_usernames_avatars: "El sistema interno de avatares no soporta nombres de usuario Unicode." list_value_count: "La lista debe contener exactamente %{count} valores." placeholder: @@ -2488,6 +2487,8 @@ es: ¡Disfruta de tu estancia! [prefs]: %{user_preferences_url} + tl2_promotion_message: + subject_template: "¡Enhorabuena por llegar a un nivel de confianza más!" backup_succeeded: title: "Copia de respaldo realizada con éxito" subject_template: "La copia de respaldo se completó exitosamente" @@ -3212,10 +3213,6 @@ es: confirm_new_email: title: "Confirmar correo electrónico nuevo" subject_template: "[%{email_prefix}] Confirma tu nueva dirección de correo electrónico" - text_body_template: | - Confirma tu nueva dirección de correo electrónico para %{site_name} haciendo clic en el siguiente enlace: - - %{base_url}/u/confirm-old-email/%{email_token} confirm_old_email: title: "Confirmar correo electrónico antiguo" subject_template: "[%{email_prefix}] Confirma tu dirección actual de correo electrónico" @@ -4141,14 +4138,6 @@ es: placeholder: "San Francisco, California" colors: title: "Tema" - themes_further_reading: - title: "Tema" - description: - - - - 'Componentes de tema populares (para ver más, ve a #theme)' logos: title: "Logos" fields: @@ -4196,9 +4185,6 @@ es: disabled: "Ya que los inicios de sesión locales están desactivados, no es posible enviar invitaciones a nadie. Por favor, ve al siguiente paso." finished: title: "¡Tu foro de Discourse está listo!" - description: | -

Si en algún momento quieres cambiar alguno de estos ajustes, vuelve a lanzar este ayudante en cualquier momento o visita el panel de administrador; lo encontrarás al lado del icono de la llave inglesa en el menú.

-

¡Pásala bien y buena suerte construyendo tu nueva comunidad!

search_logs: graph_title: "Análisis de búsquedas" joined: "Registrado" diff --git a/config/locales/server.et.yml b/config/locales/server.et.yml index 8253649380..09d2b431c4 100644 --- a/config/locales/server.et.yml +++ b/config/locales/server.et.yml @@ -1083,8 +1083,6 @@ et: title: "Organisatsioon" colors: title: "Kujundusteema" - themes_further_reading: - title: "Kujundused" logos: title: "Logod" fields: diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml index 2e87fc08ee..e2b4e7adad 100644 --- a/config/locales/server.fa_IR.yml +++ b/config/locales/server.fa_IR.yml @@ -130,8 +130,12 @@ fa_IR: site_settings: default_categories_already_selected: "شما نمیتوانید دسته‌بندی‌ای را که در یک فهرست دیگر استفاده شده را انتخاب کنید." s3_upload_bucket_is_required: "شما نمیتوانید بارگذاری به S3 را فعال کنید مگر اینکه 's3_upload_bucket' را ارائه دهید." + second_factor_cannot_be_enforced_with_sso_enabled: "اگر SSO فعال باشد، نمی‌توانید 2FA را اجرا کنید." invite: + expired: "توکن دعوت‌نامه شما منقضی شده است. لطفا با مدیران تماس بگیرید." user_exists: "نیازی به دعوت %{email} نیست، در حال حاضر حساب کاربری دارند! " + disabled_errors: + sso_enabled: "دعوت نامه‌ها به خاطر فعال بودن SSO، غیرفعال هستند." bulk_invite: file_should_be_csv: "فرمت فایل بارگذاری شده می بایست csv باشد." error: "خطایی هنگام آپلود فایل مربوطه رخ داده است. لطفا بعدا امتحان کنید." @@ -1111,7 +1115,6 @@ fa_IR: tos_url: "اگر شرایط استفاده از خدمات را در میزبان دیگری قرار دادید و می‌خواهید از آن استفاده کنید، لینک کامل را در اینجا قرار دهید." privacy_policy_url: "اگر سیاست حفظ حریم خصوصی را در میزبان دیگری قرار دادید و می‌خواهید از آن استفاده کنید، لینک کامل را در اینجا قرار دهید." newuser_spam_host_threshold: "یک کاربر چند بار می‌تواند به یک هاست لینک دهد تا `newuser_spam_host_threshold` به عنوان هرزنامه شناخته شود." - staff_like_weight: "چه عوامل بیشتری نیاز است تا به همکاران پسند داده شود." topic_view_duration_hours: "بازدید‌های موضوعات را به ازای هر آیپی/کاربر در N ساعت محاسبه کن" user_profile_view_duration_hours: "بازدید‌های پروفایل کاربران را به ازای هر آیپی/کاربر در N ساعت محاسبه کن" levenshtein_distance_spammer_emails: "هنگامی که تطبیق ایمیل هرزنامه باشد٬ تعداد نویسه‌های متفاوت که هنوز هم به یک تطبق مبهم اجازه خواهد داد." @@ -1200,7 +1203,6 @@ fa_IR: display_name_on_posts: "نام و نام‌خانوادگی کاربران را در نوشته نشان بده به‌علاوه‌ی @نام‌کاربری." show_time_gap_days: "اگر دو نوشته با این فاصله زمانی از هم نوشته شوند، فاصله زمانی را نشان بده." short_progress_text_threshold: "بعد از اینکه تعداد نوشته ها در این موضوع از این تعداد پایین تر آمد٬ نوار پیشرفت تعداد نوشته ها را در حال حاضر نشان می دهد. اگر عرض نوار پیشرفت را تعویض کنید٬ شاید لازم باشد این مقدار را هم عوض کنید. " - default_code_lang: " پیش فرض برنامه نویسی زبان سینتکس برجسته شده به بلوک های کد GitHub اعمال بشه. (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "وقتی کسی شروع می کند با پاسخ دادن به موضوعی که آخرین پاسخ برمی گردد به خیلی قبل یک هشدار نمایش داده می شود. نمایش با تنظیمات تا 0. " autohighlight_all_code: "اعمال زور برای برجسته کردن کد به تمام بلاک های کد تنظیم نشده حتی وقتی به صراحت زبان را مشخص نمی کنند. " embed_truncate: "کوتاه کردن نوشته‌های جاسازی شده " @@ -1927,6 +1929,9 @@ fa_IR: confirm_new_email: title: "تایید ایمیل" subject_template: "[%{email_prefix}] ایمیل جدیدتان را تایید کنید" + confirm_new_email_via_admin: + title: "تایید ایمیل جدید" + subject_template: "[%{email_prefix}] آدرس ایمیل جدیدتان را تایید کنید" confirm_old_email: title: "تایید ایمیل قبلی" subject_template: "[%{email_prefix}] ایمیل فعلی خود را تایید کنید" @@ -2313,8 +2318,10 @@ fa_IR: title: "سازمان" colors: title: "قالب" - themes_further_reading: - title: "قالب‌ها" + fonts: + fields: + font_preview: + label: "پیش‌نمایش" logos: title: "لوگو‌ها" fields: diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index 8ec8521ab5..a4dc46b98c 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -416,14 +416,6 @@ fi: Tämä ketju on selvästikin tärkeä sinulle – olet kirjoittanut yli %{percent}% vastauksista. Ketju voisi olla vielä parempi, jos useammilla olisi tilaa jakaa omia näkökulmiaan. Voisit kutsua heitä tänne? - get_a_room: | - ### Rohkaise kaikkia osallistumaan keskusteluun - - Olet vastannut %{count} kertaa käyttäjälle @%{reply_username} tässä ketjussa! - - Parhaissa keskusteluissa on monia ääniä ja näkökulmia. Voitko saada jonkun liittymään keskusteluun? - - Pidä mielessä, että jos haluat keskustella syvällisemmin tämän käyttäjän kanssa ei-julkisesti, [lähetä hänelle yksityisviesti](%{base_path}/u/%{reply_username}). too_many_replies: | ### Olet kirjoittanut enimmäismäärän vastauksia tähän ketjuun @@ -1641,7 +1633,6 @@ fi: privacy_policy_url: "Jos haluat ylläpitää tietosuojaselostetta sivuston ulkopuolella, syötä URL tähän." log_anonymizer_details: "Pidetäänkö käyttäjästä tietoa lokeissa anonymisoinnin jälkeen. Jos noudatat GDPR:ää sinun on kytkettävä tämä pois käytöstä." newuser_spam_host_threshold: "Kuinka monta kertaa uusi käyttäjä voi linkittää samalle sivustolle `newuser_spam_host_posts` viesteissään, ennen kuin se tulkitaan roskapostin lähettämiseksi." - staff_like_weight: "Kuinka suuri ylimääräinen arvo on henkilökunnan tykkäyksillä." 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 löydöksen." @@ -1773,7 +1764,6 @@ fi: display_name_on_posts: "Näytä käyttäjän pitkä nimi viesteissä @nimen lisäksi." show_time_gap_days: "Jos kahden viestin välissä on kulunut näin monta päivää, näytä aikaväli ketjussa." short_progress_text_threshold: "Kuinka monen viestin jälkeen ketjun edistyspalkissa näytetään vain nykyisen viestin numero. Jos muutat palkin leveyttä, voit joutua muuttamaan tätä arvoa." - default_code_lang: "Oletusarvoinen ohjelmointikieli syntaksin korostukseen Github koodiblokeissa (lang-auto, ruby, python jne.)" warn_reviving_old_topic_age: "Kun käyttäjä alkaa kirjoittamaan vastausta ketjuun, jonka uusin viesti on tätä vanhempi päivissä, näytetään varoitus. Poista käytöstä asettamalla arvoksi 0." autohighlight_all_code: "Pakota koodin korostus kaikkiin esimuotoiltuihin tekstiblokkeihin, vaikka käyttäjä ei määrittelisi kieltä." highlighted_languages: "Mitä syntaksikorostussääntöjä on käytössä. (Varoitus: liian monen kielen käyttöönotto voi vaikuttaa palstan suorituskykyyn.) Ks. demo: https://highlightjs.org/static/demo" @@ -3090,10 +3080,6 @@ fi: confirm_new_email: title: "Vahvista uusi sähköpostiosoite" subject_template: "[%{email_prefix}] Vahvista uusi sähköpostiosoite" - text_body_template: | - Vahvista uusi sähköpostiosoitteesi sivustolla %{site_name} klikkaamalla linkkiä: - - %{base_url}/u/confirm-new-email/%{email_token} confirm_old_email: title: "Vahvista vanha sähköpostiosoite" subject_template: "[%{email_prefix}] Vahvista nykyinen sähköpostiosoitteesi" @@ -3821,8 +3807,6 @@ fi: placeholder: "San Francisco, Kalifornia" colors: title: "Värimaailma" - themes_further_reading: - title: "Teemat" logos: title: "Logot" fields: @@ -3870,9 +3854,6 @@ fi: disabled: "Koska paikalliset kirjautumiset ovat pois käytöstä, ei ole mahdollista kutsua ketään. Ole hyvä jatka seuraavaan vaiheeseen." finished: title: "Discoursesi on valmis!" - description: | -

Jos sinun joskus tekee mieli muuttaa näitä asetuksia, käy läpi tämä ohjattu asennus milloin vain tai käy ylläpito-osiossa; löydät sen jakoavaimen ohesta sivuston valikosta.

-

Pidä hauskaa, ja onnea matkaan uuden yhteisön rakentamiseen!

search_logs: graph_title: "Hakujen määrä" joined: "Liittyi" diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index 0689a3f20f..96d1fd3afd 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -432,14 +432,6 @@ fr: Ce sujet est vraiment important pour vous – vous avez posté plus de %{percent}% des réponses dans cette discussion. Ce serait peut-être encore mieux si vous laissez d'autres personnes partager leur point de vue. Est-ce que vous pourriez les inviter à vous rejoindre ici ? - get_a_room: | - ### Encourager tout le monde à intervenir dans la discussion - - Vous avez répondu %{count} fois à @%{reply_username} dans ce sujet ! - - Une bonne discussion implique plusieurs avis et points de vue. Est-ce que vous pourriez impliquer quelqu'un d'autre ? - - Et n'oubliez pas, si vous souhaitez prolonger la discussion avec cette personne en particulier, à l'abri du regard général, vous pouvez lui [envoyer un message direct](%{base_path}/u/%{reply_username}). too_many_replies: | ### Vous avez atteint le nombre de réponses maximum dans ce sujet @@ -1682,7 +1674,6 @@ fr: privacy_policy_url: "Si vous disposez déjà d'une politique de confidentialité hébergée ailleurs que vous voulez utiliser, vous pouvez renseigner son URL complète ici." log_anonymizer_details: "Conserver les détails d'un utilisateur rendu anonyme dans les journaux. Si vous vous conformerez au RGPD, vous devez désactiver cette fonction." newuser_spam_host_threshold: "Combien de fois un nouvel utilisateur peut publier un lien vers le même domaine dans la limite de leur `newuser_spam_host_threshold` messages avant d'être considéré comme du spam." - staff_like_weight: "Quel poids supplémentaire donner aux J'aime de l'équipe." 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." @@ -1824,7 +1815,6 @@ fr: display_name_on_posts: "Afficher le nom complet de l'utilisateur dans ses messages en plus de son nom d'utilisateur." show_time_gap_days: "Si deux messages sont publiés avec ce nombre de jours d'écart, afficher cette durée dans le sujet." short_progress_text_threshold: "Si le nombre de messages dans un sujet dépasse cette valeur, la barre de progression affichera uniquement le numéro de message actuel. Si vous modifiez la largeur de la barre de progression, vous devrez peut-être modifier cette valeur." - default_code_lang: "Coloration syntaxique par défaut appliquée à la syntaxe des langages de programmation des blocs de code GitHub (lang-auto, Ruby, Python, etc)" warn_reviving_old_topic_age: "Lorsque quelqu'un commence à répondre à un sujet dont la dernière réponse est vielle de plusieurs jours, un avertissement sera affiché. Désactivez la fonctionnalité en indiquant 0." autohighlight_all_code: "Forcer la coloration syntaxique dans tous les blocs de codes même si aucun langage de programmation n'est défini explicitement." highlighted_languages: "Règles de coloration syntaxique supportées. (Attention : supporter trop de langages peut impacter les performances.) Voir : https://highlightjs.org/static/demo/ pour une démonstration." @@ -1926,7 +1916,6 @@ fr: shared_drafts_category: "Activez la fonction Brouillons partagés en désignant une catégorie pour les brouillons de sujet. Les sujets dans cette catégorie ne figureront pas dans les listes de sujets pour les responsables." push_notifications_prompt: "Afficher la demande de consentement de l'utilisateur" push_notifications_icon: "L'icône du badge qui apparaît dans les notifications. Une image monochrome de taille 96×96 et au format PNG avec transparence est recommandée." - base_font: "Police utilisée pour le site. Peut être changée par les thèmes." short_title: "Le titre court sera utilisé sur l'écran d'accueil de l'utilisateur, le lanceur d'applications ou d'autres endroits où la place disponible est limitée. Il devrait être limité à 12 caractères." dashboard_hidden_reports: "Permettre de masquer les rapports du tableau de bord." dashboard_visible_tabs: "Choisissez les onglets visibles sur le tableau de bord." @@ -3180,10 +3169,15 @@ fr: confirm_new_email: title: "Confirmer votre nouvelle adresse courriel" subject_template: "[%{email_prefix}] Confirmez votre nouvelle adresse courriel" + confirm_new_email_via_admin: + title: "Confirmer votre nouvelle adresse courriel" + subject_template: "[%{email_prefix}] Confirmez votre nouvelle adresse courriel" text_body_template: | - Confirmez votre nouvelle adresse courriel pour %{site_name} en cliquant sur le lien suivant : + Confirmez votre nouvelle adresse courriel pour %{site_name} en cliquant sur le lien suivant: %{base_url}/u/confirm-new-email/%{email_token} + + Ce changement de courriel a été demandé par un administrateur du site. Si vous n'avez pas demandé ce changement, veuillez contacter l'administrateur de %{site_name}. confirm_old_email: title: "Confirmez votre ancienne adresse courriel" subject_template: "[%{email_prefix}] Confirmez votre adresse courriel actuelle" @@ -4117,10 +4111,11 @@ fr: placeholder: "San Francisco, Californie" colors: title: "Thème" - themes_further_reading: - title: "Thèmes" fonts: title: "Polices" + fields: + font_preview: + label: "Aperçu" logos: title: "Logos" fields: @@ -4168,9 +4163,6 @@ fr: disabled: "Etant donné que les connexions locales sont désactivées, il n'est pas possible d'envoyer des invitations. Veuillez procéder à l'étape suivante." finished: title: "Votre Discourse est prêt !" - description: | -

Si vous souhaitez modifier ces paramètres, rejouez ce wizard à n'importe quel moment ou rendez-vous dans la section Administration ; à côté de la clef à molette dans le menu du site.

-

Amusez-vous et bonne chance pour construire votre nouvelle communauté !

search_logs: graph_title: "Nombre de recherches" joined: "Inscrit" diff --git a/config/locales/server.gl.yml b/config/locales/server.gl.yml index c8cf5836b6..b07b0feb94 100644 --- a/config/locales/server.gl.yml +++ b/config/locales/server.gl.yml @@ -439,14 +439,6 @@ gl: Parece claro que este tema che importa – publicaches máis do %{percent}% das respostas. Aínda podería ser mellor se lles deses espazo a outras persoas para compartiren o seu punto de vista. Que tal se as convidas? - get_a_room: | - ### Anima a máis xente a participar na conversa - - Neste tema en concreto respondícheslle %{count} veces a @%{reply_username}! - - Unha discusión é boa cando involucra moitas voces e perspectivas. Que tal se involucras a alguén máis? - - E non esquezas que se queres continuar a conversa con este usuario en particular e en privado, [podes enviarlle unha mensaxe privada](%{base_path}/u/%{reply_username}). too_many_replies: | ### Alcanzaches o límite de respostas para este tema. @@ -1675,7 +1667,6 @@ gl: privacy_policy_url: "Se tes un documento coa política de privacidade aloxado en calquera outro sitio e queres usalo, insire aquí o seu URL completo." log_anonymizer_details: "Manter ou non os detalles dun usuario no rexistro despois de seren anonimizados. Para cumprir co RXPD, deberás desactivalo." newuser_spam_host_threshold: "Número de veces que un novo usuario pode publicar unha ligazón ao mesmo dominio dentro de `newuser_spam_host_threshold` das súas publicacións antes de ser considerado lixo." - staff_like_weight: "Factor de peso adicional que outorgan os gústames dados polos membros do equipo." 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." @@ -2934,10 +2925,6 @@ gl: confirm_new_email: title: "Confirmar novo correo electrónico" subject_template: "[%{email_prefix}] Confirma o teu novo enderezo de correo electrónico" - text_body_template: | - Confirma o teu novo enderezo de correo electrónico para %{site_name} premendo na seguinte ligazón: - - %{base_url}/u/confirm-new-email/%{email_token} confirm_old_email: title: "Confirmar o correo electrónico antigo" subject_template: "[%{email_prefix}] Confirma o teu enderezo de correo electrónico actual" @@ -3486,8 +3473,6 @@ gl: placeholder: "Lei de California" colors: title: "Tema" - themes_further_reading: - title: "Temas" logos: title: "Logotipos" fields: @@ -3534,9 +3519,6 @@ gl: disabled: "Como os inicios de sesión locais están desactivados, non é posible enviar invitacións a ninguén. Vai ao seguinte paso." finished: title: "O teu Discourse está pronto!" - description: | -

Se nalgún momento queres cambiar algún axuste, volve lanzar este asistente en calquera momento, ou visita o teu panel de administración; atoparalo ao lado da icona coa chave inglesa no menú.

-

Pásao ben e boa sorte construíndo a túa nova comunidade!

search_logs: graph_title: "Análise das buscas" joined: "Uniuse" diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 57699fb5f7..5c48414307 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -197,10 +197,12 @@ he: share_quote_facebook_requirements: "עליך להגדיר מזהה יישומון פייסבוק כדי להפעיל שיתוף ציטוטים עבור פייסבוק" second_factor_cannot_enforce_with_socials: "לא ניתן לאכוף אימות דו־שלבי כאשר מופעלת כניסה דרך רשתות חברתיות. תחילה עליך להשבית כניסה דרך: %{auth_provider_names}" second_factor_cannot_be_enforced_with_disabled_local_login: "אי אפשר לאכוף אימות דו־שלבי אם כניסה מקומית מושבתת." + second_factor_cannot_be_enforced_with_sso_enabled: "אי אפשר לאכוף אימות דו־שלבי אם מופעל SSO." local_login_cannot_be_disabled_if_second_factor_enforced: "אי אפשר להשבית כניסה מקומית אם נאכף אימות דו־שלבי. יש להשבית את אכיפת האימות הדו־שלבי בטרם השבתת כניסה מקומית." cannot_enable_s3_uploads_when_s3_enabled_globally: "לא ניתן להפעיל העלאות ל־S3 כיוון שהעלאות ל־S3 כבר פעילות באופן גלובלי והפעלת האפשרות הזאת ברמת האתר עשויה להוביל לתקלות חמורות בהעלאה." conflicting_google_user_id: 'מזהה חשבון ה־Google לחשבון זה השתנה, התערבות של חבר סגל נדרשת מטעמי אבטחה. נא ליצור קשר עם אחד מחברי הסגל ולהפנות אותו אל
https://meta.discourse.org/t/76575' invite: + expired: "תוקף אסימון ההזמנה שלך פג. נא ליצור קשר עם הסגל." not_found: "אסימון ההזמנה שלך שגוי. נא ליצור קשר עם הסגל." not_found_json: "אסימון ההזמנה שלך שגוי. נא ליצור קשר עם הסגל." not_found_template: | @@ -213,6 +215,10 @@ he: user_exists: "אין צורך להזמין את %{email}, כבר קיים חשבון לכתובת זו!" confirm_email: "

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

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

" cant_invite_to_group: "אסור לך להזמין משתמשים לקבוצות מסוימות. נא לוודא שהקבוצות אליהן ייעדת להזמין הן בבעלותך." + disabled_errors: + sso_enabled: "הזמנות מושבתות כיוון שה־SSO מופעל." + local_logins_disabled: "הזמנות מושבתות כיוון שההגדרה ‚הפעלת כניסה מקומית’ מושבתת." + invalid_access: "אין לך הרשאות לצפות במשאב המבוקש." bulk_invite: file_should_be_csv: "הקובץ שנשלח אמור להיות בתסדיר csv." max_rows: "%{max_bulk_invites} ההזמנות הראשונות נשלחו. כדאי לנסות לפצל את הקובץ לחלקים קטנים יותר." @@ -309,6 +315,11 @@ he: other: "משתמשים חדשים יכולים להזכיר רק %{count} משתמשים אחרים בפוסט, עמך הסליחה." no_embedded_media_allowed_trust: "אין לך אפשרות להטמיע פריטי מדיה בפוסט הזה, עמך הסליחה." no_embedded_media_allowed: "משתמשים חדשים לא יכולים להטמיע פריטי מדיה בפוסטים, עמך הסליחה." + too_many_embedded_media: + one: "משתמשים חדשים יכולים להוסיף רק פרטי מדיה מוטמעים לפוסט, עמך הסליחה." + two: "משתמשים חדשים יכולים להוסיף רק %{count} פרטי מדיה מוטמעים לפוסט, עמך הסליחה." + many: "משתמשים חדשים יכולים להוסיף רק %{count} פרטי מדיה מוטמעים לפוסט, עמך הסליחה." + other: "משתמשים חדשים יכולים להוסיף רק %{count} פרטי מדיה מוטמעים לפוסט, עמך הסליחה." no_attachments_allowed: "משתמשים חדשים לא יכולים להוסיף קבצים לפוסטים, עמך הסליחה." too_many_attachments: one: "משתמשים חדשים יכולים לצרף רק קובץ אחד לפוסט, עמך הסליחה." @@ -1591,6 +1602,7 @@ he: password_unique_characters: "מספר מינימלי של תווים ייחודיים שחייבים להיות בסיסמאות." block_common_passwords: "אל תאפשרו סיסמאות מתוך 10,000 הסיסמאות הנפוצות ביותר." external_auth_skip_create_confirm: בעת הרשמה דרך אימות חיצוני, יש לדלג על החלונית ליצירת חשבון. עדיף להשתמש לצד sso_overrides_email (דריסת דוא״ל מאימות חיצוני), sso_overrides_username (דריסת שם משתמש מאימות חיצוני) ו־sso_overrides_name (דריסת שם מאימות חיצוני). + external_auth_immediately: "להפנות למערת הכניסה החיצונית ללא מעורבות המשתמש. מתרחש רק כאשר login_required (נדרשת כניסה) הוא true (אמת) ויש לפחות שיטת אימות חיצונית אחת." enable_sso: "הפעלת כניסה אחודה (Single Sign On) באמצעות אתר חיצוני (א-ז-ה-ר-ה: כתובות הדוא״ל של משתמשים *חייבות* לעבור אימות על ידי האתר החיצוני!)" verbose_sso_logging: "תיעוד ניתוח מורחב בנושא SSO אל ‎/logs" enable_sso_provider: "הטמעת פרוטוקול ספק SSO מטעם Discourse תחת נקודת הקצה ‎/session/sso_provider, נדרשת הפעלת ההגדרה sso_provider_secrets (סודות ספק SSO)" @@ -1797,7 +1809,7 @@ he: log_anonymizer_details: "האם להשאיר את פרטי המשתמש ביומן לאחר שזהותו טושטשה. כדי לעמוד בהגבלות ה־GDPR יש לכבות את האפשרות הזאת." newuser_spam_host_threshold: "מספר הפעמים שמשתמשים חדשים יכולים לקשר לאותו מחשב במסגרת `newuser_spam_host_threshold` הפרסומים שלהם לפני שייחשבו ספאם." allowed_spam_host_domains: "רשימה של שמות תחום (domains) שיוחרגו מבדיקת הספאם. משתמשים חדשים לעולם לא יוגבלו ביצירת פוסטים חדשים עם קישורים לשמות תחום אלו." - staff_like_weight: "כמה משקל עודף יש להעניק ללייקים של הצוות." + staff_like_weight: "כמה משקל לתת ללייקים מהסגל (לייקים שאינם מהסגל נשקלים כ־1.)" topic_view_duration_hours: "ספרו צפיות חדשות בנושא פעם אחת לכל IP/משתמש לכל N שעות" user_profile_view_duration_hours: "ספרו צפיות בפרופיל משתמש פעם אחת לכל IP/משתמש בכל N שעות" levenshtein_distance_spammer_emails: "כאשר מתאימים דוא\"ל של ספאמרים, מספר ההבדלים בתווים שעדיין מאפשרים התאמה מטושטשת." @@ -1913,6 +1925,7 @@ he: anonymous_posting_min_trust_level: "רמת האמון המינמלית הנדרשת כדי לאפשר פרסום אנונימי" anonymous_account_duration_minutes: "בכדי להגן על האנונימות צרו חשבון אנונימי כל N דקות לכל משתמש. לדוגמה: אם מכוון ל-600, כאשר יעברו 600 דקות מהפרסום האחרון והמשתמש/ת יחליפו לזהות אנונימית, חשבון אנונימי חדש יווצר." hide_user_profiles_from_public: "נטרלו כרטיסי משתמשים, פרופילי משתמשים ומדריך משתמשים למשתמשים אנונימיים." + allow_users_to_hide_profile: "לאפשר למשתמשים להחביא את הפרופיל והנוכחות שלהם" allow_featured_topic_on_user_profiles: "לאפשר למשתמשים להציג קישור לנושא בכרטיס המשתמש ובפרופיל שלהם." show_inactive_accounts: "לאפשר למשתמשים שנכנסו לעיין בפרופילים של חשבונות לא פעילים." hide_suspension_reasons: "לא להציג את סיבות ההשעיה באופן ציבורי בפרופילי המשתמשים." @@ -1953,7 +1966,7 @@ he: display_name_on_posts: "הצגת שמם המלא של משתמשים בפוסטים שלהם, בנוסף ל@שם_המשתמש שלהם." show_time_gap_days: "אם שני פוסטים נוצרים מספר זה של ימים אחד מהשני, הציגו את מרווח הזמן בנושא." short_progress_text_threshold: "לאחר שמספר הפוסטים בנושא עוברים את המספר הזה, מד ההתקדמות יציג רק את המספר של הפוסט הנוכחי. אם תשנו את רוחב מד ההתקדמות, ייתכן שתצטרכו לשנות ערך זה." - default_code_lang: "תחביר שפת תכנות שיסופק כברירת מחדל על קטעי קוד מגיטהאב (לאנג-אוטו, רובי, פיתון וכד׳)" + default_code_lang: "הדגשת תחביר כבררת מחדל שתחול על מקטעי קוד מ־GitHub (auto,‏ nohighlight,‏ ruby,‏ python וכו׳)" warn_reviving_old_topic_age: "כאשר מישהם מתחילים להגיב לנושא שבו התגובה האחרונה היא בת יותר מכמה ימים, אזהרה תוצג. נטרלו באמצעות הזנה של 0." autohighlight_all_code: "לחייב שימוש בקוד הדגשה לכל קוד מעוצב מראש (preformatted code blocks) אפילו אם הם אינם מציינים את השפה." highlighted_languages: "כללי הדגשת תחביר להכללה. (אזהרה: הכללה של שפות רבות מדי פוגעת בביצועים) להדגמה: https://highlightjs.org/static/demo" @@ -2061,7 +2074,8 @@ he: shared_drafts_category: "הפעלת תכונת הטיוטות המשותפות על ידי הקצאת קטגוריה שתשמש לטובת טיוטות לנושאים. נושאים בקטגוריה זו יוסתרו מרשימות הנושאים לחברי הסגל." push_notifications_prompt: "הצגת בקשה להסכמת המשתמש." push_notifications_icon: "סמל העיטור שמופיע בפינת ההתרעה. מומלץ PNG בצבעים אחידים עם שקיפות בגודל 96 × 96." - base_font: "גופן לשימוש ברוב המקומות באתר. אפשר לדרוס עם ערכות עיצוב." + base_font: "גופן בסיס לשימוש רוב הטקסט באתר. ערכות עיצוב יכולות לדרוס אותו באמצעות מאפיין ה־CSS‏ ‎`--font-family`‎." + heading_font: "גופן לשימוש כותרות באתר. ערכות עיצוב יכולות לדרוס אותו באמצעות המאפיין ‎`--heading-font-family`‎" short_title: "בכותרת הקצרה ייעשה שימוש במסך הבית של המשתמש, במשגר או במקומות אחרים שבהם המקום מוגבל. אורכה לא יעלה על 12 תווים." dashboard_hidden_reports: "לאפשר להסתיר את הדוחות המסוימים בלוח הבקרה." dashboard_visible_tabs: "נא לבחור אילו לשוניות תופענה בלוח הבקרה." @@ -2681,6 +2695,10 @@ he: [prefs]: %{user_preferences_url} tl2_promotion_message: subject_template: "ברכותינו על התקדמותך בסולם דרגות האמון!" + text_body_template: | + קידמנו אותך בסולם [דרגות האמון](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/)! + + אנו מעודדים אותך להמשיך ולגלות מעורבות – נוכחותך חשובה לנו. backup_succeeded: title: "גיבוי הצליח" subject_template: "הגיבוי הושלם בהצלחה." @@ -3417,9 +3435,20 @@ he: title: "אישור מייל חדש" subject_template: "[%{email_prefix}] אשרו את כתובת המייל החדשה שלכם" text_body_template: | - נא לאשר את כתובת הדוא״ל שתשמש אותך לגשת אל %{site_name} על ידי לחיצה על הקישור הבא: + לחיצה על הקישור %{site_name} תאשר את כתובת הדוא״ל החדשה שלך: - %{base_url}/u/confirm-new-email/%{email_token} + ‎%{base_url}/u/confirm-new-email/%{email_token} + + אם לא ביקשת לערוך שינוי שכזה, נא ליצור קשר עם ההנהלה של %{site_name}. + confirm_new_email_via_admin: + title: "אישור כתובת דוא״ל חדשה" + subject_template: "[%{email_prefix}] אישור כתובת הדוא״ל החדשה שלך" + text_body_template: | + לחיצה על הקישור %{site_name} תאשר את כתובת הדוא״ל החדשה שלך: + + ‎%{base_url}/u/confirm-new-email/%{email_token} + + החלפת כתובת הדוא״ל הוגשה על ידי הנהלת האתר. אם לא ביקשת לערוך שינוי שכזה, נא ליצור קשר עם ההנהלה של %{site_name}. confirm_old_email: title: "אישור מייל ישן" subject_template: "[%{email_prefix}] אשרו את כתובת המייל הנוכחית שלכם" @@ -4191,16 +4220,15 @@ he: placeholder: "סן פרנסיסקו, קליפורניה" colors: title: "תמה" - themes_further_reading: - title: "תמות" - description: - - - - 'רכיבי ערכת עיצוב נפוצים (כדי לצפות בנוספים, ניתן לעיין ב־‎#theme)"' fonts: title: "גופנים" + fields: + body_font: + label: "גופן גוף" + heading_font: + label: "גופן כותרת" + font_preview: + label: "תצוגה מקדימה" logos: title: "לוגואים" fields: @@ -4249,8 +4277,9 @@ he: finished: title: "ה־Discourse שלך מוכן!" description: | -

אם חשקה נפשך בשינוי ההגדרות האלו, יש להפעיל את האשף הזה מחדש, או לבקר באגף הניהול; ניתן למצוא אותו ליד סמל מפתח הברגים בתפריט האתר.

-

מאחלים לך הנאה מרובה ובהצלחה בהקמת הקהילה החדשה שלך!

+

אם אי פעם יתחשק לך לשנות את ההגדרות האלו, ניתן להפעיל את האשף הזה מחדש בכל עת, או לבקר בסעיף הניהול שלך; ניתן למצוא אותו ליד סמל מפתח הצינורות בתפריט האתר.

+

קל ופשוט להתאים את Discourse לטעמך עם מערכת עצמתית לעיצוב לפי נושא. למשל, הנה ערכות עיצוב ורכיבים מובילים ב־meta.discourse.org.

+

רצוי להתפרע והמון הצלחה בבניית הקהילה החדשה שלך!

search_logs: graph_title: "מניין חיפושים" joined: "הצטרפו" diff --git a/config/locales/server.hu.yml b/config/locales/server.hu.yml index 5cf6806807..d544e83f22 100644 --- a/config/locales/server.hu.yml +++ b/config/locales/server.hu.yml @@ -273,14 +273,6 @@ hu: until_posts: one: "%{count} bejegyzés" other: "%{count} bejegyzés" - get_a_room: | - ### Biztasson mindenkit, hogy csatlakozzanak a beszélgetéshez - - %{count} alkalommal válaszoz @%{reply_username} felhasználónak ebben a témában. - - A jó beszélgetések számos hangot és perspektívát tartalmaznak. Be tud valaki mást is vonni? - - És ne feledje, ha nem nyilvánosan szeretné folytatni a párbeszédet ezzel a felhasználóval, akkor [személyes üzenetet is küldhet neki](%{base_path}/u/%{reply_username}). activerecord: attributes: category: @@ -1244,8 +1236,6 @@ hu: placeholder: "Példa szervezet" colors: title: "Téma" - themes_further_reading: - title: "Témák" logos: title: "Logók" fields: diff --git a/config/locales/server.hy.yml b/config/locales/server.hy.yml index 09a7cd929e..92f8a53078 100644 --- a/config/locales/server.hy.yml +++ b/config/locales/server.hy.yml @@ -1435,7 +1435,6 @@ hy: privacy_policy_url: "Եթե ունեք այլ վայրում տեղադրված Գաղտնիության Քաղաքականության փաստաթուղթ, որը ցանկանում եք օգտագործել, տեղադրեք ամբողջական URL-ը այստեղ:" log_anonymizer_details: "Պահպանել արդյոք օգտատիրոջ տվյլաները գրառումներում անվանազրկումից հետո: GDPR -ի օգտագործման ժամանակ Դուք պետք է անջատեք սա:" newuser_spam_host_threshold: "Քանի անգամ նոր օգտատերը կարող է հրապարակել միևնույն հոսթի հղումը` ըստ իր `newuser_spam_host_threshold` գրառումների սահմանաչափի՝ մինչև սպամ համարվելը:" - staff_like_weight: "Որքան է լրացուցիչ կշռի գործոնը՝ անձնակազմի կողմից հավանում տալու համար:" topic_view_duration_hours: "Հաշվարկել թեմայի նոր դիտում՝ ըստ յուրաքանչյուր IP-ի/Օգտատիրոջ յուրաքանչյուր N ժամը մեկ" user_profile_view_duration_hours: "Հաշվարկել օգտատիրոջ պրոֆիլի նոր դիտում՝ ըստ յուրաքանչյուր IP-ի/Օգտատիրոջ յուրաքանչյուր N ժամը մեկ" levenshtein_distance_spammer_emails: "Սպամմերի էլ. նամակների համապատասխանեցման ժամանակ սիմվոլների տարբերության քանակը, որը դեռևս թույլ կտա անորոշ համընկնում:" @@ -1550,7 +1549,6 @@ hy: display_name_on_posts: "Ցուցադրել օգտատիրոջ ամբողջական անունը իր գրառումներում՝ ի հավելումն իր @օգտանվան:" show_time_gap_days: "Եթե երկու գրառում կատարվում են այսքան օր տարբերությամբ, ցուցադրել ժամանակի տարբերությունը թեմայում:" short_progress_text_threshold: "Թեմայի պատասխանների քանակը այս թվին գերազանցելուց հետո առաջընթացի բարը կցուցադրի միայն ընթացիկ գրառման համարը: Եթե փոփոխեք առաջընթացի բարի լայնությունը, հնարավոր է՝ պետք լինի նաև փոխել այս արժեքը:" - default_code_lang: "Լռելյայն ծրագրավորման լեզվի շարադասության ընդգծում՝ կիրառված GitHub կոդի բկոկների վրա (lang-auto, ruby, python և այլն) :" warn_reviving_old_topic_age: "Երբ որևէ մեկը սկսի պատասխանել թեմայի, որտեղ վերջին պատասխանը այսքան օրից ավելի հին է, կցուցադրվի զգուշացում: Անջատեք՝ 0 սահմանելով:" autohighlight_all_code: "Հարկադրել կոդի ընդգծումը բոլոր նախաֆորմատավորված կոդերի բլոկների համար, անգամ եթե նրանք բացահայտ չեն սահմանել լեզուն:" highlighted_languages: "Ներառված շարադասության ընդգծման կանոնները: (Ուշադրություն՝ չափազանց շատ լեզուների ներառումը կարող է ազդել կատարողականության վրա) այցելեք՝ https://highlightjs.org/static/demo դեմոյի համար:" @@ -3198,8 +3196,6 @@ hy: placeholder: "Սան-Ֆրանցիսկո, Կալիֆորնիա" colors: title: "Թեմա" - themes_further_reading: - title: "Թեմաներ" logos: title: "Լոգոներ" fields: @@ -3240,9 +3236,6 @@ hy: disabled: "Քանի որ տեղական մուտքերը անջատված են, անհնար է ուղարկել հրավերներ որևէ մեկին: Խնդրում ենք անցնել մյուս քայլին:" finished: title: "Ձեր Discourse -ը պատրաստ է!" - description: | -

Եթե Դուք երբևէ ցանկանաք փոփոխել այս կարգավորումները, վերագործարկեք այս օգնականը ցանկացած ժամանակ կամ այցելեք Ձեր ադմինի բաժին; այն կգտնեք կայքի մենյուում պտուտակի պատկերակի կողքին:

-

Վայելեք, և հաջողություն ենք մաղթում Ձեր նոր համայնքը կառուցելիս!

search_logs: graph_title: "Որոնումների Քանակ" joined: "Միացել է" diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index eb0a21d7d1..c988c24556 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -182,6 +182,7 @@ it: s3_bucket_reused: "Non puoi usare lo stesso bucket per 's3_upload_bucket' e 's3_backup_bucket'. Scegli un bucket differente, o un percorso diverso per ciascun bucket." secure_media_requirements: "I caricamenti S3 devono essere abilitati prima di abilitare i supporti sicuri." second_factor_cannot_be_enforced_with_disabled_local_login: "Non è possibile applicare 2FA se i login locali sono disabilitati." + second_factor_cannot_be_enforced_with_sso_enabled: "Non si può applicare 2FA se SSO è attivato." local_login_cannot_be_disabled_if_second_factor_enforced: "Non è possibile disabilitare l'accesso locale se 2FA è abilitato. Disabilitare 2FA prima di disabilitare gli accessi locali." cannot_enable_s3_uploads_when_s3_enabled_globally: "Non puoi abilitare i caricamenti S3 perché sono già abilitati a livello globale e l'abilitazione dei caricamenti S3 a livello di sito potrebbe causare problemi critici con i caricamenti" conflicting_google_user_id: 'Il Google Account ID per questo account è stato modificato; per motivi di sicurezza è richiesto l''intervendo dello staff. Si prega di contattare lo staff e indirizzarli a
https://meta.discourse.org/t/76575' @@ -197,6 +198,10 @@ it: error_message: "Si è verificato un errore accettando l'invito. Si prega di contattare l'amministratore del sito." user_exists: "Non è necessario invitare X, hanno già un account!\nNon è necessario invitare %{email}, hanno già un account!" confirm_email: "

Ci siamo quasi! Abbiamo mandato un'email di attivazione al tuo indirizzo email. Per favore per attivare il tuo account segui le istruzioni contenute nell'email.

Se non dovesse arrivarti, controlla la tua cartella Spam.

" + disabled_errors: + sso_enabled: "Gli inviti sono disattivati perché SSO è attivato." + local_logins_disabled: "Gli inviti sono disattivati perché l'impostazione 'abilita login locali' è disattivata." + invalid_access: "Non hai il permesso di visualizzare la risorsa richiesta." bulk_invite: file_should_be_csv: "Il file caricato deve essere in formato csv." max_rows: "I primi %{max_bulk_invites} inviti sono stati spediti. Prova a dividere il file in parti più piccole." @@ -424,7 +429,6 @@ it: E' più facile per chiunque leggere argomenti che hanno poche risposte ma più dettagliate, rispetto a molte brevi risposte individuali. dominating_topic: | ### Consenti ad altri di partecipare alla conversazione Questo argomento è chiaramente importante per te: hai pubblicato più di %{percent}% delle risposte. Potrebbe essere ancora meglio se dessi ad altre persone lo spazio per condividere anche i loro punti di vista. Puoi invitarli? - get_a_room: "### Incoraggia tutti a partecipare alla conversazione\n\nHai risposto %{count} volte a @%{reply_username} in questo particolare argomento! \n\nUna grande discussione vede protagoniste molte voci e punti di vista. Ti va di coinvolgere qualcun altro? \n\nE non dimenticare, se desideri continuare la tua conversazione privatamente con questo particolare utente, [invia un messaggio personale](%{base_path}/u/%{reply_username}).\n" too_many_replies: | ### Hai raggiungo il limite di risposte per questo argomento @@ -1657,7 +1661,6 @@ it: privacy_policy_url: "Se vuoi usare un documento sulle Politiche di Privacy ospitato da qualche altra parte, fornisci qui la sua URL completa." log_anonymizer_details: "Mantenere o meno i dettagli di un utente nel registro dopo essere stato reso anonimo. Per obbedire al GDPR, è necessario disattivarle questa opzione." newuser_spam_host_threshold: "Quante volte un nuovo utente può pubblicare un collegamento allo stesso host all'interno dei messaggi `newuser_spam_host_threshold` prima di essere considerato spam." - staff_like_weight: "Peso extra attribuito ai \"mi piace\" dati dallo staff." 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. " @@ -1755,6 +1758,7 @@ it: anonymous_posting_min_trust_level: "Livello di esperienza minimo richiesto per abilitare la pubblicazione in modalità anonima" anonymous_account_duration_minutes: "Per proteggere l'anonimato creare un nuovo account anonimo ogni N minuti per ogni utente. Esempio: se impostato a 600, non appena passano 600 minuti dall'ultimo messaggio E l'utente passa in modalità anonima, viene creato un nuovo account anonimo." hide_user_profiles_from_public: "Disabilita schede utenti, profili utenti e directory utenti per gli utenti anonimi." + allow_users_to_hide_profile: "Permetti agli utenti di nascondere il loro profilo e la loro presenza" show_inactive_accounts: "Consenti agli utenti registrati di consultare i profili degli account inattivi." hide_suspension_reasons: "Non visualizzare pubblicamente i motivi della sospensione sui profili utente." log_personal_messages_views: "Registra le visualizzazioni fatte da Amministratore dei messaggi personali di altri utenti/gruppi ." @@ -1786,7 +1790,6 @@ it: display_name_on_posts: "Mostra il nome completo dell'utente sui messaggi oltre allo @username." show_time_gap_days: "Se due messaggi vengono creati dopo tale numero di giorni, mostrare l'intervallo di tempo nell'argomento." short_progress_text_threshold: "Quando il numero di messaggi di un argomento supera questo valore, la barra di avanzamento mostrerà solo il numero di messaggi attuale. Se modifichi l'ampiezza della barra di avanzamento, potresti dover cambiare questo valore." - default_code_lang: "Ai blocchi di codice GitHub viene applicata l'evidenziazione della sintassi del linguaggio di programmazione di default (lang-auto, ruby, python ecc.)" warn_reviving_old_topic_age: "Quando qualcuno inizia a rispondere ad un argomento in cui l'ultima risposta è più vecchia di tale numero di giorni, verrà visualizzato un avviso. Disabilita impostando su 0." autohighlight_all_code: "Forza l'evidenziazione della sintassi del codice nei blocchi di testo preformattato, anche se non è stato specificato il linguaggio di programmazione. " highlighted_languages: "Regole di evidenziazione della sintassi incluse. (Attenzione: includere troppi linguaggi può influire sulle prestazioni) vedi: https://highlightjs.org/static/demo per una demo" @@ -2956,6 +2959,15 @@ it: confirm_new_email: title: "Conferma Nuova Email" subject_template: "[%{email_prefix}] Conferma il tuo nuovo indizzo email" + confirm_new_email_via_admin: + title: "Conferma nuova e-mail" + subject_template: "[%{email_prefix}] Conferma il tuo nuovo indirizzo mail" + text_body_template: | + Conferma il tuo nuovo indirizzo mail per %{site_name} by cliccando sul seguente link: + + %{base_url}/u/confirm-new-email/%{email_token} + + Questa modifica dell'e-mail è stata richiesta da un amministratore del sito. Se non hai richiesto questa modifica, contatta l'amministratore di %{site_name}. confirm_old_email: title: "Conferma Vecchia Email" subject_template: "[%{email_prefix}] Conferma il tuo attuale indirizzo email" @@ -3544,9 +3556,9 @@ it: upload_row_too_long: "Il file CSV dovrebbe avere un'Etichetta per riga. Opzionalmente l'Etichetta può essere seguita da una virgola, quindi dal nome del gruppo dell'Etichetta." forbidden: invalid: - one: "Il tag selezionato non può essere utilizzato" - other: "Nessuno dei tags selezionati può essere utilizzato" - in_this_category: '''%{tag_name}'' non può essere utilizzato in questa categoria' + one: "Non si può usare l'etichetta selezionata" + other: "Non si può usare nessuna delle etichette selezionate" + in_this_category: 'Non si può usare ''%{tag_name}'' in questa categoria' restricted_to: one: '"%{tag_name}" è limitato alla categoria "%{category_names}"' other: '"%{tag_name}" è limitato alle seguenti categorie: %{category_names}' @@ -3655,8 +3667,6 @@ it: placeholder: "San Francisco, California" colors: title: "Tema" - themes_further_reading: - title: "Temi" logos: title: "Loghi" fields: @@ -3704,9 +3714,6 @@ it: disabled: "Poiché le connessioni locali sono disabilitate, non è possibile inviare inviti a nessuno. Per favore, prosegui con il passo successivo." finished: title: "Il tuo Discourse è Pronto!" - description: | -

Se volessi modificare queste impostazioni, esegui nuovamente questa procedura guidata in qualsiasi momento o visita la tua sezione di amministrazione ; la troverai accanto all'icona a forma di chiave inglese nel menu del sito.

-

Divertiti e buona fortuna per la costruzione della tua nuova comunità!

search_logs: graph_title: "Conteggio Ricerche" joined: "Iscritto" diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index 924eb71667..f1f5d07f6d 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -930,7 +930,6 @@ ja: faq_url: "FAQが他のサイトにある場合、URLをここに指定します。" tos_url: "他のサイトに利用規約を掲載している場合は、URLをここに指定してください。" privacy_policy_url: "他のサイトにプライバシーポリシーを掲載している場合は、URLをここに指定してください。" - staff_like_weight: "スタッフにより「いいね!」された場合、どのくらい重み付けをするか" levenshtein_distance_spammer_emails: "スパマーのメールアドレスとマッチさせるとき、数文字違いの曖昧な一致でも許可する" max_new_accounts_per_registration_ip: "もし既にトラストレベル0のユーザーがそのIPから(N)個作成されていたら、そのIPから登録できないようにする(スタッフメンバー、トラストレベル2以上は除く)" 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." @@ -978,7 +977,6 @@ ja: display_name_on_posts: "@usernameに加えて、ポストにユーザのフルネームを表示する" show_time_gap_days: "2つの投稿の作成日が離れていた場合、time gapをトピックに表示する。" short_progress_text_threshold: "プログレスバーが現在のポスト番号のみを表示するようになるポスト数のしきい値。プログレスバーの幅を変更した際には、この値を変更することをおすすめします。" - default_code_lang: "Github コードブロックにデフォルトで適用されるプログラム言語シンタックスハイライト (lang-auto, ruby, python 等)" warn_reviving_old_topic_age: "最後の返信がこの設定よりも古いトピックに返信すると、警告を表示します。0を設定すると無効になります" autohighlight_all_code: "明示的に言語を指定しなくても、全てのコードブロックにコードハイライトを強制的に適用する" embed_truncate: "embedされた投稿をtruncateする" diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index 4e9930aac7..4393b2405c 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -180,17 +180,29 @@ ko: s3_bucket_reused: "'s3_upload_bucket'및 's3_backup_bucket'에 동일한 버킷을 사용할 수 없습니다. 다른 버킷을 선택하거나 각 버킷마다 다른 경로를 사용하십시오." secure_media_requirements: "보안 미디어를 활성화하기 전에 S3 업로드를 활성화해야합니다." share_quote_facebook_requirements: "페이스북으로의 공유를 활성화 하려면 페이스북 앱 ID를 설정해야 합니다." + second_factor_cannot_enforce_with_socials: "소셜 로그인이 활성화된 상태에서는 2FA를 적용할 수 없습니다. 먼저 다음을 통해 로그인을 비활성화해야합니다: %{auth_provider_names}" second_factor_cannot_be_enforced_with_disabled_local_login: "로컬 로그인이 비활성화 된 경우 2FA를 시행 할 수 없습니다." local_login_cannot_be_disabled_if_second_factor_enforced: "2FA가 적용되는 경우 로컬 로그인을 비활성화 할 수 없습니다. 로컬 로그인을 비활성화하기 전에 강제 2FA를 비활성화하십시오." cannot_enable_s3_uploads_when_s3_enabled_globally: "S3 업로드가 이미 전체적으로 활성화되어 있으므로이 사이트 수준을 활성화하면 S3 업로드를 활성화 할 수 없습니다." conflicting_google_user_id: '이 계정의 Google 계정 ID가 변경되었습니다. 보안상의 이유로 직원의 개입이 필요합니다. 직원에게 연락하여
https://meta.discourse.org/t/76575' invite: + expired: "초대 토큰이 만료되었습니다. 관리자에게 문의하십시오." not_found: "초대 토큰이 유효하지 않습니다. 직원에게 문의 하십시오." not_found_json: "초대 토큰이 유효하지 않습니다. 직원에게 문의하십시오." + not_found_template: | +

%{site_name}의 초대가 이미 사용되었습니다.

+ +

비밀번호가 기억 나시면 로그인을 하십시오.

+ +

그렇지 않으면 비밀번호 재설정을 하십시오.

error_message: "초대를 수락하는 중에 오류가 발생했습니다. 사이트 관리자에게 문의하십시오." user_exists: "%{email}님은 초대하지 않아도 됩니다. 이미 회원이거든요!" confirm_email: "

거의 다 끝났습니다! 귀하의 이메일 주소로 활성화 메일을 보냈습니다. 메일의 지침에 따라 계정을 활성화하십시오.

도착하지 않으면 스팸 폴더를 확인하십시오.

" cant_invite_to_group: "사용자를 지정된 그룹에 초대 할 수 없습니다. 초대하려는 그룹의 소유자인지 확인하십시오." + disabled_errors: + sso_enabled: "SSO가 활성화되어 있기 때문에 초대가 비활성화되었습니다." + local_logins_disabled: "'로컬 로그인 사용' 설정이 비활성화되어 있으므로 초대를 사용할 수 없습니다." + invalid_access: "요청한 리소스를 볼 수 없습니다." bulk_invite: file_should_be_csv: "업로드 파일은 CSV형식이어야 합니다." max_rows: "첫 번째 %{max_bulk_invites} 초대가 전송되었습니다. 파일을 작은 부분으로 분할하십시오." @@ -234,6 +246,8 @@ ko: likes: "좋아요" too_many_replies: other: "죄송합니다. 신규 가입자는 글 하나에 %{count}개까지만 댓글을 달 수 있습니다." + max_consecutive_replies: + other: "연속 댓글은 %{count}개까지만 허용됩니다. 이전 댓글을 수정하거나 다른 사람이 댓글을 작성 할 때까지 기다리십시오." embed: start_discussion: "토론 시작" continue: "토론 계속하기" @@ -416,14 +430,6 @@ ko: 이 글은 사용자님에게 중요합니다 – 사용자님은 여기에 %{percent}% 이상의 댓글을 게시했습니다. 다른 사람들에게도 그들의 관점을 공유 할 수있는 공간을 제공하면 더 좋을 수 있습니다. 그들을 초대 할 수 있습니까? - get_a_room: | - ### 모든 사람이 대화에 참여하도록 장려 - - 이 글에서 @%{reply_username}님은 %{count}번 댓글을 달았습니다! - - 훌륭한 토론에는 많은 목소리와 관점이 포함됩니다. 다른 사람들도 참여시킬 수 있습니까? - - 그리고 잊지 마세요. 이 특정 사용자와의 대화를 비공개로 계속하려면 [개인 메시지를 보내세요] (%{base_path}/u/%{reply_username}). too_many_replies: |2 ### 이 토픽 답글 제한량을 모두 채우셨습니다 @@ -545,8 +551,8 @@ ko: post_template: "%{replace_paragraph}\n\n다음 문단을 긴 설명을 쓰는데 활용하세요. 카테고리의 가이드라인이나 규칙을 쓰실 수도 있습니다.\n\n- 왜 이 카테고리를 사용해야하나요? 용도가 무엇인가요?\n\n- 기존의 다른 카테고리와 정확히 어떻게 다른가요?\n\n- 이 카테고리에 속한 토픽들은 대체로 무엇을 다루나요?\n\n- 이 카테고리가 필요한가요? 다른 카테고리와 병합하거나 서브 카테고리로 만들면 안되나요?\n" errors: not_found: "카테고리를 찾을수 없습니다!" - uncategorized_parent: "Uncategorized 카테고리는 부모 카테고리를 가질 수 없습니다." - self_parent: "하위 카테고리의 부모는 자신이 될 수 없습니다." + uncategorized_parent: "미분류(Uncategorized) 카테고리는 상위 카테고리를 가질 수 없습니다." + self_parent: "하위 카테고리의 상위는 그 자체가 될 수 없습니다." depth: "하위 카테고리는 계층 구조로 사용할 수 없습니다." invalid_email_in: "'%{email}' 는 올바른 이메일 주소가 아닙니다." email_already_used_in_group: "'%{email}'는 '%{group_name}' 그룹에서 이미 사용중입니다." @@ -583,8 +589,8 @@ ko: slow_down: "이 작업을 너무 많이 수행했습니다, 나중에 다시 시도하십시오." too_many_requests: "이 작업을 너무 많이 수행했습니다. 다시 시도하기 전에 %{time_left}을 기다리십시오." by_type: - first_day_replies_per_day: "새 사용자가 첫날에 만들 수있는 최대 회신 수에 도달했습니다. 다시 시도하기 전에 %{time_left}을 기다리십시오." - first_day_topics_per_day: "새 사용자가 첫날에 만들 수있는 최대 주제 수에 도달했습니다. 다시 시도하기 전에 %{time_left}을 기다리십시오." + 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}을 기다리십시오." @@ -705,11 +711,11 @@ ko: authorizing_new: title: "새 이메일 확인" description: "이메일 주소를 다음으로 변경할 것인지 확인하십시오:" - description_add: "대체 이메일 주소를 추가 할 것인지 확인하십시오:" + description_add: "보조 이메일 주소를 추가하시겠습니까?" authorizing_old: title: "이메일 주소 변경" description: "이메일 주소 변경을 확인하십시오" - description_add: "대체 이메일 주소를 추가 할 것인지 확인하십시오:" + description_add: "보조 이메일 주소를 추가 할 것인지 확인하십시오:" old_email: "오래된 이메일 : %{email}" new_email: "새로운 이메일 : %{email}" almost_done_title: "새 이메일 주소 확인" @@ -1284,6 +1290,7 @@ ko: category_search_priority_very_high_weight: "카테고리 검색 우선 순위가 매우 높은 순위에 가중치가 적용됩니다." allow_uncategorized_topics: "카테고리 없이 게시 허용. 주의: 카테고리 없는 글은 이 항목을 비활성화에 하기전에 카테고리 설정을 해야 합니다." allow_duplicate_topic_titles: "같은 제목의 동일한 주제 허용" + allow_duplicate_topic_titles_category: "카테고리가 다른 경우 동일한 중복 제목을 가진 글을 허용합니다. allow_duplicate_topic_titles는 false 여야합니다." unique_posts_mins: "같은 컨텐츠를 다시 글 할 수 있는 기간(분)" educate_until_posts: "새로운 사용자가 글를 작성할 시 글 작성 방법에 대한 교육패널을 보여주는데, 해당 패널이 보여지는 초기 글 개수" title: "타이틀 태그에 쓰일 이 사이트의 이름" @@ -1311,6 +1318,7 @@ ko: add_rel_nofollow_to_user_content: '사용자 생성 컨텐츠에 대해서 rel nofollow 를 설정함. parent domain을 포함한 internal link는 예외임. 이 설정을 바꾸려면 모든 baked markdown을 "rake posts:rebake" 명령으로 변경해주서야 함' exclude_rel_nofollow_domains: "nofollow를 링크에 추가하지 않아야하는 도메인 목록입니다. example.com은 자동으로 sub.example.com도 허용합니다. 최소한 웹 크롤러가 모든 콘텐츠를 찾을 수 있도록이 사이트의 도메인을 추가해야합니다. 웹 사이트의 다른 부분이 다른 도메인에있는 경우 해당 도메인도 추가하십시오." post_excerpt_maxlength: "글 인용에 허용되는 최대 글자수" + topic_excerpt_maxlength: "글의 첫 번째 게시물에서 생성 된 글의 발췌 / 요약의 최대 길이입니다." show_pinned_excerpt_mobile: "모바일 뷰에서 고정 토픽의 발췌 내용을 보여주기" show_pinned_excerpt_desktop: "데스크톱 뷰에서 고정 토픽의 발췌 내용을 보여주기" post_onebox_maxlength: "onebox가 적용된 Discourse 글에 허용되는 최대 글자수" @@ -1541,19 +1549,19 @@ ko: min_ratio_to_crop: "큰 이미지를 자르는 데 사용되는 비율. 너비 / 높이의 결과를 입력하십시오." simultaneous_uploads: "글 작성기에서 끌어서 놓을 수 있는 최대 파일 수" enable_flash_video_onebox: "swf, flv(어도비 플래쉬)링크를 embed 할 수 있도록 함. 주의: 보안에 대한 위험성을 알려주는 것이 좋다." - 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 자격을 얻기 위한 활동 기간(일)" + 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이 자격을 얻기 위하여 지난 (tl3 time period)일 동안 사용자가 사이트에 방문해야하는 최소 일 수. tl3 기간보다 길게 설정하면 회원레벨 3 승격을 해제합니다. (0이상)" tl3_requires_topics_replied_to: "회원 레벨 3 자격을 얻기 위하여 지난 (tl3 time period)일 동안 사용자가 댓글을 달아야 하는 최소 토픽 수 (0 이상)" tl3_requires_topics_viewed: "회원 레벨 3 자격을 얻기 위하여 지난 (tl3 time period) 일 동안 작성된 토픽 중에서 사용자가 읽어야 하는 토픽의 퍼센트 비율.(0 에서 100)" @@ -1632,7 +1640,6 @@ ko: privacy_policy_url: "개인정보 보호가 있으면 전체 URL을 적어주세요." log_anonymizer_details: "익명화 된 후 사용자 세부 정보를 로그에 유지할지 여부 GDPR을 준수 할 때는이 기능을 해제해야합니다." newuser_spam_host_threshold: "새로운 사용자가 동일한 호스트의 링크를 포스팅하더라도 스팸으로 간주되지 않는 최대 포스팅 수 `newuser_spam_host_threshold`" - staff_like_weight: "스태프가 좋아요를 눌렀을 때 부여할 가산점" topic_view_duration_hours: "N 시간마다 IP/User 별로 새 토픽 조회수를 셉니다." user_profile_view_duration_hours: "N 시간마다 IP/User 별로 새 프로필 조회수를 셉니다." levenshtein_distance_spammer_emails: "스패머 메일을 체크할 때, 허용할 다른 글자 개수(fuzzy match)" @@ -1702,6 +1709,7 @@ ko: email_in_authserv_id: "수신 이메일에서 인증 검사를 수행하는 서비스의 식별자입니다. 이를 구성하는 방법에 대한 지침은 https://meta.discourse.org/t/134358 을 참조 하십시오 ." email_in_spam_header: "스팸을 탐지하는 이메일 헤더입니다." enable_imap: "그룹 메시지를 동기화하기 위해 IMAP을 사용하도록 설정합니다." + imap_polling_period_mins: "IMAP 계정에서 이메일을 확인하는 간격(분) 입니다." email_prefix: "이메일 제목에 쓰일 [라벨]. 설정하지 않으면 기본적으로 'title(필수 설정의)' 이 된다." email_site_title: "사이트에서 전송되는 이메일의 보내는 이를 사이트 이름으로 설정. 설정하지 않으면 'title(필수 설정의)'이 된다. 만약 'title'에 보내는 이에 허용되지 않는 글자가 포함되어 있으면 이 설정이 사용된다." find_related_post_with_key: "답글을 게시하려면 '답장 키'만 사용하십시오. 경고 :이 기능을 비활성화하면 이메일 주소를 기반으로 사용자 가장이 허용됩니다." @@ -1740,6 +1748,7 @@ ko: anonymous_posting_min_trust_level: "익명 게시 할 수 있는 최소 회원등급" anonymous_account_duration_minutes: "한 익명이 익명계정을 만들 때 빨리 만드는 걸 막을 분 단위 시간 예: 600으로 정해놓으면 마지막 게시하고 익명 전환 후로 600분이 지나는 즉시 새 계정이 만들어 집니다." hide_user_profiles_from_public: "익명사용자의 사용자 카드, 사용자 프로필, 사용자 디렉토리 해제." + allow_users_to_hide_profile: "사용자가 자신의 프로필과 현재 상태를 숨기도록 허용" allow_featured_topic_on_user_profiles: "사용자가 자신의 사용자 카드 및 프로필에있는 주제에 대한 링크를 제공 할 수 있습니다." show_inactive_accounts: "로그인 한 사용자가 비활성 계정의 프로필을 찾아 볼 수 있도록합니다." hide_suspension_reasons: "사용자 프로필에 정지 이유를 공개적으로 표시하지 마십시오." @@ -1779,7 +1788,6 @@ ko: display_name_on_posts: "글에 @username 뿐만 아니라 사용자의 전체 이름도 보여준다." show_time_gap_days: "두 글의 일차를 보여주기 위해 두 글이 떨어져 있어야 할 일수" short_progress_text_threshold: "글타래의 글 개수가 이 값을 넘어서면, 글 프로그래스바는 오직 현재 글 넘버만 보여준다. 만약 글 프로그래스바의 넓이는 변경하면, 이 숫자도 변경해야합니다." - default_code_lang: "기본 programming language syntax highlighting은 GitHub code blocks이 적용된다. (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "이 값보다 오래된 글타래에 답글을 달면, 오래된 토론이라는 것을 상기시키기 위해 주의 알림이 보여진다. 비활성화 값음 0 이다. " autohighlight_all_code: "형식이 지정되기 전의(되지 않은) 모든 코드 블럭들에 대해, 사용자가 언어를 지정하지 않아도 강제로 code highlighting이 적용된다." highlighted_languages: "구문 강조 규칙이 포함되었습니다. (경고 : 너무 많은 언어를 포함하면 성능에 영향을 줄 수 있습니다.) 데모는 https://highlightjs.org/static/demo 를 참조하십시오." @@ -1882,7 +1890,8 @@ ko: shared_drafts_category: "주제 초안의 범주를 지정하여 공유 초안 기능을 사용하십시오. 이 범주의 주제는 직원 사용자의 주제 목록에서 표시되지 않습니다." push_notifications_prompt: "사용자 동의 프롬프트를 표시합니다." push_notifications_icon: "알림 모서리에 나타나는 배지 아이콘입니다. 투명도가 있는 96×96 단색 PNG 이미지를 사용하는 것이 좋습니다." - base_font: "사이트의 대부분의 페이지에서 사용할 글꼴입니다. 테마의 설정이 우선 할 수 있습니다." + base_font: "사이트에서 대부분의 텍스트에 사용할 기본 글꼴입니다. 테마는`--font-family` CSS 사용자 정의 속성을 통해 재정의 할 수 있습니다." + heading_font: "사이트의 제목에 사용할 글꼴입니다. 테마는`--heading-font-family` CSS 사용자 정의 속성을 통해 재정의 할 수 있습니다." short_title: "짧은 제목은 사용자의 홈 화면, 실행기 또는 공간이 제한 될 수있는 다른 장소에서 사용됩니다. 12 자로 제한되어야합니다." dashboard_hidden_reports: "대시 보드에서 지정된 보고서를 숨길 수 있습니다." dashboard_visible_tabs: "표시 할 대시 보드 탭을 선택합니다." @@ -1930,6 +1939,9 @@ ko: low_weight_invalid: "가중치를 'category_search_priority_very_low_weight'보다 1보다 크거나 작게 설정할 수 없습니다." high_weight_invalid: "가중치를 1보다 작거나 같거나 'category_search_priority_very_high_weight'보다 크게 설정할 수 없습니다." very_high_weight_invalid: "가중치를 'category_search_priority_high_weight'보다 작게 설정할 수 없습니다." + allowed_unicode_usernames: + regex_invalid: "정규식이 잘못되었습니다: %{error}" + leading_trailing_slash: "정규식은 슬래시로 시작하거나 끝나서는 안됩니다." unicode_usernames_avatars: "내부 시스템 아바타는 유니 코드 사용자 이름을 지원하지 않습니다." list_value_count: "목록에는 정확히 %{count} 값이 포함되어야합니다." placeholder: @@ -1972,6 +1984,10 @@ ko: move_posts: new_topic_moderator_post: other: "%{count}개의 게시물이 새 글로 분할되었습니다: %{topic_link}" + new_message_moderator_post: + other: "%{count}개의 글이 새 글로 분할되었습니다: %{topic_link}" + existing_topic_moderator_post: + other: "%{count}개의 글이 다음 글에 병합되었습니다: %{topic_link}" change_owner: post_revision_text: "소유권 이전" publish_page: @@ -1992,6 +2008,10 @@ ko: other: "마지막 댓글로부터 %{count} 시간이 지나 글타래가 자동으로 닫혔습니다. 새로운 댓글을 다실 수 없습니다." autoclosed_enabled_lastpost_minutes: other: "이 글타래는 마지막 답글이 달리고 %{count}분 뒤 자동적으로 닫혔습니다. 새로운 답글을 다실 수 없습니다." + autoclosed_disabled_days: + other: "이 글은 %{count}일 후 자동으로 열렸습니다." + autoclosed_disabled_hours: + other: "이 글은 %{count}시간 후 자동으로 열렸습니다." autoclosed_disabled: "이 글타래는 이제 열렸습니다. 새로운 답글을 허용합니다." autoclosed_disabled_lastpost: "이 글타래는 이제 열렸습니다. 새로운 답글을 허용합니다." auto_deleted_by_timer: "타이머에 의해 자동삭제됨" @@ -2118,12 +2138,31 @@ ko: invite_password_instructions: title: "비밀번호 안내 초대" subject_template: "%{site_name} 계정을 위해 비밀번호 설정" + text_body_template: | + %{site_name} 초대를 수락 해 주셔서 감사합니다. 환영합니다! + + 지금 비밀번호를 설정 하려면 아래 링크를 클릭하십시오: + %{base_url}/u/password-reset/%{email_token} + + (위 링크가 만료 된 경우 이메일 주소로 로그인 할 때 "비밀번호를 잊어 버렸습니다"를 선택하십시오.) download_backup_mailer: title: "백업 메일러 다운로드" subject_template: "[%{email_prefix}] 사이트 백업 다운로드" + text_body_template: | + 요청하신 [사이트 백업 다운로드](%{backup_file_path})입니다. + + 보안상의 이유로 이 다운로드 링크를 이메일 주소로 보냈습니다. + + (이 다운로드를 요청하지 *않은* 경우, 누군가가 사용자님의 사이트에 대한 관리자 액세스 권한을 가지고 있을 가능성이 있다는점에 대해 우려해야합니다.) + no_token: | + 죄송합니다. 이 백업 다운로드 링크는 이미 사용되었거나 만료되었습니다. admin_confirmation_mailer: title: "관리자 확인" subject_template: "[%{email_prefix}] 새 관리자 계정 확인" + text_body_template: | + **%{target_username} (%{target_email})** 을 포럼의 관리자로 추가 할 것인지 확인하세요. + + [관리자 계정 확인](%{admin_confirm_url}) test_mailer: title: "메일러 테스트" subject_template: "[%{email_prefix}] 이메일 전달 테스트" @@ -2176,6 +2215,12 @@ ko: welcome_staff: title: "직원 환영" subject_template: "축하합니다, 귀하는 %{role} 등급을 받았습니다!" + text_body_template: | + 다른 관리자에 의해 %{role} 등급으로 변경되었습니다. + + 이제 %{role}로서 관리 인터페이스에 접근 할 수 있습니다. + + 게시물 검토가 처음이라면 [관리자 가이드](https://meta.discourse.org/t/discourse-moderation-guide/63116)를 참조하세요. welcome_invite: title: "초대합니다" subject_template: "%{site_name} 사이트에 오신것을 환영합니다!" @@ -2184,15 +2229,49 @@ ko: backup_succeeded: title: "백업 성공" subject_template: "백업 성공" + text_body_template: | + 백업이 성공적으로 완료되었습니다. + + [관리자> 백업 섹션](%{base_url}/admin/backups)을 방문하여 백업을 다운로드하세요. + + 로그는 다음과 같습니다: + + ``` text + %{logs} + ``` backup_failed: title: "백업 실패" subject_template: "백업에 실패했습니다." + text_body_template: | + 백업에 실패했습니다. + + 로그는 다음과 같습니다: + + ``` text + %{logs} + ``` restore_succeeded: title: "복원 성공" subject_template: "복원 성공" + text_body_template: | + 복원에 성공했습니다. + + 로그는 다음과 같습니다: + + ``` text + %{logs} + ``` restore_failed: title: "복원 실패" subject_template: "복원 실패" + text_body_template: | + 복원에 실패했습니다. + + 로그는 다음과 같습니다: + + ``` text + %{logs} + ``` bulk_invite_succeeded: title: "대량 초대 성공" subject_template: "대량 사용자 초대가 성공적으로 진행되었습니다." @@ -2200,6 +2279,14 @@ ko: bulk_invite_failed: title: "대량 초대 실패" subject_template: "대량 사용자 초대 중 오류가 났습니다." + text_body_template: | + 대량 사용자 초대 파일이 처리되었습니다. %{sent}명의 초대가 %{failed}개의 오류와 함께 발송되었습니다. + + 로그는 다음과 같습니다: + + ``` text + %{logs} + ``` user_added_to_group_as_owner: title: "그룹에 소유자로 추가됨" subject_template: "%{group_name} 그룹의 소유자로 추가되었습니다." @@ -2281,6 +2368,10 @@ ko: email_reject_invalid_post_action: title: "이메일 거부 잘못된 사후 조치" subject_template: "[%{email_prefix}] 이메일 문제-잘못된 사후 조치" + text_body_template: | + 죄송합니다. %{destination} (제목은 %{former_title}) 으로 보내는 이메일 메시지가 작동하지 않습니다. + + 사후 작업을 인식하지 못했습니다. 다시 시도하거나 웹 사이트를 통해 게시하십시오. email_reject_reply_key: title: "이메일 거부 회신 키" subject_template: "[%{email_prefix}] 이메일 문제-알 수없는 회신 키" @@ -2314,6 +2405,12 @@ ko: email_error_notification: title: "이메일 에러 알림" subject_template: "[%{email_prefix}] 이메일 문제-POP 인증 오류" + text_body_template: | + 죄송합니다. POP 서버에서 메일을 폴링하는 동안 인증 오류가 발생했습니다. + + [사이트 설정](%{base_url}/admin/site_settings/category/email)에서 POP 자격 증명을 올바르게 구성했는지 확인하십시오. + + POP 이메일 계정에 대한 웹 UI가있는 경우 웹에 로그인하여 설정을 확인해야 할 수 있습니다. email_revoked: title: "이메일 해지" subject_template: "이메일 주소가 맞습니까?" @@ -2358,10 +2455,24 @@ ko: new_user_of_the_month: title: "이달의 신규회원으로 선정되었습니다!" subject_template: "이달의 신규회원으로 선정되었습니다!" + text_body_template: | + 축하합니다. **%{month_year}의 신규 사용자** 로 선정되었습니다. :trophy: + + 이상은 매월 2명의 신규 사용자에게만 수여되며 [배지 페이지](%{url})에 영구적으로 표시됩니다. + + 사용자님은 빠르게 우리 커뮤니티의 소중한 구성원이되었습니다. 참여 해주셔서 감사합니다! queued_posts_reminder: title: "대기중인 게시물 알림" subject_template: other: "%{count} 게시물 검토 대기" + unsubscribe_link: | + 이러한 이메일을 수신 거부하려면 [여기를 클릭](%{unsubscribe_url}). + unsubscribe_link_and_mail: | + 이러한 이메일을 수신 거부하려면 [여기를 클릭](%{unsubscribe_url}). + unsubscribe_mailing_list: | + 메일링리스트 모드를 활성화했기 때문에 이 메일을 수신 한 것입니다. + + 이러한 이메일을 수신 거부하려면 [여기를 클릭](%{unsubscribe_url}). subject_re: "덧: " subject_pm: "[PM] " email_from: "%{site_name}를 통한 %{user_name}" @@ -2379,18 +2490,56 @@ ko: visit_link_to_respond_pm: "[방문 메시지] (%{base_url}%{url})는 %{participants}에 응답합니다." posted_by: "%{username} 사용자가 %{post_date}에 게시하였습니다." pm_participants: "참가자 : %{participants}" + invited_to_topic_body: | + %{username}님이 사용자님을 대화에 초대했습니다. + + > ** [%{topic_title}] (%{topic_url}) ** + > + > %{topic_excerpt} + + 에서 + + > %{site_title} - %{site_description} + + 대화에 참여 하시려면 아래 링크를 클릭 하세요: + + %{topic_url} user_invited_to_private_message_pm_group: title: "PM에 사용자 초대 그룹" subject_template: "[%{email_prefix}] %{username} 님이 @ %{group_name}를 '%{topic_title}'메시지에 초대했습니다" + text_body_template: | + %{header_instructions} + + %{message} + + %{respond_instructions} user_invited_to_private_message_pm: title: "PM에 초대 된 사용자" subject_template: "[%{email_prefix}] %{username}가 '%{topic_title}'메시지에 초대했습니다." + text_body_template: | + %{header_instructions} + + %{message} + + %{respond_instructions} user_invited_to_private_message_pm_staged: title: "PM 단계별 사용자 초대" subject_template: "[%{email_prefix}] %{username}가 '%{topic_title}'메시지에 초대했습니다." + text_body_template: | + %{header_instructions} + + %{message} + + %{respond_instructions} user_invited_to_topic: title: "주제에 초대 된 사용자" subject_template: "[%{email_prefix}] %{username} 님이 '%{topic_title}'에 초대했습니다." + text_body_template: | + %{header_instructions} + + %{message} + + %{respond_instructions} user_replied: title: "응답 한 사용자" subject_template: "[%{email_prefix}] %{topic_title}" @@ -2532,9 +2681,23 @@ ko: account_exists: title: "계정이 이미 존재합니다" subject_template: "[%{email_prefix}] 계정이 이미 존재합니다" + text_body_template: | + %{site_name}에서 계정을 만들려고 했거나 계정의 이메일을 %{email}로 변경하려고했습니다. 그러나, %{email}의 계정이 이미 존재합니다. + + 비밀번호를 잊은 경우 [지금 재설정](%{base_url}/password-reset). + + %{email}의 계정을 만들지 않았거나 이메일 주소를 변경하지 않았다면 걱정하지 마십시오. 이 메시지를 무시해도됩니다. + + 궁금한 점이 있으면 [관리자에게 문의](%{base_url}/about)하세요. account_second_factor_disabled: title: "2단계 인증 사용 안 함" subject_template: "[%{email_prefix}] 2 단계 인증 비활성화" + text_body_template: | + %{site_name}의 계정에서 2단계 인증이 비활성화 되었습니다. 이제 비밀번호만으로 로그인 할 수 있습니다. 추가 인증 코드가 더 이상 필요하지 않습니다. + + 2단계 인증을 비활성화하지 않은 경우 누군가 사용자님의 계정을 도용했을 수도 있습니다. + + 궁금한 점이 있으면 [관리자에게 문의](%{base_url}/about)하세요. digest: why: "%{last_seen_at}부터 지금까지 %{site_link} 사이트 근황" since_last_visit: "마지막 방문 후 경과시간" @@ -2577,12 +2740,31 @@ ko: set_password: title: "비밀번호 설정" subject_template: "[%{email_prefix}] 비밀번호 설정" + text_body_template: | + 누군가 [%{site_name}](%{base_url})에서 사용자님의 계정에 비밀번호를 추가하도록 요청했습니다. 또는 소셜 로그인 (Google, Facebook 등)을 사용하여 로그인 할 수 있습니다. + + 이 요청을하지 않았면 이 이메일을 무시 하셔도됩니다. + + 비밀번호를 입력 하려면 다음 링크를 클릭하십시오: + %{base_url}/u/password-reset/%{email_token} admin_login: title: "관리자 로그인" subject_template: "[%{email_prefix}] 로그인" + text_body_template: | + 누군가 [%{site_name}](%{base_url})에서 사용자님의 계정에 로그인을 요청했습니다. + + 이 요청을 하지 않았다면 이 이메일을 무시 하셔도됩니다. + + 로그인하려면 다음 링크를 클릭하십시오: + %{base_url}/session/email-login/%{email_token} account_created: title: "계정 생성" subject_template: "[%{email_prefix}] 새 계정" + text_body_template: | + %{site_name}에서 새로운 계정이 만들어졌습니다. + + 새 계정의 비밀번호를 선택하려면 다음 링크를 클릭하십시오: + %{base_url}/u/password-reset/%{email_token} confirm_new_email: title: "새 이메일 확인" subject_template: "[%{email_prefix}] 새 이메일 주소 확인" @@ -2590,6 +2772,17 @@ ko: 다음 링크를 클릭하여 %{site_name}의 새 이메일 주소를 확인하십시오. %{base_url}/u/confirm-new-email/%{email_token} + + 이 변경을 요청하지 않은 경우 %{site_name} 관리자에게 문의하십시오. + confirm_new_email_via_admin: + title: "새 이메일 확인" + subject_template: "[%{email_prefix}] 새 이메일 주소 확인" + text_body_template: | + 다음 링크를 클릭하여 %{site_name}의 새 이메일 주소를 확인하십시오. + + %{base_url}/u/confirm-new-email/%{email_token} + + 이 이메일 변경은 사이트 관리자가 요청했습니다. 이 변경을 요청하지 않은 경우 %{site_name} 관리자에게 문의하십시오. confirm_old_email: title: "오래된 이메일 확인" subject_template: "[%{email_prefix}] 현재 이메일 주소 확인" @@ -2602,24 +2795,86 @@ ko: confirm_old_email_add: title: "이전 이메일 확인 (추가)" subject_template: "[%{email_prefix}] 현재 이메일 주소 확인" + text_body_template: | + 새로운 이메일 주소를 추가하기 위해서는 현재 이메일 계정에 대한 사용자님의 확인이 필요합니다. 이 단계를 완료 한 후, 새로운 이메일 주소를 입력해야 합니다. + + 다음 링크를 클릭하여 %{site_name}의 현재 이메일 주소를 확인하십시오: + + %{base_url}/u/confirm-old-email/%{email_token} notify_old_email: title: "오래된 이메일 알림" subject_template: "[%{email_prefix}] 귀하의 이메일 주소가 변경되었습니다" + text_body_template: | + 이것은 %{site_name}에서 사용자님의 계정 이메일 주소가 변경되었음을 알리는 자동 메일입니다. 오류가 발생한 경우 + 사이트 관리자에게 문의하십시오. + + 사용자님의 이메일 주소는 다음으로 변경되었습니다: + + %{new_email} notify_old_email_add: title: "이전 이메일 알림 (추가)" subject_template: "[%{email_prefix}] 새 이메일 주소가 추가되었습니다." + text_body_template: | + %{site_name}에서 사용자님의 이메일 주소가 추가되었음을 알리는 자동 메시지입니다. 오류가 발생한 경우 사이트 관리자에게 문의하십시오. + + 추가 된 이메일 주소: + + %{new_email} signup_after_approval: title: "승인 후 가입" subject_template: "당신은 %{site_name} 가입이 승인되었습니다!" + text_body_template: | + %{site_name}에 오신 것을 환영합니다! + + %{site_name}에서 사용자님의 계정이 승인되었습니다. + + 아래 링크를 클릭하면 새 계정에 로그인 할 수 있습니다. + %{base_url} + + 위 링크를 클릭 할 수 없으면 웹 브라우저의 주소 표시 줄에 복사하여 붙여 넣으십시오. + + %{new_user_tips} + + 사이트 이용은 [커뮤니티의 가이드라인](%{base_url}/guidelines)을 참고해 주시기 바랍니다. + + 즐거운 시간 되세요! signup: title: "가입하기" subject_template: "[%{email_prefix}] 새 계정 확인" + text_body_template: | + %{site_name}에 오신 것을 환영합니다! + + 다음 링크를 클릭하여 새 계정을 확인하고 활성화하십시오: + %{base_url}/u/activate-account/%{email_token} + + 위 링크를 클릭 할 수 없는 경우 웹 브라우저의 주소 표시 줄에 복사하여 붙여 넣으십시오. activation_reminder: title: "활성화 알림" subject_template: "[%{email_prefix}] 계정 확인 알림" + text_body_template: | + %{site_name}에 오신 것을 환영합니다! + + 사용자님의 계정을 활성화하기 위한 알림입니다. + + 다음 링크를 클릭하여 새 계정을 확인하고 활성화하십시오: + %{base_url}/u/activate-account/%{email_token} + + 위 링크를 클릭 할 수 없는 경우 웹 브라우저의 주소 표시 줄에 복사하여 붙여 넣으십시오. suspicious_login: title: "새로운 로그인 알림" subject_template: "[%{site_name}] %{location}에서 새 로그인" + text_body_template: | + 안녕하세요, + + 사용자님이 평소에 사용하지 않는 장치 또는 위치에서 로그인 한 것을 감지했습니다. + + - 위치: %{location} (%{client_ip}) + - 브라우저: %{browser} + - 기기: %{device} – %{os} + + 이것이 사용자님이 로그인 한 것이라면 다른 조치를 취하실 필요가 없습니다. + + 만약 사용자님이 아니라면 [기존 세션을 검토](%{base_url}/my/preferences/account)하고 비밀번호 변경을 고려하십시오. page_forbidden: title: "죄송합니다! 비공개 페이지 입니다." page_not_found: @@ -2732,6 +2987,8 @@ ko: wiki_editor: name: 위키 에디터 description: 첫 위키 편집 + long_description: | + 이 배지는 처음으로 위키 게시물을 편집하면 부여됩니다. basic_user: name: 초보회원 description: 모든 필수 커뮤니티 기능 부여 @@ -2758,8 +3015,10 @@ ko: long_description: | 이 배지는 게시물에서 처음 좋아요를 받았을 때 부여됩니다. 축하합니다! 다른 사용자님이 흥미롭고 멋지거나 유용하다고 생각한 내용을 게시했습니다. autobiographer: - name: 자서전 작가 + name: 프로필 작성 description: 프로필 정보 작성 + long_description: | + 이 배지는 프로필을 작성하고 프로필 사진을 추가하면 부여됩니다. 사용자님이 누구인지, 무엇에 관심이 있는지에 대해 커뮤니티에 조금 더 알리면 더 나은 소통을 할 수 있습니다! anniversary: name: 1주년 description: 1년동안 활동하고 글도 1개이상 썼습니다 @@ -2804,18 +3063,24 @@ ko: good_share: name: 훌륭한 공유 description: 공유한 포스트로 외부 방문자 300명이 들어왔습니다 + long_description: | + 이 배지는 사용자님이 공유한 링크를 300명 이상이 클릭하면 부여됩니다. 사용자님은 많은 새로운 사람들에게 훌륭한 정보를 보여 주었고, 이 커뮤니티가 성장하도록 도왔습니다. great_share: name: 위대한 공유 description: 공유한 포스트로 외부 방문자 1000명이 들어왔습니다 + long_description: | + 이 배지는 사용자님이 공유한 링크가 외부 방문자 1000명이 클릭했을때 부여됩니다. 와! 다른 사용자들에게 흥미로운 정보를 홍보하고 커뮤니티를 크게 성장시키는 데 도움이되었습니다! first_like: - name: 첫 좋아요 + name: 첫 번째 좋아요 description: 포스트에 좋아요를 보냈습니다 long_description: |2 이 배지는 :heart: 버튼을 사용해서 포스트에 좋아요를 처음으로 했을 때 수여됩니다. 포스트에 좋아요를 남기는 것은 동료 커뮤니티 멤버에게 그들의 포스트가 흥미롭고 쓸모있고 멋지고 재밌다고 표시하는 훌륭한 방법입니다. 사랑을 나누세요! first_flag: - name: 첫 신고 + name: 첫 번째 신고 description: 포스트를 신고했습니다 + long_description: | + 이 배지는 게시물을 처음 신고 할 때 부여됩니다. 신고는 우리 모두가 이곳을 모두에게 좋은 곳으로 유지하는데 도움이되는 방법입니다. 어떤 이유로든 관리자의 검토가 필요한 게시물을 발견하면 주저하지 말고 신고해 주세요. 문제가 있으면 :flag_black: 아이콘을 클릭하면 됩니다! promoter: name: 후원자 description: 사용자를 초청했습니다 @@ -2832,24 +3097,24 @@ ko: long_description: | 이 배지는 사용자님이 초대한 5명의 사용자들이 사이트에서 충분한 기간동안 활동을 했을 때 부여됩니다. 와! 새로운 회원으로 커뮤니티의 다양성을 확장해 주셔서 감사합니다! first_share: - name: 처음 공유해요 + name: 첫 번째 공유 description: 포스트를 공유했습니다 long_description: |2 이 배지는 공유 버튼으로 토픽이나 답글의 링크를 처음으로 공유했을 때 수여됩니다. 링크를 공유하는 것은 바깥 세상에 훌륭한 대화를 자랑하고 우리 커뮤니티를 성장시키는 훌륭한 방법입니다. first_link: - name: 처음 링크걸어요 + name: 첫 번째 링크 description: 다른 토픽에 링크를 추가했습니다 long_description: | 이 배지는 다른 글에 대한 링크를 처음 추가 할 때 부여됩니다. 글을 링크하면 양방향으로 게시물 간의 연관성을 보여줌으로써 다른 사용자가 관련 내용을 찾을 수 있습니다. 자유롭게 링크 하세요! first_quote: - name: 처음 인용해요 + name: 첫 번째 인용 description: 포스트를 인용함 read_guidelines: - name: 가이드라인을 읽었어요 + name: 가이드라인 읽기 description: 커뮤니티 지침을 읽으십시오 reader: - name: 독서가 + name: 글 읽기 description: 100개가 넘는 답글이 달린 토픽의 답글을 모두 읽었습니다 long_description: |2 @@ -2873,7 +3138,7 @@ ko: 이 배지는 공유한 링크가 1000클릭을 받았을 때 수여됩니다. 우와! 기본적인 세부사항, 흐름, 정보를 덧붙여주셔서 대화를 획기적으로 발전시키셨군요. 잘하셨습니다! appreciated: - name: 고맙습니다 + name: 감사합니다 description: 포스트 20개를 써서 좋아요 1개를 받았습니다 long_description: |2 @@ -2902,10 +3167,11 @@ ko: 이 배지는 5일 동안 매일 좋아요 %{max_likes_per_day}번을 모두 사용하면 부여됩니다. 매일 최고의 대화를 위해 적극적으로 시간을내어 주셔서 감사합니다! crazy_in_love: name: 사랑밖에 모르는 바보 + description: 좋아요 %{max_likes_per_day}개를 하루에 20번 사용했습니다. long_description: | 이 배지는 20일 동안 매일 좋아요 %{max_likes_per_day}번을 모두 사용하면 부여됩니다. 와! 사용자님은 다른 커뮤니티 회원을 격려하는 긍정적인 활동을 해주셨군요! thank_you: - name: 고마워요 + name: 고맙습니다 description: 10개의 포스트를 '좋아요'하고 20개의 '좋아요'를 받았습니다 long_description: | 이 배지는 받은 좋아요가 20개이고 10개 이상의 좋아요를 보냈을 때 수여됩니다. 누군가 사용자님의 글을 좋아했다면 사용자님도 다른 사용자가 쓴 글에 좋아요를 눌러보세요. @@ -2920,18 +3186,18 @@ ko: long_description: | 이 배지는 좋아요를 받은 게시물이 500개이고 좋아요를 1000개 이상 주면 부여됩니다. 와! 당신은 관대함과 상호 감사의 좋은 모델입니다 :two_hearts:. first_emoji: - name: 첫 Emoji + name: 첫 번째 이모티콘 description: 게시글에 Emoji를 사용했습니다 first_mention: - name: 첫 멘션 + name: 첫 번째 멘션 description: 처음으로 포스트에서 다른 사용자를 멘션했습니다 first_onebox: - name: 첫 박스 링크 + name: 첫 번째 박스 링크 description: 박스 링크를 처음으로 포스팅했습니다 long_description: | 이 배지는 처음으로 링크를 게시할 때 부여됩니다. 이 배지는 요약, 제목 및 (사용 가능한 경우) 사진이 포함된 onebox로 자동 확장됩니다. first_reply_by_email: - name: 첫 이메일 답글 + name: 이메일로 첫 답글 description: 포스트에 이메일로 답글을 달았습니다 long_description: |2 @@ -2951,6 +3217,8 @@ ko: devotee: name: 신봉자 description: 365 일 연속 방문 + long_description: | + 이 배지는 365일 연속 방문시 부여됩니다. 와, 일년 내내! badge_title_metadata: "%{site_title}의 %{display_name} 배지" admin_login: success: "이메일 보냄" @@ -2973,8 +3241,12 @@ ko: invalid: other: "선택한 태그를 사용할 수 없습니다." in_this_category: '이 카테고리에서는 "%{tag_name}"을 사용할 수 없습니다' + restricted_to: + other: '"%{tag_name}"은 %{category_names} 카테고리로 제한됩니다.' synonym: '동의어는 허용되지 않습니다. 대신 "%{tag_name}"을 사용하십시오.' has_synonyms: '"%{tag_name}"에는 동의어가 있으므로 사용할 수 없습니다.' + required_tags_from_group: + other: "최소한 %{count} %{tag_group_name} 태그를 포함해야합니다." invalid_target_tag: "동의어의 동의어 일 수 없습니다" synonyms_exist: "동의어가 존재하는 동안 허용되지 않습니다" rss_by_tag: "토픽에%{tag}태그가 달렸습니다" @@ -3078,16 +3350,15 @@ ko: placeholder: "샌프란시스코, 캘리포니아" colors: title: "테마" - themes_further_reading: - title: "테마" - description: - - - - '인기 테마 구성 요소 (자세한 내용은 #theme)"' fonts: title: "글꼴" + fields: + body_font: + label: "본문 글꼴" + heading_font: + label: "제목 글꼴" + font_preview: + label: "미리 보기" logos: title: "로고" fields: @@ -3158,6 +3429,8 @@ ko: user_delete_self: "%{url}에서 자체 삭제" webhook_deactivation_reason: "웹 후크가 자동으로 비활성화되었습니다. 여러 개의 '%{status}'HTTP 상태 실패 응답을 받았습니다." api_key: + automatic_revoked: + other: "자동으로 취소됨, 마지막 활동이 %{count}일 이전 입니다." revoked: 해지 restored: 복원 reviewables: @@ -3189,6 +3462,7 @@ ko: email_auth_res_enqueue: "이 이메일은 DMARC 확인에 실패했으며, 발신자가 아닌 것 같습니다. 자세한 내용은 원시 이메일 헤더를 확인하십시오." email_spam: "이 이메일은`email_in_spam_header`에 정의 된 헤더에 의해 스팸으로 표시되었습니다." suspect_user: "이 새로운 사용자는 주제 나 게시물을 읽지 않고 프로필 정보를 입력했으며, 이는 스팸 발송자 일 수 있음을 강력하게 제안합니다. `approve_suspect_users`를 참조하십시오." + contains_media: "이 게시물에는 임베디드 미디어가 포함되어 있습니다. `review_media_unless_trust_level`을 참조하십시오." actions: agree: title: "동의..." @@ -3271,6 +3545,14 @@ ko: not_in_allowed_guild: "인증 실패. 귀하는 허용 된 불일치 길드의 회원이 아닙니다." old_keys_reminder: title: "이전 자격 증명에 대한 알림" + body: | + 안녕하세요! 이것은 Discourse 인스턴스에서 제공하는 정기적인 연간 보안 알림입니다. + + 사용자님의 Discourse 인스턴스에 사용 된 다음 자격 증명이 2년 이상 업데이트되지 않았음을 알려 드립니다. + + %{keys} + + 반드시 해야 할 조치는 아니지만 보안을 위해 몇 년마다 모든 중요한 자격 증명을 업데이트 하십시오. activemodel: errors: <<: *errors diff --git a/config/locales/server.lt.yml b/config/locales/server.lt.yml index b3fb84328f..233523835b 100644 --- a/config/locales/server.lt.yml +++ b/config/locales/server.lt.yml @@ -1038,8 +1038,6 @@ lt: title: "Organizacija" colors: title: "Tema" - themes_further_reading: - title: "Temos" logos: title: "Logotipas" fields: diff --git a/config/locales/server.lv.yml b/config/locales/server.lv.yml index 37a3f9d550..dd915725d8 100644 --- a/config/locales/server.lv.yml +++ b/config/locales/server.lv.yml @@ -368,8 +368,6 @@ lv: title: "Organizācija" colors: title: "Tēma" - themes_further_reading: - title: "Dizaini" homepage: fields: homepage_style: diff --git a/config/locales/server.nb_NO.yml b/config/locales/server.nb_NO.yml index cd410dd837..3f70695d0e 100644 --- a/config/locales/server.nb_NO.yml +++ b/config/locales/server.nb_NO.yml @@ -1480,8 +1480,6 @@ nb_NO: title: "Organisasjon" colors: title: "Drakt" - themes_further_reading: - title: "Drakter" logos: title: "Logoer" fields: diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index 8709b4a9a3..bd063634b1 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -117,6 +117,8 @@ nl: unsubscribe_not_allowed: "Gebeurt wanneer uitschrijven via e-mail niet is toegestaan voor deze gebruiker." email_not_allowed: "Gebeurt wanneer het e-mailadres zich niet op de acceptatielijst of wel op de blokkeerlijst bevindt." unrecognized_error: "Niet-herkende fout" + secure_media_placeholder: "Geredigeerd: deze website heeft beveiligde media ingeschakeld. Bezoek het topic of klik op Media weergeven om de bijgevoegde media te zien." + view_redacted_media: "Media weergeven" errors: &errors format: ! "%{attribute} %{message}" format_with_full_message: "%{attribute}: %{message}" @@ -187,6 +189,7 @@ nl: cannot_enable_s3_uploads_when_s3_enabled_globally: "U kunt geen S3-uploads inschakelen, omdat S3-uploads al globaal zijn ingeschakeld, en inschakelen hiervan op websiteniveau kan kritieke problemen met uploads veroorzaken" conflicting_google_user_id: 'De Google-account-ID voor deze account is gewijzigd; om beveiligingsredenen is stafinterventie vereist. Neem contact op met een staflid en wijs hem of haar op
https://meta.discourse.org/t/76575' invite: + expired: "Uw uitnodigingstoken is verlopen. Neem contact op met een staflid." not_found: "Uw uitnodigingstoken is ongeldig. Neem contact op met een staflid." not_found_json: "Uw uitnodigingstoken is ongeldig. Neem contact op met een staflid." not_found_template: | @@ -199,6 +202,10 @@ nl: user_exists: "%{email} hoeft niet te worden uitgenodigd; hij of zij heeft al een account!" confirm_email: "

U bent er bijna! We hebben een activeringsmail naar uw e-mailadres gestuurd. Volg de instructies in het e-mailbericht om uw account te activeren.

Als dit niet aankomt, controleer dan uw spammap.

" cant_invite_to_group: "U mag geen gebruikers uitnodigen voor bepaalde groepen. Zorg ervoor dat u eigenaar bent van de groep(en) waarvoor u probeert uit te nodigen." + disabled_errors: + sso_enabled: "Uitnodigingen zijn uitgeschakeld, omdat SSO is ingeschakeld." + local_logins_disabled: "Uitnodigingen zijn uitgeschakeld, omdat de instelling 'lokale aanmeldingen inschakelen' is uitgeschakeld." + invalid_access: "U mag de opgevraagde bron niet bekijken." bulk_invite: file_should_be_csv: "Het geüploade bestand dient de CSV-indeling te hebben." max_rows: "De eerste %{max_bulk_invites} uitnodigingen zijn verstuurd. Probeer het bestand in kleinere delen op te splitsen." @@ -431,14 +438,6 @@ nl: Dit topic is duidelijk belangrijk voor u – u hebt hier meer dan %{percent}% van de antwoorden geplaatst. Het zou nog beter kunnen zijn als u ook anderen de ruimte geeft om hun ideeën te delen. Kunt u ze uitnodigen? - get_a_room: | - ### Overweeg iedereen aan de conversatie te laten deelnemen - - U hebt in dit specifieke topic %{count} keer op @%{reply_username} geantwoord! - - Een goede discussie omvat veel meningen en perspectieven. Kunt u hier nog anderen bij betrekken? - - Denk eraan: als u uw conversatie met deze bepaalde gebruiker wilt verbergen, kunt u [hem of haar een persoonlijk bericht sturen](%{base_path}/u/%{reply_username}). too_many_replies: | ### U hebt de antwoordlimiet voor dit topic bereikt @@ -1455,6 +1454,7 @@ nl: site_contact_group_name: "Een geldige groepsnaam voor uitnodiging in alle automatische berichten." send_welcome_message: "Alle nieuwe gebruikers een welkomstbericht met een snelstartgids sturen." send_tl1_welcome_message: "Nieuwe gebruikers met vertrouwensniveau 1 een welkomstbericht sturen." + send_tl2_promotion_message: "Nieuwe gebruikers met vertrouwensniveau 2 een bericht over promotie sturen." suppress_reply_directly_below: "Het uitvouwbare aantal antwoorden bij een bericht niet tonen als er maar één antwoord direct onder dit bericht staat." suppress_reply_directly_above: "Het uitvouwbare 'in antwoord op' bij een bericht niet tonen als er maar één antwoord direct boven dit bericht staat." remove_full_quote: "Volledige citaten bij directe antwoorden automatisch verwijderen." @@ -1472,6 +1472,7 @@ nl: enable_badges: "Het badgesysteem inschakelen" enable_whispers: "Privécommunicatie van stafleden binnen topics toestaan." allow_index_in_robots_txt: "Geef in robots.txt op dat deze website door webzoekmachines mag worden geïndexeerd. In uitzonderlijke gevallen kunt u robots.txt blijvend negeren." + blocked_email_domains: "Een door een verticaal streepje (pipe) gescheiden lijst van e-maildomeinen waarbij gebruikers geen accounts mogen registreren. Voorbeeld: mailinator.com|trashmail.net" auto_approve_email_domains: "Gebruikers met e-mailadressen van deze lijst met domeinen worden automatisch goedgekeurd." hide_email_address_taken: "Gebruikers niet informeren dat een account met een bepaald e-mailadres bestaat tijdens registratie en vanaf het formulier voor vergeten wachtwoorden." log_out_strict: "Bij het afmelden ALLE sessies voor de gebruiker op alle apparaten afmelden" @@ -1661,6 +1662,7 @@ nl: category_colors: "Een lijst van hexadecimale kleurwaarden die voor categorieën zijn toegestaan." category_style: "Visuele stijl voor categoriebadges." default_dark_mode_color_scheme_id: "Het kleurenschema dat wordt gebruikt in donkere modus." + dark_mode_none: "Geen" max_image_size_kb: "De maximale grootte van te uploaden afbeeldingen in kB. Dit moet ook worden geconfigureerd in nginx (client_max_body_size) / apache of proxy. Afbeeldingen die groter zijn dan deze waarde en kleiner dan client_max_body_size worden bij het uploaden verkleind." max_attachment_size_kb: "De maximale grootte van te uploaden bijlagen in kB. Dit moet ook worden geconfigureerd in nginx (client_max_body_size) / apache of proxy." authorized_extensions: "Een lijst van toegestane bestandsextensies voor uploaden (gebruik '*' voor alle bestandstypen)" @@ -1688,7 +1690,6 @@ nl: privacy_policy_url: "Als u ergens anders een document met een Privacybeleid hebt gehost, geef hier dan de volledige URL op." log_anonymizer_details: "Of de details van een gebruiker in het logboek worden behouden nadat ze zijn geanonimiseerd. Om aan de GDPR te voldoen, dient u dit uit te schakelen." newuser_spam_host_threshold: "Hoe vaak een nieuwe gebruiker een koppeling naar dezelfde host kan plaatsen binnen de `newuser_spam_host_threshold` berichten ervan voordat deze als spam worden beschouwd." - staff_like_weight: "Hoeveel zwaarder de weegfactor moet zijn voor likes die door stafleden zijn gegeven." 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." @@ -1801,6 +1802,7 @@ nl: anonymous_posting_min_trust_level: "Het minimale vertrouwensniveau dat nodig is om anonieme berichtplaatsing in te schakelen" anonymous_account_duration_minutes: "Om anonimiteit te beschermen, elke N minuten voor iedere gebruiker een nieuwe anonieme account aanmaken. Voorbeeld: als dit is ingesteld op 600, wordt een nieuwe anonieme account aangemaakt als er 600 minuten zijn verstreken na het laatste bericht EN de gebruiker naar anon overschakelt." hide_user_profiles_from_public: "Gebruikerskaarten, gebruikersprofielen en gebruikerslijst voor anonieme gebruikers uitschakelen." + allow_users_to_hide_profile: "Gebruikers mogen hun profiel en aanwezigheid verbergen" allow_featured_topic_on_user_profiles: "Gebruikers mogen een koppeling naar een topic aanbevelen op hun gebruikerskaart en profiel." show_inactive_accounts: "Aangemelde gebruikers mogen profielen van inactieve accounts doorbladeren." hide_suspension_reasons: "Schorsingsredenen niet openbaar weergeven op gebruikersprofielen." @@ -1840,7 +1842,6 @@ nl: display_name_on_posts: "Volledige naam van een gebruiker tonen bij berichten, naast de @gebruikersnaam." show_time_gap_days: "Als twee berichten dit aantal dagen na elkaar zijn gemaakt, een tijdsgat in het topic weergeven." short_progress_text_threshold: "Als het aantal berichten in een topic hoger is dan dit aantal, toont de voortgangsbalk alleen het nummer van het huidige bericht. Als u de breedte van de voortgangsbalk wijzigt, dient u deze waarde mogelijk ook te wijzigen." - default_code_lang: "Standaard programmeertaal-syntaxismarkering die op GitHub-codeblokken wordt toegepast (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "Wanneer iemand begint te antwoorden op een topic waarin het laatste antwoord ouder is dan dit aantal dagen, wordt een waarschuwing weergegeven. Schakel uit door dit op 0 te zetten." autohighlight_all_code: "Toepassen van codemarkering bij alle vooraf opgemaakte codeblokken afdwingen, zelfs als de codetaal niet expliciet is opgeven." highlighted_languages: "Opgenomen regels voor syntaxismarkering. (Waarschuwing: het opnemen van te veel talen kan prestaties beïnvloeden); bekijk https://highlightjs.org/static/demo voor een demo." @@ -1880,6 +1881,7 @@ nl: new_user_notice_tl: "Het minimale vertrouwensniveau dat nodig is om berichtmeldingen van nieuwe gebruikers te zien." returning_user_notice_tl: "Het minimale vertrouwensniveau dat nodig is om berichtmeldingen van terugkerende gebruikers te zien." returning_users_days: "Het aantal dagen dat voorbij moet gaan voordat een gebruiker als terugkerend wordt beschouwd." + review_media_unless_trust_level: "Stafleden beoordelen berichten van gebruikers met lagere vertrouwensniveaus als deze ingebedde media bevatten." enable_page_publishing: "Stafleden mogen topics naar nieuwe URL's publiceren met hun eigen stijlen." show_published_pages_login_required: "Anonieme gebruikers kunnen gepubliceerde pagina's zien, zelfs wanneer aanmelding is vereist." default_email_digest_frequency: "Hoe vaak gebruikers standaard e-mailsamenvattingen ontvangen." @@ -1943,6 +1945,7 @@ nl: shared_drafts_category: "Schakel de functie Gedeelde concepten in door een categorie voor topicconcepten aan te geven. Topics in deze categorie worden onderdrukt in topiclijsten voor stafgebruikers." push_notifications_prompt: "Prompt voor gebruikerstoestemming weergeven." push_notifications_icon: "Het badgepictogram dat in de meldingshoek verschijnt. Een eenkleurige PNG van 96×96 met transparantie wordt aanbevolen." + heading_font: "Te gebruiken lettertypen voor kopteksten op de website. Thema's kunnen worden overschreven via de aangepaste CSS-eigenschap '--heading-font-family'." short_title: "De korte titel wordt gebruikt op het startscherm van de gebruiker, de starter, of andere plaatsen waar ruimte beperkt kan zijn. Beperk de naam tot 12 tekens." dashboard_hidden_reports: "Toestaan dat de opgegeven rapporten worden verborgen op het dashboard." dashboard_visible_tabs: "Kiezen welke dashboardtabbladen zichtbaar zijn." @@ -2298,10 +2301,17 @@ nl: title: "Welkom TL1-gebruiker" subject_template: "Bedankt voor de tijd die u met ons doorbrengt" welcome_staff: + title: "Welkom staflid" subject_template: "Gefeliciteerd, de status %{role} is aan u toegekend!" welcome_invite: title: "Welkomstuitnodiging" subject_template: "Welkom bij %{site_name}!" + tl2_promotion_message: + subject_template: "Gefeliciteerd met de promotie van uw vertrouwensniveau!" + text_body_template: | + We hebben uw [vertrouwensniveau](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) gepromoveerd! + + We nodigen u uit om mee te blijven doen – we vinden het leuk om u in de buurt te hebben. backup_succeeded: title: "Back-up geslaagd" subject_template: "Back-up voltooid" @@ -2389,8 +2399,10 @@ nl: Hebt u meer dan één e-mailadres gebruikt? Hebt u vanaf een andere e-mailadres geantwoord? E-mailantwoorden vereisen dat u bij het beantwoorden hetzelfde e-mailadres gebruikt. Het is ook mogelijk dat de header van het bericht-ID in het e-mailbericht is aangepast. silenced_by_staff: + title: "Gedempt door staflid" subject_template: "Account tijdelijk geblokkeerd" user_automatically_silenced: + title: "Gebruiker automatisch gedempt" subject_template: "Nieuwe gebruiker %{username} gedempt wegens markeringen door gemeenschap" text_body_template: | Dit is een automatisch bericht. @@ -2419,6 +2431,9 @@ nl: subject_template: "Het downloaden van externe afbeeldingen is uitgeschakeld" dashboard_problems: title: "Dashboardproblemen" + new_user_of_the_month: + title: "U bent een nieuwe gebruiker van de maand!" + subject_template: "U bent een nieuwe gebruiker van de maand!" subject_re: "Re:" subject_pm: "[PM]" user_notifications: @@ -2524,6 +2539,17 @@ nl: Bevestig uw nieuwe e-mailadres voor %{site_name} door op de volgende koppeling te klikken: %{base_url}/u/confirm-new-email/%{email_token} + + Als u niet om deze wijziging hebt gevraagd, neem dan contact op met een beheerder van %{site_name}. + confirm_new_email_via_admin: + title: "Nieuw e-mailadres bevestigen" + subject_template: "[%{email_prefix}] Bevestig uw nieuwe e-mailadres" + text_body_template: | + Bevestig uw nieuwe e-mailadres voor %{site_name} door op de volgende koppeling te klikken: + + %{base_url}/u/confirm-new-email/%{email_token} + + Deze adreswijziging is aangevraagd door een websitebeheerder. Als u niet om deze wijziging hebt gevraagd, neem dan contact op met een beheerder van %{site_name}. confirm_old_email: subject_template: "[%{email_prefix}] Bevestig uw huidige e-mailadres" text_body_template: | @@ -2765,6 +2791,8 @@ nl: devotee: name: Toegewijde description: Heeft 365 opeenvolgende dagen bezocht + long_description: | + Deze badge wordt toegekend voor het bezoeken op 365 opeenvolgende dagen. Wauw, een heel jaar! badge_title_metadata: "%{display_name}-badge op %{site_title}" admin_login: success: "E-mail verstuurd" @@ -2857,8 +2885,14 @@ nl: placeholder: "San Francisco, Californië" colors: title: "Thema" - themes_further_reading: - title: "Thema's" + fonts: + fields: + body_font: + label: "Lettertype voor hoofdtekst" + heading_font: + label: "Lettertype voor koptekst" + font_preview: + label: "Voorbeeld" logos: title: "Logo's" fields: @@ -2903,9 +2937,6 @@ nl: disabled: "Omdat sociale aanmeldingen zijn uitgeschakeld, is het niet mogelijk om uitnodigingen naar iedereen te versturen. Ga verder met de volgende stap." finished: title: "Uw Discourse is gereed!" - description: | -

Als u deze instellingen ooit wilt wijzigen, voer deze wizard dan opnieuw uit, of bezoek uw beheersectie; deze vindt u naast het moersleutelpictogram in het websitemenu.

-

Veel plezier, en succes met het opbouwen van uw nieuwe gemeenschap!

search_logs: graph_title: "Aantal zoekopdrachten" joined: "Lid sinds" @@ -2951,6 +2982,7 @@ nl: email_auth_res_enqueue: "Een DMARC-controle op deze e-mail is mislukt; het bericht komt zeer waarschijnlijk niet van wie het lijkt te komen. Controleer de onbewerkte e-mailheader voor meer informatie." email_spam: "Deze e-mail is als spam gemarkeerd door de header zoals gedefinieerd in `email_in_spam_header`." suspect_user: "Deze nieuwe gebruiker heeft profielgegevens ingevoerd zonder topics of berichten te hebben gelezen, wat sterk doet vermoeden dat het een spammer kan zijn. Zie `approve_suspect_users`." + contains_media: "Dit bericht bevat ingebedde media. Zie ‘review_media_unless_trust_level’." actions: agree: title: "Akkoord..." diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index 25546ea305..f3497294fc 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -475,7 +475,6 @@ pl_PL: Ten temat jest dla Ciebie wyraźnie ważny – opublikowałeś tutaj ponad %{percent}% odpowiedzi. Byłoby jeszcze lepiej, gdybyś dał innym ludziom przestrzeń do podzielenia się swoimi punktami widzenia. Możesz ich zaprosić? - get_a_room: "### Zachęć każdego do zaangażowania się w rozmowę\n\nW tym temacie odpowiedziałaś/łeś %{reply_username} już %{count} razy!\n\nDobra dyskusja uwzględnia wiele głosów i perspektyw. Czy możesz tu zaangażować kogoś jeszcze?\n\nNie zapominaj, że jeśli chciał(a)byś kontynuować rozmowę z tym użytkownikiem bez publiczności, to możesz [wysłać im prywatną wiadomość](%{base_path}/u/{%reply_username}).\n \n" too_many_replies: | ### Osiągnąłeś limit odpowiedzi w tym temacie @@ -1767,7 +1766,6 @@ pl_PL: privacy_policy_url: "If you have a Privacy Policy document hosted elsewhere that you want to use, provide the full URL here." log_anonymizer_details: "Określa, czy zachować dane użytkownika w dzienniku po ich anonimizacji. Aby zachować zgodność z RODO, musisz to wyłączyć." newuser_spam_host_threshold: "Jak wiele razy użytkownik może zamieścić link\" original-title=\" link (click to copy)\">link do tego samego hosta wewnątrz wpisów `newuser_spam_host_threshold` przed rozważeniem spamu." - staff_like_weight: "O ile większą wagę mają mieć polubienia przyznawane przez członków zespołu?" 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." @@ -1888,7 +1886,6 @@ pl_PL: display_name_on_posts: "Pokazuj imię i nazwisko użytkownika przy jego wpisach, a także jego @nazwę użytkownika." show_time_gap_days: "Jeśli dwa wpisy zostały dodane w dużej przerwie, wyświetl okres w temacie." short_progress_text_threshold: "Po tym jak liczba wpisów w tym temacie przekroczy ten numer, ten pasek postępu pokaże tylko bieżącą liczbę wpisów. Jeśli zmienisz szerokość paska postępu, możliwe, że będziesz musiał zmienić tą wartość." - default_code_lang: "Domyślny język programowania syntax podświetlający zastosowany do bloków kodu GitHub (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "Kiedy ktoś rozpoczyna udziela odpowiedzi na dany temat, w którym ostatnia odpowiedź jest starsza niż tak wiele dni, wyświetlone zostanie ostrzeżenie. Wyłącz tą ustawienia ustawiając 0." autohighlight_all_code: "Narzuć dodawanie podświetlenia kodu do wszystkich bloków kodu, w przypadku gdy nie jest dokładnie określony język." embed_truncate: "Okrój osadzone wpisy." @@ -3262,8 +3259,6 @@ pl_PL: placeholder: "Kalifornia, San Francisco" colors: title: "Motyw" - themes_further_reading: - title: "Motywy" logos: title: "Loga" fields: diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 14644d835a..2fa2535668 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -1054,7 +1054,6 @@ pt: tos_url: "Se tem um documento de Termos de Serviço alojado noutro local e que queira usar, forneça a URL completa aqui." privacy_policy_url: "Se tem um documento de Política de Privacidade alojado noutro local e que queira usar, forneça a URL completa aqui." newuser_spam_host_threshold: "Quantas vezes um novo utilizador pode colocar um link para o mesmo endereço dentro do número de publicações `newuser_spam_host_threshold` , antes de ser considerado spam." - staff_like_weight: "Quanto é o fator de ponderação extra a dar aos gostos provenientes do pessoal." 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." @@ -1140,7 +1139,6 @@ pt: display_name_on_posts: "Mostrar o nome completo de um utilizador nas suas mensagens em adição ao seu @nome-de-utilizador." show_time_gap_days: "Se duas mensagens são feitas com todos estes dias de diferença, exibir a diferença de tempo no tópico." short_progress_text_threshold: "Após o número de mensagens num tópico passar acima deste número, a barra de progresso irá apenas mostrar o número da mensagem atual. Se alterar a largura da barra de progresso, poderá ter que mudar este valor." - default_code_lang: "O destaque de sintaxe da linguagem de programação predefinida aplicada aos blocos de código do GitHub (lang-auto, ruby, python, etc.)" warn_reviving_old_topic_age: "Quando alguém começa a responder a um tópico em que a última resposta é mais antiga que estes dias, um aviso será exibido. Desativar ao configurar para 0." autohighlight_all_code: "Forçar a aplicação do destaque de código para todos os blocos de código pré-formatados mesmo quando estes não especificam explicitamente a linguagem." embed_truncate: "Truncar mensagens incorporadas." diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index c4ceb1ef4a..f5e11b37f5 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -409,14 +409,6 @@ pt_BR: Você pode editar sua resposta anterior para adicionar uma citação selecionando um texto e clicando no botão responder citação que aparecerá. Fica mais fácil para todo mundo ler tópicos que possuem poucas respostas com muita informação em vez de muitas respostas individuais com pouca informação cada. - get_a_room: | - ### Incentive todos a se envolverem na conversa - - Você respondeu %{count} vezes para @%{reply_username} neste tópico em particular! - - Uma boa discussão envolve muitas vozes e perspectivas. Você consegue envolver mais alguém? - - E não se esqueça, se você quiser continuar sua conversa com este usuário em particular fora da visão pública, [envie-o uma mensagem pessoal](%{base_path}/u/%{reply_username}). too_many_replies: | ### Você atingiu o limite de respostas para este tópico @@ -1601,7 +1593,6 @@ pt_BR: privacy_policy_url: "Se você tem um documento de Política de Privacidade hospedado em algum outro local que você queira usar, forneça a URL completa aqui." log_anonymizer_details: "Se deseja manter os detalhes de um usuário no log depois de ser anonimizado. Ao cumprir com o GDPR, você precisará desativá-lo." newuser_spam_host_threshold: "Quantas vezes um novo usuário pode postar um link para o mesmo host em suas postagens `newuser_spam_host_threshold` antes de ser considerado spam." - staff_like_weight: "Qual o fator de ênfase extra a dar para curtidas da staff." topic_view_duration_hours: "Conte uma nova visualização de tópico uma vez por IP / Usuário a cada N horas" user_profile_view_duration_hours: "Conte uma nova visualização de perfil de usuário uma vez por IP / Usuário a cada N horas" levenshtein_distance_spammer_emails: "Ao equiparar emails de spammers, o número da diferença de caracteres que ainda assim irá permitir uma equiparação imprecisa." @@ -1724,7 +1715,6 @@ pt_BR: display_name_on_posts: "Também exibir o nome completo do usuário em suas mensagens" show_time_gap_days: "Se duas mensagens são feitas com N dias de intervalo, mostre o intervalo no tópico." short_progress_text_threshold: "Após o número de mensagens em um tópico for acima deste número, a barra de progresso mostrará apenas o número da mensagem atual. Se você alterar a largura da barra de progresso, você pode precisar alterar este valor." - default_code_lang: "Realce de sintaxe padrão da linguagem de programação aplicada a blocos de código GitHub (lang-auto, Ruby, Python, etc)" warn_reviving_old_topic_age: "Quando alguém começa a responder a um tópico mais velho do que este número de dias, um aviso será exibido para desencorajar o usuário de reviver uma velha discussão. Desabilite definindo para 0." autohighlight_all_code: "Aplicar código destacando todos os blocos de código pré-formatados, mesmo quando não for específica o idioma" highlighted_languages: "Regras de destaque de sintaxe incluídas. (Atenção: incluir muitos idiomas pode afetar o desempenho) veja: https://highlightjs.org/static/demo para uma demonstração" @@ -2971,7 +2961,6 @@ pt_BR: confirm_new_email: title: "Confirmar novo e-mail" subject_template: "[%{email_prefix}] Confirme seu novo endereço de e-mail" - text_body_template: "Confirme seu novo endereço de email para %{site_name} clicando no seguinte link: \n\n%{base_url}/u/confirm-new-email/%{email_token}\n" confirm_old_email: title: "Confirmar Antigo e-mail" subject_template: "[%{email_prefix}] Confirme seu endereço de e-mail atual" @@ -3599,8 +3588,6 @@ pt_BR: placeholder: "São Francisco, Califórnia" colors: title: "Tema" - themes_further_reading: - title: "Temas" logos: title: "Logotipos" fields: @@ -3648,9 +3635,6 @@ pt_BR: disabled: "Como os logins locais estão desabilitados, não é possível enviar convites para ninguém. Por favor, prossiga para o próximo passo." finished: title: "Seu Discourse está pronto!" - description: | -

Se você sentir vontade de alterar estas configurações, execute novamente este assistente a qualquer momento ou visite a seção de administradores; encontre-a ao lado do ícone de chave inglesa no menu do site.

-

Divirta-se e boa sorte construindo sua nova comunidade!

search_logs: graph_title: "Contagem de pesquisa" joined: "Ingressou" diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml index d9135c4891..05dc54fd73 100644 --- a/config/locales/server.ro.yml +++ b/config/locales/server.ro.yml @@ -15,6 +15,7 @@ ro: short_no_year: "%B %-d" date_only: "%B %-d, %Y" long: "%B %-d, %Y, %l:%M%P" + no_day: "%B %Y" date: month_names: - null @@ -117,6 +118,9 @@ ro: site_settings: default_categories_already_selected: "Nu poți selecta o categorie folosită într-o altă listă." s3_upload_bucket_is_required: "Nu poți activa încărcările pe S3 dacă nu ai introdus 's3_upload_bucket'." + second_factor_cannot_be_enforced_with_sso_enabled: "Nu puteți impune 2FA dacă SSO este activat." + invite: + expired: "Codul tău de invitație a expirat. Te rugăm contactezi personalul forumului." bulk_invite: file_should_be_csv: "Fișierul de încărcat trebuie să fie în format csv." error: "A apărut o eroare la încărcarea acestui fișier. Te rugăm să încerci din nou, mai târziu." @@ -1006,7 +1010,6 @@ ro: tos_url: "Dacă ai un document Condițiile generale de utilizare găzduit în altă parte pe care dorești să-l folosești, postează-i întregul URL aici." privacy_policy_url: "Dacă ai un document cu Politica de Confidențialitate găzduit în altă parte, pe care dorești să-l folosești, postează-i întregul URL aici." newuser_spam_host_threshold: "De câte ori poate un utilizator nou să posteze un link către aceeași gazdă în limita de `newuser_spam_host_threshold` postări înainte ca să fie considerat spam." - staff_like_weight: "Ce pondere adițională să aibă aprecierile de la membrii echipei." 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)." @@ -1075,6 +1078,7 @@ ro: anonymous_posting_min_trust_level: "Nivelul minim de încredere necesar pentru a putea activa postarea anonimă." anonymous_account_duration_minutes: "Pentru protejarea anonimității, creează un nou cont anonim la fiecare N minute pentru fiecare utilizator. Exemplu: dacă e setat la 600, imediat ce au trecut 600 de minute de la ultima postare șI utilizatorul a comutat pe anonim, va fi creat un nou cont anonim." hide_user_profiles_from_public: "Dezactivează cardurile, profilurile și „cartea de telefon” pentru utilizatorii anonimi." + allow_users_to_hide_profile: "Permite utilizatorilor să-şi ascundă profilul şi prezenţa" allow_profile_backgrounds: "Permite utilizatorilor să încarce fundaluri de profil." sequential_replies_threshold: "Numărul de postări succesive pe care un utilizator trebuie sa le facă într-un subiect înainte să i se reamintească că a postat un număr excesiv de postări succesive." get_a_room_threshold: "Numărul de postări pe care un utilizator poate să le facă la adresa aceleiași persoane înainte de a primi un avertisment." @@ -1092,7 +1096,6 @@ ro: display_name_on_posts: "Arată atât numele complet cât și numele de utilizator în postări." show_time_gap_days: "Dacă două postări sunt create la acest număr de zile distantă una de alta, afișează decalajul în subiect." short_progress_text_threshold: "După ce numărul de postări într-un subiect devine mai mare decât acest număr, bara de progres va afișa doar numărul postării curente. Dacă schiimbi lățimea barei de progres, va trebui să schimbi această valoare." - default_code_lang: "Evidențierea de sintaxa de limbaj de programare aplicată implicit blocurilor de cod GitHub (lang-auto, Ruby, Python etc.)" warn_reviving_old_topic_age: "Când cineva răspunde la un subiect unde ultimul răspuns e mai vechi decât atâtea zile, va fi afișată o avertizare. Dezactivează prin alegerea cifrei 0." autohighlight_all_code: "Forțează aplicarea de punere în evidență cod pe toate blocurile de cod preformatate, chiar și atunci când nu le este explicit specificată limba." embed_truncate: "Retează postările încorporate." @@ -1300,6 +1303,8 @@ ro: private_topic_title: "Discuția #%{id}" post_hidden: subject_template: "Postare ascunsă din cauza marcajelor de avertizare ale comunității" + post_hidden_again: + subject_template: "Postare ascunsă de flagurile comunității, staff notificat" welcome_user: subject_template: "Bine ai venit pe %{site_name}!" welcome_tl1_user: @@ -1969,8 +1974,6 @@ ro: title: "Organizație" colors: title: "Temă" - themes_further_reading: - title: "Teme" logos: title: "Logo-uri" fields: diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index e3089c6600..2ff9e4c7f7 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -197,10 +197,12 @@ ru: share_quote_facebook_requirements: "Вы должны установить идентификатор приложения, чтобы включить обмен цитатами для Фейсбука." second_factor_cannot_enforce_with_socials: "Вы не можете применить двухфакторную аутентификацию при включённом входе в социальные сети. Сначала вы должны отключить вход через: %{auth_provider_names}" second_factor_cannot_be_enforced_with_disabled_local_login: "Вы не можете применить двухфакторную аутентификацию, если локальный вход в систему отключён." + second_factor_cannot_be_enforced_with_sso_enabled: "Вы не можете применить двухфакторную аутентификацию, если используется технология единого входа (SSO)." local_login_cannot_be_disabled_if_second_factor_enforced: "Вы не можете отключить локальный вход в систему, если применяется двухфакторная аутентификация. Отключите её перед отключением возможности локального входа." cannot_enable_s3_uploads_when_s3_enabled_globally: "Вы не можете включить загрузку S3, потому что загрузка S3 уже включена глобально, и включение этого уровня сайта может вызвать критические проблемы с загрузками" conflicting_google_user_id: 'Идентификатор учётной записи Google для этой учётной записи изменился; по соображениям безопасности требуется вмешательство персонала. Пожалуйста, свяжитесь с персоналом и укажите
https://meta.discourse.org/t/76575' invite: + expired: "Срок действия токена приглашения истек. Пожалуйста, свяжитесь с персоналом." not_found: "Неверный код приглашения. Пожалуйста, свяжитесь с персоналом." not_found_json: "Неверный код приглашения. Пожалуйста, свяжитесь с персоналом." not_found_template: | @@ -213,6 +215,10 @@ ru: user_exists: "Нет необходимости приглашать пользователя с адресом %{email}, у него уже есть аккаунт!" confirm_email: "

Вы почти закончили! Мы отправили письмо активации на ваш адрес электронной почты. Пожалуйста, следуйте инструкциям в письме для активации учётной записи.

Если письмо не приходит, проверьте папку со спамом.

" cant_invite_to_group: "Вы не можете приглашать пользователей в указанные группы. Убедитесь, что вы являетесь владельцем группы (групп), в которую вы пытаетесь пригласить." + disabled_errors: + sso_enabled: "Возможность использования приглашений отключена, поскольку включена технология единого входа (SSO)" + local_logins_disabled: "Возможность использования приглашений отключена, поскольку отключён параметр 'Включить локальные учетные записи'." + invalid_access: "У вас нет прав для просмотра запрашиваемого ресурса." bulk_invite: file_should_be_csv: "Загружаемый файл должен быть в csv-формате." max_rows: "Первые %{max_bulk_invites} приглашения были отправлены. Попробуйте разделить файл на более мелкие части." @@ -488,7 +494,7 @@ ru: Вы уже ответили %{count} раз пользователю @%{reply_username} в этой конкретной теме! Хорошая дискуссия, как правило, представлена большим количеством участников и разнообразием точек зрения. Можете ли вы пригласить кого-нибудь ещё для участия в этом обсуждении? - И не забывайте, что если Вы хотите продолжить приватный разговор с конкретным участником, то ему стоит отправить [личное сообщение](%{base_path}/u/%{reply_username}). + И не забывайте, что если вы хотите продолжить приватный разговор с конкретным участником, то ему стоит отправить [личное сообщение](%{base_path}/u/%{reply_username}). too_many_replies: | ###Вы дали максимально возможное количество ответов в этой теме. @@ -1426,9 +1432,9 @@ ru: display_local_time_in_user_card: "Отображать местное время в карточке пользователя." censored_words: "Слова, которые будут автоматически заменены на ■■■■" delete_old_hidden_posts: "Автоматически удалять сообщения, скрытые дольше 30 дней." - default_locale: "Язык по умолчанию для этого экземпляра Discourse. Вы можете заменить текст сгенерированных системой разделов и тем в Оформление / Текстовое содержимое." + default_locale: "Язык по умолчанию для этого экземпляра Discourse. Вы можете заменить текст сгенерированных системой разделов и тем на закладке Оформление / Текст." allow_user_locale: "Позволять пользователям выбирать язык интерфейса" - set_locale_from_accept_language_header: "Установить язык интерфейса для анонимных пользователей, исходя из языковых настроек их браузера" + set_locale_from_accept_language_header: "Устанавливать язык интерфейса для анонимных пользователей, исходя из языковых настроек их браузера" support_mixed_text_direction: "Поддерживать смешанные направления текста слева направо и справа налево." min_post_length: "Минимально допустимое количество символов в одном сообщении." min_first_post_length: "Минимально допустимое количество символов в первом сообщении" @@ -1441,12 +1447,12 @@ ru: min_personal_message_title_length: "Минимально допустимое количество символов в заголовке личного сообщения" max_emojis_in_title: "Максимальное количество смайликов в названии темы" min_search_term_length: "Минимальное количество символов в поисковом запросе" - search_tokenize_chinese_japanese_korean: "Принудительный поиск для токенизации Китайского/Японского /Корейского даже на сайтах, отличных от CJK" - search_prefer_recent_posts: "Если поиск на форуме выполняется медленно, то после включения этого параметра сначала индексируются последние сообщения" - search_recent_posts_size: "Сколько последних сообщений хранить в индексе" - log_search_queries: "Журнал поисковых запросов, выполненных пользователями" - search_query_log_max_size: "Максимальное количество поисковых запросов для хранения" - search_query_log_max_retention_days: "Максимальное количество дней для хранения поисковых запросов." + search_tokenize_chinese_japanese_korean: "Принудительный поиск для токенизации Китайского/Японского /Корейского языков даже на сайтах, не содержащих символы CJK" + search_prefer_recent_posts: "Сначала индексировать последние сообщения. Актуально при медленном поиске на больших объёмах данных" + search_recent_posts_size: "Хранить в индексе указанное здесь количество сообщений" + log_search_queries: "Сохранять в журнале поисковые запросы пользователей" + search_query_log_max_size: "Максимальное количество сохраняемых поисковых запросов" + search_query_log_max_retention_days: "Максимальное количество дней хранения поисковых запросов." search_ignore_accents: "Игнорировать ударения при поиске текста." category_search_priority_very_low_weight: "Вес применяется к ранжированию для очень низкого приоритета поиска раздела." category_search_priority_low_weight: "Вес применяется к ранжированию для низкого приоритета поиска раздела." @@ -1470,9 +1476,9 @@ ru: editing_grace_period_max_diff: "Максимальное количество изменённых символов, допустимое в льготном периоде редактирования. При превышении этого значения все изменения будут записаны в историю правок (уровень доверия 0 и 1)." editing_grace_period_max_diff_high_trust: "Максимальное количество изменённых символов, допустимое в льготном периоде редактирования. При превышении этого значения все изменения будут записаны в историю правок (уровень доверия 2 и выше)." staff_edit_locks_post: "Сообщения будут недоступны для редактирования, если они редактировались персоналом" - post_edit_time_limit: "Пользователи с уровнем доверия 0 или 1 могут изменять своё сообщение в течение указанного здесь количества минут после его публикации. Если значение установлено в 0, то этот период не неограничен." - tl2_post_edit_time_limit: "Пользователи с уровнем доверия 2 и выше могут изменять своё сообщение в течение указанного здесь количества минут после его публикации. Если значение установлено в 0, то этот период не неограничен." - edit_history_visible_to_public: "Позволить обычным пользователям просматривать историю редактирования сообщений. В противном случае история редактирования будет доступна только персоналу." + post_edit_time_limit: "Пользователи с уровнем доверия 0 или 1 могут изменять своё сообщение в течение указанного здесь количества минут после его публикации. Если значение установлено в 0, то этот период не ограничен." + tl2_post_edit_time_limit: "Пользователи с уровнем доверия 2 и выше могут изменять своё сообщение в течение указанного здесь количества минут после его публикации. Если значение установлено в 0, то этот период не ограничен." + edit_history_visible_to_public: "Разрешить обычным пользователям просматривать историю редактирования сообщений. В противном случае история редактирования будет доступна только персоналу." delete_removed_posts_after: "Сообщения, удалённые автором, будут автоматически удаляться через указанное здесь количество часов. Если значение установлено в 0, то сообщения будут удаляться немедленно." max_image_width: "Максимальная ширина уменьшенных версий изображений, показываемых в сообщениях" max_image_height: "Максимальная высота уменьшенных версий изображений, показываемых в сообщениях" @@ -1508,9 +1514,9 @@ ru: email_custom_headers: "Разделённый вертикальной чертой список дополнительных заголовков в почтовых сообщениях" email_subject: "Настраиваемый формат темы для стандартных писем. См. тему https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" detailed_404: "Предоставление пользователям более подробной информации о том, почему они не могут получить доступ к определённой теме. Примечание: Это менее безопасно, поскольку пользователи будут знать, ссылается ли URL-адрес на допустимую тему." - enforce_second_factor: "Принудительное включение двухфакторной проверки подлинности. Выберите 'Все', чтобы применить его ко всем пользователям. Выберите 'Персонал', чтобы принудительно использовать её только сотрудниками." + enforce_second_factor: "Принудительное включение двухфакторной проверки подлинности. Выберите 'all', чтобы применить его ко всем пользователям. Выберите 'staff', чтобы принудительно использовать её только сотрудниками." force_https: "Принудительное использование протокола HTTPS. ВНИМАНИЕ: НЕ включайте этот параметр, пока не убедитесь, что HTTPS полностью настроен и работает абсолютно везде! Вы проверили настройки сети, доступность учётных записей в социальных сетях, доступность внешних логотипов и зависимостей, чтобы убедиться, что они корректно работают при использовании HTTPS?" - same_site_cookies: "Использовать одни и те же файлы cookie, они предотвращают межсайтовую подделку запроса в поддерживаемых браузерах (Lax или Strict). Предупреждение: Strict будет работать только на сайтах, которые принудительно входят в систему и используют SSO." + same_site_cookies: "Использовать одни и те же файлы cookie, они предотвращают межсайтовую подделку запроса в поддерживаемых браузерах (Lax или Strict). Предупреждение: Strict будет работать только на сайтах, которые поддерживают принудительный вход в систему и используют SSO." summary_score_threshold: "Минимальная оценка сообщения, необходимая для его включения в сводку по теме" summary_posts_required: "Минимальное количество сообщений, необходимое для получения сводки по теме. Изменения этого параметра будут применены к темам за прошедшую неделю." summary_likes_required: "Минимальное количество симпатий, необходимое для получения сводки по теме. Изменения этого параметра будут применены к темам за прошедшую неделю." @@ -1530,13 +1536,13 @@ ru: cooldown_minutes_after_hiding_posts: "Количество минут, в течение которых сообщение, скрытое из-за жалоб, не может быть отредактировано автором" max_topics_in_first_day: "Максимальное количество тем, которое пользователь может создать в течение 24 часов с момента создания своего первого сообщения" max_replies_in_first_day: "Максимальное количество ответов, которое пользователь может сделать в течение 24 часов с момента создания своего первого сообщения" - tl2_additional_likes_per_day_multiplier: "Увеличить лимит симпатий в день для уровня доверия 2 (участник) до" - tl3_additional_likes_per_day_multiplier: "Увеличить лимит симпатий в день для уровня доверия 3 (активный пользователь) до" - tl4_additional_likes_per_day_multiplier: "Увеличить лимит симпатий в день для уровня доверия 4 (лидер) до" + tl2_additional_likes_per_day_multiplier: "Увеличить лимит симпатий в день для уровня доверия 2 (участник), умножив его на указанное здесь число" + tl3_additional_likes_per_day_multiplier: "Увеличить лимит симпатий в день для уровня доверия 3 (активный пользователь), умножив его на указанное здесь число" + tl4_additional_likes_per_day_multiplier: "Увеличить лимит симпатий в день для уровня доверия 4 (лидер), умножив его на указанное здесь число" num_users_to_silence_new_user: "Если сообщения нового пользователя получают num_spam_flags_to_silence_new_user жалоб от указанного здесь количества пользователей, скрыть все его сообщения и предотвратить будущие публикации. Для отключения этого параметра установите значение в 0." num_tl3_flags_to_silence_new_user: "Если сообщения нового пользователя получают указанное здесь количество жалоб от num_tl3_users_to_silence_new_user разных пользователей уровня доверия 3, скрыть все его сообщения и предотвратить будущие публикации. Для отключения этого параметра установите значение в 0." num_tl3_users_to_silence_new_user: "Если сообщения нового пользователя получают num_tl3_flags_to_silence_new_user жалоб от указанного здесь количества пользователей уровня доверия 3, скрыть все его сообщения и предотвратить будущие публикации. Для отключения этого параметра установите значение в 0." - notify_mods_when_user_silenced: "Отправить сообщение всем модераторам, если пользователь автоматически заблокирован." + notify_mods_when_user_silenced: "Отправлять сообщение всем модераторам, если пользователь автоматически заблокирован." flag_sockpuppets: "Если новый пользователь отвечает на тему с того же IP-адреса, что и пользователь, создавший тему, пометить обе его публикации как потенциальный спам." traditional_markdown_linebreaks: "Использовать стандартный способ переноса строки в Markdown: строка должна заканчиваться двумя пробелами." enable_markdown_typographer: "Использовать правила типографики для улучшения читабельности текста: заменять прямые кавычки «фигурными кавычками»; (c), (tm) - соответствующими символами и т. д." @@ -1553,7 +1559,7 @@ ru: ga_universal_tracking_code: "Google Universal Analytics (analytics.js) tracking code ID, например: UA-12345678-9; больше информации можно узнать на странице: https://google.com/analytics" ga_universal_domain_name: "Доменное имя для Google Universal Analytics (analytics.js), например: mysite.com; см. https://google.com/analytics" ga_universal_auto_link_domains: "Включить междоменное отслеживание Google Universal Analytics (analytics.js). К исходящим ссылкам на эти домены будет добавлен идентификатор клиента. См. Руководство Google по междоменному отслеживанию." - gtm_container_id: "Идентификатор менеджера тегов Google, например: GTM-ABCDEF.
Примечание : Сторонние скрипты, загруженные менеджером тегов, могут быть внесены в белый список в «content security policy script src»." + gtm_container_id: "Идентификатор менеджера тегов Google, например: GTM-ABCDEF.
Примечание: Сторонние скрипты, загруженные менеджером тегов, могут быть внесены в белый список в при помощи директивы `script-src` политики защиты контента (CSP)." enable_escaped_fragments: "Обратитесь к Google Ajax-Crawling API, если веб-сканер не обнаружен. См. Https://developers.google.com/webmasters/ajax-crawling/docs/learn-more" moderators_manage_categories_and_groups: "Разрешать модераторам управлять разделами и группами" cors_origins: "Разрешённые источники для cross-origin requests (CORS). Каждый источник должен содержать http:// или https://. Для включения CORS переменная окружения DISCOURSE_ENABLE_CORS должна быть установлена в значение 'true'." @@ -1617,14 +1623,15 @@ ru: password_unique_characters: "Минимальное количество уникальных символов, которое должен иметь пароль." block_common_passwords: "Не позволять использовать пароли из списка 10 000 самых часто используемых паролей." external_auth_skip_create_confirm: При регистрации через внешнюю авторизацию пропустить всплывающее окно создания учётной записи. Лучше всего использовать вместе с sso_overrides_email, sso_overrides_username и sso_overrides_name. + external_auth_immediately: "Автоматическое перенаправление на внешнюю систему входа без вмешательства пользователя. Настройка сработает только в том случае, если параметр login_required установлен в true, и существует только один внешний метод аутентификации." enable_sso: "Включить единый вход через внешний сайт (ВНИМАНИЕ: ПОЧТОВЫЕ АДРЕСА ПОЛЬЗОВАТЕЛЕЙ *ДОЛЖНЫ БЫТЬ* ПРОВЕРЕНЫ ВНЕШНИМ САЙТОМ!)" verbose_sso_logging: "Записывать подробную диагностику, связанную с SSO, в файл журнала" enable_sso_provider: "Использовать протокол поставщика единого входа (SSO). Необходимо настроить параметр sso_provider_secrets" sso_url: "URL портала для осуществления технологии единого входа (должен содержать http:// или https://)" sso_secret: "Секретный набор символов, используемый для проверки подлинности зашифрованного входа с помощью SSO, убедитесь, что это 10 или более символов" sso_provider_secrets: "Список секретных доменов, которые используют Discourse в качестве поставщика единого входа (SSO). Убедитесь, что это 10 или более символов. Может быть использован символ звёздочки для соответствия любому домену или части домена (пример: *.example.com)" - sso_overrides_bio: "Перезаписать в профиле информацию о пользователе и запретить её редактирование" - sso_overrides_groups: "Перезаписать все группы, указанные вручную, группами, указанными в соответствующем SSO-атрибуте (ВНИМАНИЕ: Если группы в SSO-атрибуте не указаны, то список групп пользователя будет очищен" + sso_overrides_bio: "Перезаписывать в профиле информацию о пользователе с последующим запретом на её редактирование" + sso_overrides_groups: "Перезаписывать все группы, указанные вручную, группами, указанными в соответствующем SSO-атрибуте (ВНИМАНИЕ: Если группы в SSO-атрибуте не указаны, то список групп пользователя будет очищен)" sso_overrides_email: "Перезаписывать локальную электронную почту электронной почтой внешнего SSO-сайта при каждом входе в систему и запретить её редактирование. Относится ко всем провайдерам аутентификации. (ВНИМАНИЕ: из-за этого могут возникать расхождения)" sso_overrides_username: "Перезаписывать локальное имя пользователя пользователем внешнего SSO-сайта при каждом входе в систему и запретить его редактирование. Относится ко всем провайдерам аутентификации. (ВНИМАНИЕ: из-за этого могут возникать расхождения)" sso_overrides_name: "Перезаписывать локальное полное имя пользователя полным именем пользователем внешнего SSO-сайта при каждом входе в систему и запретить его редактирование. Относится ко всем провайдерам аутентификации." @@ -1640,10 +1647,10 @@ ru: allow_new_registrations: "Разрешать регистрацию новых пользователей. Отключите этот параметр, если вы хотите запретить кому-либо создавать новые учётные записи." enable_signup_cta: "Показывать уведомление возвращающимся на форум анонимным пользователям, предложив им зарегистрировать учётную запись." enable_google_oauth2_logins: "Включить аутентификацию Google Oauth2. Это метод аутентификации, который в настоящее время поддерживает Google. Требуется ключ и секрет. См. тему Configuring Google login for Discourse." - google_oauth2_client_id: "Client ID для вашего Google приложения." - google_oauth2_client_secret: "Client secret для Google приложения" - google_oauth2_prompt: "Дополнительный разделённый пробелами список строковых значений, который указывает, запрашивает ли сервер авторизации пользователя для повторной аутентификации и согласия. См. Https://developers.google.com/identity/protocols/OpenIDConnect#prompt для возможных значений." - google_oauth2_hd: "Необязательный домен Служб Google, доступ к которому будет ограничен. См. Https://developers.google.com/identity/protocols/OpenIDConnect#hd-param для получения дополнительной информации." + google_oauth2_client_id: "Client ID для Google-приложения." + google_oauth2_client_secret: "Client secret для Google-приложения." + google_oauth2_prompt: "Необязательный список строковых значений, разделенных пробелами, который указывает, запрашивает ли сервер авторизации у пользователя повторную аутентификацию и согласие. См. этот раздел для указания возможных значений." + google_oauth2_hd: "Необязательный домен, указанный в G Suite, доступ к которому будет ограничен. См. этот раздел для получения дополнительной информации." enable_twitter_logins: "Включить аутентификацию Twitter, требуются twitter_consumer_key и twitter_consumer_secret. См. тему Configuring Twitter login (and rich embeds) for Discourse." twitter_consumer_key: "Ключ пользователя для аутентификации в Twitter, зарегистрированный в https://developer.twitter.com/apps" twitter_consumer_secret: "Секретный номер для проверки подлинности Twitter, зарегистрированный в https://developer.twitter.com/apps" @@ -1657,16 +1664,16 @@ ru: github_client_id: "Идентификатор клиента для аутентификации Github, зарегистрированный в https://github.com/settings/developers" github_client_secret: "Секрет клиента для аутентификации Github, зарегистрированный по адресу https://github.com/settings/developers" enable_discord_logins: "Разрешать пользователям проходить аутентификацию с использованием Discord?" - discord_client_id: 'Discord Client ID (Нужен ID? Посетите портал разработчиков Discord )' + discord_client_id: 'Discord Client ID (Нужен ID? Посетите портал разработчиков Discord)' discord_secret: "Discord Secret Key" - discord_trusted_guilds: 'Разрешить только членам этих гильдий Discord войти через Discord. Используйте числовой идентификатор для гильдии. Для получения дополнительной информации проверьте инструкции здесь . Оставьте пустым, чтобы разрешить любую гильдию.' + discord_trusted_guilds: 'Разрешать вход в систему через Discord только членам этих гильдий. Используйте числовой идентификатор гильдии. Для получения дополнительной информации ознакомьтесь с этими инструкциями. Оставьте поле пустым, если необходимо разрешить все гильдии.' enable_backups: "Разрешить администраторам создавать резервные копии форума" - allow_restore: "Позволить восстановление, которое может заменить ВСЕ данные сайта. Оставьте опцию выключенной, если не планируете делать восстановление из резервной копии" + allow_restore: "Разрешить восстановление, которое может заменить ВСЕ данные сайта. Оставьте опцию выключенной, если не планируете делать восстановление из резервной копии" maximum_backups: "Максимальное количество сохраняемых резервных копий. Более старые резервные копии будут автоматически удалены." automatic_backups_enabled: "Запускать автоматическое создание резервных копий с указанной в настройках периодичностью" backup_frequency: "Периодичность создания резервных копий (в днях)." s3_backup_bucket: "Адрес папки удалённого сервера для резервных копий. ВНИМАНИЕ: Убедитесь, что место назначения защищено от посторонних." - s3_endpoint: "Конечная точка может быть изменена для резервного копирования в S3-совместимую службу, такую как DigitalOcean Spaces или Minio. ВНИМАНИЕ: Оставьте пустым, если используете AWS S3." + s3_endpoint: "Конечная точка для резервного копирования может быть изменена на другую S3-совместимую службу, такую как DigitalOcean Spaces или Minio. ВНИМАНИЕ: Оставьте поле пустым, если используется AWS S3." s3_configure_tombstone_policy: "Включить политику автоматического удаления загрузок, помеченных как 'удалённые'. ВАЖНО! Если этот параметр отключён, удалённые загрузки будут фактически занимать место на диске." s3_disable_cleanup: "Не допускать удаление старых резервных копий из S3, когда резервных копий больше, чем максимально допустимо." enable_s3_inventory: "Создавать отчёты и проверять загрузку с помощью инвентаря Amazon S3. ВАЖНО: требуются действительные учётные данные S3 (как идентификатор ключа доступа, так и секретный ключ доступа)." @@ -1693,11 +1700,11 @@ ru: max_edits_per_day: "Максимальное количество редактирований, которое пользователь может выполнить за один день." max_topics_per_day: "Максимальное количество тем, которое пользователь может создать за один день." max_personal_messages_per_day: "Максимальное количество личных сообщений, которое пользователь может создать за один день." - max_invites_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_post_deletions_per_minute: "Максимальное количество сообщений, которое пользователь может в течение минуты." + max_post_deletions_per_minute: "Максимальное количество сообщений, которое пользователь может удалить в течение минуты." max_post_deletions_per_day: "Максимальное количество сообщений, которое пользователь может удалить за день." invite_link_max_redemptions_limit: "Количество пользователей, получающих приглашение по ссылке, не может превышать указанное здесь значение." alert_admins_if_errors_per_minute: "Количество ошибок в минуту, необходимое для предупреждения администратора. Для отключения этого параметра установите значение в 0. ВНИМАНИЕ: требуется перезагрузка." @@ -1712,8 +1719,8 @@ ru: purge_deleted_uploads_grace_period_days: "Период (в днях) после которого удалённые вложения не могут быть восстановлены." purge_unactivated_users_grace_period_days: "Количество дней до удаления пользователя, который не активировал свою учетную запись. Установите 0, чтобы никогда не удалять неактивированных пользователей." enable_s3_uploads: "Размещать загружаемые файлы на Amazon S3. ВАЖНО: требуется правильные настройки S3 (access key id и secret access key)." - s3_use_iam_profile: 'Используйте профиль экземпляра AWS EC2 для предоставления доступа к корзине S3. ПРИМЕЧАНИЕ: для включения этого необходимо, чтобы Discourse работал в соответствующем сконфигурированном экземпляре EC2, и переопределяет настройки «id ключа доступа s3» и «секретный ключ доступа s3».' - s3_upload_bucket: "Имя Amazon S3 bucket куда будут загружаться файлы. ВНИМАНИЕ: должно быть в нижнем регистре, без пробелов, без подчёркиваний.\nhttp://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html" + s3_use_iam_profile: 'Использовать профиль экземпляра AWS EC2 для предоставления доступа к корзине S3. ПРИМЕЧАНИЕ: для включения этого параметра необходимо, чтобы Discourse работал в соответствующем сконфигурированном экземпляре EC2 и перезаписывал настройки "s3 access key id" и "s3 secret access key".' + s3_upload_bucket: "Имя Amazon S3 bucket, куда будут загружаться файлы. ВНИМАНИЕ: имя должно быть в нижнем регистре, без пробелов, без подчёркиваний.\nhttp://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html" s3_access_key_id: "Идентификатор ключа доступа Amazon S3, который будет использоваться для загрузки изображений, вложений и резервных копий." s3_secret_access_key: "Секретный ключ доступа Amazon S3, который будет использоваться для загрузки изображений, вложений и резервных копий." s3_region: "Название региона Amazon S3, которое будет использоваться для загрузки изображений и резервных копий." @@ -1729,20 +1736,20 @@ ru: allow_staff_to_upload_any_file_in_pm: "Разрешать сотрудникам загружать любые файлы в личных сообщениях." strip_image_metadata: "Удалять метаданные из изображения." min_ratio_to_crop: "Отношение, используемое для обрезки высоких изображений. Введите необходимое отношение ширины к высоте." - simultaneous_uploads: "Максимальное количество файлов, которые можно вставить в редактор сообщения" + simultaneous_uploads: "Максимальное количество файлов, которые можно вставить в редактор сообщения." enable_flash_video_onebox: "Разрешать умную вставку ссылок на файлы формата sqf и flv (Adobe Flash). ВНИМАНИЕ: Это разрешение с большой вероятностью может представлять угрозу безопасности сайта." 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." + 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: "Минимальное количество дней (от 0 и более), которое требуется пользователю для посещения сайта за указанный непрерывный период времени (tl3_time_period), чтобы получить право на повышение до уровня доверия 3. Установите значение больше, чем tl3_time_period, если необходимо отключить повышение до уровня доверия 3." tl3_requires_topics_replied_to: "Минимальное количество тем (от 0 и более), на которые пользователь должен ответить за указанный непрерывный период времени (tl3_time_period), чтобы претендовать на повышение до уровня доверия 3." @@ -1752,7 +1759,7 @@ ru: tl3_requires_posts_read_cap: "Максимально необходимое количество тем, прочитанных за указанный непрерывный период времени (tl3_time_period)." tl3_requires_topics_viewed_all_time: "Минимальное количество тем, которые пользователь должен просмотреть, чтобы претендовать на уровень доверия 3." tl3_requires_posts_read_all_time: "Минимальное количество сообщений, которые пользователь должен прочитать, чтобы претендовать на уровень доверия 3." - tl3_requires_max_flagged: "У пользователя должно быть не более X сообщений, помеченных различными X пользователями за указанный непрерывный период времени (tl3_time_period), чтобы претендовать на повышение до уровня доверия 3, где X - это значение этого параметра (от 0 и более)." + tl3_requires_max_flagged: "Максимальное количество жалоб от различных пользователей за указанный непрерывный период времени (tl3_time_period), чтобы пользователь мог претендовать на повышение до уровня доверия 3 (от 0 и более)." tl3_promotion_min_duration: "Минимальное количество дней, в течении которых пользователь с уровнем доверия 3 не может быть понижен до уровня доверия 2." tl3_requires_likes_given: "Минимальное количество симпатий, которое необходимо выразить за указанный непрерывный период времени (tl3_time_period), чтобы претендовать на уровень доверия 3." tl3_requires_likes_received: "Минимальное количество симпатий, которое необходимо получить за указанный непрерывный период времени (tl3_time_period), чтобы претендовать на уровень доверия 3." @@ -1764,7 +1771,7 @@ ru: min_trust_to_edit_post: "Минимальный уровень доверия, требуемый для редактирования сообщений." min_trust_to_allow_self_wiki: "Минимальный уровень доверия, требуемый для создания своего вики-сообщения." min_trust_to_send_messages: "Минимальный уровень доверия, необходимый для создания личных сообщений." - min_trust_to_flag_posts: "Минимальный уровень доверия, необходимый для создания жалобы на сообщение" + min_trust_to_flag_posts: "Минимальный уровень доверия, необходимый для создания жалобы на сообщение." min_trust_to_post_links: "Минимальный уровень доверия, необходимый для включения ссылок в сообщения" min_trust_to_post_embedded_media: "Минимальный уровень доверия, необходимый для встраивания медиафайлов в сообщение" min_trust_level_to_allow_profile_background: "Минимальный уровень доверия, необходимый для загрузки шапки профиля" @@ -1796,7 +1803,7 @@ ru: default_dark_mode_color_scheme_id: "Цветовая схема, используемая в тёмном режиме." dark_mode_none: "None" max_image_size_kb: "Максимальный размер загружаемого изображения в кБ. Этот параметр должен быть настроен в nginx (client_max_body_size), apache или прокси. Размер изображений, превышающий указанное здесь значение, будет изменён при загрузке." - max_attachment_size_kb: "Максимальный размер загружаемых файлов в килобайтах. Убедитесь, что вы также настроили ограничение в nginx (client_max_body_size), apache или прокси." + max_attachment_size_kb: "Максимальный размер загружаемых файлов в кБ. Убедитесь, что вы также настроили ограничение в nginx (client_max_body_size), apache или прокси." authorized_extensions: "Список расширений файлов, разрешённых к загрузке. Укажите символ '*' для загрузки любых типов файлов." authorized_extensions_for_staff: "Список расширений файлов, разрешенных для загрузки сотрудникам, в дополнение к списку, определённому в настройке authorized_extensions. Укажите символ '*' для загрузки любых типов файлов." theme_authorized_extensions: "Список расширений файлов, разрешённых для загрузки тем оформления. Укажите символ '*' для загрузки любых типов файлов." @@ -1820,10 +1827,10 @@ ru: faq_url: "Укажите полный URL к странице FAQ, если хотите использовать собственную версию." tos_url: "Укажите полный URL к документу с условиями предоставления услуг, если хотите использовать собственную версию." privacy_policy_url: "Укажите полный URL к документу с условиями политики конфиденциальности, если хотите использовать собственную версию." - log_anonymizer_details: "Следует ли сохранять данные пользователя в журнале после анонимизации. При соблюдении GDPR вам нужно будет отключить этот параметр." + log_anonymizer_details: "Сохранять данные пользователя в журнале после анонимизации. При соблюдении GDPR вам нужно будет отключить этот параметр." newuser_spam_host_threshold: "Максимальное количество ссылок на один и тот же ресурс, публикуемых новыми пользователями в своих сообщениях, прежде чем подобные сообщения будут расцениваться как спам." allowed_spam_host_domains: "Перечень доменов, исключённых из тестирования на спам. У новых пользователей не будет ограничений на создание сообщений со ссылками на эти домены." - staff_like_weight: "Дополнительный весовой коэффициент для симпатий, поставленных персоналом (модераторами и администраторами)." + staff_like_weight: "Весовой коэффициент для симпатий, выдаваемых персоналом (для остальных пользователей он равен 1)." topic_view_duration_hours: "Считать все просмотры темы как один просмотр, если просмотры происходят с одного IP-адреса в течение указанного здесь количества часов." user_profile_view_duration_hours: "Считать все просмотры профиля пользователя как один просмотр, если просмотры происходят с одного IP-адреса в течение указанного здесь количества часов." levenshtein_distance_spammer_emails: "Количество символов, на которое могут различаться сообщения, если проводится нечёткое сравнение текста при проверке писем на спам." @@ -1832,7 +1839,7 @@ ru: max_age_unmatched_emails: "Удалять отфильтрованные письма после указанного здесь количества дней." max_age_unmatched_ips: "Удалять отфильтрованные IP-адреса после указанного здесь количества дней." num_flaggers_to_close_topic: "Минимальное количество человек, жалующихся на тему, которое требуется для автоматической блокировки темы и отправки её на премодерацию" - num_hours_to_close_topic: "Количество часов для блокировки темы, необходимых для её модерации." + num_hours_to_close_topic: "Тема блокируется на указанное здесь количество часов, необходимых для её модерации." auto_respond_to_flag_actions: "Включить автоматический ответ при отклонении жалобы." min_first_post_typing_time: "Минимальное количество времени в миллисекундах, которое пользователь должен потратить на набор текста первого сообщения. Если порог не соблюдён, сообщение автоматически попадает на премодерацию. Установите 0, если необходимо отключить этот параметр (не рекомендуется)." auto_silence_fast_typers_on_first_post: "Автоматически блокировать пользователей, которые не соответствуют параметру `min_first_post_typing_time`" @@ -1845,12 +1852,12 @@ ru: cooldown_hours_until_reflag: "Количество часов, в течение которых пользователи не смогут повторно пожаловаться на сообщение" reply_by_email_enabled: "Разрешать отвечать в темах с помощью электронных писем." reply_by_email_address: "Шаблон для ответа по email в формате: %%{reply_key}@reply.example.com или replies+%%{reply_key}@example.com" - alternative_reply_by_email_addresses: "Список альтернативных шаблонов для ответа по электронной почте на входящие адреса электронной почты. Пример: %%{reply_key}@reply.example.com|replies+1%%{reply_key}@example.com" + alternative_reply_by_email_addresses: "Список альтернативных шаблонов для ответа, применяемый на основе входящих адресов электронной почты. Пример: %%{reply_key}@reply.example.com|replies+1%%{reply_key}@example.com" incoming_email_prefer_html: "Использовать HTML вместо обычного текста для входящих писем." strip_incoming_email_lines: "Удалять начальные и конечные пробелы из каждой строки входящих писем." - disable_emails: "Запретить отправлять любые электронные письма. Выберите «да», чтобы отключить электронную почту для всех пользователей. Выберите «не персонал», чтобы отключить электронную почту только для пользователей, не являющихся сотрудниками." - strip_images_from_short_emails: "Удалять картинки из писем размером менее 2800 байт" - short_email_length: "Какие письма считать короткими, в байтах" + disable_emails: "Запретить отправлять любые электронные письма. Выберите «да», чтобы отключить электронную почту для всех пользователей. Выберите «non-staff», чтобы отключить электронную почту для пользователей, не являющихся персоналом." + strip_images_from_short_emails: "Удалять картинки из коротких писем - размером менее 2800 байт" + short_email_length: "Письма будут считаться короткими, если их размер не превышает указанное здесь количество байт" display_name_on_email_from: "Отображать в поле 'От' полные имена отправителей электронных писем" unsubscribe_via_email: "Разрешать пользователям отказываться от подписки на электронные письма, отправив электронное письмо, содержащее 'unsubscribe' в теме или теле письма" unsubscribe_via_email_footer: "Отображать ссылку на отписку под текстом отправляемого письма" @@ -1863,10 +1870,10 @@ ru: block_auto_generated_emails: "Блокировать входящие Email, идентифицированные как автоматически созданные." ignore_by_title: "Игнорировать входящие Email по их заголовку." mailgun_api_key: "Секретный ключ API Mailgun, используемый для проверки сообщений вебхука." - soft_bounce_score: "Оценка отказов добавляется пользователю, когда происходит временный отскок." - hard_bounce_score: "Счет отказов добавляется пользователю, когда происходит постоянный отскок." + soft_bounce_score: "Количество возвратов, добавляемых пользователю при непостоянных возвратах писем." + hard_bounce_score: "Количество возвратов, добавляемых пользователю при постоянных возвратах писем." bounce_score_threshold: "Максимальное количество возвратов недоставленных писем, после которого мы перестанем отправлять пользователю электронную почту." - reset_bounce_score_after_days: "Автоматически сбрасывать счет отказов после X дней." + reset_bounce_score_after_days: "Автоматически сбрасывать счётчик отказов после указанного здесь количества дней." blocked_attachment_content_types: "Перечень ключевых слов, используемых для добавления вложений в черный список в зависимости от типа контента." blocked_attachment_filenames: "Список ключевых слов, используемых для добавления вложений в черный список на основе имени файла." forwarded_emails_behaviour: "Как обрабатывать переадресованное письмо в Discourse" @@ -1878,7 +1885,7 @@ ru: raw_rejected_email_max_length: "Максимальное количество символов, сохраняющихся в отклонённых сообщениях входящей электронной почты." delete_rejected_email_after_days: "Удалять отклонённые письма старше указанного здесь количества дней." manual_polling_enabled: "Отправлять электронную почту, используя API для ответов." - pop3_polling_enabled: "Загружать ответы в форум в виде писем с учетной записи POP3." + pop3_polling_enabled: "Загружать ответы на форум в виде писем с учетной записи POP3." pop3_polling_ssl: "Использовать SSL при подключении к POP3-серверу. (Рекомендуется)" pop3_polling_openssl_verify: "Проверять сертификат сервера TLS (по умолчанию: включено)" pop3_polling_period_mins: "Период в минутах между проверками учетной записи POP3 на наличие электронных писем. ВНИМАНИЕ: требуется перезапуск." @@ -1888,22 +1895,22 @@ ru: pop3_polling_password: "Пароль учетной записи POP3 для загрузки e-mail." pop3_polling_delete_from_server: "Удалять электронную почту с сервера. ПРИМЕЧАНИЕ: при отключении этого параметра необходимо вручную очищать почтовый ящик" log_mail_processing_failures: "Записывать все ошибки обработки электронной почты в файл журнала" - email_in: 'Разрешать пользователям публиковать новые темы по электронной почте (требуется опрос вручную или pop3). Настройте адреса на вкладке «Настройки» каждой категории.' + email_in: 'Разрешать пользователям публиковать новые темы по электронной почте (требуется автоматическая или ручная проверка почтового ящика - POP3). Настройте адреса на вкладке «Настройки» каждого раздела.' email_in_min_trust: "Минимальный уровень доверия, требуемый для создания новых тем через электронные письма." email_in_authserv_id: "Идентификатор службы, выполняющей проверку подлинности входящих писем. См. тему https://meta.discourse.org/t/134358 для получения более подробной информации." email_in_spam_header: "Заголовок электронного письма для обнаружения спама." enable_imap: "Включить IMAP для синхронизации групповых сообщений." enable_imap_write: "Включить двустороннюю синхронизацию IMAP. При выключенном параметре все операции записи в учётных записях IMAP отключены." enable_imap_idle: "Использовать механизм IMAP IDLE для ожидания новых писем." - enable_smtp: "Включите SMTP для отправки уведомлений для групповых сообщений." + enable_smtp: "Включить SMTP для отправки уведомлений для групповых сообщений." imap_polling_period_mins: "Период в минутах между проверкой учётных записей IMAP на наличие писем." imap_polling_old_emails: "Максимальное количество старых (обработанных) электронных писем, которые будут обновляться каждый раз при опросе окна IMAP (укажите 0 для обновления всех старых писем)." imap_polling_new_emails: "Максимальное количество новых электронных писем (необработанных), которые будут обновляться каждый раз при опросе окна IMAP." imap_batch_import_email: "Минимальное количество новых писем, которые запускают режим импорта (отключает почтовые оповещения)." - email_prefix: "[label], используемая в заголовках писем. Если не указано, по-умолчанию будет использовано значение из настройки 'title'." + email_prefix: "Метка, используемая в заголовках писем. Если не указано, по-умолчанию будет использовано значение из настройки 'title'." email_site_title: "Заголовок сайта, используемый в автоматических письмах. Если заголовок не настроен, то используется стандартное значение 'title'. Если ваш заголовок содержит символы, запрещённые в поле 'От', будет использовано это значение." find_related_post_with_key: "Использовать только 'ответный ключ', чтобы найти ответ на сообщение. ВНИМАНИЕ: отключение этого параметра позволяет выдавать себя за другого пользователя на основании адреса электронной почты." - minimum_topics_similar: "Количество существующих тем, перед тем, как начнут выводиться похожие темы." + minimum_topics_similar: "Сколько тем должно существовать, прежде чем похожие темы будут представлены при составлении новых тем." relative_date_duration: "Количество дней после создания сообщения, в течении которых его дата будет отображаться в относительном виде (7д), а не в абсолютном (20 Фев)." delete_user_max_post_age: "Запретить удаление пользователей, чьё первое сообщение было создано более указанного здесь количества дней." delete_all_posts_max: "Максимальное количество сообщений, которое может быть удалено за один раз через кнопку \"Удалить все сообщения\". Если у пользователя сообщений больше этого числа, сообщения не могут быть удалены за одно нажатие и в этом случае пользователь не может быть удалён." @@ -1912,21 +1919,21 @@ ru: email_editable: "Позволять пользователям изменять адрес электронной почты после регистрации." logout_redirect: "Адрес страницы для перенаправления после выхода из аккаунта (например: https://example.com/logout)" allow_uploaded_avatars: "Разрешать пользователям загружать свои собственные аватары." - allow_animated_avatars: "Разрешать пользователям использовать анимированные GIF-картинки в качестве аватара. ВНИМАНИЕ: после изменения данной настройки запустите rake-задачу avatars:refresh ." + allow_animated_avatars: "Разрешать пользователям использовать анимированные GIF-картинки в качестве аватара. ВНИМАНИЕ: после изменения данной настройки запустите rake-задачу avatars:refresh." allow_animated_thumbnails: "Генерировать анимированные миниатюры GIF-картинок." default_avatars: "URL для аватара, который будет использован по умолчанию для новых пользователей, пока они его не поменяют." automatically_download_gravatars: "Скачивать Gravatar пользователя при создании учётной записи или изменении электронного адреса." digest_topics: "Максимальное количество популярных тем, отображаемых в Email-дайджесте." - digest_posts: "Максимальное количество популярных тем, отображаемых в Email-дайджесте." + digest_posts: "Максимальное количество популярных сообщений, отображаемых в Email-дайджесте." digest_other_topics: "Максимальное количество тем для отображения в разделе Email-дайджеста \"Новые темы и разделы, за которыми вы следите\"." digest_min_excerpt_length: "Минимальная длина отрывка сообщения в Email-дайджесте (количество символов)." suppress_digest_email_after_days: "Не рассылать Email-дайджест для пользователей, не появлявшихся на форуме более указанного здесь количества дней." digest_suppress_categories: "Не отображать содержимое этих разделов в Email-дайджесте." disable_digest_emails: "Отключить Email-дайджест для всех пользователей." - apply_custom_styles_to_digest: "Пользовательский шаблон Email и CSS, применяемый к Email-дайджестам." - email_accent_bg_color: "Акцентирующий цвет, который будет использоваться в качестве фона некоторых элементов в e-mail письмах в HTML. Введите название цвета ('red') или hex значение ('#FF000'). " - email_accent_fg_color: "Цвет текста, отображаемого на электронном письме, цвет HTML в электронных письмах. Введите название цвета («белый») или шестнадцатеричное значение («#FFFFFF»)." - email_link_color: "Цвет ссылок в HTML-письмах. Введите название цвета ('blue') или hex-значение ('#0000FF')." + apply_custom_styles_to_digest: "Применять пользовательский Email-шаблон и CSS к Email-дайджестам." + email_accent_bg_color: "Цвет фона, отображаемого в электронных письмах, созданных в формате HTML. Введите название цвета («red») или его шестнадцатеричное значение («#FF0000»)." + email_accent_fg_color: "Цвет текста, отображаемого в электронных письмах, созданных в формате HTML. Введите название цвета («white») или его шестнадцатеричное значение («#FFFFFF»)." + email_link_color: "Цвет ссылок, отображаемых в электронных письмах, созданных в формате HTML. Введите название цвета («blue») или его шестнадцатеричное значение («#0000FF»)." detect_custom_avatars: "Следует ли проверять, загрузили ли пользователи собственные аватары." max_daily_gravatar_crawls: "Максимальное количество запросов в течение дня, когда Discourse будет опрашивать Gravatar на наличие пользовательских аватаров" public_user_custom_fields: "Список пользовательских полей, которые можно получить с помощью API." @@ -1939,16 +1946,17 @@ ru: anonymous_posting_min_trust_level: "Минимальный уровень доверия для возможности создавать темы от имени анонимного пользователя." anonymous_account_duration_minutes: "Защита от слишком частого создания новых учётных записей.\nПример: Если значение установлено в 600, это означает, что должно пройти не менее 600 минут с момента последнего сообщения пользователя И его выхода из системы, после чего он сможет создать новую учётную запись." hide_user_profiles_from_public: "Отключить карточки пользователей, профили пользователей и каталог пользователей для анонимных пользователей." + allow_users_to_hide_profile: "Разрешить пользователям скрывать свой профиль и присутствие на форуме" allow_featured_topic_on_user_profiles: "Разрешать пользователям размещать ссылку на избранную тему в карточке пользователя и в профиле." show_inactive_accounts: "Разрешать авторизованным пользователям просматривать профили неактивных учётных записей." hide_suspension_reasons: "Не отображать публично причины заморозки в профиле пользователей." log_personal_messages_views: "Регистрировать просмотры личных сообщений администратором для других пользователей / групп." - ignored_users_count_message_threshold: "Уведомлять модераторов, если определённого пользователя игнорируют многие другие пользователи." - ignored_users_message_gap_days: "Сколько дней ждать, прежде чем снова уведомить модераторов о пользователе, которого игнорировали многие другие пользователи." + ignored_users_count_message_threshold: "Уведомлять модераторов, если определённого пользователя игнорируют указанное здесь количество пользователей." + ignored_users_message_gap_days: "Количество дней ожидания, прежде чем снова уведомить модераторов о пользователе, которого игнорировали многие другие пользователи." clean_up_inactive_users_after_days: "Количество дней до удаления неактивного пользователя ( с уровнем доверия 0 и без единого сообщения). Для отключения удаления установите это значение в 0." clean_up_unused_staged_users_after_days: "Количество дней до удаления неиспользуемого сымитированного пользователя (без каких-либо сообщений). Для отключения удаления установите это значение в 0." user_selected_primary_groups: "Разрешать пользователям устанавливать свою основную группу" - max_notifications_per_user: "Максимальное количество уведомлений на пользователя, если это число будет превышено, старые уведомления будут удалены. Принудительно еженедельно. Установите 0, чтобы отключить" + max_notifications_per_user: "Максимальное количество уведомлений на пользователя. Если это число будет превышено, старые уведомления будут еженедельно удаляться. Для отключения параметра установите это значение в 0." allowed_user_website_domains: "Сайт пользователя будет проверен по этим доменам, разделённым вертикальной чертой." allow_profile_backgrounds: "Разрешать пользователям загружать фоновые картинки для своих страниц профиля." sequential_replies_threshold: "Количество сообщений, которые пользователь может опубликовать подряд в теме, прежде чем ему придёт напоминание о слишком большом количестве последовательных ответов в одной и той же теме." @@ -1958,7 +1966,7 @@ ru: disable_avatar_education_message: "Отключить обучающее сообщение при смене аватара." suppress_uncategorized_badge: "Не показывать награду в списках тем для тех тем, которые находятся в разделе РАЗНОЕ." header_dropdown_category_count: "Максимальное количество разделов, отображаемых в выпадающем меню заголовка." - permalink_normalizations: "Примените следующее регулярное выражение перед сопоставлением постоянных ссылок, например: /(topic.*)\\?.*/\\1 удалит строки запроса из тематических маршрутов. Формат - регулярное выражение + использование строки \\ 1 и т. Д. Для доступа к перехватам" + permalink_normalizations: "Применять указанное регулярное выражение перед поиском соответствий в постоянных ссылках, например, выражение: /(topic.*)\\?.*/\\1 удалит подстроку запроса из ссылок. \nИспользование: регулярное выражение + строка. Используйте \\1 и т. д. для доступа к захватам" global_notice: "Показывать глобальное постоянно отображаемое объявление со СРОЧНЫМИ / АВАРИЙНЫМИ сообщениями всем посетителям. Для скрытия объявления - удалите его содержание (разрешено использование HTML )." disable_system_edit_notifications: "Отключить уведомления системы, если включена настройка 'download_remote_images_to_local'." notification_consolidation_threshold: "Максимальное количество уведомлений о полученных симпатиях или запросах на вступление в группу, после которого уведомления будут объединяться в одно. Для отключения параметра установите это значение в 0." @@ -1971,22 +1979,22 @@ ru: app_association_android: "Содержимое конечной точки .well-known/assetlinks.json, используемой для Google Digital Asset Links API." app_association_ios: "Содержимое конечной точки apple-app-site-association, используемой для создания универсальных ссылок между этим сайтом и приложениями iOS." share_anonymized_statistics: "Делиться анонимной статистикой использования." - auto_handle_queued_age: "По истечении указанного здесь количества дней автоматически обрабатывать записи, ожидающие модерации. Жалобы будут игнорироваться. Сообщения в очереди на премодерацию будут отклоняться. Установите значение в 0, если необходимо отключить эту функцию." - svg_icon_subset: "Добавить дополнительные иконки FontAwesome 5, которые вы хотели бы включить в свои ресурсы. Используйте префикс «fa-» для solid-иконок, «far-» для regular-иконок и «fab-» для brand-иконок." - max_prints_per_hour_per_user: "Максимальное количество показов страницы / печать (установите на 0, чтобы отключить)" + auto_handle_queued_age: "По истечении указанного здесь количества дней автоматически обрабатывать записи, ожидающие модерации. Жалобы будут игнорироваться. Сообщения в очереди на премодерацию будут отклоняться. Установите значение в 0, если необходимо отключить этот параметр." + svg_icon_subset: "Добавить дополнительные иконки из коллекции Font Awesome 5, которые вы хотели бы включить в свои ресурсы. Используйте префикс «fa-» для solid-иконок, «far-» для regular-иконок и «fab-» для brand-иконок." + max_prints_per_hour_per_user: "Максимальное количество распечаток страницы в час для пользователя. Для отключения параметра установите значение в 0." full_name_required: "Обязательно указывать ПОЛНОЕ имя в профиле пользователя." enable_names: "Отображать полное имя пользователя в его профиле, карточке пользователя и в письмах. Отключите этот параметр, если необходимо полностью скрыть полное имя." display_name_on_posts: "Отображать полные имена пользователей в их сообщениях в дополнение к их @псевдониму." show_time_gap_days: "Если между двумя сообщениями в теме прошло указанное здесь количество дней, отображать этот промежуток времени текстом между этими сообщениями." short_progress_text_threshold: "После достижения в теме указанного здесь количества сообщений, индикатор сообщений будет отображать только текущий номер сообщения. Если вы измените ширину индикатора, вам может потребоваться изменение этого значения." - default_code_lang: "Подсветка синтаксиса по умолчанию для блоков кода (lang-auto, ruby, python etc.)" + default_code_lang: "Подсветка синтаксиса по умолчанию, применяемая к блокам кода GitHub (auto, nohighlight, ruby, python и т. д.)" warn_reviving_old_topic_age: "Отображать предупреждение, когда кто-то пытается ответить в тему, в которой предыдущий ответ был опубликован более указанного здесь количества дней назад. Для отключения параметра установите это значение в 0." autohighlight_all_code: "Принудительно использовать подсветку кода для всех отформатированных блоков кода, даже когда явно не указан язык." highlighted_languages: "Включить правила подсветки синтаксиса. (Предупреждение: включение слишком большого количества языков может повлиять на производительность), см. https://highlightjs.org/static/demo для демонстрации" show_copy_button_on_codeblocks: "Отображать кнопку в блоках кода для возможности копирования содержимого блока в буфер обмена. Функция не поддерживается браузером Internet Explorer." embed_any_origin: "Разрешать встраиваемый контент независимо от происхождения. Требуется для мобильных приложений со статическим HTML." embed_topics_list: "Поддержка HTML встраивания списков тем" - embed_set_canonical_url: "Установите канонический URL для встроенных тем в URL встроенного содержимого." + embed_set_canonical_url: "Установить канонический URL для встроенных тем в URL встроенного содержимого." embed_truncate: "Обрезать встроенные сообщения." embed_unlisted: "Импортированные темы будут отображаться в списке тем только после добавления в них ответа." embed_support_markdown: "Поддержка форматирования Markdown для встроенных сообщений." @@ -1997,12 +2005,12 @@ ru: notify_about_flags_after: "Отправлять личное сообщение модератору, если есть жалобы, не обработанные в течение указанного здесь количества часов. Для отключения параметра установите это значение в 0." show_create_topics_notice: "Если общее количество тем на сайте меньше 5, показывать персоналу сообщение с просьбой создать новые темы." delete_drafts_older_than_n_days: "Удалять черновики, которые старше указанного здесь количества дней." - bootstrap_mode_min_users: "Минимальное количество пользователей, необходимое для отключения режима начальной загрузки (установите 0, чтобы отключить)" + bootstrap_mode_min_users: "Минимальное количество пользователей, необходимое для отключения специального режима (Для отключения этого параметра установите значение в 0)" prevent_anons_from_downloading_files: "Запретить анонимным пользователям скачивать вложения. ВНИМАНИЕ: при этом будут недоступны любые вложения, кроме картинок." secure_media: 'Ограничивать доступ ко ВСЕМ загрузкам (изображения, видео, аудио, текст, PDF, ZIP-файлы и другие). Только зарегистрированные пользователи будут получать доступ к загрузкам. При отключённом параметре загрузки будут недоступны только в личных сообщениях и в закрытых разделах форума. ВНИМАНИЕ: Этот сложный параметр и администратор, используя его, должен отдавать себе отчёт в своих действиях. См. тему Secure Media Uploads.' secure_media_allow_embed_images_in_emails: "Разрешать встраивать изображения в письма, если их размер не превышает значения, указанного в параметре 'secure_media_max_email_embed_image_size_kb'." secure_media_max_email_embed_image_size_kb: "Количество килобайт, до которого будет уменьшен размер встроенных в письма изображений, если включён параметр 'secure_media_allow_embed_images_in_emails'." - slug_generation_method: "Выберите метод создания слагов (текстового идентификатора). При выборе 'encoded' будет выполняться процентное кодирование строки. При выборе 'none' слаги создаваться не будут." + slug_generation_method: "Метод создания слагов. При выборе 'encoded' будет выполняться процентное кодирование строки. При выборе 'none' слаги создаваться не будут." enable_emoji: "Использовать смайлы (emoji)" enable_emoji_shortcuts: "Текстовая альтернатива смайлика, например, сочетания :), :p или :( - будет преобразовываться в полноценный смайлик" emoji_set: "Какую коллекцию Emoji использовать?" @@ -2012,13 +2020,13 @@ ru: approve_unless_trust_level: "Сообщения от пользователей с уровнем доверия ниже указанного должны быть одобрены" approve_new_topics_unless_trust_level: "Новые темы от пользователей с уровнем доверия ниже указанного должны быть одобрены" approve_unless_staged: "Новые темы и сообщения сымитированных пользователей должны быть одобрены" - notify_about_queued_posts_after: "Отправлять уведомления всем модераторам, если сообщения в очереди на модерацию находятся более чем указанное здесь количество часов. Для отключения уведомлений установите это значение в 0." + notify_about_queued_posts_after: "Отправлять уведомления всем модераторам, если сообщения находятся в очереди на модерацию дольше, чем указанное здесь количество часов. Для отключения уведомлений установите это значение в 0." auto_close_messages_post_count: "Максимальное количество постов, разрешённых в личных сообщениях, после чего они будут автоматически закрыты. Для отключения параметра установите это значение в 0." auto_close_topics_post_count: "Максимальное количество сообщений, разрешённых в теме, после чего она будет автоматически закрыта. Для отключения параметра установите это значение в 0." code_formatting_style: "Кнопка кода в редакторе будет использовать по умолчанию этот стиль форматирования кода" max_allowed_message_recipients: "Максимальное число получателей личного сообщения." watched_words_regular_expressions: "Контролируемые слова представлены регулярными выражениями." - old_post_notice_days: "Дней до того, как почтовое уведомление станет старым" + old_post_notice_days: "Количество дней, после которого почтовое уведомление станет устаревшим" new_user_notice_tl: "Минимальный уровень доверия, необходимый для просмотра новых сообщений пользователей." returning_user_notice_tl: "Минимальный уровень доверия, необходимый для просмотра уведомлений о возвращающихся сообщениях." returning_users_days: "Количество дней, после которого пользователь будет считаться вернувшимся на форум." @@ -2026,15 +2034,15 @@ ru: enable_page_publishing: "Разрешить сотрудникам публиковать темы на новых URL-адресах с их собственным стилем." show_published_pages_login_required: "Опубликованные страницы доступны анонимным пользователям, для этого не требуется регистрация на форуме." default_email_digest_frequency: "Как часто пользователи получают Email-дайджест по умолчанию." - default_include_tl0_in_digests: "Включить по умолчанию сообщения от новых пользователей в Email-дайджест. Пользователи могут изменить эту настройку в своём профиле." - default_email_level: "Установите уровень уведомления по электронной почте по умолчанию для обычных тем." - default_email_messages_level: "Установите уровень уведомления по электронной почте по умолчанию, когда кто-то отправляет личное сообщение пользователю." + default_include_tl0_in_digests: "Включать по умолчанию сообщения от новых пользователей в Email-дайджест. Пользователи могут изменить эту настройку в своём профиле." + default_email_level: "Уровень уведомления по электронной почте по умолчанию для обычных тем." + default_email_messages_level: "Уровень уведомления по электронной почте по умолчанию, когда кто-то отправляет личное сообщение пользователю." default_email_mailing_list_mode: "По умолчанию присылать почтовое уведомление при каждом новом сообщении." default_email_mailing_list_mode_frequency: "Пользователи, которые включают режим почтовой рассылки, будут получать электронные письма с указанной здесь периодичностью." disable_mailing_list_mode: "Запретить пользователям включать режим почтовой рассылки." - default_email_previous_replies: "Включить по умолчанию предыдущие ответы в электронные письма." - default_email_in_reply_to: "Включить по умолчанию выдержку из ответов на сообщения в электронные письма ." - default_other_new_topic_duration_minutes: "Глобальное условие по умолчанию, по которому тема считается новой." + default_email_previous_replies: "Включать по умолчанию предыдущие ответы в электронные письма." + default_email_in_reply_to: "Включать по умолчанию выдержку из ответов на сообщения в электронные письма." + default_other_new_topic_duration_minutes: "Глобальное условие по умолчанию, по которому темы считаются новыми." default_other_auto_track_topics_after_msecs: "Глобальное время по умолчанию перед автоматическим отслеживанием темы." default_other_notification_level_when_replying: "Глобальный уровень уведомлений по умолчанию, когда пользователь отвечает на тему." default_other_external_links_in_new_tab: "По умолчанию открывать внешние ссылки в новой вкладке." @@ -2049,7 +2057,7 @@ ru: default_categories_muted: "Список разделов, в которых уведомления по умолчанию отключены." default_categories_watching_first_post: "Список разделов, в которых по умолчанию будет включено наблюдение за первым сообщением в каждой новой теме." default_categories_regular: "Перечень разделов, в которых по умолчанию уведомления всегда включены. Эта настройка может быть полезна, когда включён параметр `mute_all_categories_by_default`." - mute_all_categories_by_default: "Установить по умолчанию уровень уведомлений для всех разделов на 'Без уведомлений'. Требовать от пользователей подписки на разделы, чтобы они появлялись в секциях «Последние» и «Разделы». Если вы хотите изменить настройки по умолчанию для анонимных пользователей, установите настройки «default_categories_»." + mute_all_categories_by_default: "Установить по умолчанию уровень уведомлений для всех разделов на 'Без уведомлений'. Требовать от пользователей подписки на разделы, чтобы они появлялись в секциях «Последние» и «Разделы». Если вы хотите изменить настройки по умолчанию для анонимных пользователей - измените настройки «default_categories_»." default_tags_watching: "Список тегов по умолчанию, которыми помечаются наблюдаемые темы." default_tags_tracking: "Список тегов по умолчанию, которыми помечаются отслеживаемые темы." default_tags_muted: "Список тегов по умолчанию, которыми помечаются выключенные темы." @@ -2085,12 +2093,13 @@ ru: governing_law: "Регулирующий Закон" city_for_disputes: "Город для решения споров" shared_drafts_category: "Включить функцию «Общие черновики», назначив категорию для черновиков тем. Персонал не будет видеть подобные темы в секции 'Обсуждаемые'." - push_notifications_prompt: "Отображать запроса согласия пользователя." + push_notifications_prompt: "Отображать запрос согласия пользователя." push_notifications_icon: "Иконка значка, который появляется в углу уведомлений. Рекомендуется монохроматическое изображение в формате PNG размером 96 × 96 пикселей с поддержкой прозрачности." - base_font: "Шрифт, наиболее часто используемый на сайте. Шрифты тем имеют приоритет." - short_title: "Краткое название будет использоваться на домашнем экране пользователя, в панели запуска или в других местах, где пространство может быть ограничено. Он должен быть ограничен до 12 символов." - dashboard_hidden_reports: "Разрешать скрытие отдельных отчётов из админки." - dashboard_visible_tabs: "Выберите закладки, которые будут отображаться в админке." + base_font: "Основной шрифт, используемый для большей части текста на сайте. Шрифт тем можно переопределить с помощью настраиваемого свойства CSS `--font-family`." + heading_font: "Шрифт, используемый для заголовков. Шрифт тем можно переопределить с помощью настраиваемого свойства CSS `--font-family`." + short_title: "Краткое название будет использоваться на домашней странице, в панели запуска или в других местах, где пространство может быть ограничено. Оно должен быть не более 12 символов." + dashboard_hidden_reports: "Разрешать скрывать отдельные отчёты из админки." + dashboard_visible_tabs: "Закладки, которые будут отображаться в админке." dashboard_general_tab_activity_metrics: "Выберите отчёты, отображаемые в метриках активности на основной вкладке админки." gravatar_name: "Название провайдера Gravatar" gravatar_base_url: "URL базы API провайдера Gravatar" @@ -2126,7 +2135,7 @@ ru: reply_by_email_disabled: "Вы должны сначала включить параметр 'Ответить по электронной почте', прежде чем включить эту настройку." sso_url_is_empty: "Вы должны установить 'sso url' перед включением этой настройки." sso_invite_only: "Нельзя одновременно включить sso и пригласить." - enable_local_logins_disabled: "Вы должны сначала включить «включить локальные входы в систему» перед включением этого параметра." + enable_local_logins_disabled: "Перед включением этого параметра Вы должны включить параметр 'Включить локальные учетные записи' ." min_username_length_exists: "Вы не можете установить минимальную длину псевдонима более самого короткого - (%{username})." min_username_length_range: "Нельзя установить минимум больше максимума." max_username_length_exists: "Вы не можете установить максимальную длину псевдонима менее самого короткого - (%{username})." @@ -3397,7 +3406,7 @@ ru: follow_topic: "Следить за этой темой" join_the_discussion: "Читать далее" popular_posts: "Популярные сообщения" - more_new: "Новинки для вас" + more_new: "Новинки" subject_template: "[%{email_prefix}] — дайджест" unsubscribe: "Этот дайджест отправлен с сайта %{site_link} и состоит из новостей, появившихся за время вашего отсутствия на форуме. Измените %{email_preferences_link} или %{unsubscribe_link}, чтобы отменить подписку." your_email_settings: "ваши настройки электронной почты" @@ -3455,9 +3464,20 @@ ru: title: "Подтвердить новый адрес электронной почты" subject_template: "[%{email_prefix}] Подтвердите ваш новый адрес электронной почты" text_body_template: | - Подтвердите ваш новый адрес электронной почты для сайта %{site_name}, перейдя по следующей ссылке: + Подтвердите новый адрес электронной почты для сайта %{site_name}, перейдя по ссылке: - %{base_url}/u/verify-new-email/%{email_token} + %{base_url}/u/confirm-new-email/%{email_token} + + Если вы не запрашивали это изменение, обратитесь к администратору сайта %{site_name}. + confirm_new_email_via_admin: + title: "Подтвердить адрес электронной почты" + subject_template: "[%{email_prefix}] Подтвердите новый адрес электронной почты" + text_body_template: | + Подтвердите адрес электронной почты для сайта %{site_name}, перейдя по ссылке: + + %{base_url}/u/confirm-new-email/%{email_token} + + Это изменение адреса электронной почты было запрошено администратором сайта. Если вы не запрашивали это изменение, обратитесь к администратору сайта %{site_name}. confirm_old_email: title: "Подтвердите старый адрес электронной почты" subject_template: "%{email_prefix} Подтверждение текущего электронного адреса" @@ -3647,7 +3667,7 @@ ru: Отредактируйте первое сообщение этой темы, чтобы изменить название %{page_name}. guidelines_topic: title: "Основные принципы сообщества и часто задаваемые вопросы" - body: "\n\n ## [Это цивилизованное место для публичного обсуждения](#civilized)\n\n Пожалуйста, относитесь к этому форуму с таким же уважением, как и к общественному парку. Это дискуссионная площадка - место, где можно делиться навыками, знаниями и интересами посредством постоянного общения.\n\n Это не жёсткие и строгие правила, это лишь рекомендации, чтобы у посетителей сложилось положительное мнение о нашем сообществе. Старайтесь следовать этим рекомендациям, чтобы этот форум всегда оставался интересной и познавательной площадкой для цивилизованных публичных дискуссий. \n\n \n\n ## [Сделаем форум интереснее](#improve)\n\n Помогите нам сделать этот форум привлекательным местом для обмена мнениями, работая над улучшением обсуждения, пусть даже время от времени. Если вы не уверены в том, что ваше сообщение привносит в беседу что-то полезное, обдумайте ещё раз то, что вы хотите сказать и повторите попытку позже.\n\n Обсуждаемые здесь темы важны для нас, и мы хотим, чтобы вы вели себя здесь так, как будто они важны и для вас. С уважением относитесь к темам и обсуждающим их людям, даже если вы не согласны с какой-либо частью обсуждения.\n\n Один из способов улучшить обсуждение - использовать уже существующие темы. Потратьте немного времени и воспользуйтесь поиском по форуму, прежде чем создавать новую тему, и у вас будет больше шансов встретить тех, кто разделяет ваши интересы.\n\n \n\n ## [Будьте любезны, даже если вы не согласны](#agreeable)\n\nВозможно, вы захотите ответить на кому-то, не согласившись с мнением собеседника. В этом нет ничего необычного. Но не забывайте _критиковать идеи, а не людей_. Пожалуйста, избегайте:\n\n * навешивания ярлыков\n * перехода на личности\n * реагирования на тон сообщения, а не на его фактическое содержание\n * необдуманного поведения\n\nВместо всего этого приведите ваши контраргументы, сделав общение более содержательным.\n\n \n\n ## [Ваш вклад имеет значение](#participate)\n\nВопросы, поднимаемые на форуме, задают общий тон дискуссии. Помогите нам повлиять на будущее этого сообщества, приняв участие в тех обсуждениях, которые делают этот форум интереснее, и избегая те обсуждения, которые, по вашему мнению, этого не стоят.\n\nDiscourse предоставляет инструменты, которые позволяют сообществу коллективно определять лучший (и худший) вклад: избранное, закладки, симпатии, жалобы, ответы, правки и т.д. Используйте этот инструментарий для обогащения как своего опыта, так и опыта остальных участников.\n\nСделаем этот форум лучше, чем он был до нашего прихода.\n\n \n\n ## [Если вы видите проблему - пожалуйтесь на неё](#flag-problems)\n\nМодераторам даны особые полномочия; они несут ответственность за этот форум. Но и вы - тоже. С вашей активной помощью модераторы могут быть координаторами сообщества, а не только дворниками или полицией.\n\nЕсли вы встречаете неадекватное сообщение - не отвечайте на него. Ответ лишь поощряет плохое поведение, признавая его, потребляя вашу энергию и тратя ваше время впустую. Просто _пожалуйтесь_ на такое сообщение. Если будет набрано достаточно жалоб, необходимое действие будет выполнено автоматически или при вмешательстве модератора.\n\nВ целях сохранения нашего сообщества модераторы оставляют за собой право удалять любой контент и любую учётную запись пользователя по любой причине и в любое время. Модераторы не просматривают все новые сообщения; модераторы и администраторы сайта не несут ответственности за контент, публикуемый сообществом.\n\n \n\n ## [Будьте вежливы](#be-civil)\n\nНичто так не мешает хорошему общению, как грубость: \n\t\n* Будьте вежливы. Не публикуйте ничего, что разумный человек счёл бы обидным, оскорбительным или разжигающим ненависть.\n* Соблюдайте приличия. Не размещайте ничего непристойного или откровенно сексуального характера.\n* Уважайте друг друга. Не унижайте собеседников, не будьте назойливым, не выдавайте себя за другого и не разглашайте личную информацию других пользователей.\n* Уважайте форум. Не размещайте спам или иные ненужные сообщения.\n\nРечь не о каких-то конкретных терминах - избегайте публиковать любую подобною информацию. Если вы не уверены в качестве публикуемого сообщения - спросите себя, как бы вы себя чувствовали, если бы эта информация была бы размещена на первой странице популярного журнала.\n\nЭто открытый форум, и поисковые системы индексируют все обсуждения. Сделайте так, чтобы вам не пришлось краснеть за публикуемые сообщения, ссылки и изображения перед друзьями и близкими.\n\n \n\n ## [Поддерживайте порядок](#keep-tidy)\n\nПриложите немного усилий для расстановки всего по своим местам, чтобы мы могли проводить больше времени за обсуждениями и меньше за уборкой:\n\n * Не создавайте тему в неподходящем разделе\n * Не публикуйте одно и то же сообщение в нескольких темах\n * Не публикуйте бессодержательные ответы\n * Не уходите в сторону от сути обсуждения\n * Не подписывайте свои сообщения - в каждом сообщении доступна информация из вашего профиля\n\nВместо того, чтобы публиковать «+1» или «Согласен», используйте кнопку «Нравится». Если необходимо направить существующую тему разговора в совершенно другом направлении, используйте вариант «Ответить в новой связанной теме», нажав на кнопку в верхнем левом углу редактора.\n\n \n\n ## [Размещайте только свои материалы](#stealing)\n\nВы не можете публиковать какие-либо данные, не принадлежащие вам, без разрешения их авторов. Вы не можете публиковать описания, ссылки или методы кражи чьей-либо интеллектуальной собственности (программного обеспечения, видео, аудио, изображений) или способы нарушения любого другого закона.\n\n \n\n ## [Создано вами](#power)\n\nЭтот форум обслуживается как нашим [дружелюбным персоналом](%{base_path}/about), так и *вами* - нашим сообществом. Если у вас есть дополнительные вопросы по работе форума - откройте новую тему в разделе [Site feedback](%{base_path}/c/site-feedback) и мы обсудим их! Если у вас срочная проблема, которая не является жалобой или которая не может быть рассмотрена публично - свяжитесь с нами через [страницу персонала](%{base_path}/about).\n\n \n\n ## [Условия использования](#tos)\n\nДа, юридический язык - это скучно, но мы должны защитить себя, и, соответственно, вас и ваши данные от недружелюбных людей. У нас есть [Условия использования](%{base_path}/tos), в которых описывается ваше (и наше) поведение и права относительно контента, конфиденциальности и законов. Чтобы пользоваться этим сервисом, вы должны согласиться на соблюдение наших [Условий использования](%{base_path}/tos).\n" + body: "\n\n ## [Это цивилизованное место для публичного обсуждения](#civilized)\n\n Пожалуйста, относитесь к этому форуму с таким же уважением, как и к общественному парку. Это дискуссионная площадка - место, где можно делиться навыками, знаниями и интересами посредством постоянного общения.\n\n Это не жёсткие и строгие правила, это лишь рекомендации, чтобы у посетителей сложилось положительное мнение о нашем сообществе. Старайтесь следовать этим рекомендациям, чтобы этот форум всегда оставался интересной и познавательной площадкой для цивилизованных публичных дискуссий. \n\n \n\n ## [Сделаем форум интереснее](#improve)\n\n Помогите нам сделать этот форум привлекательным местом для обмена мнениями, работая над улучшением обсуждения, пусть даже время от времени. Если вы не уверены в том, что ваше сообщение привносит в беседу что-то полезное, обдумайте ещё раз то, что вы хотите сказать и повторите попытку позже.\n\n Обсуждаемые здесь темы важны для нас, и мы хотим, чтобы вы вели себя здесь так, как будто они важны и для вас. С уважением относитесь к темам и обсуждающим их людям, даже если вы не согласны с какой-либо частью обсуждения.\n\n Один из способов улучшить обсуждение - использовать уже существующие темы. Потратьте немного времени и воспользуйтесь поиском по форуму, прежде чем создавать новую тему, и у вас будет больше шансов встретить тех, кто разделяет ваши интересы.\n\n \n\n ## [Будьте сдержаны, даже если вы не согласны](#agreeable)\n\nВозможно, вы захотите ответить кому-то, не согласившись с мнением собеседника. В этом нет ничего необычного. Но не забывайте _критиковать идеи, а не людей_. Пожалуйста, избегайте:\n\n * навешивания ярлыков\n * перехода на личности\n * реагирования на тон сообщения, а не на его фактическое содержание\n * необдуманного поведения\n\nВместо всего этого приведите ваши контраргументы, сделав общение более содержательным.\n\n \n\n ## [Ваш вклад имеет значение](#participate)\n\nВопросы, поднимаемые на форуме, задают общий тон дискуссии. Помогите нам повлиять на будущее этого сообщества, приняв участие в тех обсуждениях, которые делают этот форум интереснее, и избегая те обсуждения, которые, по вашему мнению, этого не стоят.\n\nDiscourse предоставляет инструменты, которые позволяют сообществу коллективно определять лучший (и худший) вклад: избранное, закладки, симпатии, жалобы, ответы, историю правок сообщений и т.д. Используйте этот инструментарий для обогащения как своего опыта, так и опыта остальных участников.\n\nСделаем этот форум лучше, чем он был до нашего прихода.\n\n \n\n ## [Если вы видите проблему - пожалуйтесь на неё](#flag-problems)\n\nМодераторам даны особые полномочия; они несут ответственность за этот форум. Но и вы - тоже. С вашей активной помощью модераторы могут быть координаторами сообщества, а не только дворниками или полицией.\n\nЕсли вы встречаете неадекватное сообщение - не отвечайте на него. Ответ лишь поощряет плохое поведение, признавая его, потребляя вашу энергию и тратя ваше время впустую. Просто _пожалуйтесь_ на такое сообщение. Если будет набрано достаточно жалоб, необходимое действие будет выполнено автоматически или при вмешательстве модератора.\n\nВ целях сохранения нашего сообщества модераторы оставляют за собой право удалять любой контент и любую учётную запись пользователя по любой причине и в любое время. Модераторы не просматривают все новые сообщения; модераторы и администраторы сайта не несут ответственности за контент, публикуемый сообществом.\n\n \n\n ## [Будьте вежливы](#be-civil)\n\nНичто так не мешает хорошему общению, как грубость: \n\t\n* Будьте вежливы. Не публикуйте ничего, что разумный человек счёл бы обидным, оскорбительным или разжигающим ненависть.\n* Соблюдайте приличия. Не размещайте ничего непристойного или откровенно сексуального характера.\n* Уважайте друг друга. Не унижайте собеседников, не будьте назойливым, не выдавайте себя за другого и не разглашайте личную информацию других пользователей.\n* Уважайте форум. Не размещайте спам или иные ненужные сообщения.\n\nРечь не идёт о каких-то конкретных терминах - избегайте публиковать любую подобную информацию. Если вы не уверены в качестве публикуемого сообщения - спросите себя, как бы вы себя чувствовали, если эта информация была бы размещена на первой странице популярного журнала.\n\nЭто открытый форум, поэтому поисковые системы индексируют все обсуждения. Сделайте так, чтобы вам не пришлось краснеть за публикуемые сообщения, ссылки и изображения перед друзьями и близкими.\n\n \n\n ## [Поддерживайте порядок](#keep-tidy)\n\nПриложите немного усилий для поддержания порядка в процессе обсуждения, чтобы мы могли проводить больше времени за дискуссиями и меньше за уборкой:\n\n * Не создавайте тему в неподходящем разделе\n * Не публикуйте одно и то же сообщение в нескольких темах\n * Не публикуйте бессодержательные ответы\n * Не уходите в сторону от сути обсуждения\n * Не подписывайте свои сообщения - в каждом сообщении доступна информация из вашего профиля\n\nВместо того, чтобы публиковать «+1» или «Согласен», используйте кнопку «Нравится». Если необходимо направить существующую тему разговора в совершенно другом направлении, используйте вариант «Ответить в новой связанной теме», нажав на кнопку в левом верхнем углу редактора сообщения.\n\n \n\n ## [Размещайте только свои материалы](#stealing)\n\nВы не можете публиковать какие-либо данные, не принадлежащие вам, без разрешения их авторов. Вы не можете публиковать описания, ссылки или методы кражи чьей-либо интеллектуальной собственности (программного обеспечения, видео, аудио, изображений) или способы нарушения любого другого закона.\n\n \n\n ## [Создано вами](#power)\n\nЭтот форум обслуживается как нашим [дружелюбным персоналом](%{base_path}/about), так и *вами* - нашим сообществом. Если у вас есть дополнительные вопросы по работе форума - откройте новую тему в разделе [Site feedback](%{base_path}/c/site-feedback) и мы обсудим их! Если у вас срочная проблема, которая не является жалобой или которая не может быть рассмотрена публично - свяжитесь с нами через [страницу персонала](%{base_path}/about).\n\n \n\n ## [Условия использования](#tos)\n\nДа, юридический язык - это скучно, но мы должны защитить себя, и, соответственно, вас и ваши данные от недружелюбных действий. У нас есть [Условия использования](%{base_path}/tos), в которых описывается ваше (и наше) поведение и права относительно контента, конфиденциальности и законов. Чтобы пользоваться этим сервисом, вы должны согласиться на соблюдение наших [Условий использования](%{base_path}/tos).\n" tos_topic: title: "Пользовательское соглашение" body: | @@ -3914,7 +3934,7 @@ ru: name: Редактор description: Первое редактирование сообщения long_description: | - Эта награда выдаётся при первом редактировании одного из ваших сообщений. Хотя вы не сможете редактировать свои сообщения вечно, редактирование приветствуется - вы можете улучшить форматирование, исправить мелкие ошибки или добавить всё, что вы пропустили при первоначальной публикации. Сделайте ваши сообщения ещё лучше! + Эта награда выдаётся при первом редактировании одного из ваших сообщений. И хотя вы не сможете бесконечно редактировать свои сообщения, редактирование всё же приветствуется - вы можете улучшить форматирование, исправить мелкие ошибки или добавить всё, что вы пропустили при первоначальной публикации. Сделайте ваши сообщения ещё лучше! wiki_editor: name: Wiki-редактор description: Первое редактирование вики-сообщения @@ -4224,7 +4244,7 @@ ru: description: "Какой язык будет использоваться по умолчанию в вашем сообществе?" forum_title: title: "Название" - description: "Ваше название - это знак, видимый издалека, это первое, что потенциальные посетители подметят в вашем сообществе. Раскрывает ли указанное здесь название суть вашего сообщества?" + description: "Название вашего форума - это знак, привлекающий внимание, это первое, что заметят потенциальные посетители вашего сообщества. Раскрывает ли указанное здесь название суть вашего сообщества?" fields: title: label: "Название сообщества" @@ -4241,7 +4261,7 @@ ru: fields: welcome: label: "Приветственная тема" - description: "

Как бы Вы описали ваше сообщество незнакомцу в лифте за 1 минуту?

Ваше приветственное сообщение - это первое, что новые посетители прочтут. Думайте об этом сообщении как о \"короткой презентации в лифте\" или как о \"программном заявлении\", состоящем из одного абзаца.

" + description: "

Как бы Вы описали ваше сообщество незнакомцу в лифте за 1 минуту?

Ваше приветственное сообщение - это первое, что прочтут новые посетители. Подумайте об этом сообщении как о \"короткой презентации в лифте\" или как о \"программном заявлении\", состоящем из одного абзаца.

" one_paragraph: "Пожалуйста, ограничьте приветственное сообщение одним абзацем." privacy: title: "Доступ" @@ -4280,11 +4300,11 @@ ru: description: "Все автоматические личные сообщения будут отправляться от имени этого пользователя, такие как предупреждения о поступивших жалобах или уведомления о завершении резервного копирования." corporate: title: "Организация" - description: "Эта информация будет указана в вашем Пользовательском соглашении , которое вы всегда сможете отредактировать. Если у вас нет компании - просто пропустите этот шаг." + description: "Эта информация будет указана в вашем Пользовательском соглашении, которое вы всегда сможете отредактировать. Если у вас нет компании - просто пропустите этот шаг." fields: company_name: label: "Название компании" - placeholder: "Пример организации" + placeholder: "Название вашей организации" governing_law: label: "Регулирующий закон" placeholder: "Правила города Глупова" @@ -4293,16 +4313,15 @@ ru: placeholder: "Глупов, Российская империя" colors: title: "Цветовая схема" - themes_further_reading: - title: "Стили" - description: - - - - 'Популярные компоненты темы (подробнее см. #theme)"' fonts: title: "Шрифты" + fields: + body_font: + label: "Шрифт текста" + heading_font: + label: "Шрифт заголовка" + font_preview: + label: "Предварительный просмотр" logos: title: "Логотипы" fields: @@ -4343,16 +4362,17 @@ ru: label: "Блоки с избранными темами" emoji: title: "Смайлы" - description: "Выберите стиль смайлов для сообщества. В дальнейшем вы сможете добавить другие стили смайлов в Админке, в секции Оформление / Смайлы." + description: "Выберите стиль смайлов. В дальнейшем вы сможете добавить другие смайлы в Админке на закладке Оформление / Смайлы." invites: - title: "Пригласить как персонал" - description: "Вы почти закончили! Давайте пригласим людей, которые помогут вам начать обсуждение с интересных тем и ответов, чтобы ваше сообщество начало работу." + title: "Пригласить персонал" + description: "Вы почти закончили! Давайте пригласим сюда людей, которые помогут вам начать обсуждение с интересных тем и ответов, чтобы ваше сообщество начало работу." disabled: "Поскольку локальные учетные записи отключены, отправлять приглашения кому-либо невозможно. Пожалуйста, перейдите к следующему шагу." finished: title: "Ваш форум готов к обсуждениям!" description: | -

Если вам когда-нибудь захочется изменить эти настройки, перезапустите этот мастер в любое время, или зайдите в раздел администрирования; его можно найти по значку гаечного ключа в меню сайта.

-

Желаем успехов в построении вашего нового сообщества!

+

Если вы когда-нибудь захотите изменить эти настройки, повторно запустите этот мастер в любое времяили посетите раздел администратора, который расположен справа от значка гаечного ключа в меню сайта.

+

С помощью нашей мощной системы тем настроить внешний вид форума очень легко. В качестве примеров просмотрите популярные темы и компоненты на сайте meta.discourse.org.

+

Хорошего дня и удачи в создании нового сообщества!

search_logs: graph_title: "Количество поиска" joined: "Вступил" diff --git a/config/locales/server.sk.yml b/config/locales/server.sk.yml index f02898cb2b..e323909381 100644 --- a/config/locales/server.sk.yml +++ b/config/locales/server.sk.yml @@ -955,7 +955,6 @@ sk: faq_url: "Ak máte FAQ stránku uloženú na iných stránkach, napište sem plný odkaz na ňu." tos_url: "Ak máte stránku s Podmienkami uloženú na iných stránkach, napište sem plný odkaz na ne." privacy_policy_url: "Ak máte stránku s Podmienkami súkromia uloženú na iných stránkach, napište sem plný odkaz na ne." - staff_like_weight: "Koľkonásobne viac sa má dať zamestnaneckým páči sa mi." 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." @@ -1014,7 +1013,6 @@ sk: display_name_on_posts: "Na príspevkoch zobraziť okrem @loginpoužívateľa i plné meno používateľa." show_time_gap_days: "Koľko dní musí uplynúť medzi dvoma príspevkami v téme, aby sa medzi nimi zobrazila časová medzera." short_progress_text_threshold: "Ak počet príspevkov v téme presiahne toto číslo, ukazateľ priebehu zobrazí iba aktuálne číslo príspevku. Túto hodnotu možno budete potrebovať zmeniť, ak zmeníte šírku ukazateľa priebehu." - default_code_lang: "Vysvietenie syntaxe pre štandardné programovacie jazyky, aplikované na GitHub bloky kódu (lang-auto, ruby, python etc.)" warn_reviving_old_topic_age: "Zobrazenie varovania, ak niekto začne odpovedať na tému, kde je posledná odpoveď staršia ako uvedený počet dní. Na vypnutie varozania zadajte 0." autohighlight_all_code: "Zapnúť vysvietenie syntaxe kódu na všetky formátované bloky kódu, i keď nie je explicitne zadaný jazyk." embed_truncate: "Orezať vkladané príspevky." diff --git a/config/locales/server.sl.yml b/config/locales/server.sl.yml index 706caa4da5..93d187c76b 100644 --- a/config/locales/server.sl.yml +++ b/config/locales/server.sl.yml @@ -2033,8 +2033,6 @@ sl: title: "Organizacija" colors: title: "Videz" - themes_further_reading: - title: "Videzi" homepage: fields: homepage_style: diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml index d5a97061e4..7424296e01 100644 --- a/config/locales/server.sq.yml +++ b/config/locales/server.sq.yml @@ -770,7 +770,6 @@ sq: faq_url: "If you have a FAQ hosted elsewhere that you want to use, provide the full URL here." tos_url: "If you have a Terms of Service document hosted elsewhere that you want to use, provide the full URL here." privacy_policy_url: "If you have a Privacy Policy document hosted elsewhere that you want to use, provide the full URL here." - staff_like_weight: "How much extra weighting factor to give staff likes." 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." @@ -821,7 +820,6 @@ sq: display_name_on_posts: "Show a user's full name on their posts in addition to their @username." show_time_gap_days: "If two posts are made this many days apart, display the time gap in the topic." short_progress_text_threshold: "After the number of posts in a topic goes above this number, the progress bar will only show the current post number. If you change the progress bar's width, you may need to change this value." - default_code_lang: "Default programming language syntax highlighting applied to GitHub code blocks (lang-auto, ruby, python etc.)" 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." embed_truncate: "Truncate the embedded posts." diff --git a/config/locales/server.sr.yml b/config/locales/server.sr.yml index bed17653d2..e152c2a1b0 100644 --- a/config/locales/server.sr.yml +++ b/config/locales/server.sr.yml @@ -254,7 +254,6 @@ sr: full_name_required: "Puno ime je obavezno polje profila korisnika." display_name_on_posts: "Prikaži puno ime korisnika na njihovim porukama kao dodatak na @korisnicko_ime" show_time_gap_days: "Ako su dve poruke poslate sa ovoliko dana razmaka, prikaži vremensku razliku u temi." - default_code_lang: "Podrazumevani programski jezik za obeležavanje sintakse GitHub blokova koda (lang-auto, ruby, python itd.)" warn_reviving_old_topic_age: "Kada neko krene da odgovara na temu u kojoj je poslednji odgovor stariji od ovoliko dana, biće prikazano upozorenje. Onemogućite postavljanjem vrednosti 0. " autohighlight_all_code: "Forsiraj primenu označavanja koda za sve preformatirane blokove koda i kada nije eksplicitno naveden jezik." embed_username_required: "Korisničko ime je obavezno za kreiranje teme." diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index 24f11e8d89..92ec9ad593 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -8,7 +8,7 @@ sv: dates: short_date_no_year: "D MMM" short_date: "D MMM, YYYY" - long_date: "MMMM D, YYYY h:mma" + long_date: "D MMMM YYYY LT" datetime_formats: &datetime_formats formats: short: "%d-%m-%Y" @@ -49,7 +49,7 @@ sv: anonymous: "Anonym" remove_posts_deleted_by_author: "Borttaget av författaren" redirect_warning: "Vi kunde inte verifiera att länken som du valt faktiskt har lagts upp i forumet. Välj länken nedan om du vill fortsätta ändå." - on_another_topic: "Om annat ämne" + on_another_topic: "I ett annat ämne" themes: bad_color_scheme: "Kan inte uppdatera tema, ogiltig färgpalett" other_error: "Något blev fel när temat uppdaterades" @@ -187,10 +187,12 @@ sv: share_quote_facebook_requirements: "Du måste ange ett Facebook-app-id för att möjliggöra delning av citat på Facebook." second_factor_cannot_enforce_with_socials: "Du kan inte tvinga 2FA med sociala inloggningar aktiverade. Du måste först inaktivera inloggning via: %{auth_provider_names}" second_factor_cannot_be_enforced_with_disabled_local_login: "Du kan inte genomdriva 2FA om lokala inloggningar är inaktiverade." + second_factor_cannot_be_enforced_with_sso_enabled: "Du kan inte genomdriva 2FA om SSO har aktiverats." local_login_cannot_be_disabled_if_second_factor_enforced: "Du kan inte inaktivera lokal inloggning om 2FA har genomdrivits. Inaktivera genomdriven 2FA innan du inaktiverar lokala inloggningar." cannot_enable_s3_uploads_when_s3_enabled_globally: "Du kan inte aktivera S3-uppladdningar eftersom S3-uppladdningar redan är aktiverade globalt, och aktivering av denna webbplatsnivå kan orsaka kritiska problem med uppladdningar" conflicting_google_user_id: 'Google-konto-ID:et för det här kontot har ändrats och av säkerhetsskäl krävs personalens ingripande. Kontakta personalen och hänvisa dem till
https://meta.discourse.org/t/76575' invite: + expired: "Din inbjudningskod har löpt ut. Kontakta personalen." not_found: "Din inbjudningskod är ogiltig. Kontakta personalen." not_found_json: "Din inbjudningskod är ogiltig. Kontakta personalen." not_found_template: | @@ -203,6 +205,10 @@ sv: user_exists: "Det finns inget behov av att bjuda in %{email}: De har redan ett konto!" confirm_email: "

Du är nästan klar! Vi skickade ett aktiveringsmeddelande till din e-postadress. Följ instruktionerna i e-postmeddelandet för att aktivera ditt konto.

Om du inte ser det kan du kolla i din skräppostmapp.

" cant_invite_to_group: "Du är inte behörig att bjuda in användare till specificerade grupper. Kontrollera att du är ägare för de grupper som du försöker bjuda in till." + disabled_errors: + sso_enabled: "Inbjudningar är inaktiverade eftersom SSO är aktiverat." + local_logins_disabled: "Inbjudningar är inaktiverade eftersom inställningen 'enable local logins' (aktivera lokala inloggningar) är inaktiverad." + invalid_access: "Du har inte behörighet att visa den efterfrågade resursen." bulk_invite: file_should_be_csv: "De uppladdade filerna skall vara i csv-format." max_rows: "De första %{max_bulk_invites} inbjudningarna har skickats ut. Prova att dela upp filen i mindre delar." @@ -418,7 +424,7 @@ sv: - Konstruktiv kritik välkomnas, men kritisera då *idéer* och inte personer. - För mer information kan du läsa [riktlinjerna](%{base_path}/guidelines). Detta meddelande visas enbart vid ditt första %{education_posts_text}. + För mer information kan du läsa [forumets riktlinjer](%{base_path}/guidelines). Detta meddelande visas enbart vid ditt första %{education_posts_text}. avatar: | ### Hur vore det med en bild för ditt konto? @@ -553,7 +559,7 @@ sv: Det första stycket i detta fästa ämne kommer att visas som ett välkomstmeddelande för alla nya besökare på din hemsida. Det är viktigt! - **Ändra detta** till en kort beskrivning av din gemenskap: + **Ändra detta** till en kort beskrivning av ditt forum: -Vem är det till för? -Vad kan de hitta här? @@ -804,8 +810,8 @@ sv: email_body: "%{link}\n\n%{message}" inappropriate: title: "Olämpligt" - description: 'Detta inläggs innehåll omfattar saker som en förnuftig person skulle anse vara stötande, kränkande eller en överträdelse av våra riktlinjer.' - short_description: 'En överträdelse av våra riktlinjer' + description: 'Detta inläggs innehåll omfattar saker som en förnuftig person skulle anse vara stötande, kränkande eller en överträdelse av vårt forums riktlinjer.' + short_description: 'En överträdelse av vårt forums riktlinjer' long_form: "flagga detta som olämpligt" notify_user: title: "Skicka ett meddelande till @%{username}" @@ -876,9 +882,9 @@ sv: short_description: "Detta är en annons" inappropriate: title: "Olämpligt" - description: 'Detta inläggs innehåll omfattar saker som en förnuftig person skulle anse vara stötande, kränkande eller en överträdelse av våra riktlinjer.' + description: 'Detta inläggs innehåll omfattar saker som en förnuftig person skulle anse vara stötande, kränkande eller en överträdelse av vårt forums riktlinjer.' long_form: "flaggade detta som olämpligt" - short_description: 'En överträdelse av våra riktlinjer' + short_description: 'En överträdelse av vårt forums riktlinjer' notify_moderators: title: "Annat" description: 'Det här inlägget kräver generell uppmärksamhet från personalen baserat på riktlinjerna, användarvillkoren eller på grund av en annan anledning som inte finns med ovan.' @@ -1320,7 +1326,7 @@ sv: min_personal_message_post_length: "Lägsta antal tillåtna tecken i inlägg för meddelanden" max_post_length: "Högsta tillåtna längd på inlägg uttryckt i tecken" topic_featured_link_enabled: "Tillåt att lägga upp en länk med ämnen." - show_topic_featured_link_in_digest: "Visa ämnets visade länk i det sammanfattade e-postmeddelandet." + show_topic_featured_link_in_digest: "Visa den aktuella länken i det sammanfattade e-postmeddelandet." min_topic_title_length: "Lägsta tillåtna längd på ämnesrubrik uttryckt i tecken" max_topic_title_length: "Högsta tillåtna längd på ämnesrubrik uttryckt i tecken" min_personal_message_title_length: "Lägst tillåtna längd på rubrik för ett meddelande uttryckt i tecken" @@ -1412,7 +1418,7 @@ sv: hide_post_sensitivity: "Sannolikheten för att ett flaggat inlägg döljs" silence_new_user_sensitivity: "Sannolikheten för att en ny användare kommer att tystas baserat på skräppost-flaggor" auto_close_topic_sensitivity: "Sannolikheten för att ett flaggat ämne stängs automatiskt" - cooldown_minutes_after_hiding_posts: "Antal minuter en användare måste vänta innan de kan redigera ett inlägg som dolts på grund av flaggningar från användare" + cooldown_minutes_after_hiding_posts: "Antal minuter en användare måste vänta innan de kan redigera ett inlägg som dolts på grund av flaggningar från forumets användare" max_topics_in_first_day: "Högsta antal ämnen en användare får skapa under de första 24 timmarna efter att ha skapat sitt första inlägg" max_replies_in_first_day: "Högsta antal svar en användare får skapa under de första 24 timmarna efter att ha skapat sitt första inlägg" tl2_additional_likes_per_day_multiplier: "Höj gränsen för antal gillningar per dag för användare med förtroendenivå 2 (medlem) genom att multiplicera med det här numret" @@ -1464,7 +1470,7 @@ sv: site_contact_group_name: "Ett giltigt gruppnamn som ska bjudas in för alla automatiserade meddelanden." send_welcome_message: "Skicka ett välkomstmeddelande till alla nya användare tillsammans med en snabbstartsguide." send_tl1_welcome_message: "Skicka nya användare på förtroendenivå 1 ett välkomstmeddelande." - send_tl2_promotion_message: "Skicka ett meddelande om marknadsföring till nya Förtroendenivå 2-användare." + send_tl2_promotion_message: "Skicka ett meddelande om uppgradering till nya Förtroendenivå 2-användare." suppress_reply_directly_below: "Visa inte den utvidgade inläggsräknaren på ett inlägg när det bara finns ett svar direkt nedanför det här inlägget." suppress_reply_directly_above: "Visa inte den utvidgade som-svar-till på ett inlägg när det endast finns ett svar direkt ovanför det här inlägget." remove_full_quote: "Ta automatiskt bort fullständiga citattecken vid direkta svar." @@ -1502,6 +1508,7 @@ sv: password_unique_characters: "Minsta antalet unika tecken som ett lösenord måste ha." block_common_passwords: "Tillåt inte lösenord som är bland de 10 000 vanligaste lösenorden." external_auth_skip_create_confirm: Vid registrering genom extern auktorisering, hoppa över popup-fönster för att skapa konto. Används med fördel tillsammans med sso_overides_email, sso_overrides_username och sso_overrides_name. + external_auth_immediately: "Omdirigera automatiskt till det externa inloggningssystemet utan användarinteraktion. Detta träder endast i kraft när login_required är sant, och det bara finns en extern autentiseringsmetod" enable_sso: "Aktivera Single sign on via en extern sida (VARNING: ANVÄNDARENS E-POSTADRESS *MÅSTE* HA VALIDERATS AV DEN EXTERNA SIDAN!)" verbose_sso_logging: "Logga detaljerad SSO-relaterad diagnostik till /logs" enable_sso_provider: "Implementera Discourse SSO leverantörsprotokoll vid ändpunkten /session/sso_provider, kräver att sso_provider_secrets är inställd" @@ -1708,7 +1715,7 @@ sv: log_anonymizer_details: "Huruvida en användares detaljer ska lagras i loggen efter att ha anonymiserats. När du följer GDPR måste du stänga av detta." newuser_spam_host_threshold: "Hur många gånger en ny användare kan lägga upp en länk till samma värd inom deras `newuser_spam_host_threshold`-inlägg innan det anses vara skräppost." allowed_spam_host_domains: "En lista över alla domäner som undantas från skräpposttest. Nya användare kommer aldrig att begränsas från att skapa inlägg med länkar till dessa domäner." - staff_like_weight: "Hur stor extra viktfaktor att ge personalgillningar." + staff_like_weight: "Hur stor vikt som personalgillningar ska få (icke-personalgillningar har vikten 1.)" 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." @@ -1824,6 +1831,7 @@ sv: anonymous_posting_min_trust_level: "Lägsta förtroendenivå som krävs för att aktivera anonyma inlägg" anonymous_account_duration_minutes: "Skapa ett nytt anonymt konto var N:e minut för varje användare för att skydda anonymiteten. Exempel: om detta ställs in till 600, så skapas ett nytt anonymt konto så snart som 600 minuter har gått sedan senaste inlägget OCH användaren väljer anon." hide_user_profiles_from_public: "Inaktivera användarkort, användarprofiler och användarkataloger för anonyma användare." + allow_users_to_hide_profile: "Tillåt att användare döljer sin profil och närvaro" allow_featured_topic_on_user_profiles: "Tillåt att användarna visar en länk till ett ämne från sitt användarkort och profil." show_inactive_accounts: "Tillåt att inloggade användare bläddrar bland profiler av inaktiva konton." hide_suspension_reasons: "Visa inte avstängningsskäl offentligt i användarprofiler." @@ -1864,7 +1872,7 @@ sv: display_name_on_posts: "Visa en användares fullständiga namn på deras inlägg som tillägg till deras @användarnamn." show_time_gap_days: "Om två inlägg är gjorda med så här många dagar emellan, visa tidsskillnaden i ämnet." short_progress_text_threshold: "Efter att antalet inlägg i ett ämne överstiger den här siffran kommer förloppsmätaren endast att visa nuvarande antal inlägg. Om du ändrar förloppsmätarens bredd så kanske du behöver ändra det här värdet." - default_code_lang: "Standardspråk för syntaxmarkering som appliceras på kodblock från GitHub (lang-auto, ruby, python etc)." + default_code_lang: "Standardprogrameringsspråk för syntaxmarkering som appliceras på kodblock från GitHub (auto, nohighlight, ruby, python etc)." warn_reviving_old_topic_age: "En varning visas när någon börjar skriva ett svar på ett ämne och senaste inlägget är äldre än så här många dagar. Ange 0 för att inaktivera." autohighlight_all_code: "Tvångstillämpa syntaxmarkering på alla förformatterade kodblock även när de inte uttryckligt har specificerat språket." highlighted_languages: "Inkluderade syntax-markeringsregler (Varning: att inkludera för många språk kan påverka prestanda). Demonstration: https://highlightjs.org/static/demo" @@ -1972,7 +1980,8 @@ sv: shared_drafts_category: "Aktivera funktionen Delade utkast genom att ange en kategori för ämnesutkast. Ämnen i den här kategorin undantas från ämneslistor för personalanvändare." push_notifications_prompt: "Visa användarens samtycke genast." push_notifications_icon: "Utmärkelseikonen som visas i aviseringshörnet. En svartvit 96 x 96 stor PNG med genomskinlighet rekommenderas." - base_font: "Typsnitt att använda på de flesta platser för webbplatsen. Teman kan åsidosätta detta." + base_font: "Bastypsnitt att använda för de flesta texter på webbplatsen. Teman kan åsidosätta via CSS-anpassade egenskapen `--font-family`." + heading_font: "Typsnitt som används för rubriker på webbplatsen. Teman kan åsidosätta via CSS-anpassade egenskapen `--heading-font-family`." short_title: "Den korta titeln kommer att användas på användarens startskärm, startapparat eller andra platser där utrymmet kan vara begränsat. Den bör begränsas till 12 tecken." dashboard_hidden_reports: "Tillåt att de specificerade rapporterna döljs från översiktspanelen." dashboard_visible_tabs: "Välj vilka flikar på översiktspanelen som ska vara synliga." @@ -2404,7 +2413,7 @@ sv: ignored: "Tack för att du meddelade oss. Vi undersöker det." ignored_and_deleted: "Tack för att du meddelade oss. Vi har raderat inlägget." temporarily_closed_due_to_flags: - one: "Detta ämne är tillfälligt stängt i minst %{count} timme på grund av ett stort antal flaggor." + one: "Detta ämne är tillfälligt stängt i minst %{count} timme på grund av ett stort antal forumflaggor." other: "Detta ämne är tillfälligt stängt i minst %{count} timmar på grund av ett stort antal flaggor." system_messages: private_topic_title: "Ämne #%{id}" @@ -2425,7 +2434,7 @@ sv: Däremot, om meddelandet flaggas av medlemmarna och döljs en andra gång så kommer det fortsatt att döljas tills det har hanterats av personal. - För ytterligare information, se våra [gemensamma riktlinjer](%{base_url}/guidelines). + För ytterligare information, se våra [forumriktlinjer](%{base_url}/guidelines). post_hidden_again: title: "Inlägget dolt igen" subject_template: "Inlägget döljdes på grund av att det flaggats av forumets medlemmar, personal har aviserats" @@ -2449,7 +2458,7 @@ sv: Detta är ett automatiskt meddelande från %{site_name} för att informera dig om att [ditt inlägg](%{base_url}%{url}) återställdes.   - Detta inlägg flaggades av gemenskapen och personalen valde att återställa det. + Detta inlägg flaggades av forumet och personalen valde att återställa det. [details="Klicka för att expandera det återställda inlägget  "] @@ -2467,7 +2476,7 @@ sv: %{flag_reason} - Detta inlägg flaggades av gemenskapen och personalen valde att radera det. + Detta inlägg flaggades av forumet och personalen valde att radera det. [details="Klicka för att expandera raderade inlägget"] ``` markdown @@ -2475,12 +2484,12 @@ sv: ``` [/details] - Läs våra [gemensamma riktlinjer](%{base_url}/riktlinjer) för mer information. + Läs våra [forumriktlinjer](%{base_url}/riktlinjer) för mer information. usage_tips: text_body_template: | För att få ett par tips och förslag hur du kommer igång som ny användare, [gå till detta blogginlägg](https://blog.discourse.org/2016/12/discourse-new-user-tips-and-tricks/). - I takt med att du deltar här, lär vi känna dig, och därmed kommer tillfälliga begränsningar för nya medlemmar att tas bort. Med tiden når du [förtroendenivåer](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) som omfattar särskilda funktioner för att hantera vår gemenskap tillsammans. + I takt med att du deltar här, lär vi känna dig, och därmed kommer tillfälliga begränsningar för nya medlemmar att tas bort. Med tiden når du [förtroendenivåer](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) som omfattar särskilda funktioner för att hantera vårt forum tillsammans. welcome_user: title: "Välkommen Användare!" subject_template: "Välkommen till %{site_name}!" @@ -2489,7 +2498,7 @@ sv: %{new_user_tips} - Vi tror på [ett civiliserat beteende i gemenskapen](%{base_url}/guidelines) vid alla tillfällen. + Vi tror på [ett civiliserat beteende i forumet](%{base_url}/guidelines) vid alla tillfällen. Hoppas du kommer att trivas! welcome_tl1_user: @@ -2511,7 +2520,7 @@ sv: welcome_invite: title: "Välkomstinbjudan" subject_template: "Välkommen till %{site_name}!" - text_body_template: "Tack för att du tackade ja till din inbjudan till %{site_name} -- välkommen!\n\nVi har skapat ett nytt konto **%{username}** till dig. Du kan byta namn eller lösenord genom att besöka [din användarprofil][prefs].\n\n- När du loggar in ber vi dig **använda samma e-postadress som i din ursprungliga inbjudan** — annars kan vi inte bekräfta att det är du! \n\n%{new_user_tips}\n\nVi tror på ett [civiliserat beteende i gemenskapen](%{base_url}/guidelines) vid alla tillfällen.\n\nNjut av din vistelse!\n\n[prefs]: %{user_preferences_url}\n" + text_body_template: "Tack för att du tackade ja till din inbjudan till %{site_name} -- välkommen!\n\nVi har skapat ett nytt konto **%{username}** till dig. Du kan byta namn eller lösenord genom att besöka [din användarprofil][prefs].\n\n- När du loggar in ber vi dig **använda samma e-postadress som i din ursprungliga inbjudan** — annars kan vi inte bekräfta att det är du! \n\n%{new_user_tips}\n\nVi tror på ett [civiliserat beteende i forumet](%{base_url}/guidelines) vid alla tillfällen.\n\nNjut av din vistelse!\n\n[prefs]: %{user_preferences_url}\n" tl2_promotion_message: subject_template: "Grattis till din högre förtroendenivå!" text_body_template: | @@ -2797,19 +2806,19 @@ sv: ignored_users_summary: title: "Ignorerad användare passerade gränsvärde" subject_template: "En användare ignoreras av många andra användare" - text_body_template: "Hej! \n\nDet här är ett automatiskt meddelande från %{site_name} för att informera dig om att @%{username} har ignorerats av %{ignores_threshold}. Detta kan indikera att ett problem håller på att uppstå i din gemenskap. \n\nDu kanske vill [granska senaste inlägg](%{base_url}/u/%{username}/summary) för denna användare, och eventuellt andra användare i [rapporten över ignorerade och tystade](%{base_url}/admin/reports/top_ignored_users).\n\nFör ytterligare vägledning, se våra [gemenskapsriktlinjer](%{base_url}/guidelines).\n" + text_body_template: "Hej! \n\nDet här är ett automatiskt meddelande från %{site_name} för att informera dig om att @%{username} har ignorerats av %{ignores_threshold}. Detta kan indikera att ett problem håller på att uppstå i ditt forum. \n\nDu kanske vill [granska senaste inlägg](%{base_url}/u/%{username}/summary) för denna användare, och eventuellt andra användare i [rapporten över ignorerade och tystade](%{base_url}/admin/reports/top_ignored_users).\n\nFör ytterligare vägledning, se våra [forumriktlinjer](%{base_url}/guidelines).\n" too_many_spam_flags: title: "För många skräppostsflaggningar" subject_template: "Nytt konto pausat" - text_body_template: "Hej!\n\nDet här är ett automatiskt meddelande från %{site_name} för att informera dig om att dina inlägg har dolts tillfälligt på grund av att de flaggades av medlemmar. \n\nSom en förebyggande åtgärd har ditt nya konto tystats och kommer inte kunna skapa svar eller ämnen tills det har granskats av personal. Vi ber om ursäkt för besväret. \n\nFör ytterligare information, se våra [riktlinjer](%{base_url}/guidelines).\n" + text_body_template: "Hej!\n\nDet här är ett automatiskt meddelande från %{site_name} för att informera dig om att dina inlägg har dolts tillfälligt på grund av att de flaggades av medlemmar. \n\nSom en förebyggande åtgärd har ditt nya konto tystats och kommer inte kunna skapa svar eller ämnen tills det har granskats av personal. Vi ber om ursäkt för besväret. \n\nFör ytterligare information, se våra [forumriktlinjer](%{base_url}/guidelines).\n" too_many_tl3_flags: title: "För många FN3-flaggningar" subject_template: "Nytt konto pausat" - text_body_template: "Hej! \n\nDet här är ett automatiskt meddelande från %{site_name} för att informera dig om att dina inlägg har pausats tillfälligt på grund av att de flaggades av ett stort antal medlemmar. \n\nSom en förebyggande åtgärd har ditt nya konto tystats från att skapa nya inlägg eller ämnen tills det har granskats av personal. Vi ber om ursäkt för besväret. \n\nFör ytterligare information, se våra [riktlinjer](%{base_url}/guidelines).\n" + text_body_template: "Hej! \n\nDet här är ett automatiskt meddelande från %{site_name} för att informera dig om att dina inlägg har pausats tillfälligt på grund av att de flaggades av ett stort antal medlemmar. \n\nSom en förebyggande åtgärd har ditt nya konto tystats från att skapa nya inlägg eller ämnen tills det har granskats av personal. Vi ber om ursäkt för besväret. \n\nFör ytterligare information, se våra [forumriktlinjer](%{base_url}/guidelines).\n" silenced_by_staff: title: "Tystat av personal" subject_template: "Konto tillfälligt pausat" - text_body_template: "Hej!\n\nDet här är ett automatiskt meddelande från %{site_name} för att informera dig om att ditt konto har pausats tillfälligt som en försiktighetsåtgärd. \n\nDet går bra att fortsätta besöka sidan men du kommer inte att kunna skriva inlägg eller skapa nya ämnen förrän [personalen](%{base_url}/about) granskar dina senaste inlägg. Vi ber om ursäkt för besväret. \n\nFör mer information, se våra [riktlinjer](%{base_url}/guidelines).\n" + text_body_template: "Hej!\n\nDet här är ett automatiskt meddelande från %{site_name} för att informera dig om att ditt konto har pausats tillfälligt som en försiktighetsåtgärd. \n\nDet går bra att fortsätta besöka sidan men du kommer inte att kunna skriva inlägg eller skapa nya ämnen förrän [personalen](%{base_url}/about) granskar dina senaste inlägg. Vi ber om ursäkt för besväret. \n\nFör mer information, se våra [forumriktlinjer](%{base_url}/guidelines).\n" user_automatically_silenced: title: "Användare automatiskt tystad" subject_template: "Ny användare %{username} tystad på grund av flaggningar från forumet" @@ -2856,7 +2865,7 @@ sv: Denna utmärkelse beviljas endast till två nya användare per månad och den kommer att visas permanent på [sidan för utmärkelser] (%{url}). - Du har snabbt blivit en värdefull medlem i vår gemenskap. Tack för att du gick med och fortsätt med det fantastiska arbetet! + Du har snabbt blivit en värdefull medlem i vårt forum. Tack för att du gick med och fortsätt med det fantastiska arbetet! queued_posts_reminder: title: "Påminnelse om köande inlägg" subject_template: @@ -3128,9 +3137,20 @@ sv: title: "Bekräfta den nya e-postadressen" subject_template: "[%{email_prefix}] Bekräfta din nya e-postadress" text_body_template: | - Bekräfta din nya e-postadress för %{site_name} genom att klicka på länken nedanför: + Bekräfta din nya e-postadress för %{site_name} genom att klicka på följande länk: %{base_url}/u/confirm-new-email/%{email_token} + + Denna e-poständring begärdes av en webbplatsadministratör. Om du inte har begärt denna ändring, vänligen kontakta en administratör för %{site_name}. + confirm_new_email_via_admin: + title: "Bekräfta den nya e-postadressen" + subject_template: "[%{email_prefix}] Bekräfta din nya e-postadress" + text_body_template: | + Bekräfta din nya e-postadress för %{site_name} genom att klicka på följande länk: + + %{base_url}/u/confirm-new-email/%{email_token} + + Denna e-poständring begärdes av en webbplatsadministratör. Om du inte har begärt denna ändring, vänligen kontakta en administratör för %{site_name}. confirm_old_email: title: "Bekräfta den gamla e-postadressen" subject_template: "[%{email_prefix}] Bekräfta din nuvarande e-postadress" @@ -3176,7 +3196,7 @@ sv: signup_after_approval: title: "Ny medlem efter godkännande" subject_template: "Du har blivit godkänd på %{site_name}!" - text_body_template: "Välkommen till %{site_name}! \n\nEn administratör har godkänt ditt konto hos %{site_name}.\n\nDu har nu tillgång till ditt nya konto genom att logga in från:\n%{base_url}\n\nOm länken ovan inte går att klicka på kan du kopiera och klistra in länken i adressfältet i din webbläsare.\n\n%{new_user_tips}\n\nVi tror på ett [civiliserat gemenskapsbeteende](%{base_url}/guidelines) vid alla tillfällen.\n\nNjut av din vistelse!\n" + text_body_template: "Välkommen till %{site_name}! \n\nEn administratör har godkänt ditt konto hos %{site_name}.\n\nDu har nu tillgång till ditt nya konto genom att logga in från:\n%{base_url}\n\nOm länken ovan inte går att klicka på kan du kopiera och klistra in länken i adressfältet i din webbläsare.\n\n%{new_user_tips}\n\nVi tror på ett [civiliserat beteende i forumet](%{base_url}/guidelines) vid alla tillfällen.\n\nNjut av din vistelse!\n" signup: title: "Bli medlem" subject_template: "%{email_prefix} Bekräfta ditt nya konto" @@ -3389,7 +3409,7 @@ sv: ## [Drivs av dig](#power) - Denna webbplats drivs av din [vänliga lokala personal](%{base_path}/about) och _dig_, som tillsammans utgör gemenskapen. Om du har ytterligare frågor om hur saker och ting ska fungera här, öppna ett nytt ämne i [kategorin för feedback](%{base_path}/c/site-feedback) och låt oss diskutera! Om det finns en kritisk eller brådskande fråga som inte kan hanteras av en flaggning eller ämnestaggning, kontakta oss via [personalsidan](%{base_path}/about). + Denna webbplats drivs av din [vänliga lokala personal](%{base_path}/about) och _dig_, som tillsammans utgör forumet. Om du har ytterligare frågor om hur saker och ting ska fungera här, öppna ett nytt ämne i [kategorin för feedback](%{base_path}/c/site-feedback) och låt oss diskutera! Om det finns en kritisk eller brådskande fråga som inte kan hanteras av en flaggning eller ämnestaggning, kontakta oss via [personalsidan](%{base_path}/about). @@ -3596,7 +3616,7 @@ sv: name: Förespråkare description: Bjöd in 3 grundläggande användare long_description: | - Den här utmärkelsen beviljas när du bjudit in 3 personer som sedan tillbringade tillräckligt mycket tid på webbplatsen för att bli grundläggande användare. Ett levande forum behöver ständig tillförsel av nykomlingar som regelbundet deltar och förnyar konversationerna. + Den här utmärkelsen beviljas när du bjudit in 3 personer som sedan tillbringade tillräckligt mycket tid på webbplatsen för att bli grundläggande användare. Ett levande forum behöver ständig tillförsel av nykomlingar som regelbundet deltar och förnyar samtalen. champion: name: Förkämpe description: Bjöd in 5 medlemmar @@ -3646,22 +3666,22 @@ sv: name: Uppskattad description: Fick 1 gillning på 20 inlägg long_description: | - Den här utmärkelsen beviljas när du får minst en gillning på 20 olika inlägg. Medlemmarna uppskattar dina bidrag till samtalen här! + Den här utmärkelsen beviljas när du får minst en gillning på 20 olika inlägg. Forumet uppskattar dina bidrag till samtalen här! respected: name: Respekterad description: Fick 2 gillningar på 100 inlägg long_description: | - Den här utmärkelsen beviljas när du får minst 2 gillningar på 100 olika inlägg. Medlemmarnas respekt för dig ökar med dina många bidrag till samtalen här. + Den här utmärkelsen beviljas när du får minst 2 gillningar på 100 olika inlägg. Forumets respekt för dig ökar med dina många bidrag till samtalen här. admired: name: Beundrad description: Fick 5 gillningar på 300 inlägg long_description: | - Den här utmärkelsen beviljas när du får minst 5 gillningar på 300 olika inlägg. Wow! Medlemmarna beundrar dina frekventa, högkvalitativa bidrag till samtalen här. + Den här utmärkelsen beviljas när du får minst 5 gillningar på 300 olika inlägg. Wow! Forumet beundrar dina frekventa, högkvalitativa bidrag till samtalen här. out_of_love: name: Av kärlek description: Använde %{max_likes_per_day} gillningar på en dag long_description: | - Den här utmärkelsen beviljas när du använder alla dina %{max_likes_per_day} gillningar under en dag. Att komma ihåg att ta ett ögonblick och gilla inläggen som du tycker om och uppskattar uppmuntrar medlemmarna på forumet till att skapa ännu fler intressanta diskussioner i framtiden. + Den här utmärkelsen beviljas när du använder alla dina %{max_likes_per_day} gillningar under en dag. Att komma ihåg att ta ett ögonblick och gilla inläggen som du tycker om och uppskattar uppmuntrar forumets medlemmar till att skapa ännu fler intressanta diskussioner i framtiden. higher_love: name: Mer kärlek description: Använde %{max_likes_per_day} gillningar på en dag 5 gånger @@ -3671,7 +3691,7 @@ sv: name: Galet kär description: Använde %{max_likes_per_day} gillningar på en dag 20 gånger long_description: | - Den här utmärkelsen beviljas när du använder alla dina %{max_likes_per_day} gillningar varje dag under 20 dagar. Wow! Du är en förebild som regelbundet uppmuntrar medlemmarna på forumet! + Den här utmärkelsen beviljas när du använder alla dina %{max_likes_per_day} gillningar varje dag under 20 dagar. Wow! Du är en förebild som regelbundet uppmuntrar forumets medlemmar! thank_you: name: Tack description: Har 20 gillade inlägg och gav 10 gillningar @@ -3790,10 +3810,10 @@ sv: title: "Välkommen till din Discourse!" fields: default_locale: - description: "Vilket är standardspråket för din gemenskap?" + description: "Vilket är standardspråket för ditt forum?" forum_title: title: "Namn" - description: "Ditt namn är en skylt som syns på avstånd, det första som potentiella besökare kommer att notera om ditt forum. Vad säger ditt namn och titel om ditt forum?" + description: "Ditt namn är en skylt som syns på avstånd, det första som potentiella besökare kommer att notera om ditt forum. Vad säger ditt namn och din titel om forumet?" fields: title: label: "Ditt forums namn" @@ -3862,16 +3882,15 @@ sv: placeholder: "San Francisco, Kalifornien" colors: title: "Tema" - themes_further_reading: - title: "Teman" - description: - - - - 'Populära temakomponenter (för fler, bläddra bland #theme) "' fonts: title: "Typsnitt" + fields: + body_font: + label: "Typsnitt för brödtext" + heading_font: + label: "Typsnitt för rubrik" + font_preview: + label: "Förhandsgranska" logos: title: "Logotyper" fields: @@ -3920,8 +3939,9 @@ sv: finished: title: "Din Discourse är redo!" description: | -

Om du någonsin känner för att ändra dessa inställningar, kör du bara denna guide igen, eller går till din administratörsavdelning; du hittar den intill skiftnyckeln i webbplatsens meny.

-

Ha roligt, och lycka till med att bygga ditt nya forum!

+

Om du någonsin känner för att ändra dessa inställningar, kör den här guiden igen när som helst, eller besök din admin sektion; hitta den bredvid skiftnyckel-ikonen i webbplatsmenyn.

+

Det är lätt att anpassa Discourse ytterligare med hjälp av vårt kraftfulla temasystem. För exempel, kolla in toppteman och komponentermeta.discourse.org.

+

Ha så kul och lycka till med att bygga din nya gemenskap!

search_logs: graph_title: "Sökningar" joined: "Gick med" diff --git a/config/locales/server.sw.yml b/config/locales/server.sw.yml index 5a4d02b559..296095e364 100644 --- a/config/locales/server.sw.yml +++ b/config/locales/server.sw.yml @@ -2160,8 +2160,6 @@ sw: title: "Shirika" colors: title: "Mandhari" - themes_further_reading: - title: "Mandhari" logos: title: "Nembo" fields: diff --git a/config/locales/server.te.yml b/config/locales/server.te.yml index 599561b3ad..f1e40d80ab 100644 --- a/config/locales/server.te.yml +++ b/config/locales/server.te.yml @@ -749,8 +749,6 @@ te: title: "సంస్థ" colors: title: "అలంకారం" - themes_further_reading: - title: "అలంకారాలు" homepage: fields: homepage_style: diff --git a/config/locales/server.th.yml b/config/locales/server.th.yml index c1c7890daf..e1b662885b 100644 --- a/config/locales/server.th.yml +++ b/config/locales/server.th.yml @@ -406,8 +406,6 @@ th: title: "องค์กร" colors: title: "ธีม" - themes_further_reading: - title: "ธีม" homepage: fields: homepage_style: diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index 39e61554f4..c75761147d 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -394,14 +394,6 @@ 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" - get_a_room: | - ### Herkesi sohbete katılmaya teşvik edin - - Bu konu hakkında %{reply_username}i %{count}kere cevapladın! - - İyi bir tartışma birçok ses ve perspektifi içerir. Başka birini dahil edebilir misin? - - Şunu unutma ki, eğer bu özel kullanıcı ile daha kişisel bir konuşma gerçekleştirmek istiyorsan[ona özel mesaj gönder](%{base_path}/u/%{reply_username}) too_many_replies: | ### Bu konu için cevap limitinizi doldurdunuz @@ -1620,7 +1612,6 @@ tr_TR: privacy_policy_url: "Başka yerde barındırılan bir Gizlilik İlkeleri dökümanını kullanmak istiyorsanız, ilgili URL adresini buraya eksiksiz şekilde girin." log_anonymizer_details: "Anonimleştirildikten sonra kullanıcının ayrıntılarını günlükte tutup tutmayacağı. GDPR'ye uyarken bunu kapatmanız gerekir." newuser_spam_host_threshold: "Yeni bir kullanıcı, `newuser_spam_host_threshold` gönderileri içerisinde aynı makineye, kaç kere bağlantı paylaşınca spam olarak algılansın." - staff_like_weight: "Görevlilerin beğenilere verilecek ekstra ağırlık faktörü." 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ığı." @@ -1758,7 +1749,6 @@ tr_TR: display_name_on_posts: "Gönderilerde @kullanıcıadı'na ek olarak kullanıcının tam adını da göster." show_time_gap_days: "İki gönderinin tarihleri arasında bu kadar gün farkı varsa, konuda zaman aralığına yer ver." short_progress_text_threshold: "Bir konudaki gönderi sayısı bu sayının üzerine çıktığında, ilerleme çubuğunda sadece şu anki gönderi sayısını göster. İlerleme çubuğunun kalınlığını değiştirirseniz, bu değeri de değiştirmeniz gerekebilir." - default_code_lang: "GitHub kod parçalarına (lang-auto, ruby, python vs.) uygulanacak olan öntanımlı programlama dili söz dizimi vurgulaması." warn_reviving_old_topic_age: "Herhangi bir kullanıcı, son cevabın burada belirtilen gün sayısından daha önce yazıldığı bir konuya cevap yazmaya başladığında, bir uyarı iletisi çıkacak. Bu özelliği devre dışı bırakmak için 0 girin. " autohighlight_all_code: "Tüm önceden biçimlendirilen kod parçalarına, açıkça dil seçimi yapılmamış olsa da, zorla kod vurgulaması uygula." highlighted_languages: "Sözdizimi vurgulama kuralları dahil. (Uyarı: çok fazla dil dahil etmek performansı etkileyebilir) bkz: demo için https://highlightjs.org/static/demo" @@ -3026,10 +3016,6 @@ tr_TR: confirm_new_email: title: "Yeni E-postayı Onayla" subject_template: "[%{email_prefix}] Yeni e-posta adresinizi onaylayın" - text_body_template: | - Aşağıdaki bağlantıya tıklayarak %{site_name} için yeni e-posta adresinizi doğrulayınız: - - %{base_url}/u/confirm-new-email/%{email_token} confirm_old_email: title: "Eski E-postayı Onayla" subject_template: "[%{email_prefix}] Mevcut e-posta adresinizi onaylayın" @@ -3537,8 +3523,6 @@ tr_TR: placeholder: "San Francisco, Kaliforniya" colors: title: "Tema" - themes_further_reading: - title: "Temalar" logos: title: "Görseller" fields: @@ -3586,8 +3570,6 @@ tr_TR: disabled: "Yerel girişler devre dışı bırakıldığından, kimseye davet göndermek mümkün değildir. Lütfen bir sonraki adıma geçin." finished: title: "Discourse'unuz hazır! " - description: | -

Bu ayarları değiştirmek isterseniz, bu sihirbazı istediğiniz zaman yeniden çalıştırın veya yönetici bölümünüzü ziyaret edin ; site menüsündeki İngiliz anahtarı simgesinin yanında bulabilirsiniz.

Yeni topluluğunuzu oluşturmak için iyi eğlenceler !

search_logs: graph_title: "Arama Sayısı" joined: "Katıldı" diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml index fb5856ff82..1aae0eec07 100644 --- a/config/locales/server.uk.yml +++ b/config/locales/server.uk.yml @@ -117,6 +117,7 @@ uk: unsubscribe_not_allowed: "Буває, коли відписка через електронну пошту не дозволена для цього користувача." email_not_allowed: "Буває, коли адреса електронної пошти відсутня в білому або чорному списку." unrecognized_error: "Невідома помилка" + secure_media_placeholder: "На цьому сайті увімкнено захищений доступ до мультимедійного контенту. Щоб переглянути файли, авторизуйтесь на сайті та увійшовши в цю тему натисніть \"Переглянути вкладення\"." view_redacted_media: "Перегляд вкладень" errors: &errors format: ! "%{attribute} %{message}" @@ -200,6 +201,7 @@ uk: cannot_enable_s3_uploads_when_s3_enabled_globally: "Ви не можете ввімкнути завантаження S3, оскільки завантаження S3 вже увімкнено, і включення цього рівня на сайті може спричинити критичні проблеми з завантаженнями" conflicting_google_user_id: 'Ідентифікатор облікового запису Google для цього облікового запису змінився; втручання персоналу потрібно з міркувань безпеки. Будь ласка, зв''яжіться з персоналом і вкажіть
https://meta.discourse.org/t/76575' invite: + expired: "Термін дії вашого коду запрошення минув. Будь ласка, зв'яжіться з персоналом." not_found: "Невірний код запрошення. Будь ласка, зв'яжіться з персоналом." not_found_json: "Ваш маркер запрошення недійсний. Зверніться до персоналу." not_found_template: | @@ -212,6 +214,10 @@ uk: user_exists: "Немає необхідності надсилати запрошення %{email}, такий акаунт вже існує!" confirm_email: "

Ви майже закінчили! Ми відправили лист активації на Вашу електронну адресу e-mail пошти. Будь ласка, дотримуйтесь інструкцій в листі, щоб активувати свій акаунт.

Якщо лист не приходить, перевірте папку зі спамом.

" cant_invite_to_group: "Ви не можете запрошувати користувачів до зазначеної групи. Переконайтеся, що ви власник групи, в яку ви намагаєтеся запросити." + disabled_errors: + sso_enabled: "Запрошення вимкнено, оскільки SSO увімкнено." + local_logins_disabled: "Запрошення вимкнено, оскільки параметр \"увімкнути локальні входи\" вимкнено." + invalid_access: "Вам не дозволено переглядати запитаний ресурс." bulk_invite: file_should_be_csv: "Завантажений файл повинен мати формат CSV." max_rows: "Спочатку буде надіслано тільки %{max_bulk_invites} запрошення. Спробуйте розділити файл на менші частини." @@ -484,10 +490,10 @@ uk: get_a_room: | ### Покличте всіх взяти участь в розмові - необхідно відповісти %{count} раз користувачеві @%{reply_username} в цій конкретній темі! + Вам необхідно відповісти %{count} раз користувачу @%{reply_username} в цій конкретній темі! Важлива дискусія включає в себе багато голосувань і точок зору. Чи можете ви залучити кого-небудь ще? - Не забувайте, якщо Ви хочете продовжити приватна розмова з конкретним учасником то, [send them a personal message](%{base_path}/u/%{reply_username}). + Не забувайте, якщо Ви хочете продовжити приватну розмову з конкретним учасником то надішліть йому [приватне повідомлення](%{base_path}/u/%{reply_username}). too_many_replies: "### Ви досягли межі відповідей в цю тему. \nНа жаль, нові користувачі тимчасово обмежені %{newuser_max_replies_per_topic} відповідей в цій темі. \nЗамість того, щоб додавати ще одну відповідь, будь ласка, подумайте про редагування попередніх відповідей або відвідайте інші теми.\n" reviving_old_topic: | ### Відновити обговорення в цій темі? @@ -1488,6 +1494,9 @@ uk: logo_small: "Невелике зображення логотипу в лівому верхньому кутку вашого сайту, видно при прокручуванні вниз. Використовуйте квадратне зображення 120×120. Якщо залишити порожнім, буде показаний домашній гліф." digest_logo: "Альтернативне зображення логотипу, що використовується у верхній частині e-mail пошти вашого сайту. Використовуйте зображення широкого прямокутника. Не використовуйте SVG-образ. Якщо залишити порожнім, буде використовуватися зображення з налаштування `логотип`." mobile_logo: "Логотип використовується на мобільній версії вашого сайту. Використовуйте широке прямокутне зображення з висотою 120 і співвідношенням сторін більше 3:1. Якщо залишити порожнім, буде використано зображення з налаштування `логотип`." + logo_dark: "Темна альтернатива \"логотипу\" сайту." + logo_small_dark: "Темна альтернатива \"малого логотипу\" сайту." + mobile_logo_dark: "Темна альтернатива \"мобільного логотипу\" сайту." large_icon: "Зображення використовується в якості основи для інших значків метаданих. В ідеалі має бути більше 512 х 512. Якщо залишити поле порожнім, буде використовуватися logo_small." manifest_icon: "Зображення використовується в якості логотипу / заставки на Android. Буде автоматично змінено до 512 × 512. Якщо залишити порожнім, буде використовуватися large_icon." favicon: "Фавікон для вашого сайта, див. https://en.wikipedia.org/wiki/Favicon. Для правильної роботи із CDN він повинен бути png. Буде змінений до 32x32. Якщо залишити порожнім, буде використовуватися large_icon." @@ -1499,9 +1508,14 @@ uk: email_subject: "Налаштовуваний формат теми для стандартних листів. Дивитися: https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" 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: "Максимальна кількість повідомлень, що показуються в 'Зведенні по темі'" + enable_personal_messages: "Дозволити користувачам рівня довіри 1 (який можна налаштувати через мінімальний рівень довіри для надсилання повідомлень) створювати повідомлення та відповідати на повідомлення. Зверніть увагу, що співробітники завжди можуть надсилати повідомлення, незважаючи ні на що." enable_system_message_replies: "Дозволяє користувачам відповідати на системні повідомлення, навіть якщо приватні повідомлення відключені" enable_long_polling: "Використовувати механізм long polling для повідомлень про події" long_polling_base_url: "Базовий URL, використовуваний для long polling (при використанні CDN для роздачі динамічного контенту встановіть в цьому параметрі адресу origin pull), наприклад: http://origin.site.com" @@ -1530,15 +1544,31 @@ uk: markdown_typographer_quotation_marks: "Список подвійних та одиничних лапок змінювати парами" post_undo_action_window_mins: "Кількість хвилин, за які користувачі зможуть скасувати останні дії в публікації (наприклад, вподобайки, скарги тощо)" must_approve_users: "Персонал повинен схвалити всі нові облікові записи користувачів, перш ніж їм буде надано доступ до сайту" + invite_code: "Користувач повинен ввести цей код для дозволу реєстрації облікового запису, ігнорується, коли пусто (нечутлива до регістру)" + approve_suspect_users: "Додавати підозрілих користувачів до черги огляду. Підозрілі користувачі можуть входити до профілю, але не можуть читати повідомлення." pending_users_reminder_delay: "Повідомляти модераторів, якщо нові користувачі чекають схвалення довше ніж стільки годин. Встановіть -1 для відключення цих повідомлень." + persistent_sessions: "Користувачі залишатимуться авторизовані при закритті веб-браузера" maximum_session_age: "Користувач залишиться в системі протягом n годин з моменту останнього відвідування" + ga_universal_tracking_code: "Google Universal Analytics (analytics.js) tracking code ID, eg: UA-12345678-9; see https://google.com/analytics" + ga_universal_domain_name: "Google Universal Analytics (analytics.js) domain name, eg: mysite.com; see https://google.com/analytics" + ga_universal_auto_link_domains: "Увімкніть міждоменне відстеження Google Universal Analytics (analytics.js). До вихідних посилань на ці домени буде додано ідентифікатор клієнта. Див. Посібник з міждоменного відстеження Google." + gtm_container_id: "Контейнер Google Tag Manager. Наприклад: GTM-ABCDEF.
Примітка: Сторонні сценарії, завантажені GTM повинні бути дозволені в 'content security policy script src'." + enable_escaped_fragments: "Поверніться до API Ajax-Crawling від Google, якщо веб-сканер не виявлено. Див. https://developers.google.com/webmasters/ajax-crawling/docs/learn-more" + moderators_manage_categories_and_groups: "Дозволити модераторам керувати категоріями та групами" cors_origins: "Дозволити origins для запитів CORS. Кожне origin повинне містити http:// або https://. Змінна DISCOURSE_ENABLE_CORS env повинна бути true, щоб увімкнути CORS." + use_admin_ip_allowlist: "Адміністратори можуть увійти в систему тільки в тому випадку, якщо їх IP-адреса вказана в білому списку (Адмінка > Логи > IP-адреси)." + blocked_ip_blocks: "Список приватних діапазонів IP-адрес, які ніколи не повинні скануватися Discurse" + allowed_internal_hosts: "Список внутрішніх хостів, які дискурс може безпечно сканувати для oneboxing та інших цілей" + allowed_onebox_iframes: "Список вбудованих у фрейми доменів, контент з яких буде перетворюватися в розумну вставку. `*` дозволить всі домени для Onebox за замовчуванням." allowed_iframes: "Список префіксів домену iframe src, які discourse може безпечно дозволити в повідомленнях" + allowed_crawler_user_agents: "Користувацькі агенти веб-сканерів, яким слід дозволити доступ до сайту. ПОПЕРЕДЖЕННЯ! ЦЕ ЗАБОРОНИТЬ ДОСТУП ВСІМ ВЕБ-СКАНЕРАМ, ЯКІ НЕ ВКАЗАНІ ТУТ!" + blocked_crawler_user_agents: "Унікальне слово, не чутливе до регістру, у рядку агента користувача, що ідентифікує веб-сканери, яким не слід дозволяти доступ до сайту. Не застосовується, якщо визначено білий список." slow_down_crawler_user_agents: "Користувацькі агенти веб-сканерів, які повинні бути обмежені в robots.txt, використовуючи директиву Crawl-delay" slow_down_crawler_rate: "Якщо вказано slow_down_crawler_user_agents, ця пауза застосовуватиметься до всіх сканерів (затримка між запитами кількість секунд)" content_security_policy: "Увімкнути політику безпеки вмісту" content_security_policy_report_only: "Увімкнути тільки звіт про політику безпеки вмісту" content_security_policy_collect_reports: "Увімкнути збір звітів про порушення CSP на /csp_reports" + 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" post_menu: "Визначає, які елементи з'являються в меню допису, та в якому порядку. Наприклад, like|edit|flag|delete|share|bookmark|reply" @@ -1548,6 +1578,7 @@ uk: site_contact_group_name: "Допустима назва групи для запрошення всіх автоматичним повідомленням." send_welcome_message: "Відправляти всім новим користувачам вітальне повідомлення з короткою інструкцією про можливості форуму." send_tl1_welcome_message: "Відправляти всім новим користувачам з рівнем довіри 1 вітальне повідомлення." + send_tl2_promotion_message: "Надсилайте новим користувачам рівня довіри 2 повідомлення про підвищення." suppress_reply_directly_below: "Не показувати розгортання кількості відповідей на повідомлення, якщо є лише одна відповідь безпосередньо нижче." suppress_reply_directly_above: "Не показувати розгортаємий блок \"у відповідь на\" для повідомлення, якщо є всього лише одне повідомлення безпосередньо вище." remove_full_quote: "Автоматично видаляти повні цитати в прямих відповідях." @@ -1560,9 +1591,14 @@ uk: moderators_view_emails: "Дозволити модераторам переглядати повідомлення e-mails користувачів" prioritize_username_in_ux: "Показуйте спершу псевдонім на сторінці користувача, картці користувача та публікаціях (коли вимкнене ім’я показувати першим)" enable_rich_text_paste: "Увімкнути автоматичне перетворення HTML в Markdown при вставці тексту в композитор. (Експериментальний)" + send_old_credential_reminder_days: "Нагадати про старі облікові дані через вказану кількість днів" email_token_valid_hours: "Посилання на відновлення пароля / активацію облікового запису буде діяти протягом (n) годин." enable_badges: "Увімкнути систему нагород" enable_whispers: "Дозволити персоналу особисте спілкування в рамках теми." + allow_index_in_robots_txt: "Вкажіть в robots.txt, що цей сайт дозволено індексувати веб-пошуковими системами. У виняткових випадках ви можете назавжди перевизначити robots.txt." + blocked_email_domains: "Список поштових доменів, розділений знаком |, з яких заборонена реєстрація облікових записів. Наприклад: mailinator.com|trashmail.net" + allowed_email_domains: "Список доменів електронної пошти, розділених |, з яких користувачі повинні зареєструвати облікові записи. УВАГА: Користувачі з доменами електронної пошти, відмінними від перелічених, не будуть дозволені!" + auto_approve_email_domains: "Користувачі з адресами електронної пошти з цього списку доменів будуть автоматично схвалені." hide_email_address_taken: "Не повідомляти користувачам, що обліковий запис існує з даною адресою E-mail пошти під час реєстрації та з форми відновлення пароля." log_out_strict: "Коли користувач виходить з системи, закривати всі сесії на всіх пристроях користувача." version_checks: "Перевірити оновлення на Discourse Hub для отримання повідомлень про нові версії та відображення їх в /admin панелі" @@ -1573,24 +1609,40 @@ uk: min_username_length: "Мінімальна довжина імені користувача у символах. ПОПЕРЕДЖЕННЯ: якщо будь-які існуючі користувачі або групи мають імена, коротші за це, ваш САЙТ ЗЛАМАЄТЬСЯ!" max_username_length: "Максимальна довжина імені користувача у символах. ПОПЕРЕДЖЕННЯ: якщо будь-які існуючі користувачі або групи мають імена, довші за це, ваш САЙТ ЗЛАМАЄТЬСЯ!" unicode_usernames: "Дозволити іменам користувачів та іменам груп містити літери та цифри Unicode." + allowed_unicode_username_characters: "Регулярний вираз, щоб дозволити лише деякі символи Unicode в іменах користувачів. Букви та цифри ASCII завжди будуть дозволені, і їх не потрібно включати в список дозволів." reserved_usernames: "Імена користувачів, для яких реєстрація заборонена. Символ підстановки * може використовуватися для узгодження будь-якого символу з нулем або більше разів." min_password_length: "Мінімальна довжина пароля." min_admin_password_length: "Мінімальна довжина пароля для адміністратора." password_unique_characters: "Мінімальна довжина пароля для адміністратора." block_common_passwords: "Не дозволяти використовувати паролі зі списку 10 000 самих часто використовуваних паролів." + external_auth_skip_create_confirm: Під час реєстрації через зовнішню автентифікацію пропустіть спливаюче вікно створення облікового запису. Найкраще використовувати поряд із sso_overrides_email, sso_overrides_username та sso_overrides_name. + external_auth_immediately: "Автоматично перенаправляти на зовнішню систему входу без взаємодії з користувачем. Це набуває чинності лише тоді, коли login_required має значення true, і існує лише один зовнішній метод автентифікації" enable_sso: "Увімкніть єдиний вхід через зовнішній сайт (УВАГА: ПОШТОВІ АДРЕСИ КОРИСТУВАЧІВ *ПОВИННІ* бути перевірені ЗОВНІШНІМ САЙТОМ!)" + verbose_sso_logging: "Записувати докладні дані діагностики, пов'язані з SSO у файл /logs" + enable_sso_provider: "Впровадити протокол постачальника єдиного входу Discourse SSO provider protocol. Вимагає встановлення параметра sso_provider_secrets" sso_url: "URL-адреса входу (повинна містити http: // або https: //)" sso_secret: "Секретний набір символів, який використовується для перевірки автентичності зашифрованого входу за допомогою SSO, переконайтеся, що це 10 або більше символів" sso_provider_secrets: "Перелік доменно-секретних пар, які використовують дискурс як постачальник SSO. Переконайтеся, що секрет SSO становить 10 символів або більше. Символ підстановки * може використовуватися для відповідності будь-якому домену або лише його частині (наприклад, * .example.com)" sso_overrides_bio: "Перезаписує інформацію в профіль користувача і не дозволяє користувачеві його змінювати" sso_overrides_groups: "Синхронізуйте все членство в групі вручну з групами, вказаними в атрибуті sso груп (ПОПЕРЕДЖЕННЯ: якщо ви не вказали групи, все ручне членство в групі буде видалено для користувача)" + sso_overrides_email: "Замінює локальну електронну пошту електронною поштою зовнішнього сайту при використанні SSO при кожному вході та запобігає локальним змінам. Застосовується до всіх постачальників аутентифікації. (ПОПЕРЕДЖЕННЯ: розбіжності можуть виникати для електронних адрес)" + sso_overrides_username: "Замінює локальне ім’я користувача на зовнішнє ім’я користувача при використанні SSO при кожному вході та запобігає локальним змінам. Застосовується до всіх постачальників аутентифікації. (ПОПЕРЕДЖЕННЯ: розбіжності можуть виникати через різницю в довжині імені користувача/вимогах)" + sso_overrides_name: "Замінює локальне повне ім’я за повним іменем зовнішнього сайту при використанні SSO при кожному вході та запобігає локальним змінам. Застосовується до всіх постачальників аутентифікації." + sso_overrides_avatar: "Замінює аватар користувача на аватар зовнішнього сайту при використанні SSO. Якщо цю опцію ввімкнено, користувачам не буде дозволено завантажувати аватари в Дискурсі." + sso_overrides_location: "Замінює місце знаходження користувача зовнішнім його розташуванням при використанні SSO та запобігає локальним змінам." + sso_overrides_website: "Замінює веб-сайт користувача зовнішнім, отриманим при використанні SSO та запобігає локальним змінам." + sso_overrides_profile_background: "Замінює фон профілю користувача зовнішнім аватаром сайту отриманим при використанні SSO." + sso_overrides_card_background: "Перевизначає фон картки користувача із зовнішнім аватаром сайту при використанні SSO." sso_not_approved_url: "Перенаправляти непідтверджені SSO-аккаунти на цю URL-адресу" + sso_allows_all_return_paths: "Не обмежувати домен для return_paths, надані SSO (за замовчуванням шлях повернення повинен бути на поточному сайті)" enable_local_logins: "Увімкнути локальні акаунти для входу в систему для імені користувача та пароля. Це потрібно ввімкнути для запрошень на роботу. ПОПЕРЕДЖЕННЯ: коли його відключено, ви, можливо, не зможете увійти в систему, якщо раніше не створили хоча б один метод альтернативного входу." enable_local_logins_via_email: "Дозволити користувачам запитувати посилання для входу в один клік та надсилати їм електронною поштою цього посилання." allow_new_registrations: "Дозволити реєстрацію нових користувачів. Вимкніть, щоб заборонити відвідувачам створювати нові облікові записи." enable_signup_cta: "Покажіть повідомлення анонімним користувачам, які повернулися, з пропозицією зареєструвати обліковий запис." + enable_google_oauth2_logins: "Увімкніть автентифікацію Google Oauth2. Це метод автентифікації, який наразі підтримує Google. Потрібен ключ і секрет. Див. Налаштування входу в Google для Дискурсу." google_oauth2_client_id: "Client ID для вашого Google додатка." google_oauth2_client_secret: "Client secret для вашого Google додатка" + google_oauth2_prompt: "Необов’язковий розділений пробілом список значень, який вказує, чи запитує сервер авторизацію користувача для повторної автентифікації та згоди. Див. https://developers.google.com/identity/protocols/OpenIDConnect#prompt щодо можливих значень." twitter_consumer_key: "Ключ користувача для аутентифікації в Twitter, зареєстрований за адресою https://developer.twitter.com/apps" twitter_consumer_secret: "Секретний номер для перевірки справжності Twitter, зареєстрований в https://developer.twitter.com/apps" enable_discord_logins: "Дозволити користувачам вхід за допомогою Discord?" @@ -1707,11 +1759,15 @@ uk: desktop_category_page_style: "Візуальний стиль для сторінки категорії." category_colors: "Список шістнадцятирічних кодів кольорів, дозволених для категорій." category_style: "Стилі для виділення розділів." + default_dark_mode_color_scheme_id: "Кольорова схема, яка використовується в темному режимі." + dark_mode_none: "Немає" + max_image_size_kb: "Максимальний розмір завантаженого зображення в кБ. Це також потрібно налаштувати у nginx (client_max_body_size) / apache або проксі. Зображення, більші за цей і менші за client_max_body_size, будуть змінені перед записом під час завантаження." max_attachment_size_kb: "Максимальний розмір завантажуваних файлів в кілобайтах. Переконайтеся, що ви також налаштували обмеження в nginx (client_max_body_size) / apache або проксі." authorized_extensions: "Список розширень файлів, дозволених до завантаження. Використовуйте '*', щоб дозволити будь-які типи файлів." authorized_extensions_for_staff: "Список розширень файлів, дозволених для завантаження для користувачів персоналу на додатково до списку, визначеного в налаштуваннях сайту як `authorized_extensions`. (використовувати '*', щоб увімкнути всі типи файлів)" theme_authorized_extensions: "Список розширень файлів, дозволених для завантаження в темі (використовуйте '*', щоб увімкнути всі типи файлів)" max_similar_results: "Кількість схожих тим, що показуються користувачеві під час створення нової теми. Порівняння виконується на підставі назви і тексту теми." + max_image_megapixels: "Максимальна кількість мегапікселів, дозволена для зображення. Зображення із більшою кількістю мегапікселів буде відхилено." title_prettify: "Запобігати типовим опискам та помилкам, таким як: всі літери - великі, перша літера - мала, багаторазові ! та ?, надлишкові . в кінці, тощо." title_remove_extraneous_space: "Видаліть початкові пробіли перед кінцевою пунктуацією." automatic_topic_heat_values: 'Автоматично оновлювати параметри "topic views heat" та "topic post like heat" на основі активності сайту.' @@ -1732,7 +1788,7 @@ uk: privacy_policy_url: "Якщо ви хочете використовувати документ Privacy policy, завантажений на інший сервер, то введіть посилання на нього." log_anonymizer_details: "Чи слід зберігати дані користувача в журналі після анонімізації. При дотриманні GDPR вам потрібно буде відключити це." newuser_spam_host_threshold: "Скільки разів новий користувач може опублікувати посилання на той самий хост у своїх публікаціях `newuser_spam_host_threshold`, перш ніж вважатись спамом." - staff_like_weight: "Додатковий ваговий коефіцієнт для \"мені подобається\" поставлених персоналом (модераторами і адміністраторами)." + allowed_spam_host_domains: "Список довірених доменів виключених з перевірки на спам. У нових користувачів не буде обмежень на створення дописів з посиланнями на ці домени." topic_view_duration_hours: "Кількість нових переглядів теми один раз на IP/Користувача кожні N годин" user_profile_view_duration_hours: "Кількість нових переглядів профілю користувача один раз на IP/Користувача кожні N годин" levenshtein_distance_spammer_emails: "При перевірці листа на спам, яка кількість символів має бути різною" @@ -1749,6 +1805,8 @@ uk: reviewable_claiming: "Чи потрібно переглядати вміст, що підлягає перегляду, перш ніж на нього діяти?" reviewable_default_topics: "Показувати дописи для перегляду, групуючи за темами за замовчуванням" reviewable_default_visibility: "Не показуйте елементи для перегляду, якщо вони не відповідають цьому пріоритету" + high_trust_flaggers_auto_hide_posts: "Нові повідомлення користувачів автоматично приховуватимуться як спам, після позначення їх користувачем з рівнем довіри 3 та вище" + cooldown_hours_until_reflag: "Скільки часу доведеться чекати користувачам, поки вони зможуть повторно додати позначку на повідомлення" reply_by_email_enabled: "Увімкнути відповіді на теми електронною поштою." reply_by_email_address: "Шаблон адреси електронної скриньки у формі для відповідей через електронну пошту, наприклад: %%{reply_key}@reply.myforum.com" incoming_email_prefer_html: "Використовуйте HTML замість тексту для вхідної електронної пошти." @@ -1764,6 +1822,7 @@ uk: max_emails_per_day_per_user: "Максимальна кількість електронних листів, що надсилаються користувачу на день. 0, щоб відключити обмеження" enable_staged_users: "Автоматично створювати поетапних користувачів під час обробки вхідних електронних листів." maximum_staged_users_per_email: "Максимальна кількість поетапних користувачів, створених під час обробки вхідного електронного листа." + auto_generated_allowlist: "Список електронних адрес, які не перевірятимуться на автоматично створений вміст. Приклад: foo@bar.com|discourse@bar.com" block_auto_generated_emails: "Блокувати вхідні електронні листи, ідентифіковані як автоматично створені." ignore_by_title: "Ігнорувати вхідні електронні листи на основі їх заголовку." soft_bounce_score: "Кількість відмов додається користувачеві, коли трапляється тимчасова відмова." @@ -1855,7 +1914,6 @@ uk: display_name_on_posts: "Показувати повні імена користувачів на їх дописах у додаток до їх @username." show_time_gap_days: "Якщо два дописи зроблені за стільки днів між собою, вивести часовий проміжок у темі." short_progress_text_threshold: "Після того, як кількість дописів в темі перевищить це число, індикатор просування по темі показуватиме тільки номер поточного допису. Якщо Ви зміните ширину індикатора, Вам, ймовірно, знадобиться змінити це значення." - default_code_lang: "Мова програмування для підсвітки синтаксису, що за замовчуванням застосовуватиметься для блоків коду GitHub (lang-auto, ruby, python і т. ін.)" warn_reviving_old_topic_age: "Коли хтось почне відповідати на тему, де остання відповідь є старшою за цю кількість днів, з’явиться попередження. Щоб відключити, встановіть 0." autohighlight_all_code: "Примусово застосовувати підсвічування коду до всіх заздалегідь відформатованих блоків коду, навіть якщо чітко не вказали мову." highlighted_languages: "Включені правила підсвічування синтаксису. (Попередження: включення занадто багатьох мов може вплинути на ефективність) див.: https://highlightjs.org/static/demo для демонстрації" @@ -2145,7 +2203,10 @@ uk: second_factor_backup_title: "Двофакторний резервний код" invalid_second_factor_code: "Недійсний код аутентифікації Кожен код може бути використаний лише один раз." invalid_security_key: "Недійсний ключ безпеки." + missing_second_factor_name: "Будь ласка, вкажіть ім’я." + missing_second_factor_code: "Будь ласка, введіть код." second_factor_toggle: + totp: "Використовуйте натомість програму авторизації або ключ безпеки" backup_code: "Використовуйте замість цього резервний код" admin: email: @@ -2153,6 +2214,11 @@ uk: user: deactivated: "Відключено через багато електронних листів, відхилених на '%{email}'." deactivated_by_staff: "Деактивовано персоналом" + deactivated_by_inactivity: + one: "Автоматично деактивується через %{count} день бездіяльності" + few: "Автоматично деактивується через %{count} дні бездіяльності" + many: "Автоматично деактивується через %{count} днів бездіяльності" + other: "Автоматично деактивується через %{count} днів бездіяльності" activated_by_staff: "Активований персоналом" new_user_typed_too_fast: "Новий користувач набирає текст занадто швидко" content_matches_auto_block_regex: "Вміст відповідає автоматичному блоку regex (регулярний вираз)" @@ -2168,6 +2234,7 @@ uk: must_not_contain_two_special_chars_in_seq: "не повинна містити послідовності з 2 або більше спеціальних символів (.-_)" must_not_end_with_confusing_suffix: "не повинен закінчуватися заплутаним суфіксом, наприклад .json або .png тощо" email: + invalid: "недійсний." not_allowed: "is not allowed from that email provider. Please use another email address." blocked: "не допускається." revoked: "Не надсилатимуть електронні листи на '%{email}' до %{date}." @@ -2182,13 +2249,20 @@ uk: fixed_primary_email: "Фіксована основна електронна пошта для інсценованого користувача" same_ip_address: "Ідентична IP-адреса (%{ip_address}), як в інших користувачів" inactive_user: "Неактивний користувач" + email_in_spam_header: "Перше повідомлення користувача було позначено як спам" reviewables_reminder: + submitted: + one: "Повідомлення було надіслано понад %{count} годину тому. [Будь ласка, перегляньте їх](%{base_path}/review)." + few: "Повідомлення було надіслано понад %{count} годин тому. [Будь ласка, перегляньте їх](%{base_path}/review)." + many: "Повідомлення було надіслано понад %{count} годин тому. [Будь ласка, перегляньте їх](%{base_path}/review)." + other: "Повідомлення було надіслано понад %{count} годин тому. [Будь ласка, перегляньте їх](%{base_path}/review)." subject_template: one: "%{count} елемент потрібно переглянути" few: "%{count} необхідно переглянути" many: "%{count} необхідно переглянути" other: "%{count}записів необхідно переглянути" unsubscribe_mailer: + title: "Відписатися від розсилки" subject_template: "Підтвердіть, що більше не хочете отримувати оновлення електронною поштою від %{site_title}" invite_mailer: subject_template: "%{inviter_name} запросив вас приєднаися до '%{topic_title}' на %{site_domain_name}" @@ -2264,6 +2338,8 @@ uk: title: "Ласкаво просимо" subject_template: "Ласкаво просимо до сайта %{site_name}!" text_body_template: "Дякуємо, що прийняли наше запрошення на %{site_name} -- ласкаво просимо! \n\n- Ми створили для вас цей новий акаунт **%{username}**. Змініть своє ім’я або пароль, відвідавши [your user profile][prefs].\n\n- Коли ви входите в систему, будь ласка **використовуйте ту саму адресу електронної пошти від свого початкового запрошення** -- інакше ми не зможемо сказати, що це ви! \n\n%{new_user_tips} Ми віримо в [civilized community behavior](%{base_url}/guidelines) у всі часи. \n\nНасолоджуйтеся своїм перебуванням! \n\n[prefs]: %{user_preferences_url}\n" + tl2_promotion_message: + subject_template: "Вітаємо з підвищенням рівня довіри!" backup_succeeded: title: "Резервне копіювання вдалося" subject_template: "Резервне копіювання успішно завершено" @@ -2285,12 +2361,22 @@ uk: bulk_invite_failed: title: "Помилка масового запрошення" subject_template: "Масове запрошення користувачів оброблено з помилками" + user_added_to_group_as_owner: + title: "Додано в групу як власника" + subject_template: "Ви були додані як власник групи %{group_name}" + text_body_template: | + Ви були додані в якості власника групи [%{group_name}](%{base_url}%{group_path}). + user_added_to_group_as_member: + title: "Додано до групи як учасник" csv_export_succeeded: title: "Експорт CSV успішний" subject_template: "[%{export_title}] Експорт даних завершено" csv_export_failed: title: "Не вдалося експортувати CSV" subject_template: "Експорт даних не здійснено" + email_reject_not_allowed_email: + title: "Email відхилено. Заборонена поштова скринька" + subject_template: "[%{email_prefix}] Проблема з електронною поштою — заблокована електронна пошта" email_reject_reply_key: title: "Ключ відповіді електронною поштою відхилити" subject_template: "[%{email_prefix}] Проблема електронної пошти - невідомий ключ відповіді" @@ -3027,8 +3113,15 @@ uk: placeholder: "Сан-Франциско, Каліфорнія" colors: title: "Кольорова схема" - themes_further_reading: - title: "Стилі" + fonts: + title: "Шрифти" + fields: + body_font: + label: "Основний шрифт" + heading_font: + label: "Шрифт заголовка" + font_preview: + label: "Попередній перегляд" logos: title: "Логотипи" fields: @@ -3076,9 +3169,6 @@ uk: disabled: "Оскільки локальні облікові записи відключені, відправляти запрошення кому-небудь неможливо. Будь ласка, перейдіть до наступного кроку." finished: title: "Ваш Discourse готовий!" - description: | -

Якщо вам коли-небудь захочеться змінити ці настройки, перезапустіть цей майстер в будь-який час, або зайдіть в розділ адміністрування; його можна знайти по значку гайкового ключа в меню сайту.

-

Бажаємо успіхів в побудові вашої нової спільноти!

search_logs: graph_title: "Кількість пошуків" joined: "Приєднався(лась)" @@ -3209,8 +3299,21 @@ uk: reason: "Вилучено через чергу перегляду" email_style: html_missing_placeholder: "Шаблон html повинен містити %{placeholder}" + notification_level: + ignore_error: "Вибачте, ви не можете ігнорувати цього користувача." + mute_error: "Вибачте, але ви не можете вимкнути повідомлення цього користувача." discord: not_in_allowed_guild: "Помилка аутентифікації. Ви не є членом дозволеної гільдії Discord." + old_keys_reminder: + title: "Нагадування про старі облікові дані" + body: | + Привіт! Це звичайне щорічне нагадування про безпеку від вашого екземпляру Дискурсу. + + В якості ввічливості ми хотіли повідомити вам, що такі облікові дані, що використовуються у вашому екземплярі дискурсу, не оновлювались більше двох років: + + %{keys} + + Наразі жодних дій не потрібно, однак це вважається гарною практикою безпеки перевіряти всі свої важливі дані кожні кілька років. activemodel: errors: <<: *errors diff --git a/config/locales/server.ur.yml b/config/locales/server.ur.yml index dabe994a61..3e2ad5abf5 100644 --- a/config/locales/server.ur.yml +++ b/config/locales/server.ur.yml @@ -381,14 +381,6 @@ ur: متن کو اُجاگر کر کہ ظاہر ہونے والے جواب اقتباس کریں بٹن منتخب کرنے سے ایک اقتباس شامل کرنے کیلئے آپ اپنے پچھلے جواب میں ترمیم کرسکتے ہیں۔ ہر ایک کیلئے اُن ٹاپکس کو پڑھ نا زیادہ آسان ہے جس میں بہت سے چھوٹے، انفرادی جوابات کے مقابلے میں تفصیلی لیکن کم جوابات ہوں۔ - get_a_room: | - ### بات چیت میں ملوث ہونے کیلئے سب کی حوصلہ افزائی کریں - - آپ نے اس خاص ٹاپک میں @%{reply_username} کو %{count} مرتبہ جواب دیا ہے! - - ایک اچھی بحث میں کئی آوازیں اور نقطہ نظر شامل ہوتے ہیں۔ کیا آپ کسی اور کو شامل کرسکتے ہیں؟ - - اور مت بھولیے، اگر آپ اِس خاص صارف کے ساتھ عوامی نظر سے دور اپنی بات چیت لمبائی تک جاری رکھنا چاہتے ہیں، تو [انہیں ایک ذاتی پیغام بھیجیں](%{base_path}/u/%{reply_username})۔ too_many_replies: | ### آپ اس ٹاپک پر جوابات کے نمبر کی حد تک پہنچ گئے ہیں @@ -1558,7 +1550,6 @@ ur: privacy_policy_url: "اگر آپ کے پاس ایک نجی معلومات کی حفاظتی پالیسی کی دستاویز کسی اور جگہ پر ہَوسٹ کی ہوئی ہے جو آپ استعمال کرنا چاہتے ہیں، تو یہاں مکمل URL فراہم کریں۔" log_anonymizer_details: "کیا آیک صارف کو گمنام بنانے کے بعد اُس کی تفصیلات رکھی جائیں کہ نہیں۔ GDPR کے ساتھ تعمیل کرتے وقت آپ کو اِسے بند کر دینے کی ضرورت ہوگی۔" newuser_spam_host_threshold: "ایک نیا صارف کتنی دفعہ ایک ہی ہَوسٹ کا لِنک اپنی `newuser_spam_host_threshold` پوسٹس کے اندر شائع کر سکتا ہے، اِس سے پہلے کہ وہ سپَیم سمجھا جائے۔" - staff_like_weight: "سٹاف کے لائیکس کو کتنا اضافی وزن کا عنصر دیں۔" topic_view_duration_hours: "ہر ن گھنٹوں پر فی IP/صارف ایک نیا ٹاپک وِیو شمار کریں" user_profile_view_duration_hours: "ہر ن گھنٹوں پر فی IP/صارف ایک نیا صارف پروفائل وِیو شمار کریں" levenshtein_distance_spammer_emails: "سپَیمر ای میل کو مَیچ کرتے وقت، فرق حروف کی تعداد جس پر بھی ایک فزِّی میچ ہو سکے گا۔" @@ -1688,7 +1679,6 @@ ur: display_name_on_posts: "اپنے @username کے علاوہ صارف کی پوسٹس پر اُن کا مکمل نام بھی دکھائیں۔" show_time_gap_days: "اگر دو پوسٹس اتنے دنوں بعد شائع کی گئے ہوں، تو ٹاپک میں وقت کا فرق دکھائیں۔" short_progress_text_threshold: "اگر ٹاپک میں پوسٹس کی تعداد اِس نمبر سے اوپر چلے جاتی ہے، تو پراگرَیس بار صرف موجودہ پوسٹ نمبر دکھائے گا۔ اگر آپ پراگرَیس بار کی چوڑائی کو تبدیل کریں گے، تو آپ کو اِس قدر کو تبدیل کرنے کی ضرورت پر سکتی ہے۔" - default_code_lang: "گِٹ ہَب کَوڈ بلاکس پر لاگو ڈِیفالٹ پروگرامِنگ زبان سِنٹیکس ہائلائیٹِنگ (lang-auto، ruby، python وغیرہ)" warn_reviving_old_topic_age: "جب کوئی ایسے ٹاپک پر جواب دینا شروع کرے جہاں پچھلا آخری جواب اتنے دنوں سے زیادہ پرانا ہو، تو ایک انتباہ ظاہر کی جائے گی۔ 0 پر سَیٹ کر کہ غیر فعال کریں۔" autohighlight_all_code: "تمام پہلے سے فارمَیٹ کردہ کَوڈ بلاکس پر ذبردستی کَوڈ ہائلائیٹِنگ لاگو کریں، یہاں تک کہ جب زبان بھی خاص طور پر واضح نہ کی گئی ہو۔" highlighted_languages: "سِنٹیکس ہائلائیٹِنگ کے قوانین شامل کریں۔ (انتباہ: بہت زیادہ زبانوں کو شامل کرنے سے کارکردگی پر اثر ہو سکتا ہے) ڈَیمو کیلئے ملاحظہ کریں: https://highlightjs.org/static/demo" @@ -3598,8 +3588,6 @@ ur: placeholder: "سان فرانسِسکو، کَیلیفَورنیا" colors: title: "تھیم" - themes_further_reading: - title: "تِھیمز" logos: title: "لَوگَو" fields: @@ -3647,9 +3635,6 @@ ur: disabled: "چونکہ مقامی لاگ اِن غیر فعال ہیں، کسی کو بھی دعوت نامہ بھیجنا ممکن نہیں ہے۔ براہ مہربانی اگلے مرحلے پر آگے بڑھیے۔" finished: title: "آپ کا ڈِسکورس تیار ہے!" - description: | -

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

-

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

search_logs: graph_title: "سرچ شمار" joined: "شمولیت اختیار کی" diff --git a/config/locales/server.vi.yml b/config/locales/server.vi.yml index dcef0adeeb..e4c04dc0ce 100644 --- a/config/locales/server.vi.yml +++ b/config/locales/server.vi.yml @@ -861,7 +861,6 @@ vi: faq_url: "Nếu bạn có một địa chỉ FAQ muốn sử dụng, điền URL đầy đủ ở đây." tos_url: "Nếu bạn có một địa chỉ Quy Định Sử Dụng muốn sử dụng, điền URL đầy đủ ở đây." privacy_policy_url: "Nếu bạn có một địa chỉ Quyền Riêng Tư muốn sử dụng, điền URL đầy đủ ở đây." - staff_like_weight: "Trọng số cộng thêm khi BQT like là bao nhiêu." 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ờ." @@ -925,7 +924,6 @@ vi: display_name_on_posts: "Hiển thị tên đầy đủ của thành viên trên bài viết của họ và khi gõ @username." show_time_gap_days: "Nếu hai bài viết được thực hiện cách nhau nhiều ngày, hiển thị thời gian cách nhau đó trong chủ đề." short_progress_text_threshold: "Sau khi số bài đăng của một chủ đề vượt qua giới hạn này, thanh tiến trình sẽ chỉ hiện số thứ tự của bài đăng hiện tại. Nếu bạn thay đổi chiều rộng của thanh tiến trình, bạn có thể cần thay đổi giá trị này" - default_code_lang: "Highlight cú pháp ngôn ngữ lập trình mặc định áp dụng cho đoạn mã GitHub (lang-auto, ruby, python...)" warn_reviving_old_topic_age: "Khi ai đó trả lời chủ đề mà lần trả lời cuối cùng cũ hơn số ngày được thiết lập ở đây, hiển thị một cảnh báo. Vô hiệu hóa bằng cách đặt là 0." autohighlight_all_code: "Highlight code bắt buộc đối với các đoạn mã thực thi ngay cả khi họ không chỉ định ngôn ngữ rõ ràng." embed_truncate: "Cắt ngắn các bài viết được nhúng." diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index b4bc1faa9f..4b6e2845c0 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -182,10 +182,12 @@ zh_CN: share_quote_facebook_requirements: "你必须设置一个Facebook app id 才能启用Facebook的引用分享功能。" second_factor_cannot_enforce_with_socials: "你不能在启用社交登录时强制启用双重认证。你必须先禁用以下登录方式:%{auth_provider_names}" second_factor_cannot_be_enforced_with_disabled_local_login: "如果禁用本地登录则无法强制启用2FA。" + second_factor_cannot_be_enforced_with_sso_enabled: "如果启用了 SSO,则无法强制启用 2FA。" local_login_cannot_be_disabled_if_second_factor_enforced: "如果强制启用2FA则不能禁用本地登录。在禁用本地登录之前,请禁用强制启用2FA。" cannot_enable_s3_uploads_when_s3_enabled_globally: "你无法启用S3上传,因为S3上传已经全局启用了,另外这样站点级的启用可能会导致上传的文件出现严重问题" conflicting_google_user_id: '此账户的Google账户ID已更改; 出于安全原因,需要管理人员干预。请联系工作人员并指引他们前往https://meta.discourse.org/t/76575' invite: + expired: "你的邀请码不正确。请联系工作人员。" not_found: "你的邀请码不正确。请联系工作人员。" not_found_json: "你的邀请码不正确。请联系工作人员。" not_found_template: | @@ -198,6 +200,10 @@ zh_CN: user_exists: "不需要邀请%{email},他们已经拥有账户了!" confirm_email: "

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

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

" cant_invite_to_group: "你未被允许邀请用户到指定的群组。请确保你是群组的所有者。" + disabled_errors: + sso_enabled: "由于已启用 SSO,邀请功能已被禁用。" + local_logins_disabled: "由于“启用本地登录”设置为禁用,邀请功能也被禁用。" + invalid_access: "您没有权限查看所请求的资源。" bulk_invite: file_should_be_csv: "上传的文件应为 csv 格式。" max_rows: "前%{max_bulk_invites}个邀请已经被发出。尝试将文件分割成更小的部分。" @@ -422,13 +428,13 @@ zh_CN: 留一点空间给其他人分享他们自己的见解会更好。你能邀请他们吗? get_a_room: | - ### 鼓励大家参与讨论 + ### 鼓励每一个人都参与到讨论当中 - 你已经@%{reply_username}并在这个特定话题回复了%{count}次了! + 你已经在这个主题中给@%{reply_username}回复了%{count}次了! - 很好的讨论涉及许多声音和观点。你能让其他人参与吗? + 良好讨论应该包含许多不同的声音和观点。你能让其他人也参与进来吗? - 不要忘记,如果您想在公众视野之外继续与特定用户进行对话,[给他发私信](%{base_path}/u/%{reply_username})。 + 不要忘记了,如果你想私下继续与特定用户进行对话,可以[给他们发送私信](%{base_path}/u/%{reply_username})。 too_many_replies: | ### 你发表的回复数量已经达到了上限。 @@ -619,7 +625,7 @@ zh_CN: public_group_membership: "你加入/离开群组的速度太快了,请等待%{time_left}后再试。" topics_per_day: "你的新主题的数量已经到达上限,请等待%{time_left}后再试。" pms_per_day: "你的私信数量已经到达上限,请等待%{time_left}后再试。" - create_like: "你的“赞”数量已经到达上限,请等待%{time_left}后再试。" + create_like: "你点赞的数量已达到了上限,请等待%{time_left}后再试。" create_bookmark: "你的收藏已经到达上限,请等待%{time_left}后再试。" edit_post: "你的编辑数量已经到达上限,请等待%{time_left}后再试。" live_post_counts: "你请求帖子数量的速度太快了,请等待%{time_left}后再试。" @@ -802,9 +808,9 @@ zh_CN: long_form: "已收藏本帖" like: title: "赞" - description: "点赞该帖子" - short_description: "赞该帖" - long_form: "赞了本帖" + description: "赞这个帖子" + short_description: "赞这个帖子" + long_form: "赞了这个帖子" draft: sequence_conflict_error: title: "草稿错误" @@ -820,7 +826,7 @@ zh_CN: self: "你没有收藏帖子;收藏可让你快速查阅特定帖子。" others: "没有收藏。" no_likes_given: - self: "你还没有赞任何帖子。" + self: "你还没有点赞任何帖子。" others: "没有被赞的帖子。" no_replies: self: "你还未回复任何主题" @@ -909,7 +915,7 @@ zh_CN: read_write: "读/写" description: '“%{application_name}”正在请求访问你的账户中的以下权限:' instructions: '我们刚刚生成了一个新的用户API密钥供您使用“%{application_name}”,请将以下密钥粘贴到您的应用程序中:' - otp_description: '你允许“%{application_name}”访问此网站吗?' + otp_description: '你要允许“%{application_name}”访问此网站吗?' otp_confirmation: confirm_title: 转入到%{site_name} logging_in_as: 用%{username}登录 @@ -1025,7 +1031,7 @@ zh_CN: title: "每日活跃用户" xaxis: "天" yaxis: "活跃的用户" - description: "在过去一天中赞过或发表过的用户数。" + description: "在过去一天中赞过或发表过的用户数量。" profile_views: title: "查看用户资料" xaxis: "天" @@ -1042,10 +1048,10 @@ zh_CN: yaxis: "新帖子数" description: "此期间创建的新帖子" likes: - title: "新的赞" + title: "赞" xaxis: "天" - yaxis: "新的赞的数量" - description: "新的赞数。" + yaxis: "新的赞数量" + description: "新的赞数量。" flags: title: "标记" xaxis: "天" @@ -1256,7 +1262,7 @@ zh_CN: dashboard: rails_env_warning: "你的服务器运行在 %{env} 模式。" host_names_warning: "你的 config/database.yml 文件使用的是默认的 localhost 主机名。请更新为你的站点主机名。" - sidekiq_warning: 'Sidekiq 不在运行。很多任务,例如发送电子邮件,是异步的被 sidekiq 调度执行的。请确保至少运行一个 sidekiq 进程。了解 Sidekiq。' + sidekiq_warning: 'Sidekiq未运行。很多任务,例如发送电子邮件,是被Sidekiq异步执行的。请确保至少运行一个 Sidekiq进程。了解Sidekiq。' queue_size_warning: "队列中有较多任务,为 %{queue_size} 个。这可能是因为 Sidekiq 进程的问题导致,或者需要更多的 Sidekiq 进程。" memory_warning: "你的服务器环境内存少于 1GB,我们建议至少要有 1GB 内存。" google_oauth2_config_warning: '服务器允许使用 Google Oauth2 登录(enable_google_oauth2_logins),但client id和client secret没有设定。到站点设置更新此设定。参考设定指南。' @@ -1328,8 +1334,8 @@ zh_CN: editing_grace_period_max_diff: "允许在编辑宽限期内修改的字符数量最大值,如修改多于此数量将保存另一个修订版(信任级别0和1)" editing_grace_period_max_diff_high_trust: "编辑宽限期允许的最大字符更改数,如果更改存储后的另一个修订版(信任级别2和更高)" staff_edit_locks_post: "如果管理人员编辑过帖子,将锁定编辑" - post_edit_time_limit: "信任等级0或信任等级1的作者可以在发表的(n)分钟后编辑帖子。设置为0为永远(不可以编辑)。" - tl2_post_edit_time_limit: "信任等级2以上的的作者可以在发表的(n)分钟后编辑帖子。设置为0为永远(不可以编辑)。" + post_edit_time_limit: "信任等级 0 或信任等级 1 的作者可以在发表的(n)分钟后编辑帖子。设置为 0 为永远可以编辑。" + tl2_post_edit_time_limit: "信任等级 2 以上的的作者可以在发表的(n)分钟后编辑帖子。设置为 0 为永远可以编辑。" edit_history_visible_to_public: "允许任何人查看编辑过的帖子的老版本。当禁用时,只有管理人员才能查看浏览。" delete_removed_posts_after: "帖子被作者删除,将在(n)小时后被自动删除。" max_image_width: "帖子中图片允许的最大缩略图宽度" @@ -1371,7 +1377,7 @@ zh_CN: same_site_cookies: "使用 Same-site cookies以消除所有跨站请求伪造(CSRF)的攻击面(Lax 或 Strict)。警告:Strict 仅在强制登录并使用 SSO 的站点有效。" summary_score_threshold: "一个帖子被加入到“主题摘要”中所需的最小分值" summary_posts_required: "一个主题启用“主题摘要”前最少需要有多少个回帖。修改此设置会影响过去一个星期。" - summary_likes_required: "主题启用“主题摘要”前最少需要收到多少个赞。修改此设置会影响过去一个星期。" + summary_likes_required: "主题启用“主题摘要”前至少需要收到多少个赞。修改此设置会影响过去一个星期。" summary_percent_filter: "当用户点击摘要,显示前百分之几的帖子" summary_max_results: "“主题摘要”所返回的最大帖子数量" enable_personal_messages: "允许信任等级1(可以另外选择发送私信的信任等级)的用户创建私信和回复私信。注意:管理人员不受限制。" @@ -1388,9 +1394,9 @@ zh_CN: cooldown_minutes_after_hiding_posts: "当一个帖子因为标记而隐藏之后,用户需要等待多少分钟才能编辑帖子" max_topics_in_first_day: "新用户在24小时内在发表第一帖后允许创建的主题数" max_replies_in_first_day: "新用户在24小时内在发表第一帖后允许创建的回复数" - tl2_additional_likes_per_day_multiplier: "增加信任等级2(成员)的赞限制,可提供一个乘数与原始值相乘" - tl3_additional_likes_per_day_multiplier: "增加信任等级3(常规)的每日赞限制,将此设置项与原始值相乘" - tl4_additional_likes_per_day_multiplier: "增加信任等级4(资深)的赞限制,可提供一个乘数与原始值相乘" + tl2_additional_likes_per_day_multiplier: "提升信任等级2每日可点赞的次数,会将原始值与本设置的值相乘" + tl3_additional_likes_per_day_multiplier: "提升信任等级3每日可点赞的次数,会将原始值与本设置的值相乘" + tl4_additional_likes_per_day_multiplier: "提升信任等级4每日可点赞的次数,会将原始值与本设置的值相" num_users_to_silence_new_user: "如果新用户被不同用户标记为垃圾信息num_spam_flags_to_silence_new_user 次,隐藏他们的所有帖子并禁止发言。设置为 0 禁用。" num_tl3_flags_to_silence_new_user: "如果新用户被 num_tl3_users_to_silence_new_user 个信任等级3的用户标记垃圾信息,隐藏他们的所以帖子并禁止发音。设置为 0 禁用。" num_tl3_users_to_silence_new_user: "如果新用户被不同信任等级3的用户标记 num_tl3_flags_to_silence_new_use 为标记垃圾信息,隐藏他们的所以帖子并禁止发音。设置为 0 禁用。" @@ -1401,7 +1407,7 @@ zh_CN: enable_markdown_linkify: "自动将看起来像链接的文本视为链接:www.example.com和https://example.com将自动链接" markdown_linkify_tlds: " 自动视为链接的顶级域列表" markdown_typographer_quotation_marks: "双引号和单引号替换对的列表" - post_undo_action_window_mins: "允许用户在帖子上进行撤销操作(赞、标记等)所需等待的间隔分钟数" + post_undo_action_window_mins: "允许用户撤销帖子近期操作(赞、标记等)的分钟数。" must_approve_users: "新注册用户必须获得职员批准方能访问" invite_code: "用户必须输入此代码才能够进行账户注册,留空则无需输入(大小写敏感)" approve_suspect_users: "添加可疑用户到审核队列。可疑用户填写了简介或网站但是没有浏览行为。" @@ -1437,6 +1443,7 @@ zh_CN: site_contact_group_name: "会被邀请到所有自动私信的有效群组名。" send_welcome_message: "给所有用户发送快速开始指导的私信" send_tl1_welcome_message: "向新的信任级别1用户发送欢迎消息。" + send_tl2_promotion_message: "给新晋的信任等级2用户发送有关升级的消息" suppress_reply_directly_below: "当只有一个回复直接位于该帖下方时,不显示可展开的回复计数。" suppress_reply_directly_above: "当直接回复上一个帖子时,不要在回复上显示展开的标记。" remove_full_quote: "自动删除直接回复的完整引用。" @@ -1462,7 +1469,7 @@ zh_CN: version_checks: "Ping Discourse Hub以获取版本更新并在/admin仪表板上显示新版本消息" new_version_emails: "当新版本发布时,发送一封邮件至 contact_email 设置的地址。" invite_expiry_days: "多少天以内用户的邀请码是有效的" - invite_only: "所有新用户必须由已信用用户或管理人员邀请。公开注册已禁用。" + invite_only: "所有的新用户都需要被可信的用户或管理人员邀请。公开注册已禁用。" login_required: "需要验证才能继续在该站阅读,不允许匿名访问。" min_username_length: "最小用户名长度。警告:如果任何现存的用户或群组名字长度比这短,站点将无法正常工作!" max_username_length: "最大用户名长度。警告:如果任何现存的用户或群组名字长度比这长,站点将无法正常工作!" @@ -1474,6 +1481,7 @@ zh_CN: password_unique_characters: "密码中必须包含几个不同的字符。" block_common_passwords: "不允许使用 10,000 个最常用的密码。" external_auth_skip_create_confirm: 当通过外部授权注册时,跳过创建账户的弹出式窗口,最好与 sso_overrides_email, sso_overrides_username 和 sso_overrides_name 一起使用。 + external_auth_immediately: "自动重定向到外部登录系统而无需用户交互。仅在启用login_required并且只有一个外部认证方法时有效" enable_sso: "启用通过外部站点单点登录(警告:用户的邮件地址必须被外部站点验证!)" verbose_sso_logging: "将详细的SSO相关诊断记录到/logs" enable_sso_provider: "在端点/session/sso_provider实现Discourse SSO provider协议,需要设置sso_provider_secrets" @@ -1536,15 +1544,15 @@ zh_CN: verbose_localization: "在界面上显示详细的本地化提示" previous_visit_timeout_hours: "系统判断一次访问之后多少小时后为“上一次”访问" top_topics_formula_log_views_multiplier: "热门主题公式中访问次数因子的值(n):`log(views_count) * (n) + op_likes_count * 0.5 + LEAST(likes_count / posts_count, 3) + 10 + log(posts_count)`" - top_topics_formula_first_post_likes_multiplier: "热门主题公式中首贴赞的数量的因子的值(n):`log(views_count) * 2 + op_likes_count * (n) + LEAST(likes_count / posts_count, 3) + 10 + log(posts_count)`" - top_topics_formula_least_likes_per_post_multiplier: "热门主题公式中赞和帖子数量的比例的最小值(n):`log(views_count) * (n) + op_likes_count * 0.5 + LEAST(likes_count / posts_count, 3) + 10 + log(posts_count)`" + top_topics_formula_first_post_likes_multiplier: "热门主题的计算公式中,首帖被赞的数量的乘数(n):`log(views_count) * 2 + op_likes_count * (n) + LEAST(likes_count / posts_count, 3) + 10 + log(posts_count)`" + top_topics_formula_least_likes_per_post_multiplier: "热门主题的计算公式中,赞和帖子数量之间的比例的最小值(n):`log(views_count) * 2 + op_likes_count * 0.5 + LEAST(likes_count / posts_count, (n)) + 10 + log(posts_count)`" rebake_old_posts_count: "每 15 分钟重制老帖子的数量。" enable_safe_mode: "允许用户进入安全模式以调试插件。" rate_limit_create_topic: "在创建一个主题之后,用户必须间隔多少秒(n)才能创建另一个主题" rate_limit_create_post: "在创建一个帖子之后,用户必须间隔多少秒(n)才能创建另一个帖子" rate_limit_new_user_create_topic: "在创建一个主题后,新用户必须等待(n)秒才能创建另一个主题。" rate_limit_new_user_create_post: "在发表帖子后,新用户必须等待(n)秒才能创建另一个帖子。" - max_likes_per_day: "每个用户每天能赞的数量的最大值。" + max_likes_per_day: "每个用户每天最多能点赞多少次。" max_flags_per_day: "每个用户每天能标记的数量的最大值。" max_bookmarks_per_day: "每用户每天能收藏的最大值。" max_edits_per_day: "每个用户每天能编辑的次数的最大值。" @@ -1592,13 +1600,13 @@ zh_CN: default_trust_level: "所有新用户的默认信任等级(0-4)。警告!改变此项将使你面临广告泛滥的风险。" tl1_requires_topics_entered: "新用户升级到信任等级1所需要进入的主题数量。" tl1_requires_read_posts: "新用户升级到信任等级1所需要阅读的帖子数量。" - tl1_requires_time_spent_mins: "新用户升级到信任等级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_time_spent_mins: "新用户升级到信任等级2需要看帖多少分钟。" tl2_requires_days_visited: "一个初级用户升级到信任等级2所需要访问站点的累计天数。" - tl2_requires_likes_received: "一个初级用户升级到信任等级2所需要获得的赞数。" - tl2_requires_likes_given: "一个初级用户升级到信任等级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。(设置为 0 或比考察期更长)" @@ -1611,8 +1619,8 @@ zh_CN: tl3_requires_posts_read_all_time: "用户升至信任等级3所需查看的最小帖子数量。" tl3_requires_max_flagged: "要提升到信任等级3,用户在考察时间段内不得被x个不同用户标记超过x个帖子,x为此设置的值。(设置为0或更高)" tl3_promotion_min_duration: "信任等级3的用户可被降级至信任等级2前最小持续天数。" - tl3_requires_likes_given: "要升到信任等级3所需要的,在最近考察时间段内用户必须要给出的赞(设置为 0 或更高)" - tl3_requires_likes_received: "要升到信任等级3所需要的,在最近考察时间段内用户必须要收到的赞(设置为 0 或更高)" + tl3_requires_likes_given: "升级到信任等级3,需要在最近的考察期内给出赞的数量。" + tl3_requires_likes_received: "升级到信任等级3,需要在最近的考察期内收到赞的数量。" tl3_links_no_follow: "不移除信任等级3用户帖子中的链接中的 rel=nofollow 属性。" trusted_users_can_edit_others: "允许具有高信任级别的用户编辑其他用户的内容" min_trust_to_create_topic: "创建主题所需的最低信任等级。" @@ -1661,7 +1669,7 @@ zh_CN: max_image_megapixels: "以百万像素为单位单个图像允许的最大值。高于最大值百万像素的图像将被拒绝。" title_prettify: "防止常见标题里的错别字和错误,包括全部大写,第一个字符小写,多个'!'和'?',结尾多余的'.'等等。" title_remove_extraneous_space: "删除结尾标点前的空格。" - automatic_topic_heat_values: '基于站点活动自动更新“主题观点热度”和“主题帖子赞的主题热度”设置。' + automatic_topic_heat_values: '基于站点活跃度自动更新“topic views heat和“topic post like heat”设置。' topic_views_heat_low: "多少次浏览后,该视图将稍稍高亮。" topic_views_heat_medium: "多少次浏览后,该视图将明显高亮。" topic_views_heat_high: "多少次浏览后,该视图将强烈高亮。" @@ -1671,8 +1679,8 @@ zh_CN: history_hours_low: "帖子在编辑后编辑指示器轻微高亮的小时数" history_hours_medium: "帖子在编辑后编辑指示器明显高亮的小时数" history_hours_high: "帖子在编辑后编辑指示器强烈高亮的小时数" - topic_post_like_heat_low: "在赞与帖子的比例超过此比例后,帖子数量一栏将稍稍高亮。" - topic_post_like_heat_medium: "在赞与帖子的比例超过此比例后,帖子数量一栏将明显高亮。" + topic_post_like_heat_low: "在赞/帖子的比值超过这个值后,帖子计数栏将稍微高亮。" + topic_post_like_heat_medium: "在赞/帖子的比值超过这个值后,帖子数量一栏将明显高亮。" topic_post_like_heat_high: "在赞与帖子的比例超过此比例后,帖子数量一栏将强烈高亮。" faq_url: "如果你的 FAQ 文档在外部,那么请在此填写其完整 URL 地址。" tos_url: "如果你的服务条款文档在外部,那么请在此填写其完整 URL 地址。" @@ -1680,7 +1688,7 @@ zh_CN: log_anonymizer_details: "匿名后是否将用户的详细信息保留在日志中。在遵守GDPR时,你需要关闭它。" newuser_spam_host_threshold: "被判定为垃圾前,新用户能发表指向同一主机的链接的次数。" allowed_spam_host_domains: "不需要进行垃圾内容测试的域名列表。新用户永远不会因发表带有这些域名链接的内容而被限制。" - staff_like_weight: "管理人员赞时的额外权重。" + staff_like_weight: "管理人员的赞的权重(非管理人员的赞的权重为1)。" topic_view_duration_hours: "按照每 IP/用户每 N 小时来记录一次新的主题访问" user_profile_view_duration_hours: "按照每 IP/用户每 N 小时来记录用户资料访问数" levenshtein_distance_spammer_emails: "匹配到垃圾邮件发送者时,仍将允许字符数差异的模糊匹配。" @@ -1794,9 +1802,9 @@ zh_CN: group_in_subject: "在邮件标题中设置%%{optional_pm},取决于PM中第一个组的名称,请参阅:自定义标准邮件的标题格式" allow_anonymous_posting: "允许用户切换至匿名模式" anonymous_posting_min_trust_level: "启用匿名发帖所需的最低信任等级" - anonymous_account_duration_minutes: "为了匿名性,每 N 分钟为各个用户创建一个新的匿名账户。例如:如果设置为 600,只要自最后发帖起过去 600 分钟,并且用户切换至匿名模式,就会创建一个新的匿名账户。" + anonymous_account_duration_minutes: "为了确保匿名性,每N分钟为各个用户创建一个新的匿名账户。例如:如果设置为600,只要自最后发帖起过去了600分钟,并且用户切换至匿名模式,就会创建一个新的匿名账户。" hide_user_profiles_from_public: "为匿名用户禁用用户信息卡、用户资料和用户目录。" - allow_featured_topic_on_user_profiles: "允许用户在其用户卡片和个人资料上宣传主题的链接。" + allow_featured_topic_on_user_profiles: "允许用户在其用户卡片和个人资料上显示一个指向主题的链接。" show_inactive_accounts: "允许已登录用户查看不活跃账户。" hide_suspension_reasons: "不在用户页面公开显示封禁原因。" log_personal_messages_views: "管理员记录其他用户或群组的私信查看数。" @@ -1818,8 +1826,8 @@ zh_CN: permalink_normalizations: "在匹配永久链接之前应用如下正则表达式,例如:/(topic.*)\\?.*/\\1 将去掉所有主题路径的查询字符串。格式为使用正则表达式+使用 \\1 等字符串来访问捕获内容" global_notice: "为所有访客显示紧急的、重要的和不可忽略的全局横幅公告,修改为空以隐藏(可使用HTML)。" disable_system_edit_notifications: "当 'download_remote_images_to_local' 启用时禁用系统用户的编辑提醒。" - notification_consolidation_threshold: "将多个通知合并为单个之前,收到“赞”或“成员申请”通知的最大数量。设置为 0 禁用合并。" - likes_notification_consolidation_window_mins: "以分钟计的持续时间,被点赞的通知在达到此阈值前会被合并到单个通知中。阈值可以通过 `SiteSetting.notification_consolidation_threshold` 进行配置。" + notification_consolidation_threshold: "合并通知前,可收到的“赞”和“成员请求”通知的数量。设置为0则禁用。" + likes_notification_consolidation_window_mins: "以分钟计的持续时间,被赞的通知在达到此阈值前会被合并到单个通知中。阈值可以通过 `SiteSetting.notification_consolidation_threshold` 进行配置。" automatically_unpin_topics: "当用户到达底部时自动解除主题置顶。" read_time_word_count: "每分钟可阅读的字数,用于估算阅读时间。" topic_page_title_includes_category: "主题页面标题标签包含分类名称。" @@ -1836,7 +1844,7 @@ zh_CN: display_name_on_posts: "在用户的帖子中显示他们的全名以及他们的 @username。" show_time_gap_days: "两个帖子的发表时间相隔多少天内时,在主题中以时间间隔显示。" short_progress_text_threshold: "在主题中的帖子数超过这个数量后,进度条将只显示当前的帖子数量。如果你更改了进度条的宽度,你需要修改这个值。" - default_code_lang: "默认的编程语言语法高亮应用 GitHub 代码块(lang-auto、ruby、python等。)" + default_code_lang: "使用Github code block(auto, nohighlight, ruby, python etc)作为默认的编程语言语法高亮。" warn_reviving_old_topic_age: "当有人开始回复一个最后回复于设定天数之前的主题时,将显示一个警告。将其设置为 0 以禁用。" autohighlight_all_code: "即使未显式声明语言,仍为所有预格式化代码块应用语法高亮。" highlighted_languages: "包含语法突出显示规则。(警告:包含太多语言可能会影响性能)请参阅:https://highlightjs.org/static/demo用于演示" @@ -1879,6 +1887,7 @@ zh_CN: new_user_notice_tl: "查看新用户发帖通知所需的最低信任级别。" returning_user_notice_tl: "查看回归用户发帖通知所需的最低信任级别。" returning_users_days: "在用户被认为回归之前应该经过多少天。" + review_media_unless_trust_level: "工作人员将会审核信任等级较低的用户所发布含有嵌入多媒体的内容。" enable_page_publishing: "允许工作人员使用自定义样式主题将主题发布到新的链接。" show_published_pages_login_required: "即使要求登录,匿名用户也可以查看公开的页面。" default_email_digest_frequency: "用户收到摘要邮件的默认频率。" @@ -1898,7 +1907,7 @@ zh_CN: default_other_enable_defer: "默认启用延迟主题功能" default_other_dynamic_favicon: "默认在浏览器图标上显示新/更新的主题数量" default_other_skip_new_user_tips: "跳过新用户的流程提示和徽章。" - default_other_like_notification_frequency: "默认通知用户赞的私信" + default_other_like_notification_frequency: "默认通知被赞的用户" default_topics_automatic_unpin: "默认时当用户到达底部时自动解除主题置顶。" default_categories_watching: "默认监看的分类列表。" default_categories_tracking: "默认跟踪的分类列表。" @@ -1943,7 +1952,7 @@ zh_CN: shared_drafts_category: "为主题草稿指定分类以启用共享草稿功能。此分类中的主题将从管理人员的主题列表中删除。" push_notifications_prompt: "显示用户同意提示。" push_notifications_icon: "在通知中所使用的图标。推荐使用一个96×96的单色且为透明背景的PNG图像。" - base_font: "被用在本站点上大多数地方的字体。可用主题覆盖。" + heading_font: "网站标题使用的字体。主题可以通过 `--heading-font-family` CSS 自定义属性来覆盖。" short_title: "短标题将用于用户主页、启动器或其他可能空间有限的地方。应该控制在12个字符内。" dashboard_hidden_reports: "允许从仪表盘中隐藏指定的报告。" dashboard_visible_tabs: "选择可见的仪表盘标签页" @@ -2159,7 +2168,7 @@ zh_CN: must_begin_with_alphanumeric_or_underscore: "必须以字母、数字或下划线开头" must_end_with_alphanumeric: "必须以字母或数字结尾" must_not_contain_two_special_chars_in_seq: "必须不包括连续的 2 个或更多的特殊字符(.-_)" - must_not_end_with_confusing_suffix: "不能以难以分辨的后缀结尾,比如 .json 或者 .png 等" + must_not_end_with_confusing_suffix: "不能以迷惑性的后缀结尾,比如 .json 或者 .png 等" email: invalid: "无效。" not_allowed: "本站不允许使用该邮箱服务商提供的电子邮箱,请使用其它邮箱地址。" @@ -2425,7 +2434,7 @@ zh_CN: text_body_template: | 嘿。我们看到你一直在忙着阅读,这太棒了,所以我们已经提升你了[信任等级!](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) - 我们很高兴你和我们共度时光,我们很想知道更多关于你的事情。花一点时间[填写你的个人资料](%{base_url}/我的/设置/个人信息),或随时[开始一个新主题](%{base_url}/categories)。 + 我们很高兴你和我们共度时光,我们很想知道更多关于你的事情。花一点时间[填写你的个人资料](%{base_url}/my/preferences/profile),或随时[开始一个新主题](%{base_url}/categories)。 welcome_staff: title: "欢迎管理人员" subject_template: "恭喜,你已获得%{role}身份!" @@ -2452,6 +2461,12 @@ zh_CN: 好好享受你在论坛的时光吧! [prefs]: %{user_preferences_url} + tl2_promotion_message: + subject_template: "恭喜!你的信任等级提升了!" + text_body_template: | + 我们提升了你的[信任等级](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/)! + + 诚邀你继续参与到社区当中,很荣幸能与你同行。 backup_succeeded: title: "备份成功" subject_template: "备份成功完成" @@ -3129,7 +3144,7 @@ zh_CN: new_topics: "新主题" unread_notifications: "未读通知" unread_high_priority: "未读的高优先级通知" - liked_received: "获得赞" + liked_received: "收到的赞" new_users: "新用户" popular_topics: "热门主题" follow_topic: "关注主题" @@ -3193,10 +3208,15 @@ zh_CN: confirm_new_email: title: "确认新邮箱" subject_template: "[%{email_prefix}] 确认你的新电子邮箱地址" + confirm_new_email_via_admin: + title: "确认新邮箱" + subject_template: "[%{email_prefix}] 确认你的新电子邮箱地址" text_body_template: | - 点击以下链接以确认你在%{site_name}的邮件地址: + 点击以下链接确认你在%{site_name}的新电子邮件地址: %{base_url}/u/confirm-new-email/%{email_token} + + 此电子邮件的更改由网站管理员发送。如果你没有要求此更改,请联系 %{site_name} 管理员。 confirm_old_email: title: "确认旧邮箱" subject_template: "[%{email_prefix}] 确认你现在的电子邮箱地址" @@ -3301,7 +3321,7 @@ zh_CN: search_button: "搜索" offline: title: "无法载入应用" - offline_page_message: "看起来你掉线了!请检查网络连接并重试。" + offline_page_message: "你似乎正处于离线状态!请检查网络连接并重试。" login_required: welcome_message: | ##[欢迎来到 %{title}](#welcome) @@ -3763,22 +3783,22 @@ zh_CN: name: 社区成员 description: 授予邀请、群组消息和更多的赞 long_description: | - 该徽章授予给达到用户等级2的你。感谢你在社区待了几周,真正融入到了社区中。你现在可以在你的用户页或者主题中邀请他人或者创建群组消息了。每天你也可以点更多次赞了。 + 该徽章授予给达到信用等级2的你。感谢你在社区待了几周,真正融入到了社区中。现在你可以在你的用户页或单个主题中邀请他人加入,或者创建群组私信,同时也可以在每一天给出更多的赞。 regular: name: 活跃用户 - description: 授予重分类、重命名、跟踪链接、维基功能和更多的赞 + description: 授予重分类、重命名、重命名、跟随链接、维基还有更多的赞 long_description: | - 该徽章授予给达到用户等级 3 的你。感谢你在这几个月持续地参与社区。你现在是我们之中最活跃的读者之一了,你持续的贡献极大地帮助了我们的社区。你现在可以重新分类或者重命名主题、使用能力强大的垃圾标记功能以及访问隐藏的贵宾分类了。而且每天你可以点更多次赞了。 + 该徽章授予给达到信用等级3的你。感谢你在这几个月持续地参与社区。你现在是我们之中最活跃的读者之一了,你持续的贡献极大地帮助了我们的社区。你现在可以重新分类或者重命名主题、使用能力强大的垃圾标记功能以及访问隐藏的贵宾分类了,并且你还可以每一天送出更多的赞。 leader: name: 资深 - description: 授予全局编辑、固定、关闭、存档、分割、合并帖子的权限和更高的每日点赞上限 + description: 授予全局编辑、置顶、关闭、存档、分割、合并帖子的权限,还有更多的赞 long_description: | 当你达到信任等级 4 时,将被授予此徽章。你是管理人员选作的社区中的领导者,你的行为和言辞,为社区中的其他人树立了积极的榜样。 你现在可以编辑所有的帖子,使用常见的主题管理操作,例如置顶,关闭,不在列表中显示,存档,分割和合并。 welcome: name: 欢迎 - description: 得到一个赞 + description: 收到了一个赞 long_description: | - 该徽章授予给帖子收到了第一个赞的成员。恭喜,有社区成员发现你发表的内容有意思、酷炫或者有用! + 该徽章授予给帖子收到了第一个赞的你。恭喜,有社区成员觉得你发布的内容有意思、酷炫或者是有用的! autobiographer: name: 自传作者 description: 填写用户资料信息 @@ -3791,34 +3811,34 @@ zh_CN: 该徽章授予给在社群注册一年并超至少发布了一个帖子的你。感谢你的持续关注和对我们社区的贡献!我们希望你继续参与我们的社区。 nice_post: name: 不错的回复 - description: 回复被赞了 10 次 + description: 在单个回复中收获10个赞 long_description: | - 该徽章授予给回复被赞了 10 次的你。你的回复给社区成员们留下了印象,并且你推动了讨论的进程。 + 该徽章授予给回复得到10个赞的你。你的回复给社区成员们留下了印象,并且你推动了讨论的进程。 good_post: name: 很棒的回复 - description: 回复被赞了 25 次 + description: 在单个回复中收获25个赞 long_description: | - 该徽章授予给回复被赞了 25 次的你。你的回复很杰出,而且让讨论更有意思了。 + 该徽章授予给回复得到25个赞的你。你的回复很杰出,而且让讨论更有意思了。 great_post: name: 精彩的回复 - description: 回复被赞了 50 次 + description: 单个回复收获50个赞 long_description: | - 该徽章授予给回复被赞了 50 次的你。哇!你的回复很有启发、引经据典、令人冷俊不禁或者十分有内涵,整个社区都喜欢它! + 该徽章授予给回复收到50个赞的你。哇!你的回复很有启发、引经据典、令人冷俊不禁或者十分有内涵,整个社区都赞它! nice_topic: name: 不错的主题 - description: 主题被赞了 10 次 + description: 单个主题收获10个赞 long_description: | - 该徽章授予给主题获得 10 个赞的你。你开启了一个社区成员觉得有意思的讨论! + 该徽章授予给主题收到10个赞的你。你创建了一个社区成员觉得有意思的讨论! good_topic: name: 很好的主题 - description: 主题被赞了 25 次 + description: 单个主题收获25个赞 long_description: | - 该徽章授予给主题获得 25 个赞的你。你开启了一个有意义的主题,整个社区都在积极响应。 + 该徽章授予给主题收到25个赞的你。你创建了一个有意义的主题,整个社区都在积极响应。 great_topic: name: 精彩的主题 - description: 主题被赞了 50 次 + description: 单个主题收获50个赞 long_description: | - 该徽章授予给主题获得 50 个赞的你。你开启了一个引人入胜的主题,整个社区都沉浸于讨论之中。 + 该徽章授予给主题获得50个赞的你。你创建了一个引人入胜的主题,整个社区都沉浸于讨论之中。 nice_share: name: 不错的分享 description: 分享了一个有 25 个独立访问者的帖子 @@ -3835,10 +3855,10 @@ zh_CN: long_description: | 该徽章授予给分享链接给 1000 个其他访客的你。哇!你把一个有意思的讨论推广给了广大的读者们,并且帮助社区前进了一大步! first_like: - name: 首次赞 - description: 已赞过了一个帖子 + name: 首赞 + description: 赞了一个帖子 long_description: | - 该徽章授予给第一次使用 :heart: 按钮赞了帖子的成员。给帖子点赞是一个极好的让社区成员知道他们的帖子有意思、有用、酷炫或者好玩的方法。分享爱! + 该徽章授予给首次使用 :heart: 按钮赞了一个帖子的你。点赞帖子是一个让社区成员知道他们所发布的内容是实用或有趣的一个好方法。分享这种热情吧! first_flag: name: 首次标记 description: 已标记过了一个帖子 @@ -3901,14 +3921,14 @@ zh_CN: 该徽章授予给分享的链接被点击过 1000 次的你。哇!你贴出的链接明显地推动了交流,它提供了重要的细节、上下文和其他信息,干得好! appreciated: name: 感谢 - description: 有 20 个帖子都被赞了 1 次 + description: 有 20 个帖子都被喜欢了 1 次 long_description: | 该徽章授予给 20 个不同的帖子都收到了至少 1 个赞的你。社区感谢你对讨论的贡献! respected: name: 尊敬 - description: 在100个帖子内收获2个赞 + description: 在100个帖子内收获2个喜欢 long_description: | - 该徽章授予给 100 个不同的帖子都收到了至少 2 个赞的你。社区越来越尊重你在讨论中的贡献了。 + 该徽章授予给 100 个不同的帖子都收到了至少 2 个喜欢的你。社区越来越尊重你在讨论中的贡献了。 admired: name: 敬仰 description: 有 300 个帖子都被赞了 5 次 @@ -3916,19 +3936,19 @@ zh_CN: 该徽章授予给 300 个不同的帖子都收到了至少 5 个赞的你。哇!社区敬仰你在讨论中频繁和高质量的贡献。 out_of_love: name: 热情 - description: 1天内赞了%{max_likes_per_day}次 + description: 一天内给出了%{max_likes_per_day}个赞 long_description: | - 该徽章授予给一天内赞了 %{max_likes_per_day} 次的你。别忘记休息一下。赞你喜欢的帖子能鼓励社区将来创建更多更精彩的讨论。 + 该徽章授予给一天内给出了%{max_likes_per_day}个赞的你。别忘记休息一下,喜欢你所欣赏的帖子能鼓励社区在将来创造更多更精彩的讨论。 higher_love: name: 激情 - description: 5次每天都赞了 %{max_likes_per_day} 次 + description: 共5次一天内给出了%{max_likes_per_day}个赞 long_description: | - 此徽章授予给连续5天用完所有每日 %{max_likes_per_day} “赞”的你。感谢您每天花时间积极鼓励最好的交流! + 此徽章授予给共5天用完每天%{max_likes_per_day}个赞的你。感谢你每天花时间积极鼓励最好的交流! crazy_in_love: name: 狂热 - description: 一天内送出 %{max_likes_per_day} 个赞达 20 次 + description: 共20次一天内给出了%{max_likes_per_day}个赞 long_description: | - 此徽章授予给每天都送完所有的每日 %{max_likes_per_day} 赞的你。哇!你是鼓励社区成员的榜样! + 此徽章授予给每天都给出所有%{max_likes_per_day}个赞的你。哇!你是鼓励社区成员的榜样! thank_you: name: 感谢你 description: 有 20 个被赞的帖子,给出过 10 个赞 @@ -4115,16 +4135,15 @@ zh_CN: placeholder: "旧金山,加利福尼亚" colors: title: "主题" - themes_further_reading: - title: "主题" - description: - - - - '热门的主题组件(更多内容请浏览#theme)”' fonts: title: "字体" + fields: + body_font: + label: "正文字体" + heading_font: + label: "标题字体" + font_preview: + label: "预览" logos: title: "标志" fields: @@ -4172,9 +4191,6 @@ zh_CN: disabled: "由于禁用了本地登录,因此无法向任何人发送邀请。请继续下一步。" finished: title: "你的 Discourse 已经准备就绪!" - description: | -

如果你觉得需要修改这些设置,随时重新运行此向导或访问管理员分块;你可以在站点菜单找到扳手图标旁找到。

-

祝你玩得开心,还有祝你建设新社区好运!

search_logs: graph_title: "搜索计数" joined: "已加入" @@ -4206,12 +4222,12 @@ zh_CN: already_handled: "谢谢,但我们已经审查了那篇文章,并确定它不需要再次标记。" priorities: low: "低" - medium: "中等" + medium: "中" high: "高" sensitivity: disabled: "停用" low: "低" - medium: "中等" + medium: "中" high: "高" must_claim: "在你操作之前你必须认领此条目。" user_claimed: "此条目已被其他用户认领。" @@ -4231,6 +4247,7 @@ zh_CN: email_auth_res_enqueue: "此电子邮件未通过DMARC检查,很可能不是来自其所声称的来源。检查原始电子邮件的标头以获取更多信息。" email_spam: "该电子邮件被`email_in_spam_header`中定义的标头标记为垃圾邮件。" suspect_user: "这位新用户输入的个人资料信息但没有阅读任何主题或帖子,意味着他可能是垃圾发送者。参见`approve_suspect_users`。" + contains_media: "该帖包括嵌入式媒体。参见`review_media_unless_trust_level`。" actions: agree: title: "同意......" diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index 779cc491d1..485a00cb5a 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -350,14 +350,6 @@ zh_TW: 你可以編輯你先前的回應訊息,選擇字句後點選 引用 來回應。 對於大家而言,閱讀包含較多回覆的少量貼文,會比閱讀多篇瑣碎貼文更為輕鬆。 - get_a_room: | - ### 考慮回應更多參與者 - - 你已經在這個討論話題中,回應了 @%{reply_username} %{count} 次。 - - 你有考慮回應討論中的 *其他人* 嗎?一個好的討論,需要納入更多人的觀點與意見。 - - 如果你還是想要與他繼續討論,請考慮透過[個人訊息](%{base_path}/u/%{reply_username})來進行。 too_many_replies: | ### 你的回應數量已經達到本主題的回覆上限 @@ -1475,7 +1467,6 @@ zh_TW: privacy_policy_url: "如果你想要使用一個部署在某個地方的隱私政策文檔,那麼請在此填寫其完整URL地址。" log_anonymizer_details: "是否在匿名後將使用者的詳細資訊保留在系統日誌(log)中。 若網站遵守一般資料保護規範(GDPR)時,您需要關閉它。" newuser_spam_host_threshold: "新使用者能添加至指向同一主機的連結的 newuser_spam_host_threshold 次數。超過閾值後將使用者視為垃圾投遞者。" - staff_like_weight: "管理員按讚時所給予的額外加權係數。" topic_view_duration_hours: "按照每 IP/使用者每 N 小時來記錄一次新的主題訪問" user_profile_view_duration_hours: "按照每 IP/使用者每 N 小時來記錄使用者資料訪問數" levenshtein_distance_spammer_emails: "當比對廣告Email時,數字與文字將仍使用模糊比對" @@ -1598,7 +1589,6 @@ zh_TW: display_name_on_posts: "在使用者的貼文中顯示他們的全名以及他們的 @username。" show_time_gap_days: "如果兩個貼文發表間隔多天,在主題中顯示時間間隔提示。" short_progress_text_threshold: "在主題中的貼文數超過這個設定後,進度條將只顯示當前的貼文數。如果你更改了進度條的寬度,你需要修改這個值。" - default_code_lang: "預設語法高亮語言,使用 GitHub 代碼塊(lang-auto、ruby、python或者其他等等。)" warn_reviving_old_topic_age: "當有人開始回覆最後一貼超過一定天數前的主題時,將有一個警告顯示,不鼓勵他們復活一個老的討論。將其設置為 0 以禁用。" autohighlight_all_code: "即使未顯示特定語言,仍為所有預編排程式套用程式碼顏色提示" highlighted_languages: "已包含語法突顯(Syntax Highlighting)規則。(警告:包含太多語言可能會影響論壇效能)請參閱:https://highlightjs.org/static/demo看更多範例。" @@ -3355,8 +3345,6 @@ zh_TW: placeholder: "舊金山,加洲(California)" colors: title: "主題" - themes_further_reading: - title: "佈景主題" logos: title: "標誌" fields: @@ -3404,9 +3392,6 @@ zh_TW: disabled: "由於禁用了本地登入(local logins),因此無法向任何人發送邀請,請繼續下一步。" finished: title: "你的 Discourse 已經準備就緒!" - description: | -

如果你覺得需要修改這些設定,訪問管理員面板;你可以在網站導覽列找到板手圖示(icon),板手圖示旁找到這些設定。

-

祝你玩得開心,還有祝你建設新社群好運!

search_logs: graph_title: "搜尋計數" joined: "建立日期" diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index 222c5b3918..6fd053d5d9 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -5,7 +5,7 @@ types { upstream discourse { server unix:/var/www/discourse/tmp/sockets/nginx.http.sock; - server unix:/var/www/discourse/tmp/sockets/nginx.https.sock; + server unix:/var/www/discourse/tmp/sockets/nginx.https.sock; } # inactive means we keep stuff around for 1440m minutes regardless of last access (1 week) @@ -112,7 +112,7 @@ server { break; } - location ~* (fonts|assets|plugins|uploads)/.*\.(eot|ttf|woff|woff2|ico)$ { + location ~* (fonts|assets|plugins|uploads)/.*\.(eot|ttf|woff|woff2|ico|otf)$ { expires 1y; add_header Cache-Control public,immutable; add_header Access-Control-Allow-Origin *; diff --git a/config/routes.rb b/config/routes.rb index 342ced7813..0c5e28b424 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -66,7 +66,6 @@ Discourse::Application.routes.draw do get "site/basic-info" => 'site#basic_info' get "site/statistics" => 'site#statistics' - get "site/selectable-avatars" => "site#selectable_avatars" get "srv/status" => "forums#status" @@ -358,6 +357,7 @@ Discourse::Application.routes.draw do get "session/sso_provider" => "session#sso_provider" get "session/current" => "session#current" get "session/csrf" => "session#csrf" + get "session/hp" => "session#get_honeypot_value" get "session/email-login/:token" => "session#email_login_info" post "session/email-login/:token" => "session#email_login" get "session/otp/:token" => "session#one_time_password", constraints: { token: /[0-9a-f]+/ } @@ -406,7 +406,6 @@ Discourse::Application.routes.draw do put "#{root_path}/second_factors_backup" => "users#create_second_factor_backup" put "#{root_path}/update-activation-email" => "users#update_activation_email" - get "#{root_path}/hp" => "users#get_honeypot_value" post "#{root_path}/email-login" => "users#email_login" get "#{root_path}/admin-login" => "users#admin_login" put "#{root_path}/admin-login" => "users#admin_login" diff --git a/config/site_settings.yml b/config/site_settings.yml index 431c605fa8..42722d958f 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -329,6 +329,10 @@ basic: default: "helvetica" enum: "BaseFontSetting" refresh: true + heading_font: + default: "helvetica" + enum: "BaseFontSetting" + refresh: true login: invite_only: @@ -522,7 +526,7 @@ users: reserved_usernames: type: list list_type: compact - default: "admin|moderator|administrator|mod|sys|system|community|info|you|name|username|user|nickname|discourse|discourseorg|discourseforum|support|hp" + default: "admin|moderator|administrator|mod|sys|system|community|info|you|name|username|user|nickname|discourse|discourseorg|discourseforum|support" min_password_length: client: true default: 10 @@ -597,6 +601,9 @@ users: anonymous_account_duration_minutes: default: 10080 max: 99000 + allow_users_to_hide_profile: + default: true + client: true hide_user_profiles_from_public: default: false client: true @@ -939,7 +946,7 @@ posting: embed_any_origin: false embed_topics_list: false embed_set_canonical_url: false - embed_unlisted: false + embed_unlisted: true embed_truncate: true embed_support_markdown: false allowed_embed_selectors: "" @@ -1332,6 +1339,7 @@ files: validator: "SelectableAvatarsEnabledValidator" selectable_avatars: default: "" + client: true type: uploaded_image_list allow_all_attachments_for_group_messages: false png_to_jpg_quality: diff --git a/db/fixtures/600_themes.rb b/db/fixtures/600_themes.rb index 23cc993a27..718f650c27 100644 --- a/db/fixtures/600_themes.rb +++ b/db/fixtures/600_themes.rb @@ -2,22 +2,13 @@ # we can not guess what to do if customization already started, so skip it if !Theme.exists? - STDERR.puts "> Seeding dark and light themes" + STDERR.puts "> Seeding theme and color schemes" name = I18n.t("color_schemes.dark_theme_name") dark_scheme = ColorScheme.find_by(base_scheme_id: "Dark") - dark_scheme ||= ColorScheme.create_from_base(name: name, via_wizard: true, base_scheme_id: "Dark") - - name = I18n.t('color_schemes.dark_theme_name') - - _dark_theme = Theme.create!( - name: name, user_id: -1, - color_scheme_id: dark_scheme.id, - user_selectable: true - ) + dark_scheme ||= ColorScheme.create_from_base(name: name, via_wizard: true, base_scheme_id: "Dark", user_selectable: true) name = I18n.t('color_schemes.default_theme_name') - default_theme = Theme.create!(name: name, user_id: -1, user_selectable: true) - + default_theme = Theme.create!(name: name, user_id: -1) default_theme.set_default! end diff --git a/db/migrate/20120803191426_add_admin_flag_to_users.rb b/db/migrate/20120803191426_add_admin_flag_to_users.rb index 0e0908a817..67e5f532d0 100644 --- a/db/migrate/20120803191426_add_admin_flag_to_users.rb +++ b/db/migrate/20120803191426_add_admin_flag_to_users.rb @@ -4,8 +4,5 @@ class AddAdminFlagToUsers < ActiveRecord::Migration[4.2] def change add_column :users, :admin, :boolean, default: false, null: false add_column :users, :moderator, :boolean, default: false, null: false - - # Make all of us admins - execute "UPDATE users SET admin = TRUE where lower(username) in ('eviltrout', 'codinghorror', 'sam', 'hanzo')" end end diff --git a/db/migrate/20200311135425_clear_approved_users_from_the_review_queue.rb b/db/migrate/20200311135425_clear_approved_users_from_the_review_queue.rb index 07cb662d74..d26e4acb4c 100644 --- a/db/migrate/20200311135425_clear_approved_users_from_the_review_queue.rb +++ b/db/migrate/20200311135425_clear_approved_users_from_the_review_queue.rb @@ -5,7 +5,8 @@ class ClearApprovedUsersFromTheReviewQueue < ActiveRecord::Migration[6.0] UPDATE reviewables r SET status = #{Reviewable.statuses[:approved]} FROM users u - WHERE u.approved = true AND r.type = 'ReviewableUser' AND r.status = #{Reviewable.statuses[:pending]} + WHERE u.id = r.target_id AND u.approved = true + AND r.type = 'ReviewableUser' AND r.status = #{Reviewable.statuses[:pending]} RETURNING r.id SQL diff --git a/db/migrate/20200810194943_change_selectable_avatars_site_setting.rb b/db/migrate/20200810194943_change_selectable_avatars_site_setting.rb new file mode 100644 index 0000000000..a756615008 --- /dev/null +++ b/db/migrate/20200810194943_change_selectable_avatars_site_setting.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +class ChangeSelectableAvatarsSiteSetting < ActiveRecord::Migration[6.0] + def up + selectable_avatars = execute("SELECT value FROM site_settings WHERE name = 'selectable_avatars'") + return if selectable_avatars.cmd_tuples == 0 + + # Keep old site setting value as a backup + execute <<~SQL + UPDATE site_settings + SET name = 'selectable_avatars_urls' + WHERE name = 'selectable_avatars' + SQL + + # Extract SHA1s from URLs and then use them for upload ID lookups + urls = [] + sha1s = [] + selectable_avatars.first["value"].split("\n").each do |url| + match = url.match(/(\/original\/\dX[\/\.\w]*\/(\h+)[\.\w]*)/) + if match.present? + urls << match[1] + sha1s << match[2] + else + STDERR.puts "Could not extract a SHA1 from #{url}" + end + end + + # Ensure at least one URL or SHA1 exists so the query below can be valid + return if urls.size == 0 && sha1s.size == 0 + + uploads_query = [] + uploads_query << "url IN (#{urls.map { |url| ActiveRecord::Base.connection.quote(url) }.join(',')})" if urls.size > 0 + uploads_query << "sha1 IN (#{sha1s.map { |sha1| ActiveRecord::Base.connection.quote(sha1) }.join(',')})" if sha1s.size > 0 + uploads_query = "SELECT DISTINCT id FROM uploads WHERE #{uploads_query.join(" OR ")}" + + upload_ids = execute(uploads_query).map { |row| row["id"] } + return if upload_ids.size == 0 + + execute <<~SQL + INSERT INTO site_settings(name, data_type, value, created_at, updated_at) + SELECT 'selectable_avatars', data_type, '#{upload_ids.join("|")}', created_at, NOW() + FROM site_settings + WHERE name = 'selectable_avatars_urls' + SQL + end + + def down + execute("DELETE FROM site_settings WHERE name = 'selectable_avatars'") + execute("UPDATE site_settings SET name = 'selectable_avatars' WHERE name = 'selectable_avatars_urls'") + end +end diff --git a/db/migrate/20200918095554_add_user_api_key_scopes.rb b/db/migrate/20200918095554_add_user_api_key_scopes.rb new file mode 100644 index 0000000000..93a2ec9a7a --- /dev/null +++ b/db/migrate/20200918095554_add_user_api_key_scopes.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class AddUserApiKeyScopes < ActiveRecord::Migration[6.0] + def change + create_table :user_api_key_scopes do |t| + t.integer :user_api_key_id, null: false + t.string :name, null: false + t.timestamps + end + + add_index :user_api_key_scopes, :user_api_key_id + + reversible do |dir| + dir.up do + execute <<~SQL + INSERT INTO user_api_key_scopes + ( + user_api_key_id, + name, + created_at, + updated_at + ) + SELECT + user_api_keys.id, + unnest(user_api_keys.scopes), + created_at, + updated_at + FROM user_api_keys + SQL + + Migration::SafeMigrate.disable! + change_column_null :user_api_keys, :scopes, true + change_column_default :user_api_keys, :scopes, nil + Migration::SafeMigrate.enable! + + Migration::ColumnDropper.mark_readonly(:user_api_keys, :scopes) + end + + dir.down do + change_column_null :user_api_keys, :scopes, false + change_column_default :user_api_keys, :scopes, [] + Migration::ColumnDropper.drop_readonly(:user_api_keys, :scopes) + end + end + end +end diff --git a/db/migrate/20201006021020_add_requested_by_to_email_change_request.rb b/db/migrate/20201006021020_add_requested_by_to_email_change_request.rb new file mode 100644 index 0000000000..6753a18975 --- /dev/null +++ b/db/migrate/20201006021020_add_requested_by_to_email_change_request.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddRequestedByToEmailChangeRequest < ActiveRecord::Migration[6.0] + def up + add_column :email_change_requests, :requested_by_user_id, :integer, null: true + + DB.exec("CREATE INDEX IF NOT EXISTS idx_email_change_requests_on_requested_by ON email_change_requests(requested_by_user_id)") + end + + def down + remove_column :email_change_requests, :requested_by_user_id + end +end diff --git a/db/migrate/20201007124955_add_digest_attempted_at_to_user_stats.rb b/db/migrate/20201007124955_add_digest_attempted_at_to_user_stats.rb new file mode 100644 index 0000000000..496e69bc64 --- /dev/null +++ b/db/migrate/20201007124955_add_digest_attempted_at_to_user_stats.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddDigestAttemptedAtToUserStats < ActiveRecord::Migration[6.0] + def change + add_column :user_stats, :digest_attempted_at, :timestamp + end +end diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb index d2545179ff..010a7f14d9 100644 --- a/lib/auth/default_current_user_provider.rb +++ b/lib/auth/default_current_user_provider.rb @@ -307,7 +307,7 @@ class Auth::DefaultCurrentUserProvider protected def lookup_user_api_user_and_update_key(user_api_key, client_id) - if api_key = UserApiKey.active.with_key(user_api_key).includes(:user).first + if api_key = UserApiKey.active.with_key(user_api_key).includes(:user, :scopes).first unless api_key.allow?(@env) raise Discourse::InvalidAccess end diff --git a/lib/autospec/qunit_runner.rb b/lib/autospec/qunit_runner.rb index 579e6834ce..ff97d9896c 100644 --- a/lib/autospec/qunit_runner.rb +++ b/lib/autospec/qunit_runner.rb @@ -29,9 +29,9 @@ module Autospec end # Discourse specific - reload(%r{^test/javascripts/fixtures/.+_fixtures\.js(\.es6)?$}) - reload(%r{^test/javascripts/(helpers|mixins)/.+\.js(\.es6)?$}) - reload("test/javascripts/test_helper.js") + reload(%r{^discourse/tests/javascripts/fixtures/.+_fixtures\.js(\.es6)?$}) + reload(%r{^discourse/tests/javascripts/(helpers|mixins)/.+\.js(\.es6)?$}) + reload("app/assets/javascripts/discoruse/tests/javascripts/test_helper.js") watch(%r{^plugins/.*/test/.+\.js(\.es6)?$}) diff --git a/lib/backup_restore.rb b/lib/backup_restore.rb index 6528b1728e..b8a19efe4a 100644 --- a/lib/backup_restore.rb +++ b/lib/backup_restore.rb @@ -172,9 +172,9 @@ module BackupRestore def self.spawn_process!(type, user_id, opts) script = File.join(Rails.root, "script", "spawn_backup_restore.rb") - command = ["bundle", "exec", "ruby", script, type, user_id, opts.to_json].shelljoin + command = ["bundle", "exec", "ruby", script, type, user_id, opts.to_json].map(&:to_s) - pid = spawn({ "RAILS_DB" => RailsMultisite::ConnectionManagement.current_db }, command) + pid = spawn({ "RAILS_DB" => RailsMultisite::ConnectionManagement.current_db }, *command) Process.detach(pid) end diff --git a/lib/backup_restore/backuper.rb b/lib/backup_restore/backuper.rb index 8f00090887..dea11cda14 100644 --- a/lib/backup_restore/backuper.rb +++ b/lib/backup_restore/backuper.rb @@ -96,6 +96,8 @@ module BackupRestore end def listen_for_shutdown_signal + BackupRestore.clear_shutdown_signal! + Thread.new do while BackupRestore.is_operation_running? exit if BackupRestore.should_shutdown? diff --git a/lib/backup_restore/system_interface.rb b/lib/backup_restore/system_interface.rb index 2ed95137bc..fec6735417 100644 --- a/lib/backup_restore/system_interface.rb +++ b/lib/backup_restore/system_interface.rb @@ -44,6 +44,8 @@ module BackupRestore end def listen_for_shutdown_signal + BackupRestore.clear_shutdown_signal! + Thread.new do while BackupRestore.is_operation_running? exit if BackupRestore.should_shutdown? diff --git a/lib/configurable_urls.rb b/lib/configurable_urls.rb index 747fc7d76a..f20321ff6d 100644 --- a/lib/configurable_urls.rb +++ b/lib/configurable_urls.rb @@ -3,15 +3,15 @@ module ConfigurableUrls def faq_path - SiteSetting.faq_url.blank? ? "#{Discourse::base_uri}/faq" : SiteSetting.faq_url + SiteSetting.faq_url.blank? ? "#{Discourse.base_path}/faq" : SiteSetting.faq_url end def tos_path - SiteSetting.tos_url.blank? ? "#{Discourse::base_uri}/tos" : SiteSetting.tos_url + SiteSetting.tos_url.blank? ? "#{Discourse.base_path}/tos" : SiteSetting.tos_url end def privacy_path - SiteSetting.privacy_policy_url.blank? ? "#{Discourse::base_uri}/privacy" : SiteSetting.privacy_policy_url + SiteSetting.privacy_policy_url.blank? ? "#{Discourse.base_path}/privacy" : SiteSetting.privacy_policy_url end end diff --git a/lib/content_security_policy/default.rb b/lib/content_security_policy/default.rb index 52a116c065..29c59c44d9 100644 --- a/lib/content_security_policy/default.rb +++ b/lib/content_security_policy/default.rb @@ -40,7 +40,7 @@ class ContentSecurityPolicy if can_use_s3_cdn && s3_cdn s3_cdn + dir elsif can_use_cdn && cdn - cdn + Discourse.base_uri + dir + cdn + Discourse.base_path + dir else base + dir end diff --git a/lib/content_security_policy/middleware.rb b/lib/content_security_policy/middleware.rb index d9b8ece251..d587f5994c 100644 --- a/lib/content_security_policy/middleware.rb +++ b/lib/content_security_policy/middleware.rb @@ -15,7 +15,7 @@ class ContentSecurityPolicy # The EnforceHostname middleware ensures request.host_with_port can be trusted protocol = (SiteSetting.force_https || request.ssl?) ? "https://" : "http://" - base_url = protocol + request.host_with_port + Discourse.base_uri + base_url = protocol + request.host_with_port + Discourse.base_path theme_ids = env[:resolved_theme_ids] diff --git a/lib/discourse.rb b/lib/discourse.rb index a35a04dc60..d9cd92196c 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -380,10 +380,15 @@ module Discourse SiteSetting.force_hostname.presence || RailsMultisite::ConnectionManagement.current_hostname end - def self.base_uri(default_value = "") + def self.base_path(default_value = "") ActionController::Base.config.relative_url_root.presence || default_value end + def self.base_uri(default_value = "") + deprecate("Discourse.base_uri is deprecated, use Discourse.base_path instead") + base_path(default_value) + end + def self.base_protocol SiteSetting.force_https? ? "https" : "http" end @@ -401,7 +406,7 @@ module Discourse end def self.base_url - base_url_no_prefix + base_uri + base_url_no_prefix + base_path end def self.route_for(uri) @@ -415,8 +420,8 @@ module Discourse return unless uri path = +(uri.path || "") - if !uri.host || (uri.host == Discourse.current_hostname && path.start_with?(Discourse.base_uri)) - path.slice!(Discourse.base_uri) + if !uri.host || (uri.host == Discourse.current_hostname && path.start_with?(Discourse.base_path)) + path.slice!(Discourse.base_path) return Rails.application.routes.recognize_path(path) end @@ -426,7 +431,6 @@ module Discourse end class << self - alias_method :base_path, :base_uri alias_method :base_url_no_path, :base_url_no_prefix end diff --git a/lib/discourse_tagging.rb b/lib/discourse_tagging.rb index 7b47a52456..15d54defb3 100644 --- a/lib/discourse_tagging.rb +++ b/lib/discourse_tagging.rb @@ -23,31 +23,47 @@ module DiscourseTagging end end + # tags currently on the topic old_tag_names = topic.tags.pluck(:name) || [] + # tags we're trying to add to the topic new_tag_names = tag_names - old_tag_names + # tag names being removed from the topic removed_tag_names = old_tag_names - tag_names - # Protect staff-only tags - unless guardian.is_staff? - all_staff_tags = DiscourseTagging.staff_tag_names - hidden_tags = DiscourseTagging.hidden_tag_names + # tag names which are visible, but not usable, by *some users* + readonly_tags = DiscourseTagging.readonly_tag_names(guardian) + # tags names which are not visibile or usuable by *some users* + hidden_tags = DiscourseTagging.hidden_tag_names(guardian) - staff_tags = new_tag_names & all_staff_tags - staff_tags += new_tag_names & hidden_tags - if staff_tags.present? - topic.errors.add(:base, I18n.t("tags.staff_tag_disallowed", tag: staff_tags.join(" "))) - return false - end + # tag names which ARE permitted by *this user* + permitted_tags = DiscourseTagging.permitted_tag_names(guardian) - staff_tags = removed_tag_names & all_staff_tags - if staff_tags.present? - topic.errors.add(:base, I18n.t("tags.staff_tag_remove_disallowed", tag: staff_tags.join(" "))) - return false - end - - tag_names += removed_tag_names & hidden_tags + # If this user has explicit permission to use certain tags, + # we need to ensure those tags are removed from the list of + # restricted tags + if permitted_tags.present? + readonly_tags = readonly_tags - permitted_tags + hidden_tags = hidden_tags - permitted_tags end + # visible, but not usable, tags this user is trying to use + disallowed_tags = new_tag_names & readonly_tags + # hidden tags this user is trying to use + disallowed_tags += new_tag_names & hidden_tags + + if disallowed_tags.present? + topic.errors.add(:base, I18n.t("tags.restricted_tag_disallowed", tag: disallowed_tags.join(" "))) + return false + end + + removed_readonly_tags = removed_tag_names & readonly_tags + if removed_readonly_tags.present? + topic.errors.add(:base, I18n.t("tags.restricted_tag_remove_disallowed", tag: removed_readonly_tags.join(" "))) + return false + end + + tag_names += removed_tag_names & hidden_tags + category = topic.category tag_names = tag_names + old_tag_names if append @@ -182,8 +198,7 @@ module DiscourseTagging INNER JOIN tag_group_memberships tgm ON tgm.tag_id = t.id /*and_name_like*/ INNER JOIN tag_groups tg ON tg.id = tgm.tag_group_id INNER JOIN tag_group_permissions tgp - ON tg.id = tgp.tag_group_id - AND tgp.group_id = #{Group::AUTO_GROUPS[:everyone]} + ON tg.id = tgp.tag_group_id /*and_group_ids*/ AND tgp.permission_type = #{TagGroupPermission.permission_types[:full]} ) SQL @@ -217,6 +232,8 @@ module DiscourseTagging sql = +"WITH #{TAG_GROUP_RESTRICTIONS_SQL}, #{CATEGORY_RESTRICTIONS_SQL}" if (opts[:for_input] || opts[:for_topic]) && filter_for_non_staff sql << ", #{PERMITTED_TAGS_SQL} " + builder_params[:group_ids] = permitted_group_ids(guardian) + sql.gsub!("/*and_group_ids*/", "AND group_id IN (:group_ids)") end outer_join = category.nil? || category.allow_global_tags || !category_has_restricted_tags @@ -296,12 +313,23 @@ module DiscourseTagging end if filter_for_non_staff - # remove hidden tags - builder.where(<<~SQL, Group::AUTO_GROUPS[:everyone]) + group_ids = permitted_group_ids(guardian) + + builder.where(<<~SQL, group_ids, group_ids) id NOT IN ( - SELECT tag_id - FROM tag_group_memberships tgm - WHERE tag_group_id NOT IN (SELECT tag_group_id FROM tag_group_permissions WHERE group_id = ?) + (SELECT tgm.tag_id + FROM tag_group_permissions tgp + INNER JOIN tag_groups tg ON tgp.tag_group_id = tg.id + INNER JOIN tag_group_memberships tgm ON tg.id = tgm.tag_group_id + WHERE tgp.group_id NOT IN (?)) + + EXCEPT + + (SELECT tgm.tag_id + FROM tag_group_permissions tgp + INNER JOIN tag_groups tg ON tgp.tag_group_id = tg.id + INNER JOIN tag_group_memberships tgm ON tg.id = tgm.tag_group_id + WHERE tgp.group_id IN (?)) ) SQL end @@ -348,16 +376,52 @@ module DiscourseTagging guardian&.is_staff? ? [] : hidden_tags_query.pluck(:name) end + # most restrictive level of tag groups def self.hidden_tags_query - Tag.joins(:tag_groups) + query = Tag.joins(:tag_groups) .where('tag_groups.id NOT IN ( SELECT tag_group_id FROM tag_group_permissions WHERE group_id = ?)', Group::AUTO_GROUPS[:everyone] ) + + query end + def self.permitted_group_ids(guardian = nil) + group_ids = [Group::AUTO_GROUPS[:everyone]] + + if guardian.authenticated? + group_ids.concat(guardian.user.groups.pluck(:id)) + end + + group_ids + end + + # read-only tags for this user + def self.readonly_tag_names(guardian = nil) + return [] if guardian&.is_staff? + + query = Tag.joins(tag_groups: :tag_group_permissions) + .where('tag_group_permissions.permission_type = ?', + TagGroupPermission.permission_types[:readonly]) + + query.pluck(:name) + end + + # explicit permissions to use these tags + def self.permitted_tag_names(guardian = nil) + query = Tag.joins(tag_groups: :tag_group_permissions) + .where('tag_group_permissions.group_id IN (?) AND tag_group_permissions.permission_type = ?', + permitted_group_ids(guardian), + TagGroupPermission.permission_types[:full] + ) + + query.pluck(:name).uniq + end + + # middle level of tag group restrictions def self.staff_tag_names tag_names = Discourse.cache.read(TAGS_STAFF_CACHE_KEY) diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index f27b8518c8..e1aa8ea17d 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -395,7 +395,7 @@ module Email text, elided_text, text_format = markdown, elided_markdown, Receiver::formats[:markdown] end - if SiteSetting.strip_incoming_email_lines + if SiteSetting.strip_incoming_email_lines && text.present? in_code = nil text = text.lines.map! do |line| diff --git a/lib/email/sender.rb b/lib/email/sender.rb index 4bc8dafd6a..0668d7ea5a 100644 --- a/lib/email/sender.rb +++ b/lib/email/sender.rb @@ -345,8 +345,11 @@ module Email content = Mail::Part.new do content_type "multipart/alternative" - part html_part - part text_part + # we have to re-specify the charset and give the part the decoded body + # here otherwise the parts will get encoded with US-ASCII which makes + # a bunch of characters not render correctly in the email + part content_type: "text/html; charset=utf-8", body: html_part.body.decoded + part content_type: "text/plain; charset=utf-8", body: text_part.body.decoded end @message.parts.unshift(content) diff --git a/lib/email_updater.rb b/lib/email_updater.rb index ad886a066e..5b7b0aec95 100644 --- a/lib/email_updater.rb +++ b/lib/email_updater.rb @@ -41,14 +41,9 @@ class EmailUpdater UserHistory.create!(action: UserHistory.actions[:add_email], acting_user_id: @user.id) end - if @guardian.is_staff? && !@user.staff? - send_email_notification(@user.email, email) - update_user_email(old_email, email) - send_email(:forgot_password, @user.email_tokens.create!(email: @user.email)) - return - end - - change_req = EmailChangeRequest.find_or_initialize_by(user_id: @user.id, new_email: email) + change_req = EmailChangeRequest.find_or_initialize_by( + user_id: @user.id, new_email: email, requested_by: @guardian.user + ) if change_req.new_record? change_req.old_email = old_email change_req.new_email = email @@ -120,6 +115,7 @@ class EmailUpdater else @user.user_emails.create!(email: new_email) end + @user.reload DiscourseEvent.trigger(:user_updated, @user) @user.set_automatic_groups diff --git a/lib/file_store/local_store.rb b/lib/file_store/local_store.rb index 526b82f41b..25649532c0 100644 --- a/lib/file_store/local_store.rb +++ b/lib/file_store/local_store.rb @@ -8,7 +8,7 @@ module FileStore def store_file(file, path) copy_file(file, "#{public_dir}#{path}") - "#{Discourse.base_uri}#{path}" + "#{Discourse.base_path}#{path}" end def remove_file(url, _) @@ -36,7 +36,7 @@ module FileStore end def relative_base_url - File.join(Discourse.base_uri, upload_path) + File.join(Discourse.base_path, upload_path) end def external? diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb index d80e1ff5d7..fcc289123c 100644 --- a/lib/file_store/s3_store.rb +++ b/lib/file_store/s3_store.rb @@ -169,9 +169,9 @@ module FileStore url.sub(File.join("#{schema}#{absolute_base_url}", folder), File.join(SiteSetting.Upload.s3_cdn_url, "/")) end - def signed_url_for_path(path, expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS) + def signed_url_for_path(path, expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS, force_download: false) key = path.sub(absolute_base_url + "/", "") - presigned_url(key, expires_in: expires_in) + presigned_url(key, expires_in: expires_in, force_download: force_download) end def cache_avatar(avatar, user_id) diff --git a/lib/generators/plugin/templates/acceptance-test.js.es6.erb b/lib/generators/plugin/templates/acceptance-test.js.es6.erb index 85da8e9791..472650a266 100644 --- a/lib/generators/plugin/templates/acceptance-test.js.es6.erb +++ b/lib/generators/plugin/templates/acceptance-test.js.es6.erb @@ -1,4 +1,4 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("<%= name %>", { loggedIn: true }); diff --git a/lib/guardian.rb b/lib/guardian.rb index bba577184e..b67e34e29c 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -170,7 +170,10 @@ class Guardian end def can_moderate?(obj) - obj && authenticated? && !is_silenced? && (is_staff? || (obj.is_a?(Topic) && @user.has_trust_level?(TrustLevel[4]))) + obj && authenticated? && !is_silenced? && ( + is_staff? || + (obj.is_a?(Topic) && @user.has_trust_level?(TrustLevel[4]) && can_see_topic?(obj)) + ) end alias :can_see_flags? :can_moderate? diff --git a/lib/guardian/group_guardian.rb b/lib/guardian/group_guardian.rb index 7246f6c5e1..7aa0f9ebfc 100644 --- a/lib/guardian/group_guardian.rb +++ b/lib/guardian/group_guardian.rb @@ -35,6 +35,7 @@ module GroupGuardian def can_see_group_messages?(group) return true if is_admin? + return true if is_moderator? && group.id == Group::AUTO_GROUPS[:moderators] SiteSetting.enable_personal_messages? && group.users.include?(user) end diff --git a/lib/guardian/user_guardian.rb b/lib/guardian/user_guardian.rb index 6e2ccdb03c..a133e82f18 100644 --- a/lib/guardian/user_guardian.rb +++ b/lib/guardian/user_guardian.rb @@ -111,6 +111,7 @@ module UserGuardian def can_see_profile?(user) return false if user.blank? + return true if !SiteSetting.allow_users_to_hide_profile? # If a user has hidden their profile, restrict it to them and staff if user.user_option.try(:hide_profile_and_presence?) diff --git a/lib/middleware/anonymous_cache.rb b/lib/middleware/anonymous_cache.rb index 819723f23b..6fc70fb720 100644 --- a/lib/middleware/anonymous_cache.rb +++ b/lib/middleware/anonymous_cache.rb @@ -329,6 +329,24 @@ module Middleware helper.force_anonymous! end + if (env["HTTP_DISCOURSE_BACKGROUND"] == "true") && (queue_time = env["REQUEST_QUEUE_SECONDS"]) + max_time = GlobalSetting.background_requests_max_queue_length.to_f + if max_time > 0 && queue_time.to_f > max_time + return [ + 429, + { + "content-type" => "application/json; charset=utf-8" + }, + [{ + errors: I18n.t("rate_limiter.slow_down"), + extras: { + wait_seconds: 5 + (5 * rand).round(2) + } + }.to_json] + ] + end + end + result = if helper.cacheable? helper.cached(env) || helper.cache(@app.call(env), env) diff --git a/lib/plugin/metadata.rb b/lib/plugin/metadata.rb index 0dfcc5d124..de74293d80 100644 --- a/lib/plugin/metadata.rb +++ b/lib/plugin/metadata.rb @@ -46,7 +46,7 @@ class Plugin::Metadata "discourse-no-bump", "discourse-oauth2-basic", "discourse-patreon", - "discourse-perspective", + "discourse-perspective-api", "discourse-plugin-discord-auth", "discourse-plugin-linkedin-auth", "discourse-plugin-office365-auth", @@ -63,7 +63,8 @@ class Plugin::Metadata "discourse-solved", "discourse-spoiler-alert", "discourse-user-notes", - "discourse-styleguide", + "styleguide", + "discourse-teambuild", "discourse-tooltips", "discourse-translator", "discourse-user-card-badges", diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index 6f81932419..da60a75436 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -200,7 +200,7 @@ module PrettyText def self.paths_json paths = { - baseUri: Discourse::base_uri, + baseUri: Discourse.base_path, CDN: Rails.configuration.action_controller.asset_host, } @@ -490,13 +490,13 @@ module PrettyText case type when USER_TYPE - element['href'] = "#{Discourse::base_uri}/u/#{UrlHelper.encode_component(name)}" + element['href'] = "#{Discourse.base_path}/u/#{UrlHelper.encode_component(name)}" when GROUP_MENTIONABLE_TYPE element['class'] = 'mention-group notify' - element['href'] = "#{Discourse::base_uri}/groups/#{UrlHelper.encode_component(name)}" + element['href'] = "#{Discourse.base_path}/groups/#{UrlHelper.encode_component(name)}" when GROUP_TYPE element['class'] = 'mention-group' - element['href'] = "#{Discourse::base_uri}/groups/#{UrlHelper.encode_component(name)}" + element['href'] = "#{Discourse.base_path}/groups/#{UrlHelper.encode_component(name)}" end end end diff --git a/lib/search.rb b/lib/search.rb index dcd7ded42a..3daf57c5e7 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -88,12 +88,18 @@ class Search end end - data.gsub!(EmailCook.url_regexp) do |url| - uri = URI.parse(url) - uri.query = nil - uri.to_s - rescue URI::Error - # Don't fail even if URL turns out to be invalid + data.gsub!(/\S+/) do |str| + if str =~ /^["]?((https?:\/\/)[\S]+)["]?$/ + begin + uri = URI.parse(Regexp.last_match[1]) + uri.query = nil + str = uri.to_s + rescue URI::Error + # don't fail if uri does not parse + end + end + + str end data @@ -373,6 +379,14 @@ class Search posts.where("topics.posts_count >= ?", match.to_i) end + advanced_filter(/^min_posts:(\d+)$/i) do |posts, match| + posts.where("topics.posts_count >= ?", match.to_i) + end + + advanced_filter(/^max_posts:(\d+)$/i) do |posts, match| + posts.where("topics.posts_count <= ?", match.to_i) + end + advanced_filter(/^in:first|^f$/i) do |posts| posts.where("posts.post_number = 1") end diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index da3f6cf981..6b02292c6d 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -209,6 +209,7 @@ module SiteSettingExtension MultiJson.dump(Hash[*@client_settings.map do |name| value = self.public_send(name) value = value.to_s if type_supervisor.get_type(name) == :upload + value = value.map(&:to_s).join("|") if type_supervisor.get_type(name) == :uploaded_image_list [name, value] end.flatten]) end @@ -233,10 +234,12 @@ module SiteSettingExtension .reject { |s, _| !include_hidden && hidden_settings.include?(s) } .map do |s, v| - value = public_send(s) type_hash = type_supervisor.type_hash(s) default = defaults.get(s, default_locale).to_s + value = public_send(s) + value = value.map(&:to_s).join("|") if type_hash[:type].to_s == "uploaded_image_list" + if type_hash[:type].to_s == "upload" && default.to_i < Upload::SEEDED_ID_THRESHOLD @@ -482,7 +485,21 @@ module SiteSettingExtension def setup_methods(name) clean_name = name.to_s.sub("?", "").to_sym - if type_supervisor.get_type(name) == :upload + if type_supervisor.get_type(name) == :uploaded_image_list + define_singleton_method clean_name do + uploads_list = uploads[name] + return uploads_list if uploads_list + + if (value = current[name]).nil? + refresh! + value = current[name] + end + + value = value.split("|").map(&:to_i) + uploads_list = Upload.where(id: value).to_a + uploads[name] = uploads_list if uploads_list + end + elsif type_supervisor.get_type(name) == :upload define_singleton_method clean_name do upload = uploads[name] return upload if upload @@ -552,7 +569,7 @@ module SiteSettingExtension end def clear_uploads_cache(name) - if type_supervisor.get_type(name) == :upload && uploads.has_key?(name) + if (type_supervisor.get_type(name) == :upload || type_supervisor.get_type(name) == :uploaded_image_list) && uploads.has_key?(name) uploads.delete(name) end end diff --git a/lib/site_settings/type_supervisor.rb b/lib/site_settings/type_supervisor.rb index ae78d5dd1f..f30be8cfa0 100644 --- a/lib/site_settings/type_supervisor.rb +++ b/lib/site_settings/type_supervisor.rb @@ -182,6 +182,8 @@ class SiteSettings::TypeSupervisor type = get_data_type(name, val) elsif type == self.class.types[:enum] val = @defaults_provider[name].is_a?(Integer) ? val.to_i : val.to_s + elsif type == self.class.types[:uploaded_image_list] && val.present? + val = val.is_a?(String) ? val : val.map(&:id).join("|") elsif type == self.class.types[:upload] && val.present? val = val.is_a?(Integer) ? val : val.id end diff --git a/lib/site_settings/validations.rb b/lib/site_settings/validations.rb index c49b83910b..6a1f9958c0 100644 --- a/lib/site_settings/validations.rb +++ b/lib/site_settings/validations.rb @@ -173,6 +173,9 @@ module SiteSettings::Validations end def validate_enforce_second_factor(new_val) + if SiteSetting.enable_sso? + return validate_error :second_factor_cannot_be_enforced_with_sso_enabled + end if new_val == "all" && Discourse.enabled_auth_providers.count > 0 auth_provider_names = Discourse.enabled_auth_providers.map(&:name).join(", ") return validate_error(:second_factor_cannot_enforce_with_socials, auth_provider_names: auth_provider_names) diff --git a/lib/stylesheet/importer.rb b/lib/stylesheet/importer.rb index 882ed5c5eb..89572f0203 100644 --- a/lib/stylesheet/importer.rb +++ b/lib/stylesheet/importer.rb @@ -41,18 +41,28 @@ module Stylesheet end register_import "font" do - font = DiscourseFonts.fonts.find { |f| f[:key] == SiteSetting.base_font } + body_font = DiscourseFonts.fonts.find { |f| f[:key] == SiteSetting.base_font } + heading_font = DiscourseFonts.fonts.find { |f| f[:key] == SiteSetting.heading_font } + contents = +"" - contents = if font.present? - <<~EOF - #{font_css(font)} + if body_font.present? + contents << <<~EOF + #{font_css(body_font)} :root { - --font-family: #{font[:stack]}; + --font-family: #{body_font[:stack]}; + } + EOF + end + + if heading_font.present? + contents << <<~EOF + #{font_css(heading_font)} + + :root { + --heading-font-family: #{heading_font[:stack]}; } EOF - else - "" end Import.new("font.scss", source: contents) @@ -62,9 +72,21 @@ module Stylesheet contents = +"" DiscourseFonts.fonts.each do |font| + if font[:key] == "system" + # Overwrite font definition because the preview canvases in the wizard require explicit @font-face definitions. + # uses same technique as https://github.com/jonathantneal/system-font-css + font[:variants] = [ + { src: 'local(".SFNS-Regular"), local(".SFNSText-Regular"), local(".HelveticaNeueDeskInterface-Regular"), local(".LucidaGrandeUI"), local("Segoe UI"), local("Ubuntu"), local("Roboto-Regular"), local("DroidSans"), local("Tahoma")', weight: 400 }, + { src: 'local(".SFNS-Bold"), local(".SFNSText-Bold"), local(".HelveticaNeueDeskInterface-Bold"), local(".LucidaGrandeUI"), local("Segoe UI Bold"), local("Ubuntu Bold"), local("Roboto-Bold"), local("DroidSans-Bold"), local("Tahoma Bold")', weight: 700 } + ] + end + contents << font_css(font) contents << <<~EOF - .font-#{font[:key].tr("_", "-")} { + .body-font-#{font[:key].tr("_", "-")} { + font-family: #{font[:stack]}; + } + .heading-font-#{font[:key].tr("_", "-")} h2 { font-family: #{font[:stack]}; } EOF @@ -266,10 +288,11 @@ module Stylesheet if font[:variants].present? font[:variants].each do |variant| + src = variant[:src] ? variant[:src] : "asset-url(\"/fonts/#{variant[:filename]}?v=#{DiscourseFonts::VERSION}\") format(\"#{variant[:format]}\")" contents << <<~EOF @font-face { font-family: #{font[:name]}; - src: asset-url("/fonts/#{variant[:filename]}?v=#{DiscourseFonts::VERSION}") format("#{variant[:format]}"); + src: #{src}; font-weight: #{variant[:weight]}; } EOF diff --git a/lib/stylesheet/manager.rb b/lib/stylesheet/manager.rb index 816a98ad84..c4a565379d 100644 --- a/lib/stylesheet/manager.rb +++ b/lib/stylesheet/manager.rb @@ -406,12 +406,13 @@ class Stylesheet::Manager cs = @color_scheme || theme&.color_scheme category_updated = Category.where("uploaded_background_id IS NOT NULL").pluck(:updated_at).map(&:to_i).sum + fonts = "#{SiteSetting.base_font}-#{SiteSetting.heading_font}" if cs || category_updated > 0 theme_color_defs = theme&.resolve_baked_field(:common, :color_definitions) - Digest::SHA1.hexdigest "#{RailsMultisite::ConnectionManagement.current_db}-#{cs&.id}-#{cs&.version}-#{theme_color_defs}-#{Stylesheet::Manager.last_file_updated}-#{category_updated}-#{SiteSetting.base_font}" + Digest::SHA1.hexdigest "#{RailsMultisite::ConnectionManagement.current_db}-#{cs&.id}-#{cs&.version}-#{theme_color_defs}-#{Stylesheet::Manager.last_file_updated}-#{category_updated}-#{fonts}" else - digest_string = "defaults-#{Stylesheet::Manager.last_file_updated}-#{SiteSetting.base_font}" + digest_string = "defaults-#{Stylesheet::Manager.last_file_updated}-#{fonts}" if cdn_url = GlobalSetting.cdn_url digest_string = "#{digest_string}-#{cdn_url}" diff --git a/lib/tasks/docker.rake b/lib/tasks/docker.rake index 1d658eb987..a09a41e221 100644 --- a/lib/tasks/docker.rake +++ b/lib/tasks/docker.rake @@ -75,7 +75,7 @@ task 'docker:test' do else @good &&= run_or_fail("bundle exec rake plugin:update_all") unless ENV["SKIP_PLUGINS"] @good &&= run_or_fail("bundle exec rubocop --parallel") unless ENV["SKIP_CORE"] - @good &&= run_or_fail("yarn eslint app/assets/javascripts test/javascripts") unless ENV["SKIP_CORE"] + @good &&= run_or_fail("yarn eslint app/assets/javascripts") unless ENV["SKIP_CORE"] # TODO: remove --global I18n once plugins can be updated @good &&= run_or_fail("yarn eslint --global I18n --ext .js,.js.es6 --no-error-on-unmatched-pattern plugins") unless ENV["SKIP_PLUGINS"] @@ -85,7 +85,7 @@ task 'docker:test' do unless ENV["SKIP_CORE"] puts "Listing prettier offenses in core:" - @good &&= run_or_fail('yarn prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.{js,es6}" "test/javascripts/**/*.{js,es6}"') + @good &&= run_or_fail('yarn prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.{js,es6}"') end unless ENV["SKIP_PLUGINS"] diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake index a2a3a29761..27ba82f8a3 100644 --- a/lib/tasks/javascript.rake +++ b/lib/tasks/javascript.rake @@ -168,6 +168,16 @@ task 'javascript:update_constants' => :environment do export const SEARCH_PHRASE_REGEXP = '#{Search::PHRASE_MATCH_REGEXP_PATTERN}'; JS + pretty_notifications = Notification.types.map do |n| + " #{n[0]}: #{n[1]}," + end.join("\n") + + write_template("discourse/tests/fixtures/concerns/notification-types.js", task_name, <<~JS) + export const NOTIFICATION_TYPES = { + #{pretty_notifications} + }; + JS + write_template("pretty-text/addon/emoji/data.js", task_name, <<~JS) export const emojis = #{Emoji.standard.map(&:name).flatten.inspect}; export const tonableEmojis = #{Emoji.tonable_emojis.flatten.inspect}; diff --git a/lib/tasks/plugin.rake b/lib/tasks/plugin.rake index 70f546bb00..57f659a82f 100644 --- a/lib/tasks/plugin.rake +++ b/lib/tasks/plugin.rake @@ -12,8 +12,7 @@ task 'plugin:install_all_official' do ]) map = { - 'Canned Replies' => 'https://github.com/discourse/discourse-canned-replies', - 'discourse-perspective' => 'https://github.com/discourse/discourse-perspective-api' + 'Canned Replies' => 'https://github.com/discourse/discourse-canned-replies' } STDERR.puts "Allowing write to all repos!" if ENV['GIT_WRITE'] diff --git a/lib/tasks/qunit.rake b/lib/tasks/qunit.rake index cb27a6c726..235a317b68 100644 --- a/lib/tasks/qunit.rake +++ b/lib/tasks/qunit.rake @@ -86,7 +86,7 @@ task "qunit:test", [:timeout, :qunit_path] do |_, args| puts "Warming up Rails server" begin Net::HTTP.get(uri) - rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, Net::ReadTimeout + rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, Net::ReadTimeout, EOFError sleep 1 retry unless elapsed() > 60 puts "Timed out. Can not connect to forked server!" diff --git a/lib/topic_creator.rb b/lib/topic_creator.rb index cfac39f6b3..993abba96d 100644 --- a/lib/topic_creator.rb +++ b/lib/topic_creator.rb @@ -218,7 +218,7 @@ class TopicCreator names = usernames.split(',').flatten.map(&:downcase) len = 0 - User.includes(:user_option).where('lower(username) in (?)', names).find_each do |user| + User.includes(:user_option).where('username_lower in (?)', names).find_each do |user| check_can_send_permission!(topic, user) @added_users << user topic.topic_allowed_users.build(user_id: user.id) diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 710da62d84..a5f4bd7cd6 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -394,6 +394,39 @@ class TopicQuery regular: TopicUser.notification_levels[:regular], tracking: TopicUser.notification_levels[:tracking]) end + def self.tracked_filter(list, user_id) + sql = +<<~SQL + topics.category_id IN ( + SELECT cu.category_id FROM category_users cu + WHERE cu.user_id = :user_id AND cu.notification_level >= :tracking + ) + OR topics.category_id IN ( + SELECT c.id FROM categories c WHERE c.parent_category_id IN ( + SELECT cd.category_id FROM category_users cd + WHERE cd.user_id = :user_id AND cd.notification_level >= :tracking + ) + ) + SQL + + if SiteSetting.tagging_enabled + sql << <<~SQL + OR topics.id IN ( + SELECT tt.topic_id FROM topic_tags tt WHERE tt.tag_id IN ( + SELECT tu.tag_id + FROM tag_users tu + WHERE tu.user_id = :user_id AND tu.notification_level >= :tracking + ) + ) + SQL + end + + list.where( + sql, + user_id: user_id, + tracking: NotificationLevels.all[:tracking] + ) + end + def prioritize_pinned_topics(topics, options) pinned_clause = if options[:category_id] +"topics.category_id = #{options[:category_id].to_i} AND" @@ -667,11 +700,10 @@ class TopicQuery if options[:no_subcategories] result = result.where('categories.id = ?', category_id) else - result = result.where(<<~SQL, subcategory_ids: Category.subcategory_ids(category_id), category_id: category_id) - categories.id in (:subcategory_ids) AND ( - categories.topic_id <> topics.id OR categories.id = :category_id - ) - SQL + result = result.where("categories.id IN (?)", Category.subcategory_ids(category_id)) + if !SiteSetting.show_category_definitions_in_topic_lists + result = result.where("categories.topic_id <> topics.id OR categories.id = ?", category_id) + end end result = result.references(:categories) @@ -833,30 +865,7 @@ class TopicQuery end if filter == "tracked" - sql = +<<~SQL - topics.category_id IN ( - SELECT cu.category_id FROM category_users cu - WHERE cu.user_id = :user_id AND cu.notification_level >= :tracking - ) - SQL - - if SiteSetting.tagging_enabled - sql << <<~SQL - OR topics.id IN ( - SELECT tt.topic_id FROM topic_tags tt WHERE tt.tag_id IN ( - SELECT tu.tag_id - FROM tag_users tu - WHERE tu.user_id = :user_id AND tu.notification_level >= :tracking - ) - ) - SQL - end - - result = result.where( - sql, - user_id: @user.id, - tracking: NotificationLevels.all[:tracking] - ) + result = TopicQuery.tracked_filter(result, @user.id) end end diff --git a/lib/topics_bulk_action.rb b/lib/topics_bulk_action.rb index 5c45a01c9b..3187c2d3ee 100644 --- a/lib/topics_bulk_action.rb +++ b/lib/topics_bulk_action.rb @@ -13,7 +13,8 @@ class TopicsBulkAction def self.operations @operations ||= %w(change_category close archive change_notification_level reset_read dismiss_posts delete unlist archive_messages - move_messages_to_inbox change_tags append_tags relist) + move_messages_to_inbox change_tags append_tags remove_tags + relist) end def self.register_operation(name, &block) @@ -176,6 +177,15 @@ class TopicsBulkAction end end + def remove_tags + topics.each do |t| + if guardian.can_edit?(t) + TopicTag.where(topic_id: t.id).delete_all + @changed_ids << t.id + end + end + end + def guardian @guardian ||= Guardian.new(@user) end diff --git a/lib/upload_creator.rb b/lib/upload_creator.rb index 722bcfab6f..69985a947f 100644 --- a/lib/upload_creator.rb +++ b/lib/upload_creator.rb @@ -6,12 +6,15 @@ class UploadCreator TYPES_TO_CROP ||= %w{avatar card_background custom_emoji profile_background}.each(&:freeze) - WHITELISTED_SVG_ELEMENTS ||= %w{ + ALLOWED_SVG_ELEMENTS ||= %w{ circle clippath defs ellipse feGaussianBlur filter g line linearGradient marker path polygon polyline radialGradient rect stop style svg text textpath tref tspan use }.each(&:freeze) + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + deprecate_constant 'WHITELISTED_SVG_ELEMENTS', 'UploadCreator::ALLOWED_SVG_ELEMENTS' + # Available options # - type (string) # - origin (string) @@ -403,7 +406,7 @@ class UploadCreator end def svg_allowlist_xpath - @@svg_allowlist_xpath ||= "//*[#{WHITELISTED_SVG_ELEMENTS.map { |e| "name()!='#{e}'" }.join(" and ") }]" + @@svg_allowlist_xpath ||= "//*[#{ALLOWED_SVG_ELEMENTS.map { |e| "name()!='#{e}'" }.join(" and ") }]" end def add_metadata! diff --git a/lib/upload_recovery.rb b/lib/upload_recovery.rb index 221e1d3e95..9092ee03f9 100644 --- a/lib/upload_recovery.rb +++ b/lib/upload_recovery.rb @@ -31,12 +31,13 @@ class UploadRecovery data = Upload.extract_url(url) next unless data - sha1 = data[2] + upload = Upload.get_from_url(url) - unless upload = Upload.get_from_url(url) + if !upload || upload.verification_status == Upload.verification_statuses[:invalid_etag] if @dry_run puts "#{post.full_url} #{url}" else + sha1 = data[2] recover_post_upload(post, sha1) end end @@ -141,6 +142,8 @@ class UploadRecovery end end + upload_exists = Upload.exists?(sha1: sha1) + @object_keys.each do |key| if key =~ /#{sha1}/ tombstone_prefix = FileStore::S3Store::TOMBSTONE_PREFIX @@ -156,6 +159,8 @@ class UploadRecovery ) end + next if upload_exists + url = "https:#{SiteSetting.Upload.absolute_base_url}/#{key}" begin diff --git a/lib/url_helper.rb b/lib/url_helper.rb index 1915b78951..4b2ad9106c 100644 --- a/lib/url_helper.rb +++ b/lib/url_helper.rb @@ -38,7 +38,7 @@ class UrlHelper def self.is_local(url) url.present? && ( Discourse.store.has_been_uploaded?(url) || - !!(url =~ Regexp.new("^#{Discourse.base_uri}/(assets|plugins|images)/")) || + !!(url =~ Regexp.new("^#{Discourse.base_path}/(assets|plugins|images)/")) || url.start_with?(Discourse.asset_host || Discourse.base_url_no_prefix) ) end @@ -70,6 +70,8 @@ class UrlHelper def self.rails_route_from_url(url) path = URI.parse(encode(url)).path Rails.application.routes.recognize_path(path) + rescue Addressable::URI::InvalidURIError, URI::InvalidComponentError + nil end def self.s3_presigned_url?(url) diff --git a/lib/validators/selectable_avatars_enabled_validator.rb b/lib/validators/selectable_avatars_enabled_validator.rb index 065c5bcf98..cc54c9ce5d 100644 --- a/lib/validators/selectable_avatars_enabled_validator.rb +++ b/lib/validators/selectable_avatars_enabled_validator.rb @@ -6,7 +6,7 @@ class SelectableAvatarsEnabledValidator end def valid_value?(value) - value == "f" || SiteSetting.selectable_avatars.split("\n").size > 1 + value == "f" || SiteSetting.selectable_avatars.size > 1 end def error_message diff --git a/lib/version.rb b/lib/version.rb index e65fa22422..4e9f726e0b 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -10,12 +10,14 @@ module Discourse MAJOR = 2 MINOR = 6 TINY = 0 - PRE = 'beta3' + PRE = 'beta4' STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end end + class InvalidVersionListError < StandardError; end + def self.has_needed_version?(current, needed) Gem::Version.new(current) >= Gem::Version.new(needed) end @@ -29,10 +31,16 @@ module Discourse # 2.4.4.beta6: some-other-branch-ref # 2.4.2.beta1: v1-tag def self.find_compatible_resource(version_list, version = ::Discourse::VERSION::STRING) - return unless version_list - version_list = YAML.load(version_list).sort_by { |v, pin| Gem::Version.new(v) }.reverse + begin + version_list = YAML.safe_load(version_list) + rescue Psych::SyntaxError, Psych::DisallowedClass => e + end + + raise InvalidVersionListError unless version_list.is_a?(Hash) + + version_list = version_list.sort_by { |v, pin| Gem::Version.new(v) }.reverse # If plugin compat version is listed as less than current Discourse version, take the version/hash listed before. checkout_version = nil @@ -54,5 +62,7 @@ module Discourse return unless File.directory?("#{path}/.git") compat_resource, std_error, s = Open3.capture3("git -C '#{path}' show HEAD@{upstream}:#{Discourse::VERSION_COMPATIBILITY_FILENAME}") Discourse.find_compatible_resource(compat_resource) if s.success? + rescue InvalidVersionListError => e + $stderr.puts "Invalid version list in #{path}" end end diff --git a/lib/wizard/builder.rb b/lib/wizard/builder.rb index 27fc715aac..b6f3b41671 100644 --- a/lib/wizard/builder.rb +++ b/lib/wizard/builder.rb @@ -181,47 +181,42 @@ class Wizard next unless scheme_name.present? && ColorScheme.is_base?(scheme_name) name = I18n.t("color_schemes.#{scheme_name.downcase.gsub(' ', '_')}_theme_name") - theme = nil + scheme = ColorScheme.find_by(base_scheme_id: scheme_name, via_wizard: true) - is_light_theme = (scheme_name == ColorScheme::LIGHT_THEME_ID) scheme ||= ColorScheme.create_from_base(name: name, via_wizard: true, base_scheme_id: scheme_name) - themes = Theme.where(color_scheme_id: scheme.id).order(:id).to_a - if is_light_theme - themes = (themes || []).concat(Theme.where(color_scheme_id: nil).order(:id).to_a) - themes.sort_by(&:id) + if default_theme + default_theme.color_scheme_id = scheme.id + default_theme.save! + else + theme = Theme.create!( + name: name, + user_id: @wizard.user.id, + color_scheme_id: scheme.id + ) + + theme.set_default! end - theme = themes.find(&:default?) - theme ||= themes.first - - theme ||= Theme.create!( - name: name, - user_id: @wizard.user.id, - color_scheme_id: scheme.id - ) - - theme.set_default! end end - @wizard.append_step('themes-further-reading') do |step| - step.banner = "further-reading.png" - step.add_field(id: 'popular-themes', type: 'component') - end - @wizard.append_step('fonts') do |step| - field = step.add_field( - id: 'font_previews', - type: 'component', - value: SiteSetting.base_font - ) + body_font = step.add_field(id: 'body_font', type: 'dropdown', value: SiteSetting.base_font) + heading_font = step.add_field(id: 'heading_font', type: 'dropdown', value: SiteSetting.heading_font) DiscourseFonts.fonts.each do |font| - field.add_choice(font[:key], data: { class: font[:key].tr("_", "-"), font_stack: font[:stack] }) + body_font.add_choice(font[:key], label: font[:name]) + heading_font.add_choice(font[:key], label: font[:name]) end + step.add_field( + id: 'font_preview', + type: 'component' + ) + step.on_update do |updater| - updater.update_setting(:base_font, updater.fields[:font_previews]) + updater.update_setting(:base_font, updater.fields[:body_font]) + updater.update_setting(:heading_font, updater.fields[:heading_font]) end end @@ -281,7 +276,7 @@ class Wizard EmojiSetSiteSetting.values.each do |set| imgs = emoji.map do |e| - "" + "" end sets.add_choice(set[:value], diff --git a/plugins/discourse-details/config/locales/client.ar.yml b/plugins/discourse-details/config/locales/client.ar.yml index c31fc7e90c..3aa32d631e 100644 --- a/plugins/discourse-details/config/locales/client.ar.yml +++ b/plugins/discourse-details/config/locales/client.ar.yml @@ -7,7 +7,7 @@ ar: js: details: - title: أخفي التفاصيل + title: أخفِ التفاصيل composer: - details_title: ملخص - details_text: "سيتم إخفاء هذا النص" + details_title: الملخّص + details_text: "سيُخفى هذا النص" diff --git a/plugins/discourse-details/config/locales/server.ar.yml b/plugins/discourse-details/config/locales/server.ar.yml index 4db2172e68..37b46e2026 100644 --- a/plugins/discourse-details/config/locales/server.ar.yml +++ b/plugins/discourse-details/config/locales/server.ar.yml @@ -6,4 +6,6 @@ ar: site_settings: - details_enabled: 'فعل خاصية التفاصيل. إذا غيرت هذا ، فيجب عليك إعادة بث جميع المشاركات باستخدام: "rake posts:rebake".' + details_enabled: 'فعّل ميزة التفاصيل. إن غيّرت هذا فيمكنك إعادة خبز (rebake) كلّ المشاركات بالأمر: ”rake posts:rebake“.' + details: + excerpt_details: "(انقر لتفاصيل أكثر)" diff --git a/plugins/discourse-details/config/locales/server.ro.yml b/plugins/discourse-details/config/locales/server.ro.yml index 31ae397610..307f5c99c7 100644 --- a/plugins/discourse-details/config/locales/server.ro.yml +++ b/plugins/discourse-details/config/locales/server.ro.yml @@ -4,4 +4,8 @@ # To work with us on translations, join this project: # https://translate.discourse.org/ -ro: +ro: + site_settings: + details_enabled: 'Activați funcția de detalii. Dacă schimbați acest lucru, trebuie să reluați toate mesajele cu: "rake posts: rebake".' + details: + excerpt_details: "(click pentru mai multe detalii)" diff --git a/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6 b/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6 index d8fe217648..48a14e8a41 100644 --- a/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6 +++ b/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6 @@ -1,7 +1,7 @@ import I18n from "I18n"; -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer"; -import selectKit from "helpers/select-kit-helper"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; acceptance("Details Button", { loggedIn: true, diff --git a/plugins/discourse-details/test/javascripts/lib/details-cooked-test.js.es6 b/plugins/discourse-details/test/javascripts/lib/details-cooked-test.js.es6 index 3c9f36c8d2..11d0dac4db 100644 --- a/plugins/discourse-details/test/javascripts/lib/details-cooked-test.js.es6 +++ b/plugins/discourse-details/test/javascripts/lib/details-cooked-test.js.es6 @@ -1,6 +1,6 @@ import PrettyText, { buildOptions } from "pretty-text/pretty-text"; -QUnit.module("lib:details-cooked-test"); +module("lib:details-cooked-test"); const defaultOpts = buildOptions({ siteSettings: { diff --git a/plugins/discourse-local-dates/assets/javascripts/discourse/templates/components/discourse-local-dates-create-form.hbs b/plugins/discourse-local-dates/assets/javascripts/discourse/templates/components/discourse-local-dates-create-form.hbs index 92adae679a..196fb791c9 100644 --- a/plugins/discourse-local-dates/assets/javascripts/discourse/templates/components/discourse-local-dates-create-form.hbs +++ b/plugins/discourse-local-dates/assets/javascripts/discourse/templates/components/discourse-local-dates-create-form.hbs @@ -4,23 +4,23 @@ style="overflow: auto"}}
- {{#unless isValid}} -
- {{i18n "discourse_local_dates.create.form.invalid_date"}} -
- {{else}} + {{#if isValid}} {{#if timezoneIsDifferentFromUserTimezone}}
{{formatedCurrentUserTimezone}} {{currentPreview}}
{{/if}} - {{/unless}} + {{else}} +
+ {{i18n "discourse_local_dates.create.form.invalid_date"}} +
+ {{/if}} {{computeDate}}
-
+
{{d-icon "calendar-alt"}} {{d-button id="from-date-time" @@ -29,7 +29,7 @@ class="date-time"}}
-
+
{{d-icon "calendar-alt"}} {{d-button action=(action "focusTo") @@ -61,12 +61,12 @@ {{/if}} {{#if toSelected}} - {{#if toDate}} -
- {{d-icon "far-clock"}} - {{input maxlength=5 placeholder="hh:mm" input=(action "setToTime") type="time" value=toTime class="time-picker"}} -
- {{/if}} + {{#if toDate}} +
+ {{d-icon "far-clock"}} + {{input maxlength=5 placeholder="hh:mm" input=(action "setToTime") type="time" value=toTime class="time-picker"}} +
+ {{/if}} {{/if}}
@@ -102,7 +102,7 @@

{{i18n "discourse_local_dates.create.form.format_description"}} - + {{d-icon "question-circle"}}

diff --git a/plugins/discourse-local-dates/assets/javascripts/lib/local-date-builder.js.es6 b/plugins/discourse-local-dates/assets/javascripts/lib/local-date-builder.js.es6 index 2f4ca8eeb4..a6771da39a 100644 --- a/plugins/discourse-local-dates/assets/javascripts/lib/local-date-builder.js.es6 +++ b/plugins/discourse-local-dates/assets/javascripts/lib/local-date-builder.js.es6 @@ -188,12 +188,16 @@ export default class LocalDateBuilder { ); if (inCalendarRange && sameTimezone) { - return localDate - .datetimeWithZone(this.localTimezone) - .calendar( - moment.tz(localDate.timezone), - this._calendarFormats(this.time ? this.time : null) - ); + const date = localDate.datetimeWithZone(this.localTimezone); + + if (date.hours() === 0 && date.minutes() === 0) { + return date.format("dddd"); + } + + return date.calendar( + moment.tz(localDate.timezone), + this._calendarFormats(this.time ? this.time : null) + ); } } diff --git a/plugins/discourse-local-dates/config/locales/client.ar.yml b/plugins/discourse-local-dates/config/locales/client.ar.yml index d7fc12bdac..9970acd9c2 100644 --- a/plugins/discourse-local-dates/config/locales/client.ar.yml +++ b/plugins/discourse-local-dates/config/locales/client.ar.yml @@ -9,28 +9,31 @@ ar: discourse_local_dates: relative_dates: today: اليوم %{time} - tomorrow: غداً %{time} - yesterday: أمس%{time} - title: أدخل التاريخ / الوقت + tomorrow: غدًا %{time} + yesterday: البارحة %{time} + title: أدخِل التاريخ / الوقت create: form: - insert: ادخل - advanced_mode: الوضع المتقدم + insert: أدخِل + advanced_mode: الوضع المتقدّم simple_mode: الوضع البسيط + format_description: "النُسق المستعمل لعرض التاريخ على المستخدم. استعمل Z لإزاحة الأرقام بالصفر و zz لاسم المنطقة الزمنية." timezones_title: المناطق الزمنية لعرضها - recurring_title: تكرار - recurring_none: لا تكرار + recurring_title: التكرار + recurring_description: "حدّد تكرار الحدث. يمكنك أيضًا تعديل خيار التكرار الذي ولّدته الاستمارة تلقائيًا - تعديله يدويًا وثمّ استعمال أحد المفاتيح الآتية: years، ‏quarters، ‏months، ‏days، ‏hours، ‏minutes، ‏seconds، ‏milliseconds." + recurring_none: بلا تكرار + invalid_date: التاريخ غير صالح، تأكّد من صحّة التاريخ والوقت date_title: التاريخ time_title: الوقت - format_title: تنسيق التاريخ + format_title: نُسق التاريخ timezone: المنطقة الزمنية - until: حتى... + until: حتّى... recurring: - every_day: "كل يوم" - every_week: "كل اسبوع" - every_two_weeks: "كل أسبوعين" - every_month: "كل شهر" - every_two_months: "كل شهرين" - every_three_months: "كل ثلاثة أشهر" - every_six_months: "كل ستة اشهر" - every_year: "كل عام" + every_day: "كلّ يوم" + every_week: "كلّ أسبوع" + every_two_weeks: "كلّ أسبوعين" + every_month: "كلّ شهر" + every_two_months: "كلّ شهرين" + every_three_months: "كلّ ثلاثة أشهر" + every_six_months: "كلّ ستة أشهر" + every_year: "كلّ سنة" diff --git a/plugins/discourse-local-dates/config/locales/client.ro.yml b/plugins/discourse-local-dates/config/locales/client.ro.yml index c520418de2..604da509a8 100644 --- a/plugins/discourse-local-dates/config/locales/client.ro.yml +++ b/plugins/discourse-local-dates/config/locales/client.ro.yml @@ -7,6 +7,36 @@ ro: js: discourse_local_dates: + relative_dates: + today: Astăzi la %{time} + tomorrow: Mâine la %{time} + yesterday: Ieri %{time} + countdown: + passed: data a trecut + title: Introdu data / ora create: form: + insert: Introdu + advanced_mode: Mod avansat + simple_mode: Modul simplu + format_description: "Format utilizat pentru a afișa data utilizatorului. Utilizați Z pentru a afișa decalajul de timp și zz pentru numele fusului orar." + timezones_title: Fusuri orare de afișat + timezones_description: Fusurile orare vor fi utilizate pentru a afișa datele în previzualizare și în rezervă. + recurring_title: Recurență + recurring_description: "Definiți recurența unui eveniment. De asemenea, puteți edita manual opțiunea recurentă generată de formular și puteți utiliza una dintre următoarele chei: ani, trimestre, luni, săptămâni, zile, ore, minute, secunde, milisecunde." + recurring_none: Nicio recurență + invalid_date: Data invalidă, asigurați-vă că data și ora sunt corecte + date_title: Dată time_title: Timp + format_title: Formatul datei + timezone: Fus orar + until: Pana cand... + recurring: + every_day: "În fiecare zi" + every_week: "În fiecare săptămână" + every_two_weeks: "La fiecare două săptămâni" + every_month: "În fiecare lună" + every_two_months: "La fiecare două luni" + every_three_months: "La fiecare trei luni" + every_six_months: "La fiecare șase luni" + every_year: "În fiecare an" diff --git a/plugins/discourse-local-dates/config/locales/client.uk.yml b/plugins/discourse-local-dates/config/locales/client.uk.yml index d9d3fe2e33..d167b2a805 100644 --- a/plugins/discourse-local-dates/config/locales/client.uk.yml +++ b/plugins/discourse-local-dates/config/locales/client.uk.yml @@ -19,6 +19,7 @@ uk: insert: Вставити advanced_mode: Розширений режим simple_mode: Простий режим + format_description: "Формат, що використовується для відображення дати користувачеві. Використовуйте Z для відображення зміщення та zz для назви часового поясу." timezones_title: Часові пояси показати timezones_description: Часові пояси будуть використовуватися для відображення дат у попередньому та резервному режимі. recurring_title: Повторення diff --git a/plugins/discourse-local-dates/config/locales/client.zh_CN.yml b/plugins/discourse-local-dates/config/locales/client.zh_CN.yml index 1dee497fa8..5887d28aaf 100644 --- a/plugins/discourse-local-dates/config/locales/client.zh_CN.yml +++ b/plugins/discourse-local-dates/config/locales/client.zh_CN.yml @@ -19,7 +19,7 @@ zh_CN: insert: 插入 advanced_mode: 高级模式 simple_mode: 简单模式 - format_description: "用于向用户显示日期的格式。使用Z显示偏移量,使用zz作为时区名称。" + format_description: "用于向用户显示的日期格式。使用Z显示时区的偏移量,使用zz显示时区的名称。" timezones_title: 要显示的时区 timezones_description: 时区将用于在预览和撤回中显示日期。 recurring_title: 循环 diff --git a/plugins/discourse-local-dates/config/locales/server.ar.yml b/plugins/discourse-local-dates/config/locales/server.ar.yml index 516c850fdb..aa68d4423c 100644 --- a/plugins/discourse-local-dates/config/locales/server.ar.yml +++ b/plugins/discourse-local-dates/config/locales/server.ar.yml @@ -4,4 +4,8 @@ # To work with us on translations, join this project: # https://translate.discourse.org/ -ar: +ar: + site_settings: + discourse_local_dates_enabled: "فعّل ميزة ”التواريخ المحليّة من دسكورس“. ستُضيف هذه الميزة دعم التواريخ التي تستوعب المناطق الزمنية المحليّة في المشاركات باستعمال عنصر [date]." + discourse_local_dates_default_formats: "نُسق التاريخ والوقت الأكثر استعمالًا، طالِع: نُسق سلاسل momentjs النصيّة" + discourse_local_dates_email_format: "النُسق المستعمل لعرض التواريخ في الرسائل الإلكترونية." diff --git a/plugins/discourse-local-dates/config/locales/server.ro.yml b/plugins/discourse-local-dates/config/locales/server.ro.yml index 31ae397610..c9e0aa33f8 100644 --- a/plugins/discourse-local-dates/config/locales/server.ro.yml +++ b/plugins/discourse-local-dates/config/locales/server.ro.yml @@ -4,4 +4,9 @@ # To work with us on translations, join this project: # https://translate.discourse.org/ -ro: +ro: + site_settings: + discourse_local_dates_enabled: "Activează caracteristica Discourse date locale. Aceasta va adăuga suport pentru fusurile orare locale în postări folosind elementul [date]" + discourse_local_dates_default_formats: "Formatele frecvent utilizate pentru timp și dată, vezi: formatul momentjs" + discourse_local_dates_default_timezones: "Lista implicită a fusurilor orare, trebuie să fie un fuz orarvalid" + discourse_local_dates_email_format: "Format utilizat pentru a afișa o dată în e-mailuri." diff --git a/plugins/discourse-local-dates/config/locales/server.ru.yml b/plugins/discourse-local-dates/config/locales/server.ru.yml index 73918e9868..07fe7cdc4e 100644 --- a/plugins/discourse-local-dates/config/locales/server.ru.yml +++ b/plugins/discourse-local-dates/config/locales/server.ru.yml @@ -9,4 +9,4 @@ ru: discourse_local_dates_enabled: "Включить поддержку локальных дат. Эта функция добавит поддержку дат в сообщениях, использующих элемент [date], с учётом локального часового пояса." discourse_local_dates_default_formats: "Часто используемые форматы даты и времени, см.: форматирование строк с помощью moment.js" discourse_local_dates_default_timezones: "Список часовых поясов должен содержать допустимые часовые пояса" - discourse_local_dates_email_format: "Формат, используемый для отображения даты в сообщениях e-mail." + discourse_local_dates_email_format: "Формат, используемый для отображения даты в Email-сообщениях." diff --git a/plugins/discourse-local-dates/test/javascripts/acceptance/local-dates-composer-test.js.es6 b/plugins/discourse-local-dates/test/javascripts/acceptance/local-dates-composer-test.js.es6 index 5ab6ecf632..dfb3298e9e 100644 --- a/plugins/discourse-local-dates/test/javascripts/acceptance/local-dates-composer-test.js.es6 +++ b/plugins/discourse-local-dates/test/javascripts/acceptance/local-dates-composer-test.js.es6 @@ -1,4 +1,4 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; acceptance("Local Dates - composer", { loggedIn: true, diff --git a/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js.es6 b/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js.es6 index 40b4bd1bee..4bd504c33e 100644 --- a/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js.es6 +++ b/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js.es6 @@ -3,7 +3,7 @@ import DateWithZoneHelper from "./date-with-zone-helper"; const PARIS = "Europe/Paris"; const SYDNEY = "Australia/Sydney"; -QUnit.module("lib:date-with-zone-helper"); +module("lib:date-with-zone-helper"); function buildDateHelper(params = {}) { return new DateWithZoneHelper({ @@ -17,7 +17,7 @@ function buildDateHelper(params = {}) { }); } -QUnit.test("#format", (assert) => { +test("#format", (assert) => { let date = buildDateHelper({ day: 15, month: 2, @@ -28,7 +28,7 @@ QUnit.test("#format", (assert) => { assert.equal(date.format(), "2020-03-15T15:36:00.000+01:00"); }); -QUnit.test("#repetitionsBetweenDates", (assert) => { +test("#repetitionsBetweenDates", (assert) => { let date; date = buildDateHelper({ @@ -96,7 +96,7 @@ QUnit.test("#repetitionsBetweenDates", (assert) => { ); }); -QUnit.test("#add", (assert) => { +test("#add", (assert) => { let date; let futureLocalDate; diff --git a/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js.es6 b/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js.es6 index 2295b0726a..f64d49b8a7 100644 --- a/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js.es6 +++ b/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js.es6 @@ -1,5 +1,6 @@ import I18n from "I18n"; import LocalDateBuilder from "./local-date-builder"; +import sinon from "sinon"; const UTC = "Etc/UTC"; const SYDNEY = "Australia/Sydney"; @@ -8,7 +9,7 @@ const PARIS = "Europe/Paris"; const LAGOS = "Africa/Lagos"; const LONDON = "Europe/London"; -QUnit.module("lib:local-date-builder"); +module("lib:local-date-builder"); const sandbox = sinon.createSandbox(); @@ -62,7 +63,7 @@ QUnit.assert.buildsCorrectDate = function (options, expected, message) { } }; -QUnit.test("date", (assert) => { +test("date", (assert) => { freezeTime({ date: "2020-03-11" }, () => { assert.buildsCorrectDate( { date: "2020-03-22", timezone: PARIS }, @@ -72,7 +73,7 @@ QUnit.test("date", (assert) => { }); }); -QUnit.test("date and time", (assert) => { +test("date and time", (assert) => { assert.buildsCorrectDate( { date: "2020-04-11", time: "11:00" }, { formated: "April 11, 2020 1:00 PM" }, @@ -86,7 +87,7 @@ QUnit.test("date and time", (assert) => { ); }); -QUnit.test("option[format]", (assert) => { +test("option[format]", (assert) => { freezeTime({ date: "2020-03-11" }, () => { assert.buildsCorrectDate( { format: "YYYY" }, @@ -96,7 +97,7 @@ QUnit.test("option[format]", (assert) => { }); }); -QUnit.test("option[displayedTimezone]", (assert) => { +test("option[displayedTimezone]", (assert) => { freezeTime({}, () => { assert.buildsCorrectDate( { displayedTimezone: SYDNEY }, @@ -130,7 +131,7 @@ QUnit.test("option[displayedTimezone]", (assert) => { }); }); -QUnit.test("option[timezone]", (assert) => { +test("option[timezone]", (assert) => { freezeTime({}, () => { assert.buildsCorrectDate( { timezone: SYDNEY, displayedTimezone: PARIS }, @@ -140,7 +141,7 @@ QUnit.test("option[timezone]", (assert) => { }); }); -QUnit.test("option[recurring]", (assert) => { +test("option[recurring]", (assert) => { freezeTime({ date: "2020-04-06 06:00", timezone: LAGOS }, () => { assert.buildsCorrectDate( { @@ -220,7 +221,7 @@ QUnit.test("option[recurring]", (assert) => { }); }); -QUnit.test("option[countown]", (assert) => { +test("option[countown]", (assert) => { freezeTime({ date: "2020-03-21 23:59" }, () => { assert.buildsCorrectDate( { @@ -248,7 +249,7 @@ QUnit.test("option[countown]", (assert) => { }); }); -QUnit.test("option[calendar]", (assert) => { +test("option[calendar]", (assert) => { freezeTime({ date: "2020-03-23 23:00" }, () => { assert.buildsCorrectDate( { date: "2020-03-22", time: "23:59", timezone: PARIS }, @@ -257,10 +258,18 @@ QUnit.test("option[calendar]", (assert) => { ); }); + freezeTime({ date: "2020-03-20 23:59" }, () => + assert.buildsCorrectDate( + { date: "2020-03-21", time: "01:00", timezone: PARIS }, + { formated: "Tomorrow 1:00 AM" } + ) + ); + freezeTime({ date: "2020-03-20 23:59" }, () => assert.buildsCorrectDate( { date: "2020-03-21", time: "00:00", timezone: PARIS }, - { formated: "Tomorrow 12:00 AM" } + { formated: "Saturday" }, + "it displays the day with no time when the time in the displayed timezone is 00:00" ) ); @@ -321,7 +330,7 @@ QUnit.test("option[calendar]", (assert) => { }); }); -QUnit.test("previews", (assert) => { +test("previews", (assert) => { freezeTime({ date: "2020-03-22" }, () => { assert.buildsCorrectDate( { timezone: PARIS }, diff --git a/plugins/discourse-narrative-bot/config/locales/client.ar.yml b/plugins/discourse-narrative-bot/config/locales/client.ar.yml index 8f47eef043..14903ff951 100644 --- a/plugins/discourse-narrative-bot/config/locales/client.ar.yml +++ b/plugins/discourse-narrative-bot/config/locales/client.ar.yml @@ -8,5 +8,5 @@ ar: js: discourse_narrative_bot: welcome_post_type: - new_user_track: "ابدأ برنامج تعليم الأعضاء الجدد لكل الأعضاء الجدد !" - welcome_message: "أرسل إلى كلّ الاعضاء الجدد رسالة ترحيبيّة فيها دليل البدء سريع" + new_user_track: "ابدأ البرنامج التعليمي للمستخدمين الجدد، لكلّ المستخدمين الجدد" + welcome_message: "أرسِل رسالة ترحيبيّة فيها دليل بدء سريع إلى كلّ المستخدمين الجدد" diff --git a/plugins/discourse-narrative-bot/config/locales/client.ru.yml b/plugins/discourse-narrative-bot/config/locales/client.ru.yml index 3cfe9a056c..5adef077ce 100644 --- a/plugins/discourse-narrative-bot/config/locales/client.ru.yml +++ b/plugins/discourse-narrative-bot/config/locales/client.ru.yml @@ -9,4 +9,4 @@ ru: discourse_narrative_bot: welcome_post_type: new_user_track: "Включить интерактивное обучение для всех новых пользователей" - welcome_message: "Отправить всем новым пользователям приветственное сообщение с кратким руководством" + welcome_message: "Отправлять всем новым пользователям приветственное сообщение с кратким руководством" diff --git a/plugins/discourse-narrative-bot/config/locales/server.ar.yml b/plugins/discourse-narrative-bot/config/locales/server.ar.yml index adcd8489c6..3364cc8b4b 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.ar.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.ar.yml @@ -6,12 +6,12 @@ ar: site_settings: - discourse_narrative_bot_enabled: "تفعيل روبوت ديسكورس المتحدث " - disable_discourse_narrative_bot_welcome_post: "عطل منشور الترحيب المنشور بواسطة روبوت ديسكورس المتحدث" - discourse_narrative_bot_ignored_usernames: "اسماء الاعضاء التي يتوجب على روبوت ديسكورس المتحدث تجاهلها" - discourse_narrative_bot_disable_public_replies: "عطل الردود العامة بواسطة روبوت ديسكورس المتحدث" - discourse_narrative_bot_welcome_post_type: "نوع منشور الترحيب التي يتوجب على روبوت ديسكورس المتحدث ارسالها" - discourse_narrative_bot_welcome_post_delay: "انتظر هذا العدد من الثوان قبل ارسال منشور الترحيب بواسطة روبوت ديسكورس المتحدث" + discourse_narrative_bot_enabled: "فعّل آلة التحدّث من دسكورس (discobot)" + disable_discourse_narrative_bot_welcome_post: "عطّل مشاركة الترحيب من آلة التحدّث من دسكورس" + discourse_narrative_bot_ignored_usernames: "أسماء المستخدمين التي ستتجاهلها آلة التحدّث من دسكورس" + discourse_narrative_bot_disable_public_replies: "عطّل الردود العمومية من آلة التحدّث من دسكورس" + discourse_narrative_bot_welcome_post_type: "نوع المشاركة الترحيبية التي على آلة التحدّث من دسكورس إرسالها" + discourse_narrative_bot_welcome_post_delay: "انتظر (كذا) ثانية قبل إرسال آلة التحدّث من دسكورس للمشاركة الترحيبية." badges: certified: name: مُعتمد diff --git a/plugins/discourse-narrative-bot/config/locales/server.bs_BA.yml b/plugins/discourse-narrative-bot/config/locales/server.bs_BA.yml index 984d4c75b5..0101a5e0cb 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.bs_BA.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.bs_BA.yml @@ -26,8 +26,6 @@ bs_BA: Ovaj bedž je dodjeljen prilikom uspješnog završetka interaktivnog tutorijala za nove korisnike. Postali ste majstor naprednih alata u diskusiji - sada ste potpuno licencirani! discourse_narrative_bot: bio: "Pozdrav, ja nisam stvarna osoba. Ja sam automatski robot koji će vas naučiti kako da koristite ovaj web sajt. Kako bi komunicirali sa mnom, pošaljite mi poruku ili me spomenite pisajući **`@%{discobot_username}`** bilo gdje." - tl2_promotion_message: - subject_template: "Čestitamo na povišenju vašeg nivoa povjerenja!" timeout: message: |- Hej @%{username}, samo vas provjeravam jer nisam vas čuo neko vrijeme. @@ -141,7 +139,6 @@ bs_BA: reset_trigger: "kurs" title: "Potvrda o završetku korisničkog kursa" cert_title: "Kao priznanje za uspješan završetak novog korisničkog kursa" - delete_reason: "Korisnik je preskočio nove korisničke savjete" hello: title: "Pozdrav!" message: |- diff --git a/plugins/discourse-narrative-bot/config/locales/server.ca.yml b/plugins/discourse-narrative-bot/config/locales/server.ca.yml index d73534fcf1..51a7a98c1a 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.ca.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.ca.yml @@ -25,8 +25,6 @@ ca: Aquesta insígnia es concedeix després d'haver completat amb èxit el tutorial interactiu d'usuari avançat. Heu dominat les eines avançades de discussió, i ara teniu esteu completament acreditat! discourse_narrative_bot: bio: "Hola, no sóc una persona real, sóc un robot que us pot ensenyar coses sobre aquest lloc web. Per a interactuar amb mi, envieu-me un missatge o feu una menció a **%{discobot_username}** en qualsevol lloc. " - tl2_promotion_message: - subject_template: "Enhorabona per la vostra promoció de nivell de confiança!" timeout: message: |- Bon dia @%{username}, diff --git a/plugins/discourse-narrative-bot/config/locales/server.de.yml b/plugins/discourse-narrative-bot/config/locales/server.de.yml index c08b72d905..2116c56b00 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.de.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.de.yml @@ -25,8 +25,6 @@ de: Das Abzeichen wird verliehen, wenn das interaktive Tutorial für fortgeschrittene Benutzer erfolgreich abgeschlossen wurde. Du beherrscht die fortgeschrittenen Werkzeuge für Diskussionen erlernt und besitzt nun die Lizenz zum Diskutieren. discourse_narrative_bot: bio: "Hallo! Ich bin keine reale Person. Ich bin ein Bot, der dir etwas über diese Website beibringen kann. Schick mir eine Nachricht oder erwähne irgendwo **`@%{discobot_username}`**, um mit mir zu interagieren." - tl2_promotion_message: - subject_template: "Herzlichen Glückwunsch zur Beförderung ihrer Vertrauensstufe!" timeout: message: |- Hallo @%{username}! Ich wollte mich nur wieder einmal melden, weil ich schon länger nichts von dir gehört habe. diff --git a/plugins/discourse-narrative-bot/config/locales/server.el.yml b/plugins/discourse-narrative-bot/config/locales/server.el.yml index d8a680f4a3..68ae8ff702 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.el.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.el.yml @@ -26,8 +26,6 @@ el: Αυτό το σήμα χορηγείται μετά την επιτυχή ολοκλήρωση του διαδραστικού φροντιστηρίου προχωρημένου χρήστη. Κατακτήσατε τα προηγμένα εργαλεία συζήτησης - και τώρα έχετε πλήρη άδεια! discourse_narrative_bot: bio: "Γεια σας, δεν είμαι πραγματικό πρόσωπο - είμαι ένα bot που μπορεί να σας διδάξει για αυτήν την ιστοσελίδα. Για να επικοινωνήσετε μαζί μου, στείλτε μου απλά ένα μήνυμα ή αναφέρετε το **`@%{discobot_username}`** οπουδήποτε." - tl2_promotion_message: - subject_template: "Συγχαρητήρια για τον προβιβασμό του επιπέδου εμπιστοσύνης σας!" timeout: message: |- Γεια σας @%{username}, δεν είχα νέα σας εδώ και λίγο καιρό και ειπα να δω τι κάνετε. @@ -145,7 +143,6 @@ el: reset_trigger: "φροντιστήριο" title: "Πιστοποιητικό ολοκλήρωσης φροντιστηρίου νέου χρήστη" cert_title: "Σε αναγνώριση της επιτυχούς ολοκλήρωσης\nτου φροντιστηρίου νέου χρήστη." - delete_reason: "Ο χρήστης παρέλειψε τις συμβουλές νέου χρήστη" hello: title: "Χαιρετίσματα!" message: |- diff --git a/plugins/discourse-narrative-bot/config/locales/server.en.yml b/plugins/discourse-narrative-bot/config/locales/server.en.yml index f1ceb70cef..8371f666ba 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.en.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.en.yml @@ -24,10 +24,8 @@ en: bio: "Hi, I’m not a real person. I’m a bot that can teach you about this site. To interact with me, send me a message or mention **`@%{discobot_username}`** anywhere." tl2_promotion_message: - subject_template: "Congratulations on your trust level promotion!" + subject_template: "Now that you’ve been promoted, it’s time to learn about some advanced features!" text_body_template: | - Now that you’ve been promoted, it’s time to learn about some advanced features! - Reply to this message with `@%{discobot_username} %{reset_trigger}` to find out more about what you can do. timeout: diff --git a/plugins/discourse-narrative-bot/config/locales/server.es.yml b/plugins/discourse-narrative-bot/config/locales/server.es.yml index d500c4cad3..76b1c9a73b 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.es.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.es.yml @@ -26,8 +26,6 @@ es: Esta medalla se otorga al completar con éxito el tutorial interactivo para usuarios avanzados. Has dominado las herramientas avanzadas de discusión y ¡ahora tienes licencia completa! discourse_narrative_bot: bio: "¡Hola! No soy una persona real, soy un bot que te puede enseñar acerca este sitio. Para interactuar conmigo, envíame un mensaje o menciona **`%{discobot_username}`** en cualquier lugar." - tl2_promotion_message: - subject_template: "¡Felicitaciones por tu promoción del nivel de confianza!" timeout: message: |- Hey, @%{username}, te quería decir que estoy pendiente de ti porque no he tenido noticias tuyas en mucho tiempo. @@ -145,7 +143,6 @@ es: reset_trigger: "tutorial" title: "Certificado de finalización del tutorial de usuario nuevo" cert_title: "En reconocimiento a la finalización exitosa del tutorial de nuevo usuario" - delete_reason: "El usuario omitió las sugerencias para nuevos usuarios" hello: title: "¡Saludos!" message: |- @@ -179,13 +176,22 @@ es: - Si te gusta (¡y a quién no le gustaría!), adelante, presiona el botón me gusta: heart: debajo de esta publicación para avisarme. + Si te gusta (¡y a quién no le gustaría!), adelante, presiona el botón me gusta :heart: debajo de esta publicación para avisarme. ¿Puedes **responder con una imagen?** ¡Cualquier imagen servirá! Arrastra y suelta, presiona el botón de carga o incluso cópialo y pégalo. reply: |- - Ingeniosa imagen – presioné el botón me gusta :heart: para hacerte saber cuánto lo aprecié heart_eyes + Ingeniosa imagen – presioné el botón me gusta :heart: para hacerte saber cuánto lo aprecié :heart_eyes: like_not_found: |- ¿Te olvidaste de dar me gusta :heart: a mi [publicación?](%{url}) :crying_cat_face: + likes: + instructions: |- + Aquí va una foto de un unicornio: + + + + Si te gusta (¡y a quién no le gustaría!), pulsa el botón me gusta :heart: debajo de este mensaje para que lo sepa. + reply: |- + ¡Gracias por darle me gusta a mi mensaje! formatting: instructions: |- ¿Puedes poner algunas letras en **negrita** o _cursiva_ en tu respuesta? @@ -275,7 +281,7 @@ es: - ¿Notaste que estás de nuevo en el principio? Alimenta a este pobre capibara **respondiendo con el emoji de `%{search_answer}`** y esto te llevará automáticamente mente al final. + ¿Notaste que estás de nuevo en el principio? Alimenta a este pobre capibara **respondiendo con el emoji de `%{search_answer}`** y esto te llevará automáticamente al final. reply: |- ¡Lo encontraste! :tada: diff --git a/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml b/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml index c8b08dad35..ce179c0211 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml @@ -24,8 +24,6 @@ fa_IR: این مدال وقتی که راهنمای پیشرفته‌ کاربران با موفقیت به پایان رسیده باشد، اعطا می‌گردد. شما ابزارهای پیشرفته‌ی گفتگو را فرا گرفته اید و الان دارای مجوز کاملید! discourse_narrative_bot: bio: "سلام. من یک فرد واقعی نیستم. من رباتی هستم که می‌توانم در رابطه با این تارنما به شما آموزش بدهم. برای اینکه با من تعامل داشته باشی، برایم پیام بفرست یا با استفاده از **`@%{discobot_username}`** به من اشاره بکن." - tl2_promotion_message: - subject_template: "تبریک می‌گویم به خاطر ارتقاء شما به سطح اعتماد!" timeout: message: |- سلام @%{username}، خواستم احوالت را جویا بشم چون مدتی بود از تو نشنیدم. diff --git a/plugins/discourse-narrative-bot/config/locales/server.fi.yml b/plugins/discourse-narrative-bot/config/locales/server.fi.yml index 5408c78d47..6368f9fc6b 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.fi.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.fi.yml @@ -25,8 +25,6 @@ fi: Tämä ansiomerkki myönnetään, kun suoritat menestyksellä vuorovaikutteisen palstan käytön jatkokurssin. Olet omaksunut edistyneemmätkin toiminnot, ja olet taitosi osoittanut. discourse_narrative_bot: bio: "Moi! En ole ihminen. Olen botti, ja tarjoan opetusta sivuston käyttämisestä. Jos haluat jutella, lähetä yksityisviesti tai mainitse **`@%{discobot_username}`** missä vain." - tl2_promotion_message: - subject_template: "Onnittelut luottamustasonoususta!" timeout: message: |- Hei @%{username}, kyselen kuulumisia, kun en ole kuullut sinusta hetkeen. diff --git a/plugins/discourse-narrative-bot/config/locales/server.fr.yml b/plugins/discourse-narrative-bot/config/locales/server.fr.yml index ede777d225..69b5c642e1 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.fr.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.fr.yml @@ -25,8 +25,6 @@ fr: Ce badge est décerné quand vous avez terminé avec succès le tutoriel interactif des utilisateurs avancés. Vous avez pris l'initiative d'apprendre les outils avancés de la discussion et vous êtes maintenant certifié à 100% ! discourse_narrative_bot: bio: "Bonjour, je ne suis pas une personne réelle. Je suis un robot pour vous faire découvrir ce site. Pour interagir avec moi, envoyez-moi un message ou mentionnez **`@%{discobot_username}`** n'importe où." - tl2_promotion_message: - subject_template: "Félicitations pour votre promotion de niveau de confiance !" timeout: message: |- Hey @%{username}, je viens voir si tout va bien car je n'ai pas eu de vos nouvelles depuis un moment. @@ -144,7 +142,6 @@ fr: reset_trigger: "tutoriel" title: "Certificat d'achèvement du tutoriel pour les nouveaux utilisateurs" cert_title: "En reconnaissance de l'accomplissement avec succès du tutoriel nouvel utilisateur" - delete_reason: "L'utilisateur a ignoré les conseils du nouvel utilisateur" hello: title: "Bienvenue !" message: |- diff --git a/plugins/discourse-narrative-bot/config/locales/server.he.yml b/plugins/discourse-narrative-bot/config/locales/server.he.yml index 72c4acd4dc..32765a2af4 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.he.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.he.yml @@ -27,7 +27,9 @@ he: discourse_narrative_bot: bio: "היי, אני לא בן אדם אמיתי. אני בוט שיכול ללמד אותך על האתר הזה. כדי לתקשר איתי, יש לשלוח אלי הודעה או לאזכר את **‎`@%{discobot_username}`** בכל מקום שהוא." tl2_promotion_message: - subject_template: "ברכותינו על התקדמותך בסולם דרגות האמון!" + subject_template: "עכשיו מכשקודמת, הגיע הזמן ללמוד על כמה מהתכונות המתקדמות!" + text_body_template: | + יש להגיב להודעה הזאת עם `‎@%{discobot_username} %{reset_trigger}` כדי ללמוד עוד על מה שניתן לעשות. timeout: message: |- היי @%{username}, מוודא מה אתך כי לא שמעתי ממך זמן מה. @@ -145,7 +147,6 @@ he: reset_trigger: "מדריך" title: "תעודת השלמת הדרכה למשתמשים חדשים" cert_title: "כהוקרה על סיום מוצלח של מדריך המשתמשים החדשים" - delete_reason: "המשתמש דילג על עצות המשתמשים החדשים" hello: title: "ברכות!" message: |- diff --git a/plugins/discourse-narrative-bot/config/locales/server.hu.yml b/plugins/discourse-narrative-bot/config/locales/server.hu.yml index 389234130d..6edfd49ad9 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.hu.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.hu.yml @@ -25,8 +25,6 @@ hu: Ez a jelvényt az interaktív haladó felhasználói eligazítás sikeres teljesítéséért jár. Elsajátította a beszélgetés speciális eszközeit, és ezzel teljesen hivatalos lett! discourse_narrative_bot: bio: "Üdv, nem igazi személy vagyok. Egy bot vagyok, és megtanítom az oldal használatát. A velem történő interakcióhoz küldjön nekem egy üzenetet, vagy említsen meg így bárhol: **`@%{discobot_username}`**." - tl2_promotion_message: - subject_template: "Gratulálunk az első bizalmi szint előléptetéséért!" timeout: message: |- Üdv @%{username}, csak beköszönök, mert nem hallottam Önről egy ideje. diff --git a/plugins/discourse-narrative-bot/config/locales/server.id.yml b/plugins/discourse-narrative-bot/config/locales/server.id.yml index 4e239a915d..cf9590b983 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.id.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.id.yml @@ -25,8 +25,6 @@ id: Lencana ini diberikan atas keberhasilan menyelesaikan panduan interaktif pengguna anggota forum lanjutan. Anda telah ahli menggunakan fasilitas lanjutan dalam diskusi — sekarang Anda telah bersertifikat penuh menjadi anggota forum. discourse_narrative_bot: bio: "Salam, saya bukan manusia asli. Saya adalah robot yang akan memandu Anda menggunakan forum ini. Untuk berkomunikasi dengan saya, kirimi saya pesan atau panggil **`@%{discobot_username}`** dimana saja." - tl2_promotion_message: - subject_template: "Selamat atas promosi tingkat kepercayaan Anda!" timeout: message: |- Salam @%{username}, hanya ingin menyapa karena saya tidak melihat Anda beberapa waktu. diff --git a/plugins/discourse-narrative-bot/config/locales/server.it.yml b/plugins/discourse-narrative-bot/config/locales/server.it.yml index ddff8508d6..41aeef5445 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.it.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.it.yml @@ -25,8 +25,6 @@ it: Questo distintivo è assegnato al completamento del tutorial interattivo per utenti avanzati. Hai imparato gli strumenti avanzati di discussione — e ora sei un diplomato! discourse_narrative_bot: bio: "Ciao, io non sono una persona reale. Sono un robot che ti può insegnare ad usare questo sito. Per interagire con me inviami un messaggio o menziona**`@%{discobot_username}`** ovunque." - tl2_promotion_message: - subject_template: "Congratulazioni per la promozione del tuo livello di esperienza!" timeout: message: |- Hey @%{username}, sto solo controllando perché è da un po' che non ti sento. diff --git a/plugins/discourse-narrative-bot/config/locales/server.ko.yml b/plugins/discourse-narrative-bot/config/locales/server.ko.yml index 7683faf162..e1ae80f9ff 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.ko.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.ko.yml @@ -20,14 +20,14 @@ ko: long_description: | 이 배지는 유저 튜토리얼을 성공적으로 마친 유저들에게 부여됩니다. 토론을 위한 기본적인 도구를 익혀 인증받은 사용자가 되었습니다! licensed: - name: Discourse 사용 면허 + name: 고급 라이센스 description: "고급 사용자 튜토리얼을 완료하였습니다" long_description: | 이 배지는 고급 유저 튜토리얼을 성공적으로 완료한 사용자에게 부여됩니다. 당신은 고급 토론 도구를 숙지하여 면허를 취득하였습니다! discourse_narrative_bot: bio: "안녕, 사실 나는 사람이 아니야. 이 사이트에 대해서 알려주는 로봇이지. 나한테 물어보고 싶은 게 있다면 메세지를 보내거나 **`@%{discobot_username}`** 를 멘션하면 돼." tl2_promotion_message: - subject_template: "신뢰 수준 프로모션을 축하합니다!" + subject_template: "이제 승격 했으므로 몇 가지 고급 기능에 대해 알아볼 차례입니다!" timeout: message: |- 안녕 @%{username}, 너한테서 소식이 없어서 한 번 말 걸어봤어. @@ -115,6 +115,15 @@ ko: random_mention: reply: |- 내가 뭘 할 수 있는지 알고 싶다면 `@%{discobot_username} %{help_trigger}` 라고 말하렴. + bot_actions: |- + `@%{discobot_username} %{dice_trigger} 2d6` + > :game_die: 3, 6 + + `@%{discobot_username} %{quote_trigger}` + %{quote_sample} + + `@%{discobot_username} %{magic_8_ball_trigger}` + > :crystal_ball: 답글을 쓸 수 있습니다 do_not_understand: first_response: |- 우와, 답글을 써줘서 고마워! @@ -131,7 +140,6 @@ ko: reset_trigger: "지도 시간" title: "새로운 사용자 튜토리얼 수료증" cert_title: "새로운 사용자 튜토리얼을 성공적으로 마친 것에 대한 보상으로" - delete_reason: "사용자가 새 사용자 팁을 건너뛰었습니다." hello: title: "인사말!" message: |- @@ -173,8 +181,16 @@ ko: 다음엔 니가 한번 업로드 해봐. 사진 링크만 있는 한 줄을 남겨도 돼! likes: + instructions: |- + 다음은 유니콘 사진입니다. + + + + 마음에 들면 글 아래의 좋아요 :heart: 버튼을 눌러 알려주세요. reply: |- 내 글을 좋아해 주셔서 감사합니다! + not_found: |- + 내 글(%{url})에 좋아요 :heart: 안 눌러 줄거에요? :crying_cat_face: formatting: instructions: |- 답글에서 글을 **진하게** 만들거나 _이탤릭_처리를 할 수 있니? diff --git a/plugins/discourse-narrative-bot/config/locales/server.nl.yml b/plugins/discourse-narrative-bot/config/locales/server.nl.yml index 92facec1f2..f5a2ac5cfc 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.nl.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.nl.yml @@ -26,8 +26,6 @@ nl: Deze badge wordt toegekend wanneer de interactieve handleiding voor gevorderde gebruikers met succes is doorlopen. U hebt de geavanceerde hulpmiddelen voor discussie onder de knie – en u bent nu volledig gecertificeerd! discourse_narrative_bot: bio: "Hallo, ik ben geen echt persoon. Ik ben een bot die u uitleg over deze website kan geven. Stuur mij een bericht of noem ergens **`@%{discobot_username}`** om met mij te communiceren." - tl2_promotion_message: - subject_template: "Gefeliciteerd met de promotie van uw vertrouwensniveau!" dice: trigger: "gooien" invalid: |- @@ -99,7 +97,6 @@ nl: reset_trigger: "handleiding" title: "Certificaat voor het voltooien van de handleiding voor nieuwe gebruikers" cert_title: "Als erkenning voor de succesvolle voltooiing van de handleiding voor nieuwe gebruikers" - delete_reason: "Gebruiker heeft de tips voor nieuwe gebruikers overgeslagen" hello: title: "Gegroet!" formatting: diff --git a/plugins/discourse-narrative-bot/config/locales/server.pl_PL.yml b/plugins/discourse-narrative-bot/config/locales/server.pl_PL.yml index c021ca1826..0d7a69db04 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.pl_PL.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.pl_PL.yml @@ -25,8 +25,6 @@ pl_PL: Odznaka została nadana za ukończenie interaktywnego tutorialu dla zaawansowanych użytkowników. Poznałeś zaawansowane narzędzia dyskusji i jesteś teraz w pełni licencjonowany! discourse_narrative_bot: bio: "Witaj, nie jestem prawdziwą osobą. Jestem botem, który może nauczyć Cię korzystania z tej witryny. Aby skomunikować się ze mną, wyślij do mnie wiadomość lub oznacz **`@%{discobot_username}`** w dowolnym miejscu." - tl2_promotion_message: - subject_template: "Gratuluję zdobycia nowego poziomu zaufania!" timeout: message: |- Witaj @%{username}, przypominam o sobie bo od dawna do mnie nie zaglądałeś. diff --git a/plugins/discourse-narrative-bot/config/locales/server.pt_BR.yml b/plugins/discourse-narrative-bot/config/locales/server.pt_BR.yml index fb745fea71..ab7d055b1e 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.pt_BR.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.pt_BR.yml @@ -25,8 +25,6 @@ pt_BR: Este emblema é concedido após a conclusão bem-sucedida do tutorial interativo de usuário avançado. Você dominou as ferramentas avançadas de discussão — e agora está totalmente licenciado! discourse_narrative_bot: bio: "Oi, eu não sou uma pessoa real. Eu sou um robô que pode te ensinar sobre este site. Para interagir comigo, me envie uma mensagem ou mencione **`@%{discobot_username}`** em qualquer lugar." - tl2_promotion_message: - subject_template: "Parabéns pela sua promoção em nível de confiança!" timeout: message: |- Oi @%{username}, só estou checando para ver se está tudo bem porque não tenho notícias de você já tem um tempo. diff --git a/plugins/discourse-narrative-bot/config/locales/server.ru.yml b/plugins/discourse-narrative-bot/config/locales/server.ru.yml index e459d70736..903b575dcd 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.ru.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.ru.yml @@ -11,7 +11,7 @@ ru: discourse_narrative_bot_ignored_usernames: "Псевдонимы пользователей, которые бот будет игнорировать" discourse_narrative_bot_disable_public_replies: "Отключить общедоступные ответы в боте" discourse_narrative_bot_welcome_post_type: "Тип приветственного сообщения, отправляемого ботом" - discourse_narrative_bot_welcome_post_delay: "Подождать (n) секунд перед отправкой ботом приветственного сообщения" + discourse_narrative_bot_welcome_post_delay: "Подождать указанное здесь количество секунд перед отправкой ботом приветственного сообщения" discourse_narrative_bot_skip_tutorials: "Пропустить обучение" badges: certified: @@ -27,11 +27,9 @@ ru: discourse_narrative_bot: bio: "Здравствуйте! Я не реальный человек. Я бот, который может научить вас работать с этим сайтом. Чтобы работать со мной, отправьте мне сообщение или упоминание **`@%{discobot_username}`** в любом сообщении." tl2_promotion_message: - subject_template: "Поздравляем с повышением уровня доверия!" + subject_template: "Теперь, когда вы были повышены, пришло время узнать о дополнительном функционале форума!" text_body_template: | - Теперь, когда вас повысили, пришло время узнать о некоторых дополнительных функциях! - - Напишите `@%{discobot_username} %{reset_trigger}`, чтобы узнать больше о предоставляемых возможностях. + Ответьте на это сообщение фразой `@%{discobot_username} %{reset_trigger}`, чтобы узнать больше о новых доступных вам возможностях форума. timeout: message: |- Здравствуйте, @%{username}, это просто проверка связи, поскольку какое-то время не получал от вас ответа. @@ -149,7 +147,6 @@ ru: reset_trigger: "учебник" title: "Сертификат о прохождении обучения нового пользователя" cert_title: "В знак признания успешного усвоения руководства пользователя" - delete_reason: "В настройках пользователя отключён показ советов для новых пользователей" hello: title: "Приветствую!" message: |- diff --git a/plugins/discourse-narrative-bot/config/locales/server.sl.yml b/plugins/discourse-narrative-bot/config/locales/server.sl.yml index 7f488d14dd..7501ba65c0 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.sl.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.sl.yml @@ -26,8 +26,6 @@ sl: Ta značka vam je podeljena po uspešno opravljenem vodiču za napredne uporabnike. Sedaj obvladate napredna orodja za razpravo - in tako ste licencirani! discourse_narrative_bot: bio: "Pozdravljeni, jaz nisem prava oseba. Sem robot, ki te lahko naučim o tej strani. Za pogovor z menoj mi pošlji sporočilo ali me omeni z **`@%{discobot_username}`**." - tl2_promotion_message: - subject_template: "Čestitamo vam za napredovanje na višjo raven zaupanja!" timeout: message: |- Pozdravljeni @%{username}, samo preverjam, ker vas že nekaj časa nisem slišal. @@ -145,7 +143,6 @@ sl: reset_trigger: "vodič" title: "Priznanje o opravljenem vodiču za novega uporabnika" cert_title: "Priznanje za uspešno končan vodič za nove uporabnike." - delete_reason: "Uporabnik je preskočil nasvete za začetnike" hello: title: "Pozdravljeni!" message: |- diff --git a/plugins/discourse-narrative-bot/config/locales/server.sv.yml b/plugins/discourse-narrative-bot/config/locales/server.sv.yml index 1e3da51b1b..3cad4ac6dd 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.sv.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.sv.yml @@ -27,11 +27,9 @@ sv: discourse_narrative_bot: bio: "Hej, Jag är inte en riktig person. Jag är en bot som kan lära dig använda denna site. För att interagera med mig skickar du mig ett meddelande eller nämner **`@%{discobot_username}`** någonstans." tl2_promotion_message: - subject_template: "Grattis till din högre förtroendenivå!" + subject_template: "Nu när du har blivit befordrad är det dags att lära dig mer om några avancerade funktioner!" text_body_template: | - Nu när du har blivit befordrad är det dags att lära sig några avancerade funktioner! - - Svara på det här meddelandet med `@%{discobot_username} %{reset_trigger}` för att få veta mer om vad du kan göra. + Svara på detta meddelande med `@%{discobot_username} %{reset_trigger}` för att ta reda på mer om vad du kan göra. timeout: message: |- Hej @%{username}, bara ett meddelande för att kolla hur det går då jag ej hört av dig på ett tag. @@ -149,7 +147,6 @@ sv: reset_trigger: "handledning" title: "Certifikat för genomförd handledning för nya användare" cert_title: "Som erkännande för att handledningen för nya användare har genomförts" - delete_reason: "Användaren hoppade över tipsen för nya användare" hello: title: "Hej!" message: |- diff --git a/plugins/discourse-narrative-bot/config/locales/server.tr_TR.yml b/plugins/discourse-narrative-bot/config/locales/server.tr_TR.yml index 30cec36829..51cb9d4e61 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.tr_TR.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.tr_TR.yml @@ -25,8 +25,6 @@ tr_TR: Bu rozet, interaktif ileri kullanıcı eğitiminin başarılı tamamlanmasının ardından verilir. Gelişmiş tartışma araçlarına hakim oldunuz - ve şimdi tamamen lisanslısınız! discourse_narrative_bot: bio: "Merhaba, ben gerçek bir insan değilim. Bu site hakkındaki şeyleri size öğretebilecek bir robotum. Benimle etkileşime geçmek için bana bir mesaj gönderin veya ** `@%{discobot_username}` ** şekliyle herhangi bir yerde beni etiketleyin." - tl2_promotion_message: - subject_template: "Güven seviyesi terfiniz için tebrikler!" timeout: message: |- Merhaba @ %{username}, uzun süredir sizden haber alamadık. diff --git a/plugins/discourse-narrative-bot/config/locales/server.uk.yml b/plugins/discourse-narrative-bot/config/locales/server.uk.yml index 0c63d79673..714c06a458 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.uk.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.uk.yml @@ -12,6 +12,7 @@ uk: discourse_narrative_bot_disable_public_replies: "Вимкнути публічні відповіді дискобота" discourse_narrative_bot_welcome_post_type: "Тип привітального повідомлення, яке повинен надсилати дискобот" discourse_narrative_bot_welcome_post_delay: "Зачекайте (n) секунд, перш ніж надсилати привітальне повідомлення дискоботом." + discourse_narrative_bot_skip_tutorials: "Пропустити навчання" badges: certified: name: Сертифікований @@ -25,6 +26,10 @@ uk: Цей знак надається після успішного завершення інтерактивного навчального посібника для просунутих користувачів. Ви освоїли передові інструменти обговорення – і тепер ви повністю ліцензовані! discourse_narrative_bot: bio: "Привіт, я не справжня людина. Я бот, який може навчити вас роботі з цим сайтом. Щоб спілкуватися зі мною, надішліть мені повідомлення або згадайте ** `@ %{discobot_username}` ** де завгодно." + tl2_promotion_message: + subject_template: "Тепер, коли ви були підвищені, настав час дізнатися про деякі додаткові функції!" + text_body_template: | + Відповідайте на це повідомлення з `@%{discobot_username} %{reset_trigger}`, щоб дізнатися більше про нові доступні вам функції форуму. timeout: message: |- Привіт @%{username}, просто перевіряю, тому що я нічого не чув від вас давно. @@ -40,6 +45,8 @@ uk: trigger: "крутити" invalid: |- Вибачте, комбінація кубиків математично неможлива. :confounded: + not_enough_dice: |- + У мене лише %{num_of_dice} кості. [Shameful](http://www.therobotsvoice.com/2009/04/the_10_most_shameful_rpg_dice.php), що це таке! quote: trigger: "цитата" "1": diff --git a/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml b/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml index c48a5549cc..c7ba1fbfb3 100644 --- a/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml +++ b/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml @@ -27,7 +27,9 @@ zh_CN: discourse_narrative_bot: bio: "你好,我不是真人,而是一个教你如何使用站点的机器人。你可以给我发消息或在任意地方提及 **`@%{discobot_username}`** 与我交互。" tl2_promotion_message: - subject_template: "恭喜!你的信任等级提升了!" + subject_template: "现在你已经晋升了,是时候学习高级功能了!" + text_body_template: | + 使用`@%{discobot_username} %{reset_trigger}`回复本消息可以找出更多功能。 timeout: message: |- 你好, @%{username}。好久没见到你了,一切可好? @@ -145,7 +147,6 @@ zh_CN: reset_trigger: "教程" title: "新用户教程完成证明" cert_title: "你已经成功完成新用户教程了" - delete_reason: "用户跳过了新用户提示" hello: title: "欢迎!" message: |- @@ -185,7 +186,7 @@ zh_CN: reply: |- 好赞的图片—我点了赞 :heart: ,你知道我很乐意看见它的 :heart_eyes: like_not_found: |- - 你忘记赞 :heart: 我的[帖子](%{url})了吗? :crying_cat_face: + 你忘记点赞 :heart: 我的[帖子](%{url})了吗? :crying_cat_face: not_found: |- 看起来你还没有上传一张图片,因此我选择了一张可以令你满意的。 @@ -200,7 +201,7 @@ zh_CN: 如果你喜欢的话(谁会不喜欢呢!)点击帖子底部的赞:heart:按钮让我知道。 reply: |- - 谢谢你喜欢我的帖子! + 感谢你喜欢我的帖子! not_found: |- 你忘记赞 :heart: 我的[帖子](%{url})了吗? :crying_cat_face: formatting: diff --git a/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/base.rb b/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/base.rb index 358f59680b..066a0bdeac 100644 --- a/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/base.rb +++ b/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/base.rb @@ -184,7 +184,7 @@ module DiscourseNarrativeBot end def i18n_post_args(extra = {}) - { base_uri: Discourse.base_uri }.merge(extra) + { base_uri: Discourse.base_path }.merge(extra) end def valid_topic?(topic_id) diff --git a/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/new_user_narrative.rb b/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/new_user_narrative.rb index 86ca941815..130450a697 100644 --- a/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/new_user_narrative.rb +++ b/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/new_user_narrative.rb @@ -11,14 +11,14 @@ module DiscourseNarrativeBot begin: { init: { next_state: :tutorial_bookmark, - next_instructions: Proc.new { I18n.t("#{I18N_KEY}.bookmark.instructions", base_uri: Discourse.base_uri) }, + next_instructions: Proc.new { I18n.t("#{I18N_KEY}.bookmark.instructions", base_uri: Discourse.base_path) }, action: :say_hello } }, tutorial_bookmark: { next_state: :tutorial_onebox, - next_instructions: Proc.new { I18n.t("#{I18N_KEY}.onebox.instructions", base_uri: Discourse.base_uri) }, + next_instructions: Proc.new { I18n.t("#{I18N_KEY}.onebox.instructions", base_uri: Discourse.base_path) }, bookmark: { action: :reply_to_bookmark @@ -32,7 +32,7 @@ module DiscourseNarrativeBot tutorial_onebox: { next_state: :tutorial_emoji, - next_instructions: Proc.new { I18n.t("#{I18N_KEY}.emoji.instructions", base_uri: Discourse.base_uri) }, + next_instructions: Proc.new { I18n.t("#{I18N_KEY}.emoji.instructions", base_uri: Discourse.base_path) }, reply: { action: :reply_to_onebox @@ -45,7 +45,7 @@ module DiscourseNarrativeBot next_instructions: Proc.new { I18n.t("#{I18N_KEY}.mention.instructions", discobot_username: self.discobot_username, - base_uri: Discourse.base_uri) + base_uri: Discourse.base_path) }, reply: { action: :reply_to_emoji @@ -55,7 +55,7 @@ module DiscourseNarrativeBot tutorial_mention: { prerequisite: Proc.new { SiteSetting.enable_mentions }, next_state: :tutorial_formatting, - next_instructions: Proc.new { I18n.t("#{I18N_KEY}.formatting.instructions", base_uri: Discourse.base_uri) }, + next_instructions: Proc.new { I18n.t("#{I18N_KEY}.formatting.instructions", base_uri: Discourse.base_path) }, reply: { action: :reply_to_mention @@ -64,7 +64,7 @@ module DiscourseNarrativeBot tutorial_formatting: { next_state: :tutorial_quote, - next_instructions: Proc.new { I18n.t("#{I18N_KEY}.quoting.instructions", base_uri: Discourse.base_uri) }, + next_instructions: Proc.new { I18n.t("#{I18N_KEY}.quoting.instructions", base_uri: Discourse.base_path) }, reply: { action: :reply_to_formatting @@ -73,7 +73,7 @@ module DiscourseNarrativeBot tutorial_quote: { next_state: :tutorial_images, - next_instructions: Proc.new { I18n.t("#{I18N_KEY}.images.instructions", base_uri: Discourse.base_uri) }, + next_instructions: Proc.new { I18n.t("#{I18N_KEY}.images.instructions", base_uri: Discourse.base_path) }, reply: { action: :reply_to_quote @@ -85,7 +85,7 @@ module DiscourseNarrativeBot tutorial_images: { prerequisite: Proc.new { @user.has_trust_level?(SiteSetting.min_trust_to_post_embedded_media) }, next_state: :tutorial_likes, - next_instructions: Proc.new { I18n.t("#{I18N_KEY}.likes.instructions", base_uri: Discourse.base_uri) }, + next_instructions: Proc.new { I18n.t("#{I18N_KEY}.likes.instructions", base_uri: Discourse.base_path) }, reply: { action: :reply_to_image }, @@ -101,7 +101,7 @@ module DiscourseNarrativeBot I18n.t("#{I18N_KEY}.flag.instructions", guidelines_url: url_helpers(:guidelines_url), about_url: url_helpers(:about_index_url), - base_uri: Discourse.base_uri) + base_uri: Discourse.base_path) }, like: { action: :reply_to_likes @@ -115,7 +115,7 @@ module DiscourseNarrativeBot tutorial_flag: { prerequisite: Proc.new { SiteSetting.allow_flagging_staff }, next_state: :tutorial_search, - next_instructions: Proc.new { I18n.t("#{I18N_KEY}.search.instructions", base_uri: Discourse.base_uri) }, + next_instructions: Proc.new { I18n.t("#{I18N_KEY}.search.instructions", base_uri: Discourse.base_path) }, flag: { action: :reply_to_flag }, diff --git a/plugins/discourse-narrative-bot/plugin.rb b/plugins/discourse-narrative-bot/plugin.rb index 10ddf09c9d..2a97edf640 100644 --- a/plugins/discourse-narrative-bot/plugin.rb +++ b/plugins/discourse-narrative-bot/plugin.rb @@ -108,7 +108,7 @@ after_initialize do private def fetch_avatar(user) - avatar_url = UrlHelper.absolute(Discourse.base_uri + user.avatar_template.gsub('{size}', '250')) + avatar_url = UrlHelper.absolute(Discourse.base_path + user.avatar_template.gsub('{size}', '250')) FileHelper.download( avatar_url.to_s, max_file_size: SiteSetting.max_image_size_kb.kilobytes, @@ -194,6 +194,15 @@ after_initialize do return if topic.blank? first_post = topic.ordered_posts.first + + notification = Notification.where(topic_id: topic.id, post_number: first_post.post_number).first + if notification.present? + Notification.read(self, notification.id) + self.saw_notification_id(notification.id) + self.reload + self.publish_notifications_state + end + PostDestroyer.new(Discourse.system_user, first_post, skip_staff_log: true).destroy DiscourseNarrativeBot::Store.remove(self.id) end @@ -293,12 +302,15 @@ after_initialize do discobot_username: ::DiscourseNarrativeBot::Base.new.discobot_username, reset_trigger: "#{::DiscourseNarrativeBot::TrackSelector.reset_trigger} #{::DiscourseNarrativeBot::AdvancedUserNarrative.reset_trigger}") + recipient = args[:post].topic.topic_users.where.not(user_id: args[:post].user_id).last.user + PostCreator.create!( ::DiscourseNarrativeBot::Base.new.discobot_user, title: I18n.t("discourse_narrative_bot.tl2_promotion_message.subject_template"), raw: raw, - topic_id: args[:post].topic_id, - skip_validations: true + skip_validations: true, + archetype: Archetype.private_message, + target_usernames: recipient.username ) end end diff --git a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/advanced_user_narrative_spec.rb b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/advanced_user_narrative_spec.rb index 10b499e9fe..31b94e83ea 100644 --- a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/advanced_user_narrative_spec.rb +++ b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/advanced_user_narrative_spec.rb @@ -732,4 +732,13 @@ RSpec.describe DiscourseNarrativeBot::AdvancedUserNarrative do end end end + + it 'invites to advanced training when user is promoted to TL2' do + recipient = Fabricate(:user) + expect { + DiscourseEvent.trigger(:system_message_sent, post: Post.last, message_type: 'tl2_promotion_message') + }.to change { Topic.count } + expect(Topic.last.title).to eq(I18n.t("discourse_narrative_bot.tl2_promotion_message.subject_template")) + expect(Topic.last.topic_users.map(&:user_id).sort).to eq([DiscourseNarrativeBot::Base.new.discobot_user.id, recipient.id]) + end end diff --git a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb index f67f58c236..bde16d3db7 100644 --- a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb +++ b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb @@ -745,7 +745,7 @@ describe DiscourseNarrativeBot::NewUserNarrative do it "should use correct path to images on subfolder installs" do GlobalSetting.stubs(:relative_url_root).returns('/forum') - Discourse.stubs(:base_uri).returns("/forum") + Discourse.stubs(:base_path).returns("/forum") post.update!(raw: skip_trigger) diff --git a/plugins/discourse-narrative-bot/spec/user_spec.rb b/plugins/discourse-narrative-bot/spec/user_spec.rb index 3e514cc4ae..f775dd7111 100644 --- a/plugins/discourse-narrative-bot/spec/user_spec.rb +++ b/plugins/discourse-narrative-bot/spec/user_spec.rb @@ -123,6 +123,8 @@ describe User do user.user_option.save! }.to change { Topic.count }.by(-1) .and change { UserHistory.count }.by(0) + .and change { user.unread_high_priority_notifications }.by(-1) + .and change { user.notifications.count }.by(-1) end end diff --git a/plugins/discourse-presence/assets/javascripts/discourse/lib/presence.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/lib/presence.js.es6 index da8b7ef97c..aa070d68ab 100644 --- a/plugins/discourse-presence/assets/javascripts/discourse/lib/presence.js.es6 +++ b/plugins/discourse-presence/assets/javascripts/discourse/lib/presence.js.es6 @@ -102,7 +102,18 @@ const Presence = EmberObject.extend({ }, publish(state, whisper, postId, staffOnly) { - if (this.get("currentUser.hide_profile_and_presence")) { + // 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; } diff --git a/plugins/discourse-presence/assets/javascripts/discourse/templates/components/topic-presence-display.hbs b/plugins/discourse-presence/assets/javascripts/discourse/templates/components/topic-presence-display.hbs index 636ba39a26..002a2d3189 100644 --- a/plugins/discourse-presence/assets/javascripts/discourse/templates/components/topic-presence-display.hbs +++ b/plugins/discourse-presence/assets/javascripts/discourse/templates/components/topic-presence-display.hbs @@ -6,7 +6,7 @@ {{/each}}
- {{i18n 'presence.replying_to_topic' count=users.length}}{{!-- (using comment to stop whitespace) + {{i18n "presence.replying_to_topic" count=users.length}}{{!-- (using comment to stop whitespace) --}}...
diff --git a/plugins/discourse-presence/config/locales/client.hu.yml b/plugins/discourse-presence/config/locales/client.hu.yml index 9390402c3f..0b01d505e5 100644 --- a/plugins/discourse-presence/config/locales/client.hu.yml +++ b/plugins/discourse-presence/config/locales/client.hu.yml @@ -7,8 +7,8 @@ hu: js: presence: - replying: "Válaszol" - editing: "Szerkesztés" + replying: "válaszol" + editing: "szerkeszt" replying_to_topic: - one: "Válaszolni" - other: "Válaszolni" + one: "válaszol" + other: "válaszol" diff --git a/plugins/discourse-presence/config/locales/server.hu.yml b/plugins/discourse-presence/config/locales/server.hu.yml index 17dc9ad291..fc6d2194cc 100644 --- a/plugins/discourse-presence/config/locales/server.hu.yml +++ b/plugins/discourse-presence/config/locales/server.hu.yml @@ -6,5 +6,5 @@ hu: site_settings: - presence_enabled: "Felhasználók mutatása akik jelenleg válaszolnak a jelenlegi témára, vagy szerkesztik a jelenlegi bejegyzést." - presence_max_users_shown: "Maximális számú ember amit mutat." + presence_enabled: "Megjeleníti azon felhasználókat, akik jelenleg válaszolnak a jelenlegi témára, vagy szerkesztik a jelenlegi bejegyzést?" + presence_max_users_shown: "A megjelenített felhasználók legnagyobb száma." diff --git a/plugins/discourse-presence/config/locales/server.pt_BR.yml b/plugins/discourse-presence/config/locales/server.pt_BR.yml index 4a278d9b3f..9c9f577dce 100644 --- a/plugins/discourse-presence/config/locales/server.pt_BR.yml +++ b/plugins/discourse-presence/config/locales/server.pt_BR.yml @@ -7,4 +7,4 @@ pt_BR: site_settings: presence_enabled: "Mostrar usuários que estão atualmente respondendo ao tópico atual, ou editando a postagem atual?" - presence_max_users_shown: "Número máximo de usuários exibidos." + presence_max_users_shown: "Número máximo de usuários mostrados." diff --git a/plugins/discourse-presence/config/locales/server.ru.yml b/plugins/discourse-presence/config/locales/server.ru.yml index 2d215c6810..5c35ff74b1 100644 --- a/plugins/discourse-presence/config/locales/server.ru.yml +++ b/plugins/discourse-presence/config/locales/server.ru.yml @@ -6,5 +6,5 @@ ru: site_settings: - presence_enabled: "Показать пользователей, которые в данный момент отвечают на текущую тему или редактируют текущее сообщение?" + presence_enabled: "Показывать пользователей, которые в данный момент отвечают на текущую тему или редактируют текущее сообщение" presence_max_users_shown: "Максимальное количество показываемых пользователей, отвечающих в данный момент." diff --git a/plugins/discourse-presence/plugin.rb b/plugins/discourse-presence/plugin.rb index de6a3e0fd9..34eb342986 100644 --- a/plugins/discourse-presence/plugin.rb +++ b/plugins/discourse-presence/plugin.rb @@ -155,7 +155,8 @@ after_initialize do def ensure_presence_enabled if !SiteSetting.presence_enabled || - current_user.user_option.hide_profile_and_presence? + (SiteSetting.allow_users_to_hide_profile && + current_user.user_option.hide_profile_and_presence?) raise Discourse::NotFound end diff --git a/plugins/discourse-presence/spec/requests/presence_controller_spec.rb b/plugins/discourse-presence/spec/requests/presence_controller_spec.rb index b18d731b9b..8df3086aef 100644 --- a/plugins/discourse-presence/spec/requests/presence_controller_spec.rb +++ b/plugins/discourse-presence/spec/requests/presence_controller_spec.rb @@ -45,6 +45,15 @@ describe ::Presence::PresencesController do 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/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 diff --git a/plugins/poll/app/models/poll.rb b/plugins/poll/app/models/poll.rb index 210533836f..cbb3016f46 100644 --- a/plugins/poll/app/models/poll.rb +++ b/plugins/poll/app/models/poll.rb @@ -83,6 +83,7 @@ end # updated_at :datetime not null # chart_type :integer default("bar"), not null # groups :string +# title :string # # Indexes # diff --git a/plugins/poll/app/serializers/poll_serializer.rb b/plugins/poll/app/serializers/poll_serializer.rb index 171eb95387..6444349bac 100644 --- a/plugins/poll/app/serializers/poll_serializer.rb +++ b/plugins/poll/app/serializers/poll_serializer.rb @@ -14,7 +14,8 @@ class PollSerializer < ApplicationSerializer :close, :preloaded_voters, :chart_type, - :groups + :groups, + :title def public true diff --git a/plugins/poll/assets/javascripts/controllers/poll-breakdown.js.es6 b/plugins/poll/assets/javascripts/controllers/poll-breakdown.js.es6 index 6a47eeeaa9..89effd9272 100644 --- a/plugins/poll/assets/javascripts/controllers/poll-breakdown.js.es6 +++ b/plugins/poll/assets/javascripts/controllers/poll-breakdown.js.es6 @@ -2,6 +2,7 @@ import I18n from "I18n"; import Controller from "@ember/controller"; import { action } from "@ember/object"; import { classify } from "@ember/string"; +import { htmlSafe } from "@ember/template"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import loadScript from "discourse/lib/load-script"; @@ -15,6 +16,11 @@ export default Controller.extend(ModalFunctionality, { highlightedOption: null, displayMode: "percentage", + @discourseComputed("model.poll.title", "model.post.topic.title") + title(pollTitle, topicTitle) { + return pollTitle ? htmlSafe(pollTitle) : topicTitle; + }, + @discourseComputed("model.groupableUserFields") groupableUserFields(fields) { return fields.map((field) => { diff --git a/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6 b/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6 index e5aa341108..9090e92f65 100644 --- a/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6 +++ b/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6 @@ -28,6 +28,7 @@ export default Controller.extend({ pollType: null, pollResult: null, + pollTitle: null, init() { this._super(...arguments); @@ -214,6 +215,7 @@ export default Controller.extend({ "pollType", "pollResult", "publicPoll", + "pollTitle", "pollOptions", "pollMin", "pollMax", @@ -230,6 +232,7 @@ export default Controller.extend({ pollType, pollResult, publicPoll, + pollTitle, pollOptions, pollMin, pollMax, @@ -293,6 +296,10 @@ export default Controller.extend({ pollHeader += "]"; output += `${pollHeader}\n`; + if (pollTitle) { + output += `# ${pollTitle.trim()}\n`; + } + if (pollOptions.length > 0 && !isNumber) { pollOptions.split("\n").forEach((option) => { if (option.length !== 0) { @@ -382,6 +389,7 @@ export default Controller.extend({ chartType: BAR_CHART_TYPE, pollResult: this.alwaysPollResult, pollGroups: null, + pollTitle: null, date: moment().add(1, "day").format("YYYY-MM-DD"), time: moment().add(1, "hour").format("HH:mm"), }); diff --git a/plugins/poll/assets/javascripts/discourse/templates/components/poll-breakdown-option.hbs b/plugins/poll/assets/javascripts/discourse/templates/components/poll-breakdown-option.hbs index b56106982e..80431a1335 100644 --- a/plugins/poll/assets/javascripts/discourse/templates/components/poll-breakdown-option.hbs +++ b/plugins/poll/assets/javascripts/discourse/templates/components/poll-breakdown-option.hbs @@ -3,6 +3,7 @@ style={{this.colorBackgroundStyle}} {{on "mouseover" @onMouseOver}} {{on "mouseout" @onMouseOut}} + role="button" > @@ -13,5 +14,5 @@ {{@option.votes}} {{/if}} - {{{@option.html}}} + {{html-safe @option.html}} diff --git a/plugins/poll/assets/javascripts/discourse/templates/modal/poll-breakdown.hbs b/plugins/poll/assets/javascripts/discourse/templates/modal/poll-breakdown.hbs index 0cb26c08d5..693e31a34e 100644 --- a/plugins/poll/assets/javascripts/discourse/templates/modal/poll-breakdown.hbs +++ b/plugins/poll/assets/javascripts/discourse/templates/modal/poll-breakdown.hbs @@ -1,7 +1,8 @@ {{#d-modal-body title="poll.breakdown.title"}}
- {{!-- TODO: replace with the (optional) poll title --}} -

{{this.model.post.topic.title}}

+

+ {{this.title}} +

{{i18n "poll.breakdown.votes" count=this.model.poll.voters}}
diff --git a/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs b/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs index 03e08acee7..3679c83037 100644 --- a/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs +++ b/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs @@ -77,6 +77,11 @@ {{/if}} {{/if}} +
+ + {{input value=pollTitle}} +
+ {{#unless isNumber}}
diff --git a/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 b/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 index d8a2262b80..46ec6e86c4 100644 --- a/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 +++ b/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 @@ -88,11 +88,14 @@ function initializePolls(api) { } if (poll) { + const titleElement = pollElem.querySelector(".poll-title"); + const attrs = { id: `${pollName}-${pollPost.id}`, post: pollPost, poll, vote, + titleHTML: titleElement && titleElement.outerHTML, groupableUserFields: ( api.container.lookup("site-settings:main") .poll_groupable_user_fields || "" diff --git a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6 b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6 index 1cd88d0ad6..da28392b1e 100644 --- a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6 +++ b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6 @@ -3,7 +3,7 @@ import I18n from "I18n"; const DATA_PREFIX = "data-poll-"; const DEFAULT_POLL_NAME = "poll"; -const WHITELISTED_ATTRIBUTES = [ +const ALLOWED_ATTRIBUTES = [ "close", "max", "min", @@ -81,6 +81,29 @@ function invalidPoll(state, tag) { token.content = "[/" + tag + "]"; } +function getTitle(tokens, startToken) { + const startIndex = tokens.indexOf(startToken); + + if (startIndex === -1) { + return; + } + + const pollTokens = tokens.slice(startIndex); + const open = pollTokens.findIndex((token) => token.type === "heading_open"); + const close = pollTokens.findIndex((token) => token.type === "heading_close"); + + if (open === -1 || close === -1) { + return; + } + + const titleTokens = pollTokens.slice(open + 1, close); + + // Remove the heading element + tokens.splice(startIndex + open, close - open + 1); + + return titleTokens; +} + const rule = { tag: "poll", @@ -92,7 +115,9 @@ const rule = { }, after: function (state, openToken, raw) { + const titleTokens = getTitle(state.tokens, openToken); let items = getListItems(state.tokens, openToken); + if (!items) { return invalidPoll(state, raw); } @@ -106,7 +131,7 @@ const rule = { attributes.push([DATA_PREFIX + "status", "open"]); } - WHITELISTED_ATTRIBUTES.forEach((name) => { + ALLOWED_ATTRIBUTES.forEach((name) => { if (attrs[name]) { attributes.push([DATA_PREFIX + name, attrs[name]]); } @@ -139,9 +164,19 @@ const rule = { token = new state.Token("poll_open", "div", 1); token.attrs = [["class", "poll-container"]]; - header.push(token); + if (titleTokens) { + token = new state.Token("title_open", "div", 1); + token.attrs = [["class", "poll-title"]]; + header.push(token); + + header.push(...titleTokens); + + token = new state.Token("title_close", "div", -1); + header.push(token); + } + // generate the options when the type is "number" if (attrs["type"] === "number") { // default values @@ -175,6 +210,7 @@ const rule = { token = new state.Token("list_item_close", "li", -1); header.push(token); } + token = new state.Token("bullet_item_close", "", -1); header.push(token); } @@ -240,6 +276,7 @@ export function setup(helper) { "div.poll", "div.poll-info", "div.poll-container", + "div.poll-title", "div.poll-buttons", "div[data-*]", "span.info-number", diff --git a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6 b/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6 index cc5c2bea5a..5ec3850a0c 100644 --- a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6 +++ b/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6 @@ -356,6 +356,10 @@ createWidget("discourse-poll-container", { } else if (options) { const contents = []; + if (attrs.titleHTML) { + contents.push(new RawHtml({ html: attrs.titleHTML })); + } + if (!checkUserGroups(this.currentUser, poll)) { contents.push( h( @@ -511,6 +515,8 @@ createWidget("discourse-poll-pie-chart", { contents.push(button); } + contents.push(new RawHtml({ html: attrs.titleHTML })); + const chart = this.attach("discourse-poll-pie-canvas", attrs); contents.push(chart); diff --git a/plugins/poll/assets/stylesheets/common/poll-ui-builder.scss b/plugins/poll/assets/stylesheets/common/poll-ui-builder.scss index 7f38887ed4..a4955b8eec 100644 --- a/plugins/poll/assets/stylesheets/common/poll-ui-builder.scss +++ b/plugins/poll/assets/stylesheets/common/poll-ui-builder.scss @@ -56,17 +56,26 @@ $poll-margin: 10px; } } - .poll-textarea { + .poll-textarea, + .poll-title { flex-direction: column; } + .poll-title input { + width: 100%; + } + .poll-textarea textarea { width: 100%; height: 90px; box-sizing: border-box; } - .poll-select + .poll-textarea { + .poll-select + .poll-title { + margin-top: $poll-margin; + } + + .poll-textarea { margin-top: $poll-margin; } diff --git a/plugins/poll/assets/stylesheets/common/poll.scss b/plugins/poll/assets/stylesheets/common/poll.scss index a40bb78917..828a5e12ed 100644 --- a/plugins/poll/assets/stylesheets/common/poll.scss +++ b/plugins/poll/assets/stylesheets/common/poll.scss @@ -149,21 +149,21 @@ div.poll { } .poll-results-chart { - height: 320px; + height: 340px; overflow-y: auto; overflow-x: hidden; } .poll-show-breakdown { - margin-bottom: 10px; + margin-bottom: 0.25em; } } div.poll.pie { .poll-container { display: inline-block; - height: 320px; - max-height: 320px; + height: 340px; + max-height: 340px; overflow-y: auto; } .poll-info { diff --git a/plugins/poll/assets/stylesheets/desktop/poll.scss b/plugins/poll/assets/stylesheets/desktop/poll.scss index bbfffda269..d6e3aa0771 100644 --- a/plugins/poll/assets/stylesheets/desktop/poll.scss +++ b/plugins/poll/assets/stylesheets/desktop/poll.scss @@ -28,6 +28,11 @@ div.poll { border-right: 1px solid var(--primary-low); } + .poll-title { + border-bottom: 1px solid var(--primary-low); + padding: 0.5em 0; + } + .poll-buttons { border-top: 1px solid var(--primary-low); padding: 1em; diff --git a/plugins/poll/config/locales/client.en.yml b/plugins/poll/config/locales/client.en.yml index 856009b504..9b47e0f400 100644 --- a/plugins/poll/config/locales/client.en.yml +++ b/plugins/poll/config/locales/client.en.yml @@ -112,6 +112,8 @@ en: step: Step poll_public: label: Show who voted + poll_title: + label: Title (optional) poll_options: label: Enter one poll option per line automatic_close: diff --git a/plugins/poll/config/locales/client.fa_IR.yml b/plugins/poll/config/locales/client.fa_IR.yml index a23f93438f..d39367f8f7 100644 --- a/plugins/poll/config/locales/client.fa_IR.yml +++ b/plugins/poll/config/locales/client.fa_IR.yml @@ -62,5 +62,7 @@ fa_IR: step: مرحله poll_public: label: نمایش رای دهندگان + poll_title: + label: عنوان (اختیاری) poll_options: label: در هر خط یک گزینه نظرسنجی را وارد کنید diff --git a/plugins/poll/config/locales/client.fr.yml b/plugins/poll/config/locales/client.fr.yml index 69de41fe0c..22fa743c62 100644 --- a/plugins/poll/config/locales/client.fr.yml +++ b/plugins/poll/config/locales/client.fr.yml @@ -97,6 +97,8 @@ fr: step: Pas poll_public: label: Afficher les votants + poll_title: + label: Titre (facultatif) poll_options: label: Entrez une option de sondage par ligne automatic_close: diff --git a/plugins/poll/config/locales/client.he.yml b/plugins/poll/config/locales/client.he.yml index 24c18fb2c0..9b863fc5a2 100644 --- a/plugins/poll/config/locales/client.he.yml +++ b/plugins/poll/config/locales/client.he.yml @@ -113,6 +113,8 @@ he: step: צעד poll_public: label: להציג מי המצביעים + poll_title: + label: כותרת (רשות) poll_options: label: נא למלא אפשרות בחירה אחת בכל שורה automatic_close: diff --git a/plugins/poll/config/locales/client.hy.yml b/plugins/poll/config/locales/client.hy.yml index 53020c2dc5..92f17d0540 100644 --- a/plugins/poll/config/locales/client.hy.yml +++ b/plugins/poll/config/locales/client.hy.yml @@ -49,7 +49,7 @@ hy: title: Ստեղծել Հարցում insert: Ներմուծել Հարցում help: - invalid_values: "Նվազագույն արժեքը պետք է լինի առավելագույն արժեքից փոքր:" + invalid_values: 'Նվազագույն արժեքը պետք է լինի առավելագույն արժեքից փոքր:' min_step_value: Քայլի նվազագույն արժեքն է 1 poll_type: label: Տիպը diff --git a/plugins/poll/config/locales/client.it.yml b/plugins/poll/config/locales/client.it.yml index b53b6ccaa9..bc945adbe0 100644 --- a/plugins/poll/config/locales/client.it.yml +++ b/plugins/poll/config/locales/client.it.yml @@ -97,6 +97,8 @@ it: step: Passo poll_public: label: Mostra i votanti + poll_title: + label: Titolo (facoltativo) poll_options: label: Inserisci un'opzione di sondaggio per riga automatic_close: diff --git a/plugins/poll/config/locales/client.ko.yml b/plugins/poll/config/locales/client.ko.yml index d92c342c35..4c164d8ed5 100644 --- a/plugins/poll/config/locales/client.ko.yml +++ b/plugins/poll/config/locales/client.ko.yml @@ -61,6 +61,7 @@ ko: breakdown: title: "투표 결과" votes: "%{count} 투표" + breakdown: "분석" percentage: "백분율" count: "수" error_while_toggling_status: "죄송합니다. 이 투표의 상태를 바꾸는 도중 에러가 발생하였습니다." @@ -97,6 +98,8 @@ ko: step: 단계 poll_public: label: 투표한 사람 보기 + poll_title: + label: 제목 (선택 사항) poll_options: label: 투표 선택지는 한줄에 하나씩 입력하세요 automatic_close: diff --git a/plugins/poll/config/locales/client.nl.yml b/plugins/poll/config/locales/client.nl.yml index 179b8b472a..202b0a0e9c 100644 --- a/plugins/poll/config/locales/client.nl.yml +++ b/plugins/poll/config/locales/client.nl.yml @@ -103,6 +103,8 @@ nl: step: Stap poll_public: label: Tonen wie er heeft gestemd + poll_title: + label: Titel (optioneel) poll_options: label: Voer één polloptie per regel in automatic_close: diff --git a/plugins/poll/config/locales/client.ro.yml b/plugins/poll/config/locales/client.ro.yml index b4e45a12c6..516470ba2a 100644 --- a/plugins/poll/config/locales/client.ro.yml +++ b/plugins/poll/config/locales/client.ro.yml @@ -16,6 +16,17 @@ ro: few: "total voturi" other: "total voturi" average_rating: "Media: %{average}." + public: + title: "Voturile sunt publice." + results: + groups: + title: "Trebuie să fii membru al %{groups} pentru a vota în acest sondaj." + vote: + title: "Rezultatele vor fi afișate după vot." + closed: + title: "Rezultatele vor fi afișate odată cu închiderea votului." + staff: + title: "Rezultatele sunt afișate numai pentru membrii personalului." cast-votes: title: "Exprimă-ți votul" label: "Votează acum!" @@ -56,6 +67,8 @@ ro: step: Pas poll_public: label: Arată cine a votat + poll_title: + label: Titlu (opţional) poll_options: label: Arată o singură opțiune de sondaj pe linie automatic_close: diff --git a/plugins/poll/config/locales/client.ru.yml b/plugins/poll/config/locales/client.ru.yml index 691eccf322..45650ce67d 100644 --- a/plugins/poll/config/locales/client.ru.yml +++ b/plugins/poll/config/locales/client.ru.yml @@ -113,6 +113,8 @@ ru: step: Шаг poll_public: label: Показывать проголосовавших (не отмечайте, чтобы голосование было анонимным) + poll_title: + label: Заголовок (необязательно) poll_options: label: Введите варианты ответа, по одному в строке automatic_close: diff --git a/plugins/poll/config/locales/client.sv.yml b/plugins/poll/config/locales/client.sv.yml index 140c19e629..57763ed504 100644 --- a/plugins/poll/config/locales/client.sv.yml +++ b/plugins/poll/config/locales/client.sv.yml @@ -103,6 +103,8 @@ sv: step: Steg poll_public: label: Visa vem som röstat + poll_title: + label: Titel (valfritt) poll_options: label: Ange ett omröstningsalternativ per rad automatic_close: diff --git a/plugins/poll/config/locales/client.uk.yml b/plugins/poll/config/locales/client.uk.yml index 370ef384ef..dedc52ba7d 100644 --- a/plugins/poll/config/locales/client.uk.yml +++ b/plugins/poll/config/locales/client.uk.yml @@ -21,12 +21,32 @@ uk: public: title: "Голоси публічні ." results: + groups: + title: "Ви повинні бути учасником %{groups} для голосування в цьому опитуванні." vote: title: "Результати будуть показані при голосуванні ." closed: title: "Результати будуть показані після закриття ." staff: title: "Результати показуються лише співробітникам ." + multiple: + help: + at_least_min_options: + one: "Виберіть принаймні %{count} варіант." + few: "Виберіть принаймні %{count} варіанти." + many: "Виберіть принаймні %{count} варіантів." + other: "Виберіть принаймні %{count} варіанти." + up_to_max_options: + one: "Виберіть не більше як %{count} варіант." + few: "Виберіть не більше %{count} варіанти." + many: "Виберіть не більше %{count} варіантів." + other: "Виберіть не більше %{count} варіантів." + x_options: + one: "Виберіть %{count} варіант." + few: "Виберіть %{count} варіанти." + many: "Виберіть %{count} варіантів." + other: "Виберіть %{count} варіантів." + between_min_and_max_options: "Виберіть між %{min} і %{max} варіантами." cast-votes: title: "Проголосуйте" label: "Проголосувати!" @@ -53,6 +73,12 @@ uk: automatic_close: closes_in: "Закривається в %{timeLeft} ." age: "Закрито %{age}" + breakdown: + title: "Результати опитування" + votes: "%{count} голоси" + breakdown: "Розбивка" + percentage: "Відсоток" + count: "Кількість" error_while_toggling_status: "На жаль, виникла помилка зміни статусу цього опитування." error_while_casting_votes: "На жаль, сталася помилка під час голосування." error_while_fetching_voters: "На жаль, під час відображення тих, хто проголосував, сталася помилка." @@ -61,6 +87,7 @@ uk: title: Створити опитування insert: Вставити опитування help: + options_count: Введіть принаймні 1 варіант invalid_values: Мінімальне значення має бути меншим, ніж максимальне. min_step_value: Мінімальне значення кроку 1 poll_type: @@ -74,14 +101,20 @@ uk: vote: На голосування closed: Коли закрито staff: Для персоналу тільки + poll_groups: + label: Дозволені групи poll_chart_type: label: Тип діаграми + bar: Гістограма + pie: Кругова poll_config: max: Макс min: Мін step: Крок poll_public: label: Покажіть, хто проголосував + poll_title: + label: Заголовок (необов'язково) poll_options: label: Введіть один варіант опитування на рядок automatic_close: diff --git a/plugins/poll/config/locales/client.zh_CN.yml b/plugins/poll/config/locales/client.zh_CN.yml index 65f443b183..4b57735507 100644 --- a/plugins/poll/config/locales/client.zh_CN.yml +++ b/plugins/poll/config/locales/client.zh_CN.yml @@ -98,6 +98,8 @@ zh_CN: step: 梯级 poll_public: label: 显示投票人 + poll_title: + label: 标题(可选) poll_options: label: 每行输入一个调查选项 automatic_close: diff --git a/plugins/poll/config/locales/server.ar.yml b/plugins/poll/config/locales/server.ar.yml index a42300701c..99f28f489a 100644 --- a/plugins/poll/config/locales/server.ar.yml +++ b/plugins/poll/config/locales/server.ar.yml @@ -6,12 +6,17 @@ ar: site_settings: - poll_enabled: "السماح بالإستفتاء" - poll_maximum_options: "أقصى عدد للخيارات في كلّ تصويت." - poll_edit_window_mins: "عدد الدقائق بعد نشر المنشور المسموح فيها بتعديل التصويت." + poll_enabled: "هل تسمح بالاستطلاعات؟" + poll_maximum_options: "أقصى عدد من الخيارات في كلّ استطلاع." + poll_edit_window_mins: "عدد الدقائق المسموح خلالها تعديل الاستطلاع بعد نشر المشاركة." + poll_minimum_trust_level_to_create: "حدّد أدنى ”مستوى ثقة“ مطلوب لصنع الاستطلاعات." + poll_groupable_user_fields: "مجموعة من أسماء حقول المستخدمين لتجميع نتائج الاستطلاعات وترشيحها." poll: - multiple_polls_without_name: "توجد بضعة تصويتات بلا اسم. استخدم خاصية 'name' لتمييزها." - multiple_polls_with_same_name: "توجد بضعة تصويتات بالاسم %{name} نفسه. استخدم خاصية 'name' لتمييزها." + invalid_argument: "القيمة ”%{value}“ غير صالحة حسب المُعطى ”%{argument}“" + multiple_polls_without_name: "هناك أكثر من استطلاع واحد لا اسم له. استعمل صفة ”name“ لتعريف استطلاعاتك تعريفًا فريدًا." + multiple_polls_with_same_name: "هناك أكثر من استطلاع واحد يحمل نفس الاسم: %{name}. استعمل صفة ”name“ لتعريف استطلاعاتك تعريفًا فريدًا." + default_poll_must_have_at_least_1_option: "يجب أن يكون للاستطلاع خيارًا واحدًا على الأقلّ." + named_poll_must_have_at_least_1_option: "يجب أن يكون للاستطلاع بالاسم %{name} خيارًا واحدًا على الأقلّ." default_poll_must_have_less_options: zero: "على التصويت ألا يوفّر أي خيار." one: "على التصويت توفير خيار واحد على الأقل." diff --git a/plugins/poll/config/locales/server.fi.yml b/plugins/poll/config/locales/server.fi.yml index 7e2cfac9f0..522ed8214e 100644 --- a/plugins/poll/config/locales/server.fi.yml +++ b/plugins/poll/config/locales/server.fi.yml @@ -14,7 +14,7 @@ fi: poll_export_data_explorer_query_id: "ID, jonka määräät äänestystulosten viemiseen liittyville tietoselauskyselyille (0 poistaa käytöstä)." poll: poll: "äänestys" - invalid_argument: 'Arvo "%{value}" ei kelpaa argumentiksi "%{argument}".' + invalid_argument: "Arvo \"%{value}\" ei kelpaa argumentiksi \"%{argument}\"." multiple_polls_without_name: "Viesti sisältää useamman nimettömän äänestyskyselyn. Nimeä ne 'name'-määreellä." multiple_polls_with_same_name: "Useamman kyselyn nimi on %{name}. Anna kaikille eri nimet 'name'-määreellä." default_poll_must_have_at_least_1_option: "Äänestyksessä tulee olla ainakin 1 vaihtoehto." diff --git a/plugins/poll/config/locales/server.ru.yml b/plugins/poll/config/locales/server.ru.yml index eaffce3845..49efc1f5f0 100644 --- a/plugins/poll/config/locales/server.ru.yml +++ b/plugins/poll/config/locales/server.ru.yml @@ -6,7 +6,7 @@ ru: site_settings: - poll_enabled: "Разрешать пользователям создавать опросы?" + poll_enabled: "Разрешить пользователям создавать опросы" poll_maximum_options: "Максимальное количество вариантов ответов, допустимых в опросе." poll_edit_window_mins: "Количество минут с момента создания сообщения, в течение которых разрешено редактирование опросов." poll_minimum_trust_level_to_create: "Определить минимальный уровень доверия, необходимый для создания опросов." @@ -25,10 +25,10 @@ ru: many: "В опросе может быть не более %{count} вариантов ответов." other: "В опросе может быть не более %{count} вариантов ответов." named_poll_must_have_less_options: - one: 'В опросе под названием "%{name}" должно быть не более %{count} варианта ответа.' - few: 'В опросе под названием "%{name}" должно быть не более %{count} вариантов ответов.' - many: 'В опросе под названием "%{name}" должно быть не более %{count} вариантов ответов.' - other: 'В опросе под названием "%{name}" должно быть не более %{count} вариантов ответов.' + one: "В опросе под названием \"%{name}\" должно быть не более %{count} варианта ответа." + few: "В опросе под названием \"%{name}\" должно быть не более %{count} вариантов ответов." + many: "В опросе под названием \"%{name}\" должно быть не более %{count} вариантов ответов." + other: "В опросе под названием \"%{name}\" должно быть не более %{count} вариантов ответов." default_poll_must_have_different_options: "В опросе не должно быть одинаковых вариантов ответа." named_poll_must_have_different_options: "В опросе %{name} не должно быть одинаковых вариантов ответа." default_poll_must_not_have_any_empty_options: "Настройки опроса не могут быть пустыми." diff --git a/plugins/poll/config/locales/server.te.yml b/plugins/poll/config/locales/server.te.yml index 03967bdbb0..ac5d945ae8 100644 --- a/plugins/poll/config/locales/server.te.yml +++ b/plugins/poll/config/locales/server.te.yml @@ -4,4 +4,4 @@ # To work with us on translations, join this project: # https://translate.discourse.org/ -te: +te: diff --git a/plugins/poll/config/locales/server.uk.yml b/plugins/poll/config/locales/server.uk.yml index 7db1c8bbbc..0ad7806b57 100644 --- a/plugins/poll/config/locales/server.uk.yml +++ b/plugins/poll/config/locales/server.uk.yml @@ -17,6 +17,8 @@ uk: invalid_argument: "Неправильне значення '%{value}' для аргументу '%{argument}'." multiple_polls_without_name: "Існує кілька опитувань без імені. Використовуйте атрибут 'name', щоб однозначно визначити свої опитування." multiple_polls_with_same_name: "Існує кілька опитувань з однаковою назвою: %{name} . Використовуйте атрибут 'name', щоб однозначно визначити свої опитування." + default_poll_must_have_at_least_1_option: "Опитування повинно мати принаймні 1 варіант." + named_poll_must_have_at_least_1_option: "Опитування з назвою %{name} повинно мати принаймні 1 варіант." default_poll_must_have_less_options: one: "Опитування повинно мати менше опції %{count}." few: "Опитування повинно мати менше варіантів %{count}." @@ -29,6 +31,8 @@ uk: other: "Опитування з назвою %{name} повинно мати менше %{count} варіантів." default_poll_must_have_different_options: "Опитування повинно мати різні варіанти." named_poll_must_have_different_options: "Голосування під назвою %{name} повинно мати різні варіанти." + default_poll_must_not_have_any_empty_options: "Опитування не повинно мати порожніх варіантів." + named_poll_must_not_have_any_empty_options: "Опитування з назвою %{name} не повинно мати порожніх варіантів." default_poll_with_multiple_choices_has_invalid_parameters: "Опитування з кількома варіантами має недійсні параметри." named_poll_with_multiple_choices_has_invalid_parameters: "Опитування з назвою %{name} з множинним вибором має недійсні параметри." requires_at_least_1_valid_option: "Ви маєте обрати щонайменше 1 валідний варіант." diff --git a/plugins/poll/db/migrate/20200804144550_add_title_to_polls.rb b/plugins/poll/db/migrate/20200804144550_add_title_to_polls.rb new file mode 100644 index 0000000000..253f2cc39a --- /dev/null +++ b/plugins/poll/db/migrate/20200804144550_add_title_to_polls.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddTitleToPolls < ActiveRecord::Migration[6.0] + def change + add_column :polls, :title, :string + end +end diff --git a/plugins/poll/lib/polls_updater.rb b/plugins/poll/lib/polls_updater.rb index f50edc0f4e..39b48f45d2 100644 --- a/plugins/poll/lib/polls_updater.rb +++ b/plugins/poll/lib/polls_updater.rb @@ -3,7 +3,7 @@ module DiscoursePoll class PollsUpdater - POLL_ATTRIBUTES ||= %w{close_at max min results status step type visibility groups} + POLL_ATTRIBUTES ||= %w{close_at max min results status step type visibility title groups} def self.update(post, polls) ::Poll.transaction do diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb index 391610c26f..513a2e494d 100644 --- a/plugins/poll/plugin.rb +++ b/plugins/poll/plugin.rb @@ -329,6 +329,7 @@ after_initialize do type: poll["type"].presence || "regular", status: poll["status"].presence || "open", visibility: poll["public"] == "true" ? "everyone" : "secret", + title: poll["title"], results: poll["results"].presence || "always", min: poll["min"], max: poll["max"], @@ -367,6 +368,12 @@ after_initialize do poll["options"] << { "id" => option_id, "html" => o.inner_html.strip } end + # title + title_element = p.css(".poll-title").first + if title_element + poll["title"] = title_element.inner_html.strip + end + poll end end diff --git a/plugins/poll/spec/controllers/posts_controller_spec.rb b/plugins/poll/spec/controllers/posts_controller_spec.rb index be26174871..9024c8d6e9 100644 --- a/plugins/poll/spec/controllers/posts_controller_spec.rb +++ b/plugins/poll/spec/controllers/posts_controller_spec.rb @@ -139,6 +139,17 @@ describe PostsController do expect(Poll.where(post_id: json["id"]).count).to eq(1) end + it "accepts polls with titles" do + post :create, params: { + title: title, raw: "[poll]\n# What's up?\n- one\n[/poll]" + }, format: :json + + expect(response).to be_successful + poll = Poll.last + expect(poll).to_not be_nil + expect(poll.title).to eq("What’s up?") + end + describe "edit window" do describe "within the first 5 minutes" do diff --git a/plugins/poll/spec/lib/pretty_text_spec.rb b/plugins/poll/spec/lib/pretty_text_spec.rb index c4d09d2aac..d887b10e75 100644 --- a/plugins/poll/spec/lib/pretty_text_spec.rb +++ b/plugins/poll/spec/lib/pretty_text_spec.rb @@ -95,7 +95,7 @@ describe PrettyText do cooked = PrettyText.cook md - expected = <<~MD + expected = <<~HTML
@@ -113,7 +113,7 @@ describe PrettyText do
- MD + HTML # note, hashes should remain stable even if emoji changes cause text content is hashed expect(n cooked).to eq(n expected) @@ -153,4 +153,65 @@ describe PrettyText do excerpt = PrettyText.excerpt(post.cooked, SiteSetting.post_onebox_maxlength) expect(excerpt).to eq("A post with a poll \npoll") end + + it "supports the title attribute" do + cooked = PrettyText.cook <<~MD + [poll] + # What's your favorite *berry*? :wink: https://google.com/ + * Strawberry + * Raspberry + * Blueberry + [/poll] + MD + + expect(cooked).to include(<<~HTML) +
What’s your favorite berry? :wink: https://google.com/ +
+ HTML + end + + it "does not break when there are headings before/after a poll with a title" do + cooked = PrettyText.cook <<~MD + # Pre-heading + + [poll] + # What's your favorite *berry*? :wink: https://google.com/ + * Strawberry + * Raspberry + * Blueberry + [/poll] + + # Post-heading + MD + + expect(cooked).to include(<<~HTML) +
What’s your favorite berry? :wink: https://google.com/ +
+ HTML + + expect(cooked).to include("

Pre-heading

") + expect(cooked).to include("

Post-heading

") + end + + it "does not break when there are headings before/after a poll without a title" do + cooked = PrettyText.cook <<~MD + # Pre-heading + + [poll] + * Strawberry + * Raspberry + * Blueberry + [/poll] + + # Post-heading + MD + + expect(cooked).to_not include('
') + expect(cooked).to include(<<~HTML) +
+ HTML + + expect(cooked).to include("

Pre-heading

") + expect(cooked).to include("

Post-heading

") + end end diff --git a/plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js.es6 index edbfd29fee..521805ce2e 100644 --- a/plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js.es6 +++ b/plugins/poll/test/javascripts/acceptance/poll-breakdown-test.js.es6 @@ -1,4 +1,4 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer"; import { Promise } from "rsvp"; 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.es6 index f22228244e..f91c492dbf 100644 --- a/plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js.es6 +++ b/plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js.es6 @@ -1,4 +1,4 @@ -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import { acceptance, updateCurrentUser } from "discourse/tests/helpers/qunit-helpers"; import { displayPollBuilderButton } from "discourse/plugins/poll/helpers/display-poll-builder-button"; import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer"; 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.es6 index 2229ad5c77..ea63232205 100644 --- a/plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js.es6 +++ b/plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js.es6 @@ -1,5 +1,5 @@ -import selectKit from "helpers/select-kit-helper"; -import { acceptance, updateCurrentUser } from "helpers/qunit-helpers"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { acceptance, updateCurrentUser } from "discourse/tests/helpers/qunit-helpers"; import { displayPollBuilderButton } from "discourse/plugins/poll/helpers/display-poll-builder-button"; import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer"; 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.es6 index d0ae61b13f..e3f3d32128 100644 --- a/plugins/poll/test/javascripts/acceptance/poll-pie-chart-test.js.es6 +++ b/plugins/poll/test/javascripts/acceptance/poll-pie-chart-test.js.es6 @@ -1,6 +1,6 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; -acceptance("Rendering polls with pie charts - desktop", { +acceptance("Rendering polls with pie charts", { loggedIn: true, settings: { poll_enabled: true, poll_groupable_user_fields: "something" }, }); diff --git a/plugins/poll/test/javascripts/acceptance/poll-quote-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-quote-test.js.es6 index 9105e5e976..ffee71a0d3 100644 --- a/plugins/poll/test/javascripts/acceptance/poll-quote-test.js.es6 +++ b/plugins/poll/test/javascripts/acceptance/poll-quote-test.js.es6 @@ -1,4 +1,4 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer"; import { Promise } from "rsvp"; 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.es6 index a8dc1f4701..30db0ec80f 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.es6 @@ -1,4 +1,4 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer"; acceptance("Rendering polls with bar charts - desktop", { @@ -7,6 +7,37 @@ acceptance("Rendering polls with bar charts - desktop", { beforeEach() { clearPopupMenuOptionsCallback(); }, + pretend(server) { + server.get("/polls/voters.json", (request) => { + let body = {}; + if ( + request.queryParams.option_id === "68b434ff88aeae7054e42cd05a4d9056" + ) { + body = { + voters: { + "68b434ff88aeae7054e42cd05a4d9056": [ + { + id: 777, + username: "bruce777", + avatar_template: "/images/avatar.png", + name: "Bruce Wayne", + }, + ], + }, + }; + } else { + body = { + voters: Array.from(new Array(5), (_, i) => ({ + id: 600 + i, + username: `bruce${600 + i}`, + avatar_template: "/images/avatar.png", + name: "Bruce Wayne", + })), + }; + } + return [200, { "Content-Type": "application/json" }, body]; + }); + }, }); test("Polls", async (assert) => { @@ -43,24 +74,6 @@ test("Public poll", async (assert) => { "it should display the right number of voters" ); - // eslint-disable-next-line - server.get("/polls/voters.json", () => { - const body = { - voters: { - "68b434ff88aeae7054e42cd05a4d9056": [ - { - id: 777, - username: "bruce777", - avatar_template: "/images/avatar.png", - name: "Bruce Wayne", - }, - ], - }, - }; - - return [200, { "Content-Type": "application/json" }, body]; - }); - await click(".poll-voters-toggle-expand:first a"); assert.equal( @@ -89,20 +102,6 @@ test("Public number poll", async (assert) => { "user URL does not exist" ); - // eslint-disable-next-line - server.get("/polls/voters.json", () => { - const body = { - voters: Array.from(new Array(5), (_, i) => ({ - id: 600 + i, - username: `bruce${600 + i}`, - avatar_template: "/images/avatar.png", - name: "Bruce Wayne", - })), - }; - - return [200, { "Content-Type": "application/json" }, body]; - }); - await click(".poll-voters-toggle-expand:first a"); assert.equal( 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.es6 index 0e0ffffb1b..1ce4b5f483 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.es6 @@ -1,4 +1,4 @@ -import { acceptance } from "helpers/qunit-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer"; acceptance("Rendering polls with bar charts - mobile", { @@ -8,6 +8,21 @@ acceptance("Rendering polls with bar charts - mobile", { beforeEach() { clearPopupMenuOptionsCallback(); }, + pretend(server) { + // eslint-disable-next-line + server.get("/polls/voters.json", () => { + const body = { + voters: Array.from(new Array(10), (_, i) => ({ + id: 500 + i, + username: `bruce${500 + i}`, + avatar_template: "/images/avatar.png", + name: "Bruce Wayne", + })), + }; + + return [200, { "Content-Type": "application/json" }, body]; + }); + }, }); test("Public number poll", async (assert) => { @@ -29,20 +44,6 @@ test("Public number poll", async (assert) => { "user URL does not exist" ); - // eslint-disable-next-line - server.get("/polls/voters.json", () => { - const body = { - voters: Array.from(new Array(10), (_, i) => ({ - id: 500 + i, - username: `bruce${500 + i}`, - avatar_template: "/images/avatar.png", - name: "Bruce Wayne", - })), - }; - - return [200, { "Content-Type": "application/json" }, body]; - }); - await click(".poll-voters-toggle-expand:first a"); assert.equal( 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.es6 index 1721967e68..00906a1b1a 100644 --- a/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6 +++ b/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6 @@ -1,4 +1,4 @@ -import { controllerModule } from "helpers/qunit-helpers"; +import { controllerModule } from "discourse/tests/helpers/qunit-helpers"; controllerModule("controller:poll-ui-builder", { setupController(controller) { diff --git a/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6 b/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6 index 5288ed9b95..8d9318cba7 100644 --- a/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6 +++ b/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6 @@ -1,4 +1,4 @@ -import selectKit from "helpers/select-kit-helper"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; export async function displayPollBuilderButton() { await visit("/"); 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.es6 index 8fde7df280..5643f5f262 100644 --- a/plugins/poll/test/javascripts/widgets/discourse-poll-option-test.js.es6 +++ b/plugins/poll/test/javascripts/widgets/discourse-poll-option-test.js.es6 @@ -1,4 +1,4 @@ -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { moduleForWidget, widgetTest } from "discourse/tests/helpers/widget-test"; moduleForWidget("discourse-poll-option"); const template = `{{mount-widget 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.es6 index 6bc9cb892c..39556b20f7 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.es6 @@ -1,5 +1,5 @@ import EmberObject from "@ember/object"; -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { moduleForWidget, widgetTest } from "discourse/tests/helpers/widget-test"; moduleForWidget("discourse-poll-standard-results"); diff --git a/plugins/poll/test/javascripts/widgets/discourse-poll-test.js.es6 b/plugins/poll/test/javascripts/widgets/discourse-poll-test.js.es6 index 984b99d0cd..84b500707f 100644 --- a/plugins/poll/test/javascripts/widgets/discourse-poll-test.js.es6 +++ b/plugins/poll/test/javascripts/widgets/discourse-poll-test.js.es6 @@ -1,8 +1,62 @@ import I18n from "I18n"; import EmberObject from "@ember/object"; -import { moduleForWidget, widgetTest } from "helpers/widget-test"; +import { + moduleForWidget, + widgetTest, +} from "discourse/tests/helpers/widget-test"; -moduleForWidget("discourse-poll"); +let requests = 0; + +moduleForWidget("discourse-poll", { + pretend(server) { + server.put("/polls/vote", () => { + ++requests; + return [ + 200, + { "Content-Type": "application/json" }, + { + poll: { + name: "poll", + type: "regular", + status: "open", + results: "always", + options: [ + { id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 1 }, + { id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 }, + ], + voters: 1, + chart_type: "bar", + }, + vote: ["1f972d1df351de3ce35a787c89faad29"], + }, + ]; + }); + + server.put("/polls/vote", () => { + ++requests; + return [ + 200, + { "Content-Type": "application/json" }, + { + poll: { + name: "poll", + type: "regular", + status: "open", + results: "always", + options: [ + { id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 1 }, + { id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 }, + ], + voters: 1, + chart_type: "bar", + groups: "foo", + }, + vote: ["1f972d1df351de3ce35a787c89faad29"], + }, + ]; + }); + }, +}); const template = `{{mount-widget widget="discourse-poll" @@ -41,36 +95,20 @@ widgetTest("can vote", { }, async test(assert) { - let requests = 0; - - /* global server */ - server.put("/polls/vote", () => { - ++requests; - return [ - 200, - { "Content-Type": "application/json" }, - { - poll: { - name: "poll", - type: "regular", - status: "open", - results: "always", - options: [ - { id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 1 }, - { id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 }, - ], - voters: 1, - chart_type: "bar", - }, - vote: ["1f972d1df351de3ce35a787c89faad29"], - }, - ]; - }); + requests = 0; await click("li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']"); assert.equal(requests, 1); assert.equal(find(".chosen").length, 1); assert.equal(find(".chosen").text(), "100%yes"); + assert.equal(find(".toggle-results").text(), "Show vote"); + + await click(".toggle-results"); + assert.equal( + find("li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']").length, + 1 + ); + assert.equal(find(".toggle-results").text(), "Show results"); }, }); @@ -104,32 +142,7 @@ widgetTest("cannot vote if not member of the right group", { }, async test(assert) { - let requests = 0; - - /* global server */ - server.put("/polls/vote", () => { - ++requests; - return [ - 200, - { "Content-Type": "application/json" }, - { - poll: { - name: "poll", - type: "regular", - status: "open", - results: "always", - options: [ - { id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 1 }, - { id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 }, - ], - voters: 1, - chart_type: "bar", - groups: "foo", - }, - vote: ["1f972d1df351de3ce35a787c89faad29"], - }, - ]; - }); + requests = 0; await click("li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']"); assert.equal( diff --git a/plugins/styleguide/README.md b/plugins/styleguide/README.md new file mode 100644 index 0000000000..e070fd33a6 --- /dev/null +++ b/plugins/styleguide/README.md @@ -0,0 +1,6 @@ +# styleguide + +Adds a URL of `/styleguide` to discourse that renders widgets in various +configurations to aid in styling. + +![Screenshot](screenshot.png) diff --git a/plugins/styleguide/app/controllers/styleguide/styleguide_controller.rb b/plugins/styleguide/app/controllers/styleguide/styleguide_controller.rb new file mode 100644 index 0000000000..94f853c15f --- /dev/null +++ b/plugins/styleguide/app/controllers/styleguide/styleguide_controller.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Styleguide + class StyleguideController < ApplicationController + requires_plugin Styleguide::PLUGIN_NAME + skip_before_action :check_xhr + + def index + ensure_admin if SiteSetting.styleguide_admin_only + + render 'default/empty' + end + end +end diff --git a/plugins/styleguide/assets/javascripts/discourse/components/color-example.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/color-example.js.es6 new file mode 100644 index 0000000000..52f4e4eb19 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/color-example.js.es6 @@ -0,0 +1,4 @@ +export default Ember.Component.extend({ + tagName: "section", + classNameBindings: [":color-example"], +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-example.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-example.js.es6 new file mode 100644 index 0000000000..b5fa4460ea --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-example.js.es6 @@ -0,0 +1,4 @@ +export default Ember.Component.extend({ + tagName: "section", + classNames: ["styleguide-example"], +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-icons.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-icons.js.es6 new file mode 100644 index 0000000000..a11836c7a3 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-icons.js.es6 @@ -0,0 +1,21 @@ +import { later } from "@ember/runloop"; + +export default Ember.Component.extend({ + tagName: "section", + classNames: ["styleguide-icons"], + iconIDs: [], + + didInsertElement() { + this._super(...arguments); + + later(() => { + let IDs = $("#svg-sprites symbol") + .map(function () { + return this.id; + }) + .get(); + + this.set("iconIDs", IDs); + }, 2000); + }, +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-link.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-link.js.es6 new file mode 100644 index 0000000000..eed7fd0127 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-link.js.es6 @@ -0,0 +1,3 @@ +export default Ember.Component.extend({ + tagName: "", +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-markdown.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-markdown.js.es6 new file mode 100644 index 0000000000..b62b0568c7 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-markdown.js.es6 @@ -0,0 +1,10 @@ +import { cookAsync } from "discourse/lib/text"; + +export default Ember.Component.extend({ + didInsertElement() { + this._super(...arguments); + + const contents = $(this.element).html(); + cookAsync(contents).then((cooked) => $(this.element).html(cooked.string)); + }, +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide-section.js.es6 b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-section.js.es6 new file mode 100644 index 0000000000..d1db7ebc8f --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide-section.js.es6 @@ -0,0 +1,18 @@ +import computed from "discourse-common/utils/decorators"; + +export default Ember.Component.extend({ + tagName: "section", + classNameBindings: [":styleguide-section", "sectionClass"], + + didReceiveAttrs() { + this._super(...arguments); + window.scrollTo(0, 0); + }, + + @computed("section") + sectionClass(section) { + if (section) { + return `${section.id}-examples`; + } + }, +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide-show.js.es6 b/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide-show.js.es6 new file mode 100644 index 0000000000..6c67440757 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide-show.js.es6 @@ -0,0 +1,5 @@ +export default Ember.Controller.extend({ + actions: { + dummy() {}, + }, +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide.js.es6 b/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide.js.es6 new file mode 100644 index 0000000000..6f3f001998 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/controllers/styleguide.js.es6 @@ -0,0 +1,3 @@ +export default Ember.Controller.extend({ + sections: null, +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/helpers/section-title.js.es6 b/plugins/styleguide/assets/javascripts/discourse/helpers/section-title.js.es6 new file mode 100644 index 0000000000..2426ccf833 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/helpers/section-title.js.es6 @@ -0,0 +1,5 @@ +import I18n from "I18n"; + +export default Ember.Helper.helper(function (params) { + return I18n.t(`styleguide.sections.${params[0].replace(/\-/g, "_")}.title`); +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js.es6 b/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js.es6 new file mode 100644 index 0000000000..1ace401581 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/lib/dummy-data.js.es6 @@ -0,0 +1,269 @@ +import NavItem from "discourse/models/nav-item"; + +let topicId = 2000000; +let userId = 1000000; + +let _data; + +export function createData(store) { + if (_data) { + return _data; + } + + let categories = [ + { + id: 1234, + name: "Fruit", + description_excerpt: "All about various kinds of fruit", + color: "ff0", + slug: "fruit", + }, + { + id: 2345, + name: "Vegetables", + description_excerpt: "Full of delicious vitamins", + color: "f00", + slug: "vegetables", + }, + { + id: 3456, + name: "Beverages", + description_excerpt: "Thirsty?", + color: "99f", + slug: "beverages", + read_restricted: true, + }, + ].map((c) => store.createRecord("category", c)); + + let createUser = (attrs) => { + userId++; + + let userData = { + id: userId, + username: `user_${userId}`, + name: "John Doe", + avatar_template: "/images/avatar.png", + website: "discourse.com", + website_name: "My Website is Discourse", + location: "Toronto", + suspend_reason: "Some reason", + displayGroups: [{ name: "Group 1" }, { name: "Group 2" }], + created_at: moment().subtract(10, "days"), + last_posted_at: moment().subtract(3, "days"), + last_seen_at: moment().subtract(1, "days"), + profile_view_count: 378, + invited_by: { + username: "user_2", + }, + trustLevel: { name: "Dummy" }, + publicUserFields: [ + { + field: { + dasherized_name: "puf_1", + name: "Public User Field 1", + }, + value: "Some value 1", + }, + { + field: { + dasherized_name: "puf_2", + name: "Public User Field 2", + }, + value: "Some value 2", + }, + ], + }; + + Object.assign(userData, attrs || {}); + + return store.createRecord("user", userData); + }; + + // This bg image is public domain: http://hubblesite.org/image/3999/gallery + let user = createUser({ + profile_background: "/plugins/styleguide/images/hubble-orion-nebula-bg.jpg", + has_profile_background: true, + }); + + let createTopic = (attrs) => { + topicId++; + return store.createRecord( + "topic", + $.extend( + { + id: topicId, + title: `Example Topic Title ${topicId}`, + fancyTitle: `Example Topic Title ${topicId}`, + slug: `example-topic-title-${topicId}`, + posts_count: ((topicId * 1234) % 100) + 1, + views: ((topicId * 123) % 1000) + 1, + like_count: topicId % 3, + created_at: `2017-03-${topicId}`, + invisible: false, + posters: [ + { extras: "latest", user }, + { user: createUser() }, + { user: createUser() }, + { user: createUser() }, + { user: createUser() }, + ], + }, + attrs || {} + ) + ); + }; + + let topic = createTopic(); + topic.set("category", categories[0]); + topic.get("details").setProperties({ + can_create_post: true, + suggested_topics: [topic, topic, topic], + }); + + let invisibleTopic = createTopic({ invisible: true }); + let closedTopic = createTopic({ closed: true }); + closedTopic.set("category", categories[1]); + let archivedTopic = createTopic({ archived: true }); + let pinnedTopic = createTopic({ pinned: true }); + pinnedTopic.set("clearPin", () => pinnedTopic.set("pinned", "unpinned")); + pinnedTopic.set("rePin", () => pinnedTopic.set("pinned", "pinned")); + pinnedTopic.set("category", categories[2]); + let unpinnedTopic = createTopic({ unpinned: true }); + let warningTopic = createTopic({ is_warning: true }); + + const bunchOfTopics = [ + topic, + invisibleTopic, + closedTopic, + archivedTopic, + pinnedTopic, + unpinnedTopic, + warningTopic, + ]; + + let sentence = + "Donec viverra lacus id sapien aliquam, tempus tincidunt urna porttitor."; + + let cooked = `

Lorem ipsum dolor sit amet, et nec quis viderer prompta, ex omnium ponderum insolens eos, sed discere invenire principes in. Fuisset constituto per ad. Est no scripta propriae facilisis, viderer impedit deserunt in mel. Quot debet facilisis ne vix, nam in detracto tacimates. At quidam petentium vulputate pro. Alia iudico repudiandae ad vel, erat omnis epicuri eos id. Et illum dolor graeci vel, quo feugiat consulatu ei.

+ +

Case everti equidem ius ea, ubique veritus vim id. Eros omnium conclusionemque qui te, usu error alienum imperdiet ut, ex ius meis adipisci. Libris reprehendunt eos ex, mea at nisl suavitate. Altera virtute democritum pro cu, melius latine in ius.

`; + + let transformedPost = { + id: 1234, + cooked, + created_at: moment().subtract(3, "days"), + user_id: user.get("id"), + username: user.get("username"), + avatar_template: user.get("avatar_template"), + showLike: true, + canToggleLike: true, + canFlag: true, + canEdit: false, + canCreatePost: true, + canBookmark: true, + canManage: true, + canDelete: true, + createdByUsername: user.get("username"), + createdByAvatarTemplate: user.get("avatar_template"), + lastPostUsername: user.get("username"), + lastPostAvatarTemplate: user.get("avatar_template"), + topicReplyCount: 123, + topicViews: 3456, + participantCount: 10, + topicLikeCount: 14, + topicLinkLength: 5, + topicPostsCount: 4, + participants: [createUser(), createUser(), createUser(), createUser()], + topicLinks: [ + { + title: "Evil Trout", + url: "https://eviltrout.com", + domain: "eviltrout.com", + clicks: 1024, + }, + { + title: "Cool Site", + url: "http://coolsite.example.com", + domain: "coolsite.example.com", + clicks: 512, + }, + ], + }; + + _data = { + options: [ + { id: 1, name: "Orange" }, + { id: 2, name: "Blue" }, + { id: 3, name: "Red" }, + { id: 4, name: "Yellow" }, + ], + + categories, + + buttonSizes: [ + { class: "btn-large", text: "large" }, + { class: "btn-default", text: "default" }, + ], + + buttonStates: [ + { class: "btn-hover", text: "hover" }, + { class: "btn-active", text: "active" }, + { disabled: true, text: "disabled" }, + ], + + navItems: ["latest", "categories", "top"].map((name) => { + let item = NavItem.fromText(name); + + item.set("href", "#"); + + if (name === "categories") { + item.set("styleGuideActive", true); + } + + return item; + }), + + topic, + invisibleTopic, + closedTopic, + archivedTopic, + pinnedTopic, + unpinnedTopic, + warningTopic, + + topics: bunchOfTopics, + + sentence, + short_sentence: "Lorem ipsum dolor sit amet.", + soon: moment().add(2, "days"), + + transformedPost, + + user, + + userWithUnread: createUser({ + unread_notifications: 3, + unread_private_messages: 7, + }), + + lorem: cooked, + + topicTimerUpdateDate: "2017-10-18 18:00", + + categoryNames: categories.map((c) => c.name), + + groups: [ + { name: "staff", id: 1, automatic: false }, + { name: "lounge", id: 2, automatic: true }, + { name: "admin", id: 3, automatic: false }, + ], + + selectedGroups: [1, 2], + + settings: "bold|italic|strike|underline", + + colors: "f49|c89|564897", + }; + + return _data; +} diff --git a/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js.es6 b/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js.es6 new file mode 100644 index 0000000000..a1c06df8fa --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js.es6 @@ -0,0 +1,72 @@ +let _allCategories = null; +let _sectionsById = {}; +let _notes = {}; + +export const CATEGORIES = ["atoms", "molecules", "organisms"]; + +export function sectionById(id) { + // prime cache + allCategories(); + + return _sectionsById[id]; +} + +function sortSections(a, b) { + let result = a.priority - b.priority; + if (result === 0) { + return a.id < b.id ? -1 : 1; + } + return result; +} + +export function allCategories() { + if (_allCategories) { + return _allCategories; + } + + let categories = {}; + + let paths = CATEGORIES.join("|"); + + // Find a list of sections based on what templates are available + Object.keys(Ember.TEMPLATES).forEach((e) => { + let regexp = new RegExp(`styleguide\/(${paths})\/(\\d+)?\\-?([^\\/]+)$`); + let matches = e.match(regexp); + if (matches) { + let section = { + id: matches[3], + priority: parseInt(matches[2] || "100", 10), + category: matches[1], + templateName: e.replace(/^.*styleguide\//, ""), + }; + if (!categories[section.category]) { + categories[section.category] = []; + } + categories[section.category].push(section); + _sectionsById[section.id] = section; + } + + // Look for notes + regexp = new RegExp(`components\/notes\/(\\d+)?\\-?([^\\/]+)$`); + matches = e.match(regexp); + if (matches) { + _notes[matches[2]] = e.replace(/^.*notes\//, ""); + } + }); + + _allCategories = []; + CATEGORIES.forEach((c) => { + let sections = categories[c]; + if (sections) { + _allCategories.push({ + id: c, + sections: sections.sort(sortSections), + }); + } + }); + return _allCategories; +} + +export function findNote(section) { + return _notes[section.id]; +} diff --git a/plugins/styleguide/assets/javascripts/discourse/routes/styleguide-show.js.es6 b/plugins/styleguide/assets/javascripts/discourse/routes/styleguide-show.js.es6 new file mode 100644 index 0000000000..71ca411ddc --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/routes/styleguide-show.js.es6 @@ -0,0 +1,28 @@ +import { + sectionById, + findNote, +} from "discourse/plugins/styleguide/discourse/lib/styleguide"; +import { createData } from "discourse/plugins/styleguide/discourse/lib/dummy-data"; + +export default Ember.Route.extend({ + model(params) { + return sectionById(params.section); + }, + + setupController(controller, section) { + let note = findNote(section); + + controller.setProperties({ + section, + note, + dummy: createData(this.store), + }); + }, + + renderTemplate(controller, section) { + this.render("styleguide.show"); + this.render(`styleguide/${section.templateName}`, { + into: "styleguide.show", + }); + }, +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/routes/styleguide.js.es6 b/plugins/styleguide/assets/javascripts/discourse/routes/styleguide.js.es6 new file mode 100644 index 0000000000..1dc83c6a30 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/routes/styleguide.js.es6 @@ -0,0 +1,11 @@ +import { allCategories } from "discourse/plugins/styleguide/discourse/lib/styleguide"; + +export default Ember.Route.extend({ + model() { + return allCategories(); + }, + + setupController(controller, categories) { + controller.set("categories", categories); + }, +}); diff --git a/plugins/styleguide/assets/javascripts/discourse/styleguide-route-map.js.es6 b/plugins/styleguide/assets/javascripts/discourse/styleguide-route-map.js.es6 new file mode 100644 index 0000000000..155e556489 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/styleguide-route-map.js.es6 @@ -0,0 +1,11 @@ +export default function () { + const { disabled_plugins = [] } = this.site; + + if (disabled_plugins.indexOf("styleguide") !== -1) { + return; + } + + this.route("styleguide", function () { + this.route("show", { path: ":category/:section" }); + }); +} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/components/color-example.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/components/color-example.hbs new file mode 100644 index 0000000000..6c52205dc3 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/components/color-example.hbs @@ -0,0 +1,2 @@ +
+
${{color}}
diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/components/notes/.keep b/plugins/styleguide/assets/javascripts/discourse/templates/components/notes/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-example.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-example.hbs new file mode 100644 index 0000000000..9a98389ca3 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-example.hbs @@ -0,0 +1,3 @@ +
{{title}}
+
{{yield}}
+
diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-icons.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-icons.hbs new file mode 100644 index 0000000000..996790bb48 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-icons.hbs @@ -0,0 +1,6 @@ +{{#each iconIDs as |id|}} +
+ {{ d-icon id }} + {{id}} +
+{{/each}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-link.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-link.hbs new file mode 100644 index 0000000000..f4f8e6fb4b --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-link.hbs @@ -0,0 +1,3 @@ +{{#link-to "styleguide.show" section.category section.id}} + {{section-title section.id}} +{{/link-to}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-section.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-section.hbs new file mode 100644 index 0000000000..31264c0633 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/components/styleguide-section.hbs @@ -0,0 +1,11 @@ +

+ {{#if section}} + {{section-title section.id}} + {{else}} + {{i18n title}} + {{/if}} +

+ +
+ {{yield}} +
diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide.hbs new file mode 100644 index 0000000000..9647b54759 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide.hbs @@ -0,0 +1,15 @@ +
+
+ {{#each categories as |c|}} +
    +
  • {{i18n (concat "styleguide.categories." c.id)}}
  • + {{#each c.sections as |s|}} +
  • {{styleguide-link section=s}}
  • + {{/each}} +
+ {{/each}} +
+
+ {{outlet}} +
+
diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/00-typography.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/00-typography.hbs new file mode 100644 index 0000000000..46ed67a5d0 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/00-typography.hbs @@ -0,0 +1,27 @@ +{{#styleguide-example title="h1"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="h2"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="h3"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="h4"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="h5"}} +
{{i18n "styleguide.sections.typography.example"}}
+{{/styleguide-example}} + +{{#styleguide-example title="h6"}} +
{{i18n "styleguide.sections.typography.example"}}
+{{/styleguide-example}} + +{{#styleguide-example title="p"}} +

{{i18n "styleguide.sections.typography.paragraph"}}

+{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/01-font-scale.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/01-font-scale.hbs new file mode 100644 index 0000000000..f2a9d6595c --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/01-font-scale.hbs @@ -0,0 +1,99 @@ + +
+

+ Discourse users can select from 4 different text sizes in their user settings, by default these are: +

+      Smaller: 14px
+      Normal: 15px (default)
+      Larger: 17px
+      Largest: 19px
+    
+

+ +

+ If you'd like to increase the font size of your entire Discourse community, you can override the font-size of the HTML element. You can also provide different font sizes for the user text size settings defined above. The example below increases all text size options by 1px. +

+      html {
+        font-size: 16px; // default font-size  
+        &.text-size-smaller {
+          font-size: 15px;
+        }
+        &.text-size-larger {
+          font-size: 18px;
+        }
+        &.text-size-largest {
+          font-size: 20px;
+        }
+      }
+    
+

+

+ If you want to scale the fonts of a specific element, you can use Discourse's font scaling variables. Using the variable system ensures you're using a consistent set of font-sizes throughout your community. +

+ Changing the font-size of a parent element will proportionately scale the font sizes of all its children. +

+
+      .parent {
+        font-size: $font-up-3;
+        // Increases the relative font-size of this element and its children by 3 steps in the scale
+        .child {
+          // If this is set to $font-down-3 in Discourse's default CSS,
+             the parent font-size increase above would make this equivilant to $font-0
+             ($font-down-3 + $font-up-3 = $font-0)
+        }
+      }
+    
+

+
+ +{{#styleguide-example title="$font-up-6, 2.296em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-up-5, 2em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-up-4, 1.7511em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-up-3, 1.5157em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-up-2, 1.3195em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-up-1, 1.1487em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-0, 1em — base font"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-down-1, 0.8706em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-down-2, 0.7579em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-down-3, 0.6599em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-down-4, 0.5745em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-down-5, 0.5em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} + +{{#styleguide-example title="$font-down-6, 0.4355em"}} +

{{i18n "styleguide.sections.typography.example"}}

+{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/02-buttons.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/02-buttons.hbs new file mode 100644 index 0000000000..9ffac0bd10 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/02-buttons.hbs @@ -0,0 +1,91 @@ +{{#styleguide-example title=".btn-icon - sizes"}} + {{#each dummy.buttonSizes as |bs|}} + {{d-button icon="times" translatedTitle=bs.text class=bs.class disabled=bs.disabled}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-icon - states"}} + {{#each dummy.buttonStates as |bs|}} + {{d-button icon="times" translatedTitle=bs.text class=bs.class disabled=bs.disabled}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-text - sizes"}} + {{#each dummy.buttonSizes as |bs|}} + {{d-button translatedLabel=bs.text class=bs.class disabled=bs.disabled}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-text - states"}} + {{#each dummy.buttonStates as |bs|}} + {{d-button translatedLabel=bs.text class=bs.class disabled=bs.disabled}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-default .btn-icon-text - sizes"}} + {{#each dummy.buttonSizes as |bs|}} + {{d-button icon="plus" translatedLabel=bs.text class=bs.class disabled=bs.disabled}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-default .btn-icon-text - states"}} + {{#each dummy.buttonStates as |bs|}} + {{d-button icon="plus" translatedLabel=bs.text class=bs.class disabled=bs.disabled}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-primary .btn-icon-text"}} + {{#each dummy.buttonSizes as |bs|}} + {{d-button + class=(concat "btn-primary " bs.class) + icon="plus" + translatedLabel=bs.text + disabled=bs.disabled + }} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-primary .btn-icon-text - states"}} + {{#each dummy.buttonStates as |bs|}} + {{d-button + class=(concat "btn-primary " bs.class) + icon="plus" + translatedLabel=bs.text + disabled=bs.disabled + }} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-danger .btn-icon-text - sizes"}} + {{#each dummy.buttonSizes as |bs|}} + {{d-button + class=(concat "btn-danger " bs.class) + icon="trash-alt" + translatedLabel=bs.text + disabled=bs.disabled + }} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-danger .btn-icon-text - states"}} + {{#each dummy.buttonStates as |bs|}} + {{d-button + class=(concat "btn-danger " bs.class) + icon="trash-alt" + translatedLabel=bs.text + disabled=bs.disabled + }} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-flat - sizes"}} + {{#each dummy.buttonSizes as |bs|}} + {{flat-button icon="trash-alt" disabled=bs.disabled transaltedTitle=bs.title}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title=".btn-flat - states"}} + {{#each dummy.buttonStates as |bs|}} + {{flat-button icon="trash-alt" disabled=bs.disabled transaltedTitle=bs.title}} + {{/each}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/03-colors.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/03-colors.hbs new file mode 100644 index 0000000000..a153fccf82 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/03-colors.hbs @@ -0,0 +1,70 @@ +{{#styleguide-example title="$primary"}} +
+ {{color-example color="primary-very-low"}} + {{color-example color="primary-low"}} + {{color-example color="primary-low-mid"}} +
+
+ {{color-example color="primary-medium"}} + {{color-example color="primary-high"}} + {{color-example color="primary"}} +
+{{/styleguide-example}} + +{{#styleguide-example title="$secondary"}} +
+ {{color-example color="secondary-low"}} + {{color-example color="secondary-medium"}} + {{color-example color="secondary-high"}} + {{color-example color="secondary"}} +
+{{/styleguide-example}} + +{{#styleguide-example title="$tertiary"}} +
+ {{color-example color="tertiary-low"}} + {{color-example color="tertiary-medium"}} + {{color-example color="tertiary-high"}} + {{color-example color="tertiary"}} +
+{{/styleguide-example}} + +{{#styleguide-example title="$quaternary"}} +
+ {{color-example color="quaternary-low"}} + {{color-example color="quaternary"}} +
+{{/styleguide-example}} + +{{#styleguide-example title="$highlight"}} +
+ {{color-example color="highlight-low"}} + {{color-example color="highlight-medium"}} + {{color-example color="highlight"}} + {{color-example color="highlight-high"}} +
+{{/styleguide-example}} + +{{#styleguide-example title="$danger"}} +
+ {{color-example color="danger-low"}} + {{color-example color="danger-low-mid"}} + {{color-example color="danger-medium"}} + {{color-example color="danger"}} +
+{{/styleguide-example}} + +{{#styleguide-example title="$success"}} +
+ {{color-example color="success-low"}} + {{color-example color="success-medium"}} + {{color-example color="success"}} +
+{{/styleguide-example}} + +{{#styleguide-example title="$love"}} +
+ {{color-example color="love-low"}} + {{color-example color="love"}} +
+{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/04-icons.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/04-icons.hbs new file mode 100644 index 0000000000..a79483a359 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/04-icons.hbs @@ -0,0 +1,15 @@ +
+

Discourse uses a free set of SVG icons from Font Awesome ({{i18n "styleguide.sections.icons.full_list"}}).

+

Plugins and themes can add SVG icons to the SVG spritesheet, or replace existing icons entirely.

+

+

+

+

By default, all icons have the

.d-icon
class applied along with a class containing the name of the icon (e.g.,
.d-icon-link
)

+
+ +{{#styleguide-example title="d-icon - all available icons"}} + {{styleguide-icons}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/05-input-fields.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/05-input-fields.hbs new file mode 100644 index 0000000000..5623886be5 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/05-input-fields.hbs @@ -0,0 +1,15 @@ +{{#styleguide-example title="text-field"}} + {{text-field placeholder="Placeholder"}} +{{/styleguide-example}} + +{{#styleguide-example title="password"}} + {{password-field type="password" placeholder="Placeholder"}} +{{/styleguide-example}} + +{{#styleguide-example title="text-field search"}} + {{text-field type="search" placeholder="Placeholder"}} +{{/styleguide-example}} + +{{#styleguide-example title="textarea"}} + {{textarea placeholder="Placeholder"}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/06-spinners.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/06-spinners.hbs new file mode 100644 index 0000000000..c7d17e3efb --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/06-spinners.hbs @@ -0,0 +1,7 @@ +{{#styleguide-example title="spinner - small"}} +
+{{/styleguide-example}} + +{{#styleguide-example title="spinner - regular"}} +
+{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/date-time-inputs.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/date-time-inputs.hbs new file mode 100644 index 0000000000..756ede94ec --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/date-time-inputs.hbs @@ -0,0 +1,15 @@ +{{#styleguide-example title="time-input"}} + {{time-input}} +{{/styleguide-example}} + +{{#styleguide-example title="date-input"}} + {{date-input}} +{{/styleguide-example}} + +{{#styleguide-example title="date-time-input"}} + {{date-time-input}} +{{/styleguide-example}} + +{{#styleguide-example title="date-time-input-range"}} + {{date-time-input-range}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/dropdowns.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/dropdowns.hbs new file mode 100644 index 0000000000..15508cddf6 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/dropdowns.hbs @@ -0,0 +1,84 @@ +{{#styleguide-example title="combo-box"}} + {{combo-box content=dummy.options}} +{{/styleguide-example}} + +{{#styleguide-example title="filterable combo-box"}} + {{combo-box content=dummy.categoryNames filterable=true}} +{{/styleguide-example}} + +{{#styleguide-example title="combo-box with a default state"}} + {{combo-box content=dummy.options none="category.none"}} +{{/styleguide-example}} + +{{#styleguide-example title="combo-box clearable"}} + {{combo-box content=dummy.options none="category.none" clearable=true}} +{{/styleguide-example}} + +{{#styleguide-example title="topic-notifications-options"}} + {{topic-notifications-options topic=dummy.topic}} +{{/styleguide-example}} + +{{#styleguide-example title="topic-notifications-button"}} + +{{/styleguide-example}} + +{{#styleguide-example title="topic-footer-mobile-dropdown"}} + {{topic-footer-mobile-dropdown topic=dummy.topic}} +{{/styleguide-example}} + +{{#styleguide-example title="category-chooser"}} + {{category-chooser}} +{{/styleguide-example}} + +{{#styleguide-example title="pinned-button"}} + {{pinned-button topic=dummy.pinnedTopic}} +{{/styleguide-example}} + +{{#styleguide-example title="pinned-options"}} + {{pinned-options topic=dummy.pinnedTopic}} +{{/styleguide-example}} + +{{#styleguide-example title="categories-admin-dropdown"}} + {{categories-admin-dropdown}} +{{/styleguide-example}} + +{{#styleguide-example title="category-notifications-button"}} + {{category-notifications-button category=dummy.categories.[0]}} +{{/styleguide-example}} + +{{#styleguide-example title="notifications-button"}} + {{notifications-button i18nPrefix="groups.notifications" value=1}} +{{/styleguide-example}} + +{{#styleguide-example title="dropdown-select-box"}} + {{dropdown-select-box content=dummy.options}} +{{/styleguide-example}} + +{{#styleguide-example title="future-date-input-selector"}} + {{future-date-input-selector + minimumResultsForSearch=-1 + statusType="open" + input=dummy.topicTimerUpdateDate + includeWeekend=true + includeForever=true + none="topic.auto_update_input.none" + }} +{{/styleguide-example}} + +{{#styleguide-example title="multi-select"}} + {{multi-select none="test.none" content=dummy.options}} +{{/styleguide-example}} + +{{#styleguide-example title="admin-group-selector"}} + {{admin-group-selector selected=dummy.selectedGroups content=dummy.groups}} +{{/styleguide-example}} + +{{#styleguide-example title="list-setting"}} + {{list-setting settingValue=dummy.settings}} +{{/styleguide-example}} + +{{#styleguide-example title="list-setting with colors"}} + {{list-setting settingValue=dummy.colors nameProperty="color"}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/topic-link.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/topic-link.hbs new file mode 100644 index 0000000000..3096ac4cf7 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/topic-link.hbs @@ -0,0 +1,3 @@ +{{#styleguide-example title="topic-link"}} + {{topic-link dummy.topic}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/topic-statuses.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/topic-statuses.hbs new file mode 100644 index 0000000000..76cedf46ad --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/atoms/topic-statuses.hbs @@ -0,0 +1,27 @@ +{{#styleguide-example title="invisible"}} + {{topic-status topic=dummy.invisibleTopic}} +{{/styleguide-example}} + +{{#styleguide-example title="closed"}} + {{topic-status topic=dummy.closedTopic}} +{{/styleguide-example}} + +{{#styleguide-example title="pinned"}} + {{topic-status topic=dummy.pinnedTopic}} +{{/styleguide-example}} + +{{#styleguide-example title="unpinned"}} + {{topic-status topic=dummy.unpinnedTopic}} +{{/styleguide-example}} + +{{#styleguide-example title="archived"}} + {{topic-status topic=dummy.archivedTopic}} +{{/styleguide-example}} + +{{#styleguide-example title="warning"}} + {{topic-status topic=dummy.warningTopic}} +{{/styleguide-example}} + +{{#styleguide-example title="no status"}} + {{topic-status topic=dummy.topic}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/index.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/index.hbs new file mode 100644 index 0000000000..12e2e2a813 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/index.hbs @@ -0,0 +1,5 @@ +{{#styleguide-section title="styleguide.title"}} +
+ {{i18n "styleguide.welcome"}} +
+{{/styleguide-section}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/bread-crumbs.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/bread-crumbs.hbs new file mode 100644 index 0000000000..38778c2731 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/bread-crumbs.hbs @@ -0,0 +1,9 @@ +{{#styleguide-example title="category-breadcrumbs"}} + {{bread-crumbs categories=dummy.categories showTags=false}} +{{/styleguide-example}} + +{{#if siteSettings.tagging_enabled}} + {{#styleguide-example title="category-breadcrumbs - tags"}} + {{bread-crumbs categories=dummy.categories showTags=true}} + {{/styleguide-example}} +{{/if}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/categories.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/categories.hbs new file mode 100644 index 0000000000..e3d83bb76b --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/categories.hbs @@ -0,0 +1,23 @@ +{{#styleguide-example title="category-badge - bullet"}} + {{#each dummy.categories as |c|}} + {{category-badge c categoryStyle="bullet"}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title="category-badge - bar"}} + {{#each dummy.categories as |c|}} + {{category-badge c categoryStyle="bar"}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title="category-badge - box"}} + {{#each dummy.categories as |c|}} + {{category-badge c categoryStyle="box"}} + {{/each}} +{{/styleguide-example}} + +{{#styleguide-example title="category-badge - none"}} + {{#each dummy.categories as |c|}} + {{category-badge c categoryStyle="none"}} + {{/each}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/footer-message.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/footer-message.hbs new file mode 100644 index 0000000000..b7c6c200c1 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/footer-message.hbs @@ -0,0 +1,23 @@ +{{#styleguide-example title="footer-message - default"}} + {{footer-message education=dummy.sentence message=dummy.short_sentence}} +{{/styleguide-example}} + +{{#styleguide-example title="footer-message - latest"}} + {{footer-message + education=dummy.sentence + message=dummy.short_sentence + latest=true + canCreateTopicOnCategory=true + createTopic=(action "dummy") + }} +{{/styleguide-example}} + +{{#styleguide-example title="footer-message - top"}} + {{footer-message + education=dummy.sentence + message=dummy.short_sentence + top=true + changePeriod=(action "dummy") + }} +{{/styleguide-example}} + diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/header-icons.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/header-icons.hbs new file mode 100644 index 0000000000..aaeaddabfd --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/header-icons.hbs @@ -0,0 +1,13 @@ +{{#styleguide-example title="header-icons"}} + {{mount-widget widget="header-icons"}} +{{/styleguide-example}} + +{{#styleguide-example title="header-icons - user"}} + {{mount-widget widget="header-icons" args=(hash user=dummy.user)}} +{{/styleguide-example}} + +{{#styleguide-example title="header-icons - notifications"}} + {{mount-widget + widget="header-icons" + args=(hash user=dummy.userWithUnread flagCount=5)}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/navigation-bar.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/navigation-bar.hbs new file mode 100644 index 0000000000..4d37a461f9 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/navigation-bar.hbs @@ -0,0 +1,11 @@ +{{#styleguide-example title="navigation-bar"}} + {{navigation-bar navItems=dummy.navItems filterMode="latest"}} +{{/styleguide-example}} + +{{#styleguide-example title=".user-main .nav-pills"}} + {{#mobile-nav class="main-nav" desktopClass="nav nav-pills user-nav" currentPath=currentPath}} + {{#each dummy.navItems as |ni|}} +
  • {{ni.displayName}}
  • + {{/each}} + {{/mobile-nav}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/navigation-stacked.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/navigation-stacked.hbs new file mode 100644 index 0000000000..59c78e7f06 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/navigation-stacked.hbs @@ -0,0 +1,17 @@ +{{#styleguide-example title=".nav-stacked" class="half-size"}} + {{#mobile-nav class="preferences-nav" desktopClass="preferences-list action-list nav-stacked" currentPath=application.currentPath}} + {{#each dummy.navItems as |ni|}} +
  • {{ni.displayName}}
  • + {{/each}} + {{/mobile-nav}} +{{/styleguide-example}} + +{{#styleguide-example title=".user-navigation .nav-stacked" class="half-size"}} + {{#d-section class="user-navigation"}} + {{#mobile-nav class="preferences-nav" desktopClass="preferences-list action-list nav-stacked" currentPath=application.currentPath}} + {{#each dummy.navItems as |ni|}} +
  • {{ni.displayName}}
  • + {{/each}} + {{/mobile-nav}} + {{/d-section}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/post-menu.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/post-menu.hbs new file mode 100644 index 0000000000..ab1bcb0816 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/post-menu.hbs @@ -0,0 +1,3 @@ +{{#styleguide-example title="post-menu"}} + {{mount-widget widget="post-menu" args=dummy.transformedPost}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/signup-cta.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/signup-cta.hbs new file mode 100644 index 0000000000..bf7842f1b4 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/signup-cta.hbs @@ -0,0 +1,4 @@ +{{#styleguide-example title="signup-cta"}} + {{signup-cta}} +{{/styleguide-example}} + diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/topic-list-item.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/topic-list-item.hbs new file mode 100644 index 0000000000..1f7d4e1773 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/topic-list-item.hbs @@ -0,0 +1,27 @@ +{{#styleguide-example title="topic list item"}} + + + {{topic-list-item topic=dummy.topic showPosters=true}} + +
    +{{/styleguide-example}} + +{{#styleguide-example title="topic list item - hide category"}} + + + {{topic-list-item topic=dummy.topic hideCategory=true showPosters=true}} + +
    +{{/styleguide-example}} + +{{#styleguide-example title="topic list item - show likes"}} + + + {{topic-list-item topic=dummy.topic showLikes=true showPosters=true}} + +
    +{{/styleguide-example}} + +{{#styleguide-example title="topic list item - latest" class="half-size"}} + {{latest-topic-list-item topic=dummy.topic}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/topic-notifications.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/topic-notifications.hbs new file mode 100644 index 0000000000..744777ab65 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/topic-notifications.hbs @@ -0,0 +1,3 @@ +{{#styleguide-example title="topic-notifications-button"}} + {{topic-notifications-button topic=dummy.topic}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/topic-timer-info.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/topic-timer-info.hbs new file mode 100644 index 0000000000..9427420ca6 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/molecules/topic-timer-info.hbs @@ -0,0 +1,3 @@ +{{#styleguide-example title="topic-timer-info"}} + {{topic-timer-info statusType="reminder" executeAt=dummy.soon}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/00-post.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/00-post.hbs new file mode 100644 index 0000000000..c05ebb918c --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/00-post.hbs @@ -0,0 +1,3 @@ +{{#styleguide-example title="post"}} + {{mount-widget widget="post" args=dummy.transformedPost}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/01-topic-map.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/01-topic-map.hbs new file mode 100644 index 0000000000..679a6869c2 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/01-topic-map.hbs @@ -0,0 +1,3 @@ +{{#styleguide-example title="topic-map"}} + {{mount-widget widget="topic-map" args=dummy.transformedPost}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/03-topic-footer-buttons.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/03-topic-footer-buttons.hbs new file mode 100644 index 0000000000..0e415beac8 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/03-topic-footer-buttons.hbs @@ -0,0 +1,11 @@ +{{#styleguide-example title="topic-footer-buttons - logged in"}} + {{topic-footer-buttons + canInviteTo=true + topic=dummy.topic}} +{{/styleguide-example}} + +{{#styleguide-example title="topic-footer-buttons - anonymous"}} + +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/04-topic-list.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/04-topic-list.hbs new file mode 100644 index 0000000000..4b1ef8e577 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/04-topic-list.hbs @@ -0,0 +1,7 @@ +{{#styleguide-example title="topic-list"}} + {{topic-list topics=dummy.topics showPosters=true}} +{{/styleguide-example}} + +{{#styleguide-example title="topic-list - hide posters"}} + {{topic-list topics=dummy.topics showPosters=false}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/categories-list.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/categories-list.hbs new file mode 100644 index 0000000000..f7d8b2bc03 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/categories-list.hbs @@ -0,0 +1,3 @@ +{{#styleguide-example title="categories-only"}} + {{categories-only categories=dummy.categories}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/latest-topic-list.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/latest-topic-list.hbs new file mode 100644 index 0000000000..6a3d7e77c7 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/latest-topic-list.hbs @@ -0,0 +1,3 @@ +{{#styleguide-example title="latest-topic-list" class="half-size"}} + {{latest-topic-list topics=dummy.topics}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/modal.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/modal.hbs new file mode 100644 index 0000000000..8f50d06c35 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/modal.hbs @@ -0,0 +1,10 @@ +{{#styleguide-example title="d-modal"}} + {{#d-modal closeModal=(action "dummy") modalStyle="inline-modal" title=(i18n "styleguide.sections.modal.header")}} + {{#d-modal-body}} + {{html-safe dummy.lorem}} + {{/d-modal-body}} + + {{/d-modal}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/navigation.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/navigation.hbs new file mode 100644 index 0000000000..3d54f3fbae --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/navigation.hbs @@ -0,0 +1,12 @@ +{{#styleguide-example title="navigation"}} +
    +
    + {{#d-section class="navigation-container"}} + {{bread-crumbs categories=dummy.categories}} + {{navigation-bar navItems=dummy.navItems filterMode="latest"}} + {{categories-admin-dropdown}} + {{create-topic-button canCreateTopic=true}} + {{/d-section}} +
    +
    +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/suggested-topics.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/suggested-topics.hbs new file mode 100644 index 0000000000..5a0971d802 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/suggested-topics.hbs @@ -0,0 +1,3 @@ +{{#styleguide-example title="suggested-topics"}} + {{suggested-topics topic=dummy.topic}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/user-about.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/user-about.hbs new file mode 100644 index 0000000000..d8de1158d4 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/organisms/user-about.hbs @@ -0,0 +1,253 @@ +{{#styleguide-example title=".user-main .about.collapsed-info.no-background"}} + {{#d-section class="user-main"}} +
    +
    +
    +
    + {{bound-avatar dummy.user "huge"}} +
    + +
    + +
    +

    {{dummy.user.username}} {{d-icon "shield-alt"}}

    +

    {{dummy.user.name}}

    +

    {{dummy.user.title}}

    +
    +
    +
    +
    +
    + {{/d-section}} +{{/styleguide-example}} + +{{#styleguide-example title=".user-main .about.collapsed-info.has-background"}} + {{#d-section class="user-main"}} +
    +
    +
    +
    + {{bound-avatar dummy.user "huge"}} +
    + +
    + +
    +

    {{dummy.user.username}} {{d-icon "shield-alt"}}

    +

    {{dummy.user.name}}

    +

    {{dummy.user.title}}

    +
    +
    +
    +
    +
    + {{/d-section}} +{{/styleguide-example}} + +{{#styleguide-example title=".user-main .about.no-background"}} + {{#d-section class="user-main"}} +
    + +
    +
    {{dummy.user.number_of_flags_given}} {{i18n "user.staff_counters.flags_given"}}
    + + +
    {{dummy.user.number_of_suspensions}} {{i18n "user.staff_counters.suspensions"}}
    +
    {{dummy.user.warnings_received_count}} {{i18n "user.staff_counters.warnings_received"}}
    +
    + +
    +
    +
    + {{bound-avatar dummy.user "huge"}} +
    + +
    + +
    +

    {{dummy.user.username}} {{d-icon "shield-alt"}}

    +

    {{dummy.user.name}}

    +

    {{dummy.user.title}}

    +

    + {{d-icon "map-marker-alt"}} {{dummy.user.location}} + {{d-icon "globe"}} + {{dummy.user.website_name}} +

    + +
    +
    + {{d-icon "ban"}} + {{i18n "user.suspended_notice" date=dummy.user.suspendedTillDate}}
    + {{i18n "user.suspended_reason"}} {{dummy.user.suspend_reason}} +
    + {{html-safe dummy.user.bio_cooked}} +
    + +
    + {{#each dummy.user.publicUserFields as |uf|}} + {{#if uf.value}} +
    + {{uf.field.name}}: + {{uf.value}} +
    + {{/if}} + {{/each}} +
    +
    +
    +
    +
    + +
    +
    +
    {{i18n "user.created"}}
    {{bound-date dummy.user.created_at}}
    +
    {{i18n "user.last_posted"}}
    {{bound-date dummy.user.last_posted_at}}
    +
    {{i18n "user.last_seen"}}
    {{bound-date dummy.user.last_seen_at}}
    +
    {{i18n "views"}}
    {{dummy.user.profile_view_count}}
    +
    {{i18n "user.invited_by"}}
    {{dummy.user.invited_by.username}}
    +
    {{i18n "user.trust_level"}}
    {{dummy.user.trustLevel.name}}
    +
    {{i18n "user.email.title"}}
    +
    + {{d-button icon="far-envelope" label="admin.users.check_email.text" class="btn-primary"}} +
    +
    {{i18n "groups.title" count=dummy.user.displayGroups.length}}
    +
    + {{#each dummy.user.displayGroups as |group|}} + {{group.name}} + {{/each}} +
    + {{d-button icon="exclamation-triangle" label="user.admin_delete" class="btn-danger"}} +
    +
    +
    + {{/d-section}} +{{/styleguide-example}} + +{{#styleguide-example title=".user-main .about.has-background"}} + {{#d-section class="user-main"}} +
    +
    +
    {{dummy.user.number_of_flags_given}} {{i18n "user.staff_counters.flags_given"}}
    + + +
    {{dummy.user.number_of_suspensions}} {{i18n "user.staff_counters.suspensions"}}
    +
    {{dummy.user.warnings_received_count}} {{i18n "user.staff_counters.warnings_received"}}
    +
    + +
    +
    +
    + {{bound-avatar dummy.user "huge"}} +
    + +
    + +
    +

    {{dummy.user.username}} {{d-icon "shield-alt"}}

    +

    {{dummy.user.name}}

    +

    {{dummy.user.title}}

    +

    + {{d-icon "map-marker-alt"}} {{dummy.user.location}} + {{d-icon "globe"}} + {{dummy.user.website_name}} +

    + +
    +
    + {{d-icon "ban"}} + {{i18n "user.suspended_notice" date=dummy.user.suspendedTillDate}}
    + {{i18n "user.suspended_reason"}} {{dummy.user.suspend_reason}} +
    + {{html-safe dummy.user.bio_cooked}} +
    + +
    + {{#each dummy.user.publicUserFields as |uf|}} + {{#if uf.value}} +
    + {{uf.field.name}}: + {{uf.value}} +
    + {{/if}} + {{/each}} +
    + +
    +
    +
    +
    + +
    +
    +
    {{i18n "user.created"}}
    {{bound-date dummy.user.created_at}}
    +
    {{i18n "user.last_posted"}}
    {{bound-date dummy.user.last_posted_at}}
    +
    {{i18n "user.last_seen"}}
    {{bound-date dummy.user.last_seen_at}}
    +
    {{i18n "views"}}
    {{dummy.user.profile_view_count}}
    +
    {{i18n "user.invited_by"}}
    {{dummy.user.invited_by.username}}
    +
    {{i18n "user.trust_level"}}
    {{dummy.user.trustLevel.name}}
    +
    {{i18n "user.email.title"}}
    +
    + {{d-button icon="far-envelope" label="admin.users.check_email.text" class="btn-primary"}} +
    +
    {{i18n "groups.title" count=dummy.user.displayGroups.length}}
    +
    + {{#each dummy.user.displayGroups as |group|}} + {{group.name}} + {{/each}} +
    + {{d-button icon="exclamation-triangle" label="user.admin_delete" class="btn-danger"}} +
    +
    +
    + {{/d-section}} +{{/styleguide-example}} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/show.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/show.hbs new file mode 100644 index 0000000000..f2cc79d883 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide/show.hbs @@ -0,0 +1,9 @@ +{{#styleguide-section section=section}} + {{#if note}} +
    + {{component (concat "notes/" note)}} +
    + {{/if}} + + {{outlet}} +{{/styleguide-section}} diff --git a/plugins/styleguide/assets/stylesheets/colors.scss b/plugins/styleguide/assets/stylesheets/colors.scss new file mode 100644 index 0000000000..a59afb0904 --- /dev/null +++ b/plugins/styleguide/assets/stylesheets/colors.scss @@ -0,0 +1,89 @@ +.color-row { + .primary-very-low { + background-color: var(--primary-very-low, $primary-very-low); + } + .primary-low { + background-color: var(--primary-low, $primary-low); + } + .primary-low-mid { + background-color: var(--primary-low-mid, $primary-low-mid); + } + .primary-medium { + background-color: var(--primary-medium, $primary-medium); + } + .primary-high { + background-color: var(--primary-high, $primary-high); + } + .primary { + background-color: var(--primary, $primary); + } + .secondary-low { + background-color: var(--secondary-low, $secondary-low); + } + .secondary-medium { + background-color: var(--secondary-medium, $secondary-medium); + } + .secondary-high { + background-color: var(--secondary-high, $secondary-high); + } + .secondary { + background-color: var(--secondary, $secondary); + } + .tertiary-low { + background-color: var(--tertiary-low, $tertiary-low); + } + .tertiary-medium { + background-color: var(--tertiary-medium, $tertiary-medium); + } + .tertiary-high { + background-color: var(--tertiary-high, $tertiary-high); + } + .tertiary { + background-color: var(--tertiary, $tertiary); + } + .quaternary-low { + background-color: var(--quaternary-low, $quaternary-low); + } + .quaternary { + background-color: var(--quaternary, $quaternary); + } + .highlight-low { + background-color: var(--highlight-low, $highlight-low); + } + .highlight-medium { + background-color: var(--highlight-medium, $highlight-medium); + } + .highlight-high { + background-color: var(--highlight-high, $highlight-high); + } + .highlight { + background-color: var(--highlight, $highlight); + } + .danger-low { + background-color: var(--danger-low, $danger-low); + } + .danger-low-mid { + background-color: var(--danger-low-mid, $danger-low-mid); + } + .danger-medium { + background-color: var(--danger-medium, $danger-medium); + } + .danger { + background-color: var(--danger, $danger); + } + .success-low { + background-color: var(--success-low, $success-low); + } + .success-medium { + background-color: var(--success-medium, $success-medium); + } + .success { + background-color: var(--success, $success); + } + .love-low { + background-color: var(--love-low, $love-low); + } + .love { + background-color: var(--love, $love); + } +} diff --git a/plugins/styleguide/assets/stylesheets/styleguide.scss b/plugins/styleguide/assets/stylesheets/styleguide.scss new file mode 100644 index 0000000000..942a1d9496 --- /dev/null +++ b/plugins/styleguide/assets/stylesheets/styleguide.scss @@ -0,0 +1,166 @@ +@import "colors"; +@import "typography"; + +.styleguide { + display: flex; + + .styleguide-note { + padding: 1em; + background-color: var(--tertiary, $tertiary); + margin-bottom: 1em; + } + + .styleguide-menu { + flex: 1 0 0; + + ul { + list-style: none; + margin-bottom: 2em; + + li.styleguide-heading { + color: var(--primary-medium, $primary-medium); + text-transform: uppercase; + font-size: 14px; + } + + li { + margin-bottom: 0.25em; + + a { + color: var(--primary, $primary); + font-size: 14px; + } + + a.active { + font-weight: bold; + } + } + } + } + + .styleguide-contents { + flex: 4 0 0; + font-size: 14px; + } + + .styleguide-section { + .section-title { + font-size: $font-up-6; + font-weight: normal; + margin-bottom: 1em; + } + + .section-description { + margin-bottom: 2em; + } + + .description { + margin: 1em 0; + } + + .half-size { + width: 50%; + } + + .styleguide-example { + .example-title { + color: var(--primary-medium, $primary-medium); + font-size: 0.8em; + border-bottom: 1px solid var(--primary-low, $primary-low); + margin-bottom: 0.8em; + } + + .rendered { + width: 100%; + } + margin-bottom: 2em; + } + } + + .color-row { + display: flex; + + .color-example { + flex: 1; + display: flex; + flex-direction: column; + height: 120px; + margin: 0.5em 0.5em 0.5em 0; + + .color-bg { + flex: 4; + } + .color-name { + flex: 1; + display: flex; + align-items: center; + padding: 0.25em 0.5em; + background-color: black; + color: white; + } + } + } + + pre { + background: var(--primary-very-low, $primary-very-low); + white-space: pre-wrap; + &.pre-inline { + display: inline-block; + margin: 0; + vertical-align: bottom; + } + span { + color: var(--primary-medium, $primary-medium); + &.hljs-attribute { + color: teal; + } + } + } + + p.reason { + display: inline; + color: var(--primary-medium, $primary-medium); + margin: 0 0 0 10px; + } +} + +.buttons-examples { + button { + margin-right: 0.5em; + margin-bottom: 0.5em; + } +} + +.icons-examples, +.topic-statuses-examples .d-icon { + margin-right: 0.5em; + margin-bottom: 0.5em; +} + +.dropdowns-examples { + .select-kit.is-hidden { + display: inline-block; + } +} + +.styleguide-icons { + display: grid; + grid-template-columns: 150px 150px 150px 150px 150px; + grid-gap: 10px; + + .styleguide-icon { + background-color: var(--primary-low, $primary-low); + margin: 3px; + text-align: center; + padding: 10px; + overflow: hidden; + svg { + display: block; + font-size: 3em; + margin: 5px auto; + } + span { + font-size: $font-down-1; + } + } +} diff --git a/plugins/styleguide/assets/stylesheets/typography.scss b/plugins/styleguide/assets/stylesheets/typography.scss new file mode 100644 index 0000000000..29cd8db9c8 --- /dev/null +++ b/plugins/styleguide/assets/stylesheets/typography.scss @@ -0,0 +1,43 @@ +p[class*="font-"] { + margin-top: 0; +} + +.font-up-6 { + font-size: $font-up-6; +} +.font-up-5 { + font-size: $font-up-5; +} +.font-up-4 { + font-size: $font-up-4; +} +.font-up-3 { + font-size: $font-up-3; +} +.font-up-2 { + font-size: $font-up-2; +} +.font-up-1 { + font-size: $font-up-1; +} +.font-0 { + font-size: $font-0; +} +.font-down-1 { + font-size: $font-down-1; +} +.font-down-2 { + font-size: $font-down-2; +} +.font-down-3 { + font-size: $font-down-3; +} +.font-down-4 { + font-size: $font-down-4; +} +.font-down-5 { + font-size: $font-down-5; +} +.font-down-6 { + font-size: $font-down-6; +} diff --git a/plugins/styleguide/config/locales/client.ar.yml b/plugins/styleguide/config/locales/client.ar.yml new file mode 100644 index 0000000000..d64a235343 --- /dev/null +++ b/plugins/styleguide/config/locales/client.ar.yml @@ -0,0 +1,69 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ar: + js: + styleguide: + title: "دليل الأنماط" + welcome: "اختر قسمًا من القائمة على اليمين لتبدأ." + categories: + organisms: المخلوقات الحيّة + sections: + typography: + example: "مرحبًا بك في دِسكورس" + font_scale: + title: "نظام الخطوط" + colors: + title: "الألوان" + icons: + title: "الأيقونات" + full_list: "طالِع قائمة أيقونات Font Awesome كاملةً" + input_fields: + title: "حقول الإدخال" + buttons: + title: "الأزرار" + dropdowns: + title: "المُنسدلات" + categories: + title: "الفئات" + navigation: + title: "التنقّل" + navigation_bar: + title: "شريط التنقّل" + navigation_stacked: + title: "التنقّل حين يُرصّ" + categories_list: + title: "قائمة الفئات" + topic_link: + title: "رابط الموضوع" + topic_list_item: + title: "العنصر في قائمة المواضيع" + topic_statuses: + title: "حالات المواضيع" + topic_list: + title: "قائمة المواضيع" + latest_topic_list: + title: "قائمة المواضيع الحديثة" + footer_message: + title: "رسالة التذييل" + topic_timer_info: + title: "مؤقّتات المواضيع" + topic_footer_buttons: + title: "أزرار تذييل الموضوع" + topic_notifications: + title: "إخطارات المواضيع" + post: + title: "المنشور" + topic_map: + title: "خريطة الموضوع" + post_menu: + title: "قائمة الموضوع" + user_about: + title: "مربّع ”عن المستخدم“" + header_icons: + title: "أيقونات الترويسات" + spinners: + title: "المُنزلقات" diff --git a/plugins/styleguide/config/locales/client.be.yml b/plugins/styleguide/config/locales/client.be.yml new file mode 100644 index 0000000000..2e3980a624 --- /dev/null +++ b/plugins/styleguide/config/locales/client.be.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +be: diff --git a/plugins/styleguide/config/locales/client.bg.yml b/plugins/styleguide/config/locales/client.bg.yml new file mode 100644 index 0000000000..6c61d82645 --- /dev/null +++ b/plugins/styleguide/config/locales/client.bg.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +bg: diff --git a/plugins/styleguide/config/locales/client.bs_BA.yml b/plugins/styleguide/config/locales/client.bs_BA.yml new file mode 100644 index 0000000000..509ba581f8 --- /dev/null +++ b/plugins/styleguide/config/locales/client.bs_BA.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +bs_BA: diff --git a/plugins/styleguide/config/locales/client.ca.yml b/plugins/styleguide/config/locales/client.ca.yml new file mode 100644 index 0000000000..3eba30e02a --- /dev/null +++ b/plugins/styleguide/config/locales/client.ca.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ca: diff --git a/plugins/styleguide/config/locales/client.cs.yml b/plugins/styleguide/config/locales/client.cs.yml new file mode 100644 index 0000000000..f3265c8c88 --- /dev/null +++ b/plugins/styleguide/config/locales/client.cs.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +cs: diff --git a/plugins/styleguide/config/locales/client.da.yml b/plugins/styleguide/config/locales/client.da.yml new file mode 100644 index 0000000000..1aa92f22af --- /dev/null +++ b/plugins/styleguide/config/locales/client.da.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +da: diff --git a/plugins/styleguide/config/locales/client.de.yml b/plugins/styleguide/config/locales/client.de.yml new file mode 100644 index 0000000000..9a40fdba4b --- /dev/null +++ b/plugins/styleguide/config/locales/client.de.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +de: diff --git a/plugins/styleguide/config/locales/client.el.yml b/plugins/styleguide/config/locales/client.el.yml new file mode 100644 index 0000000000..1ef87229b3 --- /dev/null +++ b/plugins/styleguide/config/locales/client.el.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +el: diff --git a/plugins/styleguide/config/locales/client.en.yml b/plugins/styleguide/config/locales/client.en.yml new file mode 100644 index 0000000000..b9e2bf77ee --- /dev/null +++ b/plugins/styleguide/config/locales/client.en.yml @@ -0,0 +1,81 @@ +en: + js: + styleguide: + title: "Styleguide" + welcome: "To get started, choose a section from the menu on the left." + + categories: + atoms: Atoms + molecules: Molecules + organisms: Organisms + + sections: + typography: + title: "Typography" + example: "Welcome to Discourse" + paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + date_time_inputs: + title: "Date/Time inputs" + font_scale: + title: "Font System" + colors: + title: "Colors" + icons: + title: "Icons" + full_list: "See the full list of Font Awesome Icons" + input_fields: + title: "Input Fields" + buttons: + title: "Buttons" + dropdowns: + title: "Dropdowns" + categories: + title: "Categories" + bread_crumbs: + title: "Bread Crumbs" + navigation: + title: "Navigation" + navigation_bar: + title: "Navigation Bar" + navigation_stacked: + title: "Navigation Stacked" + categories_list: + title: "Categories List" + topic_link: + title: "Topic Link" + topic_list_item: + title: "Topic List Item" + topic_statuses: + title: "Topic Statuses" + topic_list: + title: "Topic List" + latest_topic_list: + title: "Latest Topic List" + footer_message: + title: "Footer Message" + signup_cta: + title: "Signup CTA" + topic_timer_info: + title: "Topic Timers" + topic_footer_buttons: + title: "Topic Footer Buttons" + topic_notifications: + title: "Topic Notifications" + post: + title: "Post" + topic_map: + title: "Topic Map" + suggested_topics: + title: "Suggested Topics" + post_menu: + title: "Post Menu" + modal: + title: "Modal" + header: "Modal Title" + footer: "Modal Footer" + user_about: + title: "User About Box" + header_icons: + title: "Header Icons" + spinners: + title: "Spinners" diff --git a/plugins/styleguide/config/locales/client.es.yml b/plugins/styleguide/config/locales/client.es.yml new file mode 100644 index 0000000000..ad076714d5 --- /dev/null +++ b/plugins/styleguide/config/locales/client.es.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +es: diff --git a/plugins/styleguide/config/locales/client.et.yml b/plugins/styleguide/config/locales/client.et.yml new file mode 100644 index 0000000000..5702d705bb --- /dev/null +++ b/plugins/styleguide/config/locales/client.et.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +et: diff --git a/plugins/styleguide/config/locales/client.fa_IR.yml b/plugins/styleguide/config/locales/client.fa_IR.yml new file mode 100644 index 0000000000..edcb1f27e2 --- /dev/null +++ b/plugins/styleguide/config/locales/client.fa_IR.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +fa_IR: diff --git a/plugins/styleguide/config/locales/client.fi.yml b/plugins/styleguide/config/locales/client.fi.yml new file mode 100644 index 0000000000..b10a8718e0 --- /dev/null +++ b/plugins/styleguide/config/locales/client.fi.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +fi: diff --git a/plugins/styleguide/config/locales/client.fr.yml b/plugins/styleguide/config/locales/client.fr.yml new file mode 100644 index 0000000000..c2f24c3fcb --- /dev/null +++ b/plugins/styleguide/config/locales/client.fr.yml @@ -0,0 +1,77 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +fr: + js: + styleguide: + title: "Guide de style" + welcome: "Pour commencer, choisissez une section dans le menu de gauche." + categories: + atoms: Atomes + molecules: Molécules + organisms: Organismes + sections: + typography: + title: "Typographie" + example: "Bienvenue sur Discourse" + paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + date_time_inputs: + title: "Date / Heure" + font_scale: + title: "Système de polices" + colors: + title: "Couleurs" + icons: + title: "Icônes" + full_list: "Voir la liste complète des icônes Font Awesome" + input_fields: + title: "Champs de saisie" + buttons: + title: "Boutons" + dropdowns: + title: "Listes déroulantes" + categories: + title: "Catégories" + navigation: + title: "Navigation" + navigation_bar: + title: "Barre de navigation" + categories_list: + title: "Liste des catégories" + topic_link: + title: "Lien vers le sujet" + topic_statuses: + title: "Statuts des sujets" + topic_list: + title: "Liste des sujets" + latest_topic_list: + title: "Sujets récents" + footer_message: + title: "Message de pied de page" + signup_cta: + title: "CTA d'inscription" + topic_timer_info: + title: "Actions planifiées pour le sujet" + topic_footer_buttons: + title: "Boutons du pied de page du sujet" + topic_notifications: + title: "Notifications de sujet" + post: + title: "Message" + topic_map: + title: "Carte des sujets" + suggested_topics: + title: "Sujets suggérés" + post_menu: + title: "Menu du message" + modal: + title: "Modale" + header: "Titre de la modale" + footer: "Pied de la modale" + header_icons: + title: "Icônes d'en-tête" + spinners: + title: "Icónes de chargement" diff --git a/plugins/styleguide/config/locales/client.gl.yml b/plugins/styleguide/config/locales/client.gl.yml new file mode 100644 index 0000000000..0b8e230d47 --- /dev/null +++ b/plugins/styleguide/config/locales/client.gl.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +gl: diff --git a/plugins/styleguide/config/locales/client.he.yml b/plugins/styleguide/config/locales/client.he.yml new file mode 100644 index 0000000000..c1fac7031d --- /dev/null +++ b/plugins/styleguide/config/locales/client.he.yml @@ -0,0 +1,85 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +he: + js: + styleguide: + title: "מדריך סגנון" + welcome: "כדי להתחיל, יש לבחור בסעיף מהתפריט שמימין." + categories: + atoms: אטומים + molecules: מולקולות + organisms: אורגניזמים + sections: + typography: + title: "טיפוגרפיה" + example: "ברוך בואך ל־Discourse" + paragraph: "לורם איפסום דולור סיט אמט, קונסקטורר אדיפיסינג אלית קולורס מונפרד אדנדום סילקוף, מרגשי ומרגשח. עמחליף לפרומי בלוף קינץ תתיח לרעח. לת צשחמי צש בליא, מנסוטו צמלח לביקו ננבי, צמוקו בלוקריה שיצמה ברורק. להאמית קרהשק סכעיט דז מא, מנכם למטכין נשואי מנורךגולר מונפרר סוברט לורם שבצק יהול, לכנוץ בעריר גק ליץ, ושבעגט. ושבעגט לבם סולגק. בראיט ולחת צורק מונחף, בגורמי מגמש. תרבנך וסתעד לכנו סתשם השמה - לתכי מורגם בורק? לתיג ישבעס." + date_time_inputs: + title: "קלטי תאריך/שעה" + font_scale: + title: "מערכת גופנים" + colors: + title: "צבעים" + icons: + title: "סמלים" + full_list: "הצגת רשימת הסמלים המלאה של Font Awesome" + input_fields: + title: "שדות קלט" + buttons: + title: "כפתורים" + dropdowns: + title: "נגללים" + categories: + title: "קטגוריות" + bread_crumbs: + title: "מחווני ניווט" + navigation: + title: "ניווט" + navigation_bar: + title: "סרגל ניווט" + navigation_stacked: + title: "ניווט מוערם" + categories_list: + title: "רשימת קטגוריות" + topic_link: + title: "קישור נושא" + topic_list_item: + title: "פריט ברשימת נושאים" + topic_statuses: + title: "מצבי נושא" + topic_list: + title: "רשימת נושאים" + latest_topic_list: + title: "רשימת נושאים אחרונים" + footer_message: + title: "הודעת כותרת תחתונה" + signup_cta: + title: "קריאה להרשמה" + topic_timer_info: + title: "שעוני עצר לנושא" + topic_footer_buttons: + title: "כפתורי כותרת תחתונה של נושא" + topic_notifications: + title: "התראות על נושאים" + post: + title: "פוסט" + topic_map: + title: "מפת נושאים" + suggested_topics: + title: "נושאים מוצעים" + post_menu: + title: "תפריט פוסט" + modal: + title: "מודאלי" + header: "כותרת מודאלי" + footer: "כותרת תחתונה של מודאלי" + user_about: + title: "תיבת על אודות משתמש" + header_icons: + title: "סמלי כותרת עליונה" + spinners: + title: "שבשבות" diff --git a/plugins/styleguide/config/locales/client.hu.yml b/plugins/styleguide/config/locales/client.hu.yml new file mode 100644 index 0000000000..ffa7a40423 --- /dev/null +++ b/plugins/styleguide/config/locales/client.hu.yml @@ -0,0 +1,67 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hu: + js: + styleguide: + title: "Stílus útmutató" + welcome: "A kezdéshez válasszon egy szakaszt a bal oldali menüből." + categories: + atoms: Atomok + molecules: Molekulák + organisms: Szervezetek + sections: + typography: + title: "Tipográfia" + example: "Üdvözöljük a Discourse-ban" + paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + date_time_inputs: + title: "Dátum/idő bemenetek" + font_scale: + title: "Betűtípus rendszer" + colors: + title: "Színek" + icons: + title: "Ikonok" + full_list: "A Font Awesome ikonok teljes listájának megtekintése" + input_fields: + title: "Beviteli mezők" + buttons: + title: "Gombok" + dropdowns: + title: "Legördülők" + categories: + title: "Kategóriák" + navigation: + title: "Navigáció" + navigation_bar: + title: "Navigációs sáv" + navigation_stacked: + title: "Halmozott navigáció" + categories_list: + title: "Kategóriák listája" + topic_link: + title: "Téma hivatkozás" + topic_list_item: + title: "Témalista elem" + topic_statuses: + title: "Témaállapotok" + topic_list: + title: "Témalista" + latest_topic_list: + title: "Legutóbbi témák listája" + footer_message: + title: "Lábléc üzenet" + post: + title: "Bejegyzés" + topic_map: + title: "Téma térkép" + suggested_topics: + title: "Ajánlott témák" + post_menu: + title: "Bejegyzés menü" + header_icons: + title: "Fejléc ikonok" diff --git a/plugins/styleguide/config/locales/client.hy.yml b/plugins/styleguide/config/locales/client.hy.yml new file mode 100644 index 0000000000..ba5e8380d2 --- /dev/null +++ b/plugins/styleguide/config/locales/client.hy.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hy: diff --git a/plugins/styleguide/config/locales/client.id.yml b/plugins/styleguide/config/locales/client.id.yml new file mode 100644 index 0000000000..a4d1527157 --- /dev/null +++ b/plugins/styleguide/config/locales/client.id.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +id: diff --git a/plugins/styleguide/config/locales/client.it.yml b/plugins/styleguide/config/locales/client.it.yml new file mode 100644 index 0000000000..bf1761f628 --- /dev/null +++ b/plugins/styleguide/config/locales/client.it.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +it: diff --git a/plugins/styleguide/config/locales/client.ja.yml b/plugins/styleguide/config/locales/client.ja.yml new file mode 100644 index 0000000000..1d8afc616b --- /dev/null +++ b/plugins/styleguide/config/locales/client.ja.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ja: diff --git a/plugins/styleguide/config/locales/client.ko.yml b/plugins/styleguide/config/locales/client.ko.yml new file mode 100644 index 0000000000..ad47146fb4 --- /dev/null +++ b/plugins/styleguide/config/locales/client.ko.yml @@ -0,0 +1,76 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ko: + js: + styleguide: + title: "스타일 가이드" + welcome: "시작하려면 왼쪽 메뉴에서 섹션을 선택하세요." + categories: + atoms: Atoms + sections: + typography: + title: "타이포그래피" + example: "Discourse에 오신 것을 환영합니다" + date_time_inputs: + title: "날짜/시간 입력" + font_scale: + title: "글꼴 시스템" + colors: + title: "색상" + icons: + title: "아이콘" + full_list: "Font Awesome Icons의 전체 목록보기" + input_fields: + title: "입력 필드" + buttons: + title: "버튼" + dropdowns: + title: "드롭다운" + categories: + title: "카테고리" + navigation: + title: "내비게이션" + navigation_bar: + title: "내비게이션 바" + navigation_stacked: + title: "내비게이션 스택" + categories_list: + title: "카테고리 목록" + topic_link: + title: "글 링크" + topic_list: + title: "글 목록" + latest_topic_list: + title: "최신 글 목록" + footer_message: + title: "하단 메시지" + signup_cta: + title: "가입 CTA" + topic_timer_info: + title: "글 타이머" + topic_footer_buttons: + title: "글 하단 버턴" + topic_notifications: + title: "글 알림" + post: + title: "글" + topic_map: + title: "글 맵" + suggested_topics: + title: "주요 글" + post_menu: + title: "글쓰기 메뉴" + modal: + title: "모달" + header: "모달 제목" + footer: "모달 하단" + user_about: + title: "사용자 소개 상자" + header_icons: + title: "헤더 아이콘" + spinners: + title: "스피너" diff --git a/plugins/styleguide/config/locales/client.lt.yml b/plugins/styleguide/config/locales/client.lt.yml new file mode 100644 index 0000000000..1a57c12695 --- /dev/null +++ b/plugins/styleguide/config/locales/client.lt.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +lt: diff --git a/plugins/styleguide/config/locales/client.lv.yml b/plugins/styleguide/config/locales/client.lv.yml new file mode 100644 index 0000000000..5b0066d4cb --- /dev/null +++ b/plugins/styleguide/config/locales/client.lv.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +lv: diff --git a/plugins/styleguide/config/locales/client.nb_NO.yml b/plugins/styleguide/config/locales/client.nb_NO.yml new file mode 100644 index 0000000000..e01d66c96b --- /dev/null +++ b/plugins/styleguide/config/locales/client.nb_NO.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +nb_NO: diff --git a/plugins/styleguide/config/locales/client.nl.yml b/plugins/styleguide/config/locales/client.nl.yml new file mode 100644 index 0000000000..c042a6b956 --- /dev/null +++ b/plugins/styleguide/config/locales/client.nl.yml @@ -0,0 +1,85 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +nl: + js: + styleguide: + title: "Stijlgids" + welcome: "Kies een sectie in het menu aan de linkerkant om te beginnen." + categories: + atoms: Atomen + molecules: Moleculen + organisms: Organismen + sections: + typography: + title: "Typografie" + example: "Welkom bij Discourse" + paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + date_time_inputs: + title: "Datum-/tijdinvoer" + font_scale: + title: "Lettertypesysteem" + colors: + title: "Kleuren" + icons: + title: "Pictogrammen" + full_list: "Volledige lijst met Font Awesome-pictogrammen bekijken" + input_fields: + title: "Invoervelden" + buttons: + title: "Knoppen" + dropdowns: + title: "Vervolgkeuzelijsten" + categories: + title: "Categorieën" + bread_crumbs: + title: "Broodkruimels" + navigation: + title: "Navigatie" + navigation_bar: + title: "Navigatiebalk" + navigation_stacked: + title: "Navigatie gestapeld" + categories_list: + title: "Categorielijst" + topic_link: + title: "Topickoppeling" + topic_list_item: + title: "Topiclijstitem" + topic_statuses: + title: "Topicstatussen" + topic_list: + title: "Topiclijst" + latest_topic_list: + title: "Lijst met nieuwste topics" + footer_message: + title: "Voettekstbericht" + signup_cta: + title: "Registratie-CTA" + topic_timer_info: + title: "Topictimers" + topic_footer_buttons: + title: "Knoppen voor topicvoettekst" + topic_notifications: + title: "Topicmeldingen" + post: + title: "Bericht" + topic_map: + title: "Topickaart" + suggested_topics: + title: "Voorgestelde topics" + post_menu: + title: "Berichtmenu" + modal: + title: "Modaal" + header: "Modaaltitel" + footer: "Modaalvoettekst" + user_about: + title: "Gebruikersinfovak" + header_icons: + title: "Koptekstpictogrammen" + spinners: + title: "Kringvelden" diff --git a/plugins/styleguide/config/locales/client.pl_PL.yml b/plugins/styleguide/config/locales/client.pl_PL.yml new file mode 100644 index 0000000000..fb27e29001 --- /dev/null +++ b/plugins/styleguide/config/locales/client.pl_PL.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +pl_PL: diff --git a/plugins/styleguide/config/locales/client.pt.yml b/plugins/styleguide/config/locales/client.pt.yml new file mode 100644 index 0000000000..d064f4d964 --- /dev/null +++ b/plugins/styleguide/config/locales/client.pt.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +pt: diff --git a/plugins/styleguide/config/locales/client.pt_BR.yml b/plugins/styleguide/config/locales/client.pt_BR.yml new file mode 100644 index 0000000000..61e4d44129 --- /dev/null +++ b/plugins/styleguide/config/locales/client.pt_BR.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +pt_BR: diff --git a/plugins/styleguide/config/locales/client.ro.yml b/plugins/styleguide/config/locales/client.ro.yml new file mode 100644 index 0000000000..d855599844 --- /dev/null +++ b/plugins/styleguide/config/locales/client.ro.yml @@ -0,0 +1,85 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ro: + js: + styleguide: + title: "Ghid de stil" + welcome: "Pentru a începe, alege o secțiune din meniul din stânga." + categories: + atoms: Atomi + molecules: Molecule + organisms: Organisme + sections: + typography: + title: "Corpuri de literă" + example: "Bine ai venit la Discourse" + paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + date_time_inputs: + title: "Introducerea datei/orei" + font_scale: + title: "Sistem corpuri de literă" + colors: + title: "Culori" + icons: + title: "Pictograme" + full_list: "Vezi lista completă a pictogramelor din Font Awesome" + input_fields: + title: "Rubrici de completat" + buttons: + title: "Butoane" + dropdowns: + title: "Meniuri cascadă" + categories: + title: "Categorii" + bread_crumbs: + title: "Urmărire cale acces" + navigation: + title: "Navigare" + navigation_bar: + title: "Bară de navigare" + navigation_stacked: + title: "Navigare suprapusă" + categories_list: + title: "Listă de categorii" + topic_link: + title: "Legătură spre subiect" + topic_list_item: + title: "Element din lista subiectelor" + topic_statuses: + title: "Statusuri pentru subiecte" + topic_list: + title: "Listă de subiecte" + latest_topic_list: + title: "Lista cu subiecte recente" + footer_message: + title: "Mesaj subsol" + signup_cta: + title: "Îndemn la înscriere" + topic_timer_info: + title: "Temporizatoare subiecte" + topic_footer_buttons: + title: "Butoanele de la subsolul subiectului" + topic_notifications: + title: "Notificări despre subiecte" + post: + title: "Postare" + topic_map: + title: "Harta subiectului" + suggested_topics: + title: "Subiecte sugerate" + post_menu: + title: "Meniu postare" + modal: + title: "Dialog modal" + header: "Titlu modal" + footer: "Subsol modal" + user_about: + title: "Casetă despre utilizator" + header_icons: + title: "Pictograme Antet" + spinners: + title: "Indicatori încărcare" diff --git a/plugins/styleguide/config/locales/client.ru.yml b/plugins/styleguide/config/locales/client.ru.yml new file mode 100644 index 0000000000..477d9539a3 --- /dev/null +++ b/plugins/styleguide/config/locales/client.ru.yml @@ -0,0 +1,81 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ru: + js: + styleguide: + title: "Styleguide" + welcome: "Чтобы начать, выберите раздел в меню слева." + categories: + atoms: Атомы + molecules: Молекулы + organisms: Организмы + sections: + typography: + title: "Типографика" + example: "Добро пожаловать в Discourse" + paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + date_time_inputs: + title: "Поля ввода Даты/Времени" + font_scale: + title: "Шрифты" + colors: + title: "Цвета" + icons: + title: "Иконки" + full_list: "См. полный список иконок Font Awesome" + input_fields: + title: "Поля ввода" + buttons: + title: "Кнопки" + dropdowns: + title: "Выпадающие списки" + categories: + title: "Разделы" + bread_crumbs: + title: "Хлебные крошки" + navigation: + title: "Навигация" + navigation_bar: + title: "Панель навигации" + categories_list: + title: "Список разделов" + topic_link: + title: "Ссылка на тему" + topic_list_item: + title: "Тема в списке тем" + topic_statuses: + title: "Статусы темы" + topic_list: + title: "Список тем" + latest_topic_list: + title: "Темы в секции 'Последние'" + footer_message: + title: "Содержимое нижнего колонтитула" + signup_cta: + title: "CTA" + topic_timer_info: + title: "Таймеры темы" + topic_footer_buttons: + title: "Кнопки под последним сообщением темы" + topic_notifications: + title: "Уведомления темы" + post: + title: "Сообщение" + topic_map: + title: "Карта темы" + suggested_topics: + title: "Похожие темы" + post_menu: + title: "Меню сообщения" + modal: + title: "Модальный" + header: "Модальный заголовок" + footer: "Модальный нижний колонтитул" + user_about: + title: "Окно \"О пользователе\"" + header_icons: + title: "Значки заголовков" diff --git a/plugins/styleguide/config/locales/client.sk.yml b/plugins/styleguide/config/locales/client.sk.yml new file mode 100644 index 0000000000..143b5bf2d6 --- /dev/null +++ b/plugins/styleguide/config/locales/client.sk.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sk: diff --git a/plugins/styleguide/config/locales/client.sl.yml b/plugins/styleguide/config/locales/client.sl.yml new file mode 100644 index 0000000000..8e51613b8f --- /dev/null +++ b/plugins/styleguide/config/locales/client.sl.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sl: diff --git a/plugins/styleguide/config/locales/client.sq.yml b/plugins/styleguide/config/locales/client.sq.yml new file mode 100644 index 0000000000..1413494f57 --- /dev/null +++ b/plugins/styleguide/config/locales/client.sq.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sq: diff --git a/plugins/styleguide/config/locales/client.sr.yml b/plugins/styleguide/config/locales/client.sr.yml new file mode 100644 index 0000000000..adfafeee68 --- /dev/null +++ b/plugins/styleguide/config/locales/client.sr.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sr: diff --git a/plugins/styleguide/config/locales/client.sv.yml b/plugins/styleguide/config/locales/client.sv.yml new file mode 100644 index 0000000000..ac8b78e2e3 --- /dev/null +++ b/plugins/styleguide/config/locales/client.sv.yml @@ -0,0 +1,85 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sv: + js: + styleguide: + title: "Stilguide" + welcome: "Kom igång genom att välja ett avsnitt i menyn till vänster." + categories: + atoms: Atomer + molecules: Molekyler + organisms: Organismer + sections: + typography: + title: "Typografi" + example: "Välkommen till Discourse" + paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + date_time_inputs: + title: "Inmatad datum/tid" + font_scale: + title: "Teckensnittssystem" + colors: + title: "Färger" + icons: + title: "Ikoner" + full_list: "Se hela listan över Font Awesome-ikoner" + input_fields: + title: "Inmatningsfält" + buttons: + title: "Knappar" + dropdowns: + title: "Rullgardinsmenyer" + categories: + title: "Kategorier" + bread_crumbs: + title: "Brödsmulor" + navigation: + title: "Navigering" + navigation_bar: + title: "Navigeringsfält" + navigation_stacked: + title: "Staplad navigering" + categories_list: + title: "Kategorilista" + topic_link: + title: "Ämneslänk" + topic_list_item: + title: "Punkt i ämneslista" + topic_statuses: + title: "Ämnesstatus" + topic_list: + title: "Ämneslista" + latest_topic_list: + title: "Senaste ämneslista" + footer_message: + title: "Sidfotsmeddelande" + signup_cta: + title: "Registrera CTA" + topic_timer_info: + title: "Ämnestidtagningar" + topic_footer_buttons: + title: "Sidfotsknappar för ämne" + topic_notifications: + title: "Ämnesaviseringar" + post: + title: "Inlägg" + topic_map: + title: "Ämneskarta" + suggested_topics: + title: "Föreslagna ämnen" + post_menu: + title: "Inläggsmeny" + modal: + title: "Modal" + header: "Modal titel" + footer: "Modal sidfot" + user_about: + title: "Användares Om-ruta" + header_icons: + title: "Rubrikikoner" + spinners: + title: "Spinnare" diff --git a/plugins/styleguide/config/locales/client.sw.yml b/plugins/styleguide/config/locales/client.sw.yml new file mode 100644 index 0000000000..7e07c4560a --- /dev/null +++ b/plugins/styleguide/config/locales/client.sw.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sw: diff --git a/plugins/styleguide/config/locales/client.te.yml b/plugins/styleguide/config/locales/client.te.yml new file mode 100644 index 0000000000..ac5d945ae8 --- /dev/null +++ b/plugins/styleguide/config/locales/client.te.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +te: diff --git a/plugins/styleguide/config/locales/client.th.yml b/plugins/styleguide/config/locales/client.th.yml new file mode 100644 index 0000000000..9b38ad300e --- /dev/null +++ b/plugins/styleguide/config/locales/client.th.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +th: diff --git a/plugins/styleguide/config/locales/client.tr_TR.yml b/plugins/styleguide/config/locales/client.tr_TR.yml new file mode 100644 index 0000000000..c32afd624c --- /dev/null +++ b/plugins/styleguide/config/locales/client.tr_TR.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +tr_TR: diff --git a/plugins/styleguide/config/locales/client.uk.yml b/plugins/styleguide/config/locales/client.uk.yml new file mode 100644 index 0000000000..602f40aeed --- /dev/null +++ b/plugins/styleguide/config/locales/client.uk.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +uk: diff --git a/plugins/styleguide/config/locales/client.ur.yml b/plugins/styleguide/config/locales/client.ur.yml new file mode 100644 index 0000000000..1e9587a363 --- /dev/null +++ b/plugins/styleguide/config/locales/client.ur.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ur: diff --git a/plugins/styleguide/config/locales/client.vi.yml b/plugins/styleguide/config/locales/client.vi.yml new file mode 100644 index 0000000000..bddd73d4e8 --- /dev/null +++ b/plugins/styleguide/config/locales/client.vi.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +vi: diff --git a/plugins/styleguide/config/locales/client.zh_CN.yml b/plugins/styleguide/config/locales/client.zh_CN.yml new file mode 100644 index 0000000000..c954226960 --- /dev/null +++ b/plugins/styleguide/config/locales/client.zh_CN.yml @@ -0,0 +1,85 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +zh_CN: + js: + styleguide: + title: "风格指南" + welcome: "要开始使用,请从左侧菜单中选择一个部分。" + categories: + atoms: 原子 + molecules: 分子 + organisms: 生物体 + sections: + typography: + title: "版式" + example: "欢迎来到 Discourse" + paragraph: "占位符占位符占位符,那只敏捷的棕毛狐狸跳过了那条懒狗,那只敏捷的棕毛狐狸跳过了那条懒狗,占位符占位符占位符" + date_time_inputs: + title: "日期/时间 输入" + font_scale: + title: "字体系统" + colors: + title: "颜色" + icons: + title: "图标" + full_list: "查看 Font Awesome Icons 的完整列表" + input_fields: + title: "输入字段" + buttons: + title: "按钮" + dropdowns: + title: "下拉菜单" + categories: + title: "分类" + bread_crumbs: + title: "面包屑" + navigation: + title: "导航" + navigation_bar: + title: "导航栏" + navigation_stacked: + title: "导航已折叠" + categories_list: + title: "分类列表" + topic_link: + title: "主题链接" + topic_list_item: + title: "主题列表项" + topic_statuses: + title: "主题状态" + topic_list: + title: "主题列表" + latest_topic_list: + title: "最新主题列表" + footer_message: + title: "页脚消息" + signup_cta: + title: "注册 CTA" + topic_timer_info: + title: "主题计时器" + topic_footer_buttons: + title: "主题页脚按钮" + topic_notifications: + title: "主题通知" + post: + title: "帖子" + topic_map: + title: "主题地图" + suggested_topics: + title: "推荐主题" + post_menu: + title: "帖子菜单" + modal: + title: "模式" + header: "模式标题" + footer: "模式页脚" + user_about: + title: "用户关于框" + header_icons: + title: "标头图标" + spinners: + title: "下拉列表" diff --git a/plugins/styleguide/config/locales/client.zh_TW.yml b/plugins/styleguide/config/locales/client.zh_TW.yml new file mode 100644 index 0000000000..831b6f16fb --- /dev/null +++ b/plugins/styleguide/config/locales/client.zh_TW.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +zh_TW: diff --git a/plugins/styleguide/config/locales/server.ar.yml b/plugins/styleguide/config/locales/server.ar.yml new file mode 100644 index 0000000000..ee84827464 --- /dev/null +++ b/plugins/styleguide/config/locales/server.ar.yml @@ -0,0 +1,9 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ar: + site_settings: + styleguide_admin_only: "يقصر ظهور دليل الأنماط على المشرفين" diff --git a/plugins/styleguide/config/locales/server.be.yml b/plugins/styleguide/config/locales/server.be.yml new file mode 100644 index 0000000000..2e3980a624 --- /dev/null +++ b/plugins/styleguide/config/locales/server.be.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +be: diff --git a/plugins/styleguide/config/locales/server.bg.yml b/plugins/styleguide/config/locales/server.bg.yml new file mode 100644 index 0000000000..6c61d82645 --- /dev/null +++ b/plugins/styleguide/config/locales/server.bg.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +bg: diff --git a/plugins/styleguide/config/locales/server.bs_BA.yml b/plugins/styleguide/config/locales/server.bs_BA.yml new file mode 100644 index 0000000000..509ba581f8 --- /dev/null +++ b/plugins/styleguide/config/locales/server.bs_BA.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +bs_BA: diff --git a/plugins/styleguide/config/locales/server.ca.yml b/plugins/styleguide/config/locales/server.ca.yml new file mode 100644 index 0000000000..3eba30e02a --- /dev/null +++ b/plugins/styleguide/config/locales/server.ca.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ca: diff --git a/plugins/styleguide/config/locales/server.cs.yml b/plugins/styleguide/config/locales/server.cs.yml new file mode 100644 index 0000000000..f3265c8c88 --- /dev/null +++ b/plugins/styleguide/config/locales/server.cs.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +cs: diff --git a/plugins/styleguide/config/locales/server.da.yml b/plugins/styleguide/config/locales/server.da.yml new file mode 100644 index 0000000000..1aa92f22af --- /dev/null +++ b/plugins/styleguide/config/locales/server.da.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +da: diff --git a/plugins/styleguide/config/locales/server.de.yml b/plugins/styleguide/config/locales/server.de.yml new file mode 100644 index 0000000000..9a40fdba4b --- /dev/null +++ b/plugins/styleguide/config/locales/server.de.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +de: diff --git a/plugins/styleguide/config/locales/server.el.yml b/plugins/styleguide/config/locales/server.el.yml new file mode 100644 index 0000000000..1ef87229b3 --- /dev/null +++ b/plugins/styleguide/config/locales/server.el.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +el: diff --git a/plugins/styleguide/config/locales/server.en.yml b/plugins/styleguide/config/locales/server.en.yml new file mode 100644 index 0000000000..f7cdd1bbe1 --- /dev/null +++ b/plugins/styleguide/config/locales/server.en.yml @@ -0,0 +1,4 @@ +en: + site_settings: + styleguide_enabled: "Enable a `/styleguide` path to aid in styling of Discourse" + styleguide_admin_only: "Limits visibility of the styleguide to admins" diff --git a/plugins/styleguide/config/locales/server.es.yml b/plugins/styleguide/config/locales/server.es.yml new file mode 100644 index 0000000000..ad076714d5 --- /dev/null +++ b/plugins/styleguide/config/locales/server.es.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +es: diff --git a/plugins/styleguide/config/locales/server.et.yml b/plugins/styleguide/config/locales/server.et.yml new file mode 100644 index 0000000000..5702d705bb --- /dev/null +++ b/plugins/styleguide/config/locales/server.et.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +et: diff --git a/plugins/styleguide/config/locales/server.fa_IR.yml b/plugins/styleguide/config/locales/server.fa_IR.yml new file mode 100644 index 0000000000..edcb1f27e2 --- /dev/null +++ b/plugins/styleguide/config/locales/server.fa_IR.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +fa_IR: diff --git a/plugins/styleguide/config/locales/server.fi.yml b/plugins/styleguide/config/locales/server.fi.yml new file mode 100644 index 0000000000..b10a8718e0 --- /dev/null +++ b/plugins/styleguide/config/locales/server.fi.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +fi: diff --git a/plugins/styleguide/config/locales/server.fr.yml b/plugins/styleguide/config/locales/server.fr.yml new file mode 100644 index 0000000000..82a752c15d --- /dev/null +++ b/plugins/styleguide/config/locales/server.fr.yml @@ -0,0 +1,9 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +fr: + site_settings: + styleguide_admin_only: "Limiter la visibilité du guide de style aux administrateurs" diff --git a/plugins/styleguide/config/locales/server.gl.yml b/plugins/styleguide/config/locales/server.gl.yml new file mode 100644 index 0000000000..0b8e230d47 --- /dev/null +++ b/plugins/styleguide/config/locales/server.gl.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +gl: diff --git a/plugins/styleguide/config/locales/server.he.yml b/plugins/styleguide/config/locales/server.he.yml new file mode 100644 index 0000000000..f123c02b6e --- /dev/null +++ b/plugins/styleguide/config/locales/server.he.yml @@ -0,0 +1,10 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +he: + site_settings: + styleguide_enabled: "להפעיל נתיב `‎/styleguide` כדי לסייע בעיצוב של Discourse" + styleguide_admin_only: "מגביל את חשיפת מדריך הסגנון למנהלים" diff --git a/plugins/styleguide/config/locales/server.hu.yml b/plugins/styleguide/config/locales/server.hu.yml new file mode 100644 index 0000000000..0a2b3ed09f --- /dev/null +++ b/plugins/styleguide/config/locales/server.hu.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hu: diff --git a/plugins/styleguide/config/locales/server.hy.yml b/plugins/styleguide/config/locales/server.hy.yml new file mode 100644 index 0000000000..ba5e8380d2 --- /dev/null +++ b/plugins/styleguide/config/locales/server.hy.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hy: diff --git a/plugins/styleguide/config/locales/server.id.yml b/plugins/styleguide/config/locales/server.id.yml new file mode 100644 index 0000000000..a4d1527157 --- /dev/null +++ b/plugins/styleguide/config/locales/server.id.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +id: diff --git a/plugins/styleguide/config/locales/server.it.yml b/plugins/styleguide/config/locales/server.it.yml new file mode 100644 index 0000000000..bf1761f628 --- /dev/null +++ b/plugins/styleguide/config/locales/server.it.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +it: diff --git a/plugins/styleguide/config/locales/server.ja.yml b/plugins/styleguide/config/locales/server.ja.yml new file mode 100644 index 0000000000..1d8afc616b --- /dev/null +++ b/plugins/styleguide/config/locales/server.ja.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ja: diff --git a/plugins/styleguide/config/locales/server.ko.yml b/plugins/styleguide/config/locales/server.ko.yml new file mode 100644 index 0000000000..5e44c122b1 --- /dev/null +++ b/plugins/styleguide/config/locales/server.ko.yml @@ -0,0 +1,10 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ko: + site_settings: + styleguide_enabled: "Discourse 스타일링에 도움이되는`/ styleguide` 경로 활성화" + styleguide_admin_only: "스타일 가이드의 접근을 관리자로 제한합니다." diff --git a/plugins/styleguide/config/locales/server.lt.yml b/plugins/styleguide/config/locales/server.lt.yml new file mode 100644 index 0000000000..1a57c12695 --- /dev/null +++ b/plugins/styleguide/config/locales/server.lt.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +lt: diff --git a/plugins/styleguide/config/locales/server.lv.yml b/plugins/styleguide/config/locales/server.lv.yml new file mode 100644 index 0000000000..5b0066d4cb --- /dev/null +++ b/plugins/styleguide/config/locales/server.lv.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +lv: diff --git a/plugins/styleguide/config/locales/server.nb_NO.yml b/plugins/styleguide/config/locales/server.nb_NO.yml new file mode 100644 index 0000000000..e01d66c96b --- /dev/null +++ b/plugins/styleguide/config/locales/server.nb_NO.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +nb_NO: diff --git a/plugins/styleguide/config/locales/server.nl.yml b/plugins/styleguide/config/locales/server.nl.yml new file mode 100644 index 0000000000..0586c43a02 --- /dev/null +++ b/plugins/styleguide/config/locales/server.nl.yml @@ -0,0 +1,10 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +nl: + site_settings: + styleguide_enabled: "Een '/styleguide'-pad voor hulp bij het vormgeven van Discourse inschakelen" + styleguide_admin_only: "Beperkt de zichtbaarheid van de stijlgids tot beheerders" diff --git a/plugins/styleguide/config/locales/server.pl_PL.yml b/plugins/styleguide/config/locales/server.pl_PL.yml new file mode 100644 index 0000000000..fb27e29001 --- /dev/null +++ b/plugins/styleguide/config/locales/server.pl_PL.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +pl_PL: diff --git a/plugins/styleguide/config/locales/server.pt.yml b/plugins/styleguide/config/locales/server.pt.yml new file mode 100644 index 0000000000..d064f4d964 --- /dev/null +++ b/plugins/styleguide/config/locales/server.pt.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +pt: diff --git a/plugins/styleguide/config/locales/server.pt_BR.yml b/plugins/styleguide/config/locales/server.pt_BR.yml new file mode 100644 index 0000000000..61e4d44129 --- /dev/null +++ b/plugins/styleguide/config/locales/server.pt_BR.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +pt_BR: diff --git a/plugins/styleguide/config/locales/server.ro.yml b/plugins/styleguide/config/locales/server.ro.yml new file mode 100644 index 0000000000..4837bb27c6 --- /dev/null +++ b/plugins/styleguide/config/locales/server.ro.yml @@ -0,0 +1,10 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ro: + site_settings: + styleguide_enabled: "Activează o cale `/styleguide pentru a ajuta la personalizarea Discourse" + styleguide_admin_only: "Limitează vizibilitatea ghidului de stil la administratori" diff --git a/plugins/styleguide/config/locales/server.ru.yml b/plugins/styleguide/config/locales/server.ru.yml new file mode 100644 index 0000000000..9be6c0be5c --- /dev/null +++ b/plugins/styleguide/config/locales/server.ru.yml @@ -0,0 +1,10 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ru: + site_settings: + styleguide_enabled: "Включить путь `/ styleguide` для упрощения применения стилей в Discourse" + styleguide_admin_only: "Ограничить видимость styleguide только администраторами" diff --git a/plugins/styleguide/config/locales/server.sk.yml b/plugins/styleguide/config/locales/server.sk.yml new file mode 100644 index 0000000000..143b5bf2d6 --- /dev/null +++ b/plugins/styleguide/config/locales/server.sk.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sk: diff --git a/plugins/styleguide/config/locales/server.sl.yml b/plugins/styleguide/config/locales/server.sl.yml new file mode 100644 index 0000000000..8e51613b8f --- /dev/null +++ b/plugins/styleguide/config/locales/server.sl.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sl: diff --git a/plugins/styleguide/config/locales/server.sq.yml b/plugins/styleguide/config/locales/server.sq.yml new file mode 100644 index 0000000000..1413494f57 --- /dev/null +++ b/plugins/styleguide/config/locales/server.sq.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sq: diff --git a/plugins/styleguide/config/locales/server.sr.yml b/plugins/styleguide/config/locales/server.sr.yml new file mode 100644 index 0000000000..adfafeee68 --- /dev/null +++ b/plugins/styleguide/config/locales/server.sr.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sr: diff --git a/plugins/styleguide/config/locales/server.sv.yml b/plugins/styleguide/config/locales/server.sv.yml new file mode 100644 index 0000000000..13ef3be4b1 --- /dev/null +++ b/plugins/styleguide/config/locales/server.sv.yml @@ -0,0 +1,10 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sv: + site_settings: + styleguide_enabled: "Aktivera en `/styleguide`-sökväg för att underlätta Discourse-stilar" + styleguide_admin_only: "Begränsar stilguidens synlighet till administratörer" diff --git a/plugins/styleguide/config/locales/server.sw.yml b/plugins/styleguide/config/locales/server.sw.yml new file mode 100644 index 0000000000..7e07c4560a --- /dev/null +++ b/plugins/styleguide/config/locales/server.sw.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +sw: diff --git a/plugins/styleguide/config/locales/server.te.yml b/plugins/styleguide/config/locales/server.te.yml new file mode 100644 index 0000000000..ac5d945ae8 --- /dev/null +++ b/plugins/styleguide/config/locales/server.te.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +te: diff --git a/plugins/styleguide/config/locales/server.th.yml b/plugins/styleguide/config/locales/server.th.yml new file mode 100644 index 0000000000..9b38ad300e --- /dev/null +++ b/plugins/styleguide/config/locales/server.th.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +th: diff --git a/plugins/styleguide/config/locales/server.tr_TR.yml b/plugins/styleguide/config/locales/server.tr_TR.yml new file mode 100644 index 0000000000..c32afd624c --- /dev/null +++ b/plugins/styleguide/config/locales/server.tr_TR.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +tr_TR: diff --git a/plugins/styleguide/config/locales/server.uk.yml b/plugins/styleguide/config/locales/server.uk.yml new file mode 100644 index 0000000000..602f40aeed --- /dev/null +++ b/plugins/styleguide/config/locales/server.uk.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +uk: diff --git a/plugins/styleguide/config/locales/server.ur.yml b/plugins/styleguide/config/locales/server.ur.yml new file mode 100644 index 0000000000..1e9587a363 --- /dev/null +++ b/plugins/styleguide/config/locales/server.ur.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +ur: diff --git a/plugins/styleguide/config/locales/server.vi.yml b/plugins/styleguide/config/locales/server.vi.yml new file mode 100644 index 0000000000..bddd73d4e8 --- /dev/null +++ b/plugins/styleguide/config/locales/server.vi.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +vi: diff --git a/plugins/styleguide/config/locales/server.zh_CN.yml b/plugins/styleguide/config/locales/server.zh_CN.yml new file mode 100644 index 0000000000..2af6852037 --- /dev/null +++ b/plugins/styleguide/config/locales/server.zh_CN.yml @@ -0,0 +1,10 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +zh_CN: + site_settings: + styleguide_enabled: "启用 `/styleguide` 路径来帮助 Discourse 风格化" + styleguide_admin_only: "样式指南仅对管理员可见" diff --git a/plugins/styleguide/config/locales/server.zh_TW.yml b/plugins/styleguide/config/locales/server.zh_TW.yml new file mode 100644 index 0000000000..831b6f16fb --- /dev/null +++ b/plugins/styleguide/config/locales/server.zh_TW.yml @@ -0,0 +1,7 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +zh_TW: diff --git a/plugins/styleguide/config/routes.rb b/plugins/styleguide/config/routes.rb new file mode 100644 index 0000000000..57efad0014 --- /dev/null +++ b/plugins/styleguide/config/routes.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +Styleguide::Engine.routes.draw do + get "/" => 'styleguide#index' + get "/:category/:section" => 'styleguide#index' +end diff --git a/plugins/styleguide/config/settings.yml b/plugins/styleguide/config/settings.yml new file mode 100644 index 0000000000..ee937dd3a5 --- /dev/null +++ b/plugins/styleguide/config/settings.yml @@ -0,0 +1,5 @@ +plugins: + styleguide_enabled: + default: false + styleguide_admin_only: + default: true diff --git a/plugins/styleguide/lib/styleguide/engine.rb b/plugins/styleguide/lib/styleguide/engine.rb new file mode 100644 index 0000000000..66bd0d982b --- /dev/null +++ b/plugins/styleguide/lib/styleguide/engine.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ::Styleguide + PLUGIN_NAME = "styleguide" + + class Engine < ::Rails::Engine + engine_name Styleguide::PLUGIN_NAME + isolate_namespace Styleguide + end +end diff --git a/plugins/styleguide/plugin.rb b/plugins/styleguide/plugin.rb new file mode 100644 index 0000000000..2e0703359c --- /dev/null +++ b/plugins/styleguide/plugin.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# name: styleguide +# about: Preview how Widgets are Styled in Discourse +# version: 0.2 +# author: Robin Ward + +register_asset "stylesheets/styleguide.scss" +enabled_site_setting :styleguide_enabled + +load File.expand_path('../lib/styleguide/engine.rb', __FILE__) + +Discourse::Application.routes.append do + mount ::Styleguide::Engine, at: '/styleguide' +end + +after_initialize do + register_asset_filter do |type, request| + request&.fullpath&.start_with?('/styleguide') + end +end diff --git a/plugins/styleguide/public/images/hubble-orion-nebula-bg.jpg b/plugins/styleguide/public/images/hubble-orion-nebula-bg.jpg new file mode 100644 index 0000000000..ec29045043 Binary files /dev/null and b/plugins/styleguide/public/images/hubble-orion-nebula-bg.jpg differ diff --git a/plugins/styleguide/screenshot.png b/plugins/styleguide/screenshot.png new file mode 100644 index 0000000000..9393297db5 Binary files /dev/null and b/plugins/styleguide/screenshot.png differ diff --git a/plugins/styleguide/spec/integration/access_spec.rb b/plugins/styleguide/spec/integration/access_spec.rb new file mode 100644 index 0000000000..7836ddaadb --- /dev/null +++ b/plugins/styleguide/spec/integration/access_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'SiteSetting.styleguide_admin_only' do + before do + SiteSetting.styleguide_enabled = true + end + + context 'styleguide is admin only' do + before do + SiteSetting.styleguide_admin_only = true + end + + context 'user is admin' do + before do + sign_in(Fabricate(:admin)) + end + + it 'shows the styleguide' do + get '/styleguide' + expect(response.status).to eq(200) + end + end + + context 'user is not admin' do + before do + sign_in(Fabricate(:user)) + end + + it 'doesn’t allow access' do + get '/styleguide' + expect(response.status).to eq(403) + end + end + end +end + +describe 'SiteSetting.styleguide_enabled' do + before do + sign_in(Fabricate(:admin)) + end + + context 'style is enabled' do + before do + SiteSetting.styleguide_enabled = true + end + + it 'shows the styleguide' do + get '/styleguide' + expect(response.status).to eq(200) + end + end + + context 'styleguide is disabled' do + before do + SiteSetting.styleguide_enabled = false + end + + it 'returns a page not found' do + get '/styleguide' + expect(response.status).to eq(404) + end + end +end diff --git a/plugins/styleguide/spec/integration/assets_spec.rb b/plugins/styleguide/spec/integration/assets_spec.rb new file mode 100644 index 0000000000..562730b7dd --- /dev/null +++ b/plugins/styleguide/spec/integration/assets_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Styleguide assets' do + before do + SiteSetting.styleguide_enabled = true + sign_in(Fabricate(:admin)) + end + + context 'visits homepage' do + it 'doesn’t load styleguide assets' do + get '/' + expect(response.body).to_not include('styleguide') + end + end + + context 'visits styleguide' do + it 'loads styleguide assets' do + get '/styleguide' + expect(response.body).to include('styleguide') + end + end +end diff --git a/public/403.ar.html b/public/403.ar.html index eb7a243f91..1f7fac59c0 100644 --- a/public/403.ar.html +++ b/public/403.ar.html @@ -1,7 +1,7 @@ - لا يمكنك فعل ذلك (403) + لا يمكنك فعل هذا الأمر (403) " -); - -App.rootElement = "#ember-testing"; -App.setupForTesting(); -App.injectTestHelpers(); -App.start(); - -// disable logster error reporting -if (window.Logster) { - Logster.enabled = false; -} else { - window.Logster = { enabled: false }; -} - -var createPretender = require("helpers/create-pretender", null, null, false), - fixtures = require("fixtures/site-fixtures", null, null, false).default, - flushMap = require("discourse/models/store", null, null, false).flushMap, - ScrollingDOMMethods = require("discourse/mixins/scrolling", null, null, false) - .ScrollingDOMMethods, - _DiscourseURL = require("discourse/lib/url", null, null, false).default, - applyPretender = require("helpers/qunit-helpers", null, null, false) - .applyPretender, - getOwner = require("discourse-common/lib/get-owner").getOwner, - setDefaultOwner = require("discourse-common/lib/get-owner").setDefaultOwner, - server, - acceptanceModulePrefix = "Acceptance: "; - -function dup(obj) { - return jQuery.extend(true, {}, obj); -} - -function resetSite(siteSettings, extras) { - let createStore = require("helpers/create-store").default; - let siteAttrs = $.extend({}, fixtures["site.json"].site, extras || {}); - let Site = require("discourse/models/site").default; - siteAttrs.store = createStore(); - siteAttrs.siteSettings = siteSettings; - return Site.resetCurrent(Site.create(siteAttrs)); -} - -QUnit.testStart(function(ctx) { - let settings = resetSettings(); - server = createPretender.default; - createPretender.applyDefaultHandlers(server); - server.handlers = []; - - server.prepareBody = function(body) { - if (body && typeof body === "object") { - return JSON.stringify(body); - } - return body; - }; - - if (QUnit.config.logAllRequests) { - server.handledRequest = function(verb, path, request) { - console.log("REQ: " + verb + " " + path); - }; - } - - server.unhandledRequest = function(verb, path) { - if (QUnit.config.logAllRequests) { - console.log("REQ: " + verb + " " + path + " missing"); - } - - const error = - "Unhandled request in test environment: " + path + " (" + verb + ")"; - - window.console.error(error); - throw new Error(error); - }; - - server.checkPassthrough = request => - request.requestHeaders["Discourse-Script"]; - - if (ctx.module.startsWith(acceptanceModulePrefix)) { - var helper = { - parsePostData: createPretender.parsePostData, - response: createPretender.response, - success: createPretender.success - }; - - applyPretender( - ctx.module.replace(acceptanceModulePrefix, ""), - server, - helper - ); - } - - let getURL = require("discourse-common/lib/get-url"); - getURL.setupURL(null, "http://localhost:3000", ""); - getURL.setupS3CDN(null, null); - - let User = require("discourse/models/user").default; - let Session = require("discourse/models/session").default; - Session.resetCurrent(); - User.resetCurrent(); - let site = resetSite(settings); - createHelperContext({ - siteSettings: settings, - capabilities: {}, - site - }); - - _DiscourseURL.redirectedTo = null; - _DiscourseURL.redirectTo = function(url) { - _DiscourseURL.redirectedTo = url; - }; - - var ps = require("discourse/lib/preload-store").default; - ps.reset(); - - window.sandbox = sinon; - window.sandbox.stub(ScrollingDOMMethods, "screenNotFull"); - window.sandbox.stub(ScrollingDOMMethods, "bindOnScroll"); - window.sandbox.stub(ScrollingDOMMethods, "unbindOnScroll"); - - // Unless we ever need to test this, let's leave it off. - $.fn.autocomplete = function() {}; -}); - -QUnit.testDone(function() { - window.sandbox.restore(); - - // Destroy any modals - $(".modal-backdrop").remove(); - flushMap(); - - // ensures any event not removed is not leaking between tests - // most likely in intialisers, other places (controller, component...) - // should be fixed in code - require("discourse/services/app-events").clearAppEventsCache(getOwner(this)); - - MessageBus.unsubscribe("*"); - delete window.server; - window.Mousetrap.reset(); -}); - -// Load ES6 tests -var helpers = require("helpers/qunit-helpers"); - -function getUrlParameter(name) { - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"); - var results = regex.exec(location.search); - return results === null - ? "" - : decodeURIComponent(results[1].replace(/\+/g, " ")); -} - -var skipCore = getUrlParameter("qunit_skip_core") == "1"; -var pluginPath = getUrlParameter("qunit_single_plugin") - ? "/" + getUrlParameter("qunit_single_plugin") + "/" - : "/plugins/"; - -Object.keys(requirejs.entries).forEach(function(entry) { - var isTest = /\-test/.test(entry); - var regex = new RegExp(pluginPath); - var isPlugin = regex.test(entry); - - if (isTest && (!skipCore || isPlugin)) { - require(entry, null, null, true); - } -}); - -// forces 0 as duration for all jquery animations -jQuery.fx.off = true; -setDefaultOwner(App.__container__); -resetSite(); diff --git a/translator.yml b/translator.yml index d8e0c80c62..ffb695fc0d 100644 --- a/translator.yml +++ b/translator.yml @@ -42,3 +42,8 @@ files: destination_path: plugins/poll/client.yml - source_path: plugins/poll/config/locales/server.en.yml destination_path: plugins/poll/server.yml + + - source_path: plugins/styleguide/config/locales/client.en.yml + destination_path: plugins/styleguide/client.yml + - source_path: plugins/styleguide/config/locales/server.en.yml + destination_path: plugins/styleguide/server.yml diff --git a/vendor/assets/javascripts/browser-update.js.erb b/vendor/assets/javascripts/browser-update.js.erb index be54f3bc0f..eedfc71302 100644 --- a/vendor/assets/javascripts/browser-update.js.erb +++ b/vendor/assets/javascripts/browser-update.js.erb @@ -18,12 +18,19 @@ var $buo = function() { return; } - document.getElementsByTagName('body')[0].classList.add("crawler"); + document.getElementsByTagName('body')[0].className += " crawler"; var mainElement = document.getElementById("main"); var noscriptElements = document.getElementsByTagName("noscript"); - // noscriptElements[0].innerHTML contains encoded HTML - var innerHTML = noscriptElements[0].childNodes[0].nodeValue; - mainElement.innerHTML = innerHTML; + // find the element with the "data-path" attribute set + for (var i = 0; i < noscriptElements.length; ++i) { + if (noscriptElements[i].getAttribute("data-path")) { + // noscriptElements[i].innerHTML contains encoded HTML + if (noscriptElements[i].childNodes.length > 0) { + mainElement.innerHTML = noscriptElements[i].childNodes[0].nodeValue; + break; + } + } + } // retrieve localized browser upgrade text var t = <%= "'" + I18n.t('js.browser_update') + "'" %>;