diff --git a/.gitattributes b/.gitattributes index ae7015b472..546b134a0c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,11 +1,14 @@ # Set default behaviour, in case users don't have core.autocrlf set. * text=auto -# Explicitly declare text files we want to always be normalized and converted +# Treat email fixtures as binary files so CRLF are not converted to LF. +*.eml binary + +# Explicitly declare text files we want to always be normalized and converted # to native line endings on checkout. *.yml text -# Custom for Visual Studio, very unlikely, but lets keep it +# Custom for Visual Studio, very unlikely, but lets keep it *.cs diff=csharp *.sln merge=union *.csproj merge=union diff --git a/Brewfile b/Brewfile index ba4f913899..89595137b6 100644 --- a/Brewfile +++ b/Brewfile @@ -1,11 +1,5 @@ # Install development dependencies on Mac OS X using Homebrew (http://mxcl.github.com/homebrew) -# add this repo to Homebrew's sources -tap 'homebrew/dupes' - -# install the gcc compiler required for ruby -brew 'apple-gcc42' - # you probably already have git installed; ensure that it is the latest version brew 'git' diff --git a/Gemfile b/Gemfile index cf25046564..d7ed141f27 100644 --- a/Gemfile +++ b/Gemfile @@ -2,8 +2,7 @@ source 'https://rubygems.org' # if there is a super emergency and rubygems is playing up, try #source 'http://production.cf.rubygems.org' -# does not install in linux ATM, so hack this for now -gem 'bootsnap', require: false +gem 'bootsnap', require: false, platform: :mri def rails_master? ENV["RAILS_MASTER"] == '1' @@ -36,7 +35,7 @@ gem 'redis-namespace' gem 'active_model_serializers', '~> 0.8.3' -gem 'onebox', '1.8.38' +gem 'onebox', '1.8.42' gem 'http_accept_language', '~>2.0.5', require: false @@ -49,9 +48,10 @@ gem 'message_bus' gem 'rails_multisite' -gem 'fast_xs' +gem 'fast_xs', platform: :mri -gem 'fast_xor' +# may move to xorcist post: https://github.com/fny/xorcist/issues/4 +gem 'fast_xor', platform: :mri gem 'fastimage' @@ -141,7 +141,7 @@ end # this is an optional gem, it provides a high performance replacement # to String#blank? a method that is called quite frequently in current # ActiveRecord, this may change in the future -gem 'fast_blank' +gem 'fast_blank', platform: :mri # this provides a very efficient lru cache gem 'lru_redux' @@ -155,7 +155,7 @@ gem 'htmlentities', require: false gem 'flamegraph', require: false gem 'rack-mini-profiler', require: false -gem 'unicorn', require: false +gem 'unicorn', require: false, platform: :mri gem 'puma', require: false gem 'rbtrace', require: false, platform: :mri gem 'gc_tracer', require: false, platform: :mri @@ -175,9 +175,13 @@ gem 'logster' gem 'sassc', require: false +gem 'rotp' +gem 'rqrcode' + if ENV["IMPORT"] == "1" gem 'mysql2' gem 'redcarpet' gem 'sqlite3', '~> 1.3.13' gem 'ruby-bbcode-to-md', github: 'nlalonde/ruby-bbcode-to-md' + gem 'reverse_markdown' end diff --git a/Gemfile.lock b/Gemfile.lock index 09d9ff3bb3..1b81cc2495 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -63,9 +63,9 @@ GEM coderay (>= 1.0.0) erubis (>= 2.6.6) rack (>= 0.9.0) - binding_of_caller (0.7.2) + binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.0.0) + bootsnap (1.1.8) msgpack (~> 1.0) builder (3.2.3) bullet (5.5.1) @@ -73,13 +73,14 @@ GEM uniform_notifier (~> 1.10.0) byebug (9.0.6) certified (1.0.0) + chunky_png (1.3.8) coderay (1.1.2) concurrent-ruby (1.0.5) connection_pool (2.2.1) cppjieba_rb (0.3.0) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.2) + crass (1.0.3) debug_inspector (0.0.3) diff-lcs (1.3) discourse-qunit-rails (0.0.11) @@ -183,14 +184,14 @@ GEM metaclass (~> 0.0.1) mock_redis (0.17.3) moneta (1.0.0) - msgpack (1.1.0) - multi_json (1.12.1) + msgpack (1.2.4) + multi_json (1.13.1) multi_xml (0.6.0) multipart-post (2.0.0) mustache (1.0.5) nokogiri (1.8.2) mini_portile2 (~> 2.3.0) - nokogumbo (1.4.13) + nokogumbo (1.5.0) nokogiri oauth (0.5.1) oauth2 (1.3.1) @@ -199,7 +200,7 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (3.1.0) + oj (3.4.0) omniauth (1.6.1) hashie (>= 3.4.6, < 3.6.0) rack (>= 1.6.2, < 3) @@ -228,8 +229,7 @@ GEM omniauth-twitter (1.3.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.38) - fast_blank (>= 1.0.0) + onebox (1.8.42) htmlentities (~> 4.3) moneta (~> 1.0) multi_json (~> 1.11) @@ -298,6 +298,9 @@ GEM redis (~> 3.0, >= 3.0.4) request_store (1.3.2) rinku (2.0.2) + rotp (3.3.0) + rqrcode (0.10.1) + chunky_png (~> 1.0) rspec (3.6.0) rspec-core (~> 3.6.0) rspec-expectations (~> 3.6.0) @@ -338,10 +341,10 @@ GEM nokogiri (>= 1.6.0) ruby_dep (1.5.0) safe_yaml (1.0.4) - sanitize (4.5.0) + sanitize (4.6.0) crass (~> 1.0.2) nokogiri (>= 1.4.4) - nokogumbo (~> 1.4.1) + nokogumbo (~> 1.4) sass (3.4.24) sassc (1.11.2) bundler @@ -461,7 +464,7 @@ DEPENDENCIES omniauth-oauth2 omniauth-openid omniauth-twitter - onebox (= 1.8.38) + onebox (= 1.8.42) openid-redis-store pg (~> 0.21.0) pry-nav @@ -479,6 +482,8 @@ DEPENDENCIES redis redis-namespace rinku + rotp + rqrcode rspec rspec-html-matchers rspec-rails diff --git a/README.md b/README.md index 9a4c6f7595..1069b7ee84 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ Plus *lots* of Ruby Gems, a complete list of which is at [/master/Gemfile](https ## Contributing [![Build Status](https://api.travis-ci.org/discourse/discourse.svg?branch=master)](https://travis-ci.org/discourse/discourse) -[![Code Climate](https://codeclimate.com/github/discourse/discourse.svg)](https://codeclimate.com/github/discourse/discourse) Discourse is **100% free** and **open source**. We encourage and support an active, healthy community that accepts contributions from the public – including you! diff --git a/app/assets/javascripts/admin/components/site-setting.js.es6 b/app/assets/javascripts/admin/components/site-setting.js.es6 index 73e8160a28..98bdf950f6 100644 --- a/app/assets/javascripts/admin/components/site-setting.js.es6 +++ b/app/assets/javascripts/admin/components/site-setting.js.es6 @@ -1,96 +1,10 @@ import BufferedContent from 'discourse/mixins/buffered-content'; import SiteSetting from 'admin/models/site-setting'; -import { propertyNotEqual } from 'discourse/lib/computed'; -import computed from 'ember-addons/ember-computed-decorators'; -import { categoryLinkHTML } from 'discourse/helpers/category-link'; - -const CustomTypes = ['bool', 'enum', 'list', 'url_list', 'host_list', 'category_list', 'value_list']; - -export default Ember.Component.extend(BufferedContent, { - classNameBindings: [':row', ':setting', 'setting.overridden', 'typeClass'], - content: Ember.computed.alias('setting'), - dirty: propertyNotEqual('buffered.value', 'setting.value'), - validationMessage: null, - - @computed("setting", "buffered.value") - preview(setting, value) { - // A bit hacky, but allows us to use helpers - if (setting.get('setting') === 'category_style') { - let category = this.site.get('categories.firstObject'); - if (category) { - return categoryLinkHTML(category, { - categoryStyle: value - }); - } - } - - let preview = setting.get('preview'); - if (preview) { - return new Handlebars.SafeString("
" + preview.replace(/\{\{value\}\}/g, value) + "
"); - } - }, - - @computed('componentType') - typeClass(componentType) { - return componentType.replace(/\_/g, '-'); - }, - - @computed("setting.setting") - settingName(setting) { - return setting.replace(/\_/g, ' '); - }, - - @computed("setting.type") - componentType(type) { - return CustomTypes.indexOf(type) !== -1 ? type : 'string'; - }, - - @computed("typeClass") - componentName(typeClass) { - return "site-settings/" + typeClass; - }, - - _watchEnterKey: function() { - const self = this; - this.$().on("keydown.site-setting-enter", ".input-setting-string", function (e) { - if (e.keyCode === 13) { // enter key - self._save(); - } - }); - }.on('didInsertElement'), - - _removeBindings: function() { - this.$().off("keydown.site-setting-enter"); - }.on("willDestroyElement"), +import SettingComponent from 'admin/mixins/setting-component'; +export default Ember.Component.extend(BufferedContent, SettingComponent, { _save() { - const setting = this.get('buffered'), - action = SiteSetting.update(setting.get('setting'), setting.get('value')); - action.then(() => { - this.set('validationMessage', null); - this.commitBuffer(); - }).catch((e) => { - if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { - this.set('validationMessage', e.jqXHR.responseJSON.errors[0]); - } else { - this.set('validationMessage', I18n.t('generic_error')); - } - }); - }, - - actions: { - save() { - this._save(); - }, - - resetDefault() { - this.set('buffered.value', this.get('setting.default')); - this._save(); - }, - - cancel() { - this.rollbackBuffer(); - } + const setting = this.get('buffered'); + return SiteSetting.update(setting.get('setting'), setting.get('value')); } - }); diff --git a/app/assets/javascripts/admin/components/site-settings/bool.js.es6 b/app/assets/javascripts/admin/components/site-settings/bool.js.es6 index 40fcfb354b..6be1a14e27 100644 --- a/app/assets/javascripts/admin/components/site-settings/bool.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/bool.js.es6 @@ -6,7 +6,7 @@ export default Ember.Component.extend({ enabled: { get(value) { if (Ember.isEmpty(value)) { return false; } - return value === "true"; + return value.toString() === "true"; }, set(value) { this.set("value", value ? "true" : "false"); diff --git a/app/assets/javascripts/admin/components/theme-setting.js.es6 b/app/assets/javascripts/admin/components/theme-setting.js.es6 new file mode 100644 index 0000000000..eb576e9b64 --- /dev/null +++ b/app/assets/javascripts/admin/components/theme-setting.js.es6 @@ -0,0 +1,9 @@ +import BufferedContent from 'discourse/mixins/buffered-content'; +import SettingComponent from 'admin/mixins/setting-component'; + +export default Ember.Component.extend(BufferedContent, SettingComponent, { + layoutName: 'admin/templates/components/site-setting', + _save() { + return this.get('model').saveSettings(this.get('setting.setting'), this.get('buffered.value')); + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 index fb91322edf..c4407b53d4 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 @@ -6,11 +6,22 @@ export default Ember.Controller.extend({ section: null, targets: [ - {id: 0, name: I18n.t('admin.customize.theme.common')}, - {id: 1, name: I18n.t('admin.customize.theme.desktop')}, - {id: 2, name: I18n.t('admin.customize.theme.mobile')} + { id: 0, name: 'common' }, + { id: 1, name: 'desktop' }, + { id: 2, name: 'mobile' }, + { id: 3, name: 'settings' } ], + fieldsForTarget: function (target) { + const common = ["scss", "head_tag", "header", "after_header", "body_tag", "footer"]; + switch(target) { + case "common": return [...common, "embedded_scss"]; + case "desktop": return common; + case "mobile": return common; + case "settings": return ["yaml"]; + } + }, + @computed('onlyOverridden') showCommon() { return this.shouldShow('common'); @@ -26,6 +37,11 @@ export default Ember.Controller.extend({ return this.shouldShow('mobile'); }, + @computed('onlyOverridden', 'model.remote_theme') + showSettings() { + return this.shouldShow('settings') && !this.get('model.remote_theme'); + }, + @observes('onlyOverridden') onlyOverriddenChanged() { if (this.get('onlyOverridden')) { @@ -51,27 +67,19 @@ export default Ember.Controller.extend({ currentTarget: 0, setTargetName: function(name) { - let target; - switch(name) { - case "common": target = 0; break; - case "desktop": target = 1; break; - case "mobile": target = 2; break; - } - - this.set("currentTarget", target); + const target = this.get('targets').find(t => t.name === name); + this.set("currentTarget", target && target.id); }, @computed("currentTarget") - currentTargetName(target) { - switch(parseInt(target)) { - case 0: return "common"; - case 1: return "desktop"; - case 2: return "mobile"; - } + currentTargetName(id) { + const target = this.get('targets').find(t => t.id === parseInt(id, 10)); + return target && target.name; }, @computed("fieldName") activeSectionMode(fieldName) { + if (fieldName === "yaml") return "yaml"; return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html"; }, @@ -96,15 +104,9 @@ export default Ember.Controller.extend({ } }, - @computed("currentTarget", "onlyOverridden") + @computed("currentTargetName", "onlyOverridden") fields(target, onlyOverridden) { - let fields = [ - "scss", "head_tag", "header", "after_header", "body_tag", "footer" - ]; - - if (parseInt(target) === 0) { - fields.push("embedded_scss"); - } + let fields = this.fieldsForTarget(target); if (onlyOverridden) { const model = this.get("model"); @@ -155,5 +157,4 @@ export default Ember.Controller.extend({ }); } } - }); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 index 2873f62a9f..b8850b10f8 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 @@ -2,6 +2,7 @@ import { default as computed } from 'ember-addons/ember-computed-decorators'; import { url } from 'discourse/lib/computed'; import { popupAjaxError } from 'discourse/lib/ajax-error'; import showModal from 'discourse/lib/show-modal'; +import ThemeSettings from 'admin/models/theme-settings'; const THEME_UPLOAD_VAR = 2; @@ -30,7 +31,7 @@ export default Ember.Controller.extend({ return text + ": " + localized.join(" , "); } }; - ['common','desktop','mobile'].forEach(target=> { + ['common', 'desktop', 'mobile'].forEach(target => { descriptions.push(description(target)); }); return descriptions.reject(d=>Em.isBlank(d)); @@ -77,6 +78,16 @@ export default Ember.Controller.extend({ return themes; }, + @computed("model.settings") + settings(settings) { + return settings.map(setting => ThemeSettings.create(setting)); + }, + + @computed("settings") + hasSettings(settings) { + return settings.length > 0; + }, + downloadUrl: url('model.id', '/admin/themes/%@'), actions: { diff --git a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 index 41869d976e..b80dd29cec 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 @@ -19,6 +19,11 @@ export default Ember.Controller.extend(CanCheckEmails, { primaryGroupDirty: propertyNotEqual('originalPrimaryGroupId', 'model.primary_group_id'), + canDisableSecondFactor: Ember.computed.and( + 'model.second_factor_enabled', + 'model.can_disable_second_factor' + ), + automaticGroups: function() { return this.get("model.automaticGroups").map((g) => g.name).join(", "); }.property("model.automaticGroups"), @@ -62,7 +67,16 @@ export default Ember.Controller.extend(CanCheckEmails, { silence() { return this.get("model").silence(); }, deleteAllPosts() { return this.get("model").deleteAllPosts(); }, anonymize() { return this.get('model').anonymize(); }, - destroy() { return this.get('model').destroy(); }, + disableSecondFactor() { return this.get('model').disableSecondFactor(); }, + + destroy() { + const postCount = this.get('model.post_count'); + if (postCount <= 5) { + return this.get('model').destroy({ deletePosts: true }); + } else { + return this.get('model').destroy(); + } + }, viewActionLogs() { this.get('adminTools').showActionLogs(this, { diff --git a/app/assets/javascripts/admin/controllers/modals/admin-import-theme.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-import-theme.js.es6 index d59d419ef5..d971a555bc 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-import-theme.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-import-theme.js.es6 @@ -1,12 +1,13 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; import { ajax } from 'discourse/lib/ajax'; -// import computed from 'ember-addons/ember-computed-decorators'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; export default Ember.Controller.extend(ModalFunctionality, { local: Ember.computed.equal('selection', 'local'), remote: Ember.computed.equal('selection', 'remote'), selection: 'local', adminCustomizeThemes: Ember.inject.controller(), + loading: false, actions: { importTheme() { @@ -24,11 +25,12 @@ export default Ember.Controller.extend(ModalFunctionality, { options.data = {remote: this.get('uploadUrl')}; } + this.set('loading', true); ajax('/admin/themes/import', options).then(result=>{ const theme = this.store.createRecord('theme',result.theme); this.get('adminCustomizeThemes').send('addTheme', theme); this.send('closeModal'); - }); + }).catch(popupAjaxError).finally(() => this.set('loading', false)); } } diff --git a/app/assets/javascripts/admin/mixins/setting-component.js.es6 b/app/assets/javascripts/admin/mixins/setting-component.js.es6 new file mode 100644 index 0000000000..a5b85af9a3 --- /dev/null +++ b/app/assets/javascripts/admin/mixins/setting-component.js.es6 @@ -0,0 +1,98 @@ +import computed from 'ember-addons/ember-computed-decorators'; +import { categoryLinkHTML } from 'discourse/helpers/category-link'; + +const CustomTypes = ['bool', 'enum', 'list', 'url_list', 'host_list', 'category_list', 'value_list']; + +export default Ember.Mixin.create({ + classNameBindings: [':row', ':setting', 'setting.overridden', 'typeClass'], + content: Ember.computed.alias('setting'), + validationMessage: null, + + @computed("buffered.value", "setting.value") + dirty(bufferVal, settingVal) { + if (bufferVal === null || bufferVal === undefined) bufferVal = ''; + if (settingVal === null || settingVal === undefined) settingVal = ''; + + return bufferVal.toString() !== settingVal.toString(); + }, + + @computed("setting", "buffered.value") + preview(setting, value) { + // A bit hacky, but allows us to use helpers + if (setting.get('setting') === 'category_style') { + let category = this.site.get('categories.firstObject'); + if (category) { + return categoryLinkHTML(category, { + categoryStyle: value + }); + } + } + + let preview = setting.get('preview'); + if (preview) { + return new Handlebars.SafeString("
" + preview.replace(/\{\{value\}\}/g, value) + "
"); + } + }, + + @computed('componentType') + typeClass(componentType) { + return componentType.replace(/\_/g, '-'); + }, + + @computed("setting.setting") + settingName(setting) { + return setting.replace(/\_/g, ' '); + }, + + @computed("setting.type") + componentType(type) { + return CustomTypes.indexOf(type) !== -1 ? type : 'string'; + }, + + @computed("typeClass") + componentName(typeClass) { + return "site-settings/" + typeClass; + }, + + _watchEnterKey: function() { + const self = this; + this.$().on("keydown.setting-enter", ".input-setting-string", function (e) { + if (e.keyCode === 13) { // enter key + self._save(); + } + }); + }.on('didInsertElement'), + + _removeBindings: function() { + this.$().off("keydown.setting-enter"); + }.on("willDestroyElement"), + + _save() { + Em.warn("You should define a `_save` method", { id: "admin.mixins.setting-component" }); + return Ember.RSVP.resolve(); + }, + + actions: { + save() { + this._save().then(() => { + this.set('validationMessage', null); + this.commitBuffer(); + }).catch(e => { + if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { + this.set('validationMessage', e.jqXHR.responseJSON.errors[0]); + } else { + this.set('validationMessage', I18n.t('generic_error')); + } + }); + }, + + resetDefault() { + this.set('buffered.value', this.get('setting.default')); + this._save(); + }, + + cancel() { + this.rollbackBuffer(); + } + } +}); diff --git a/app/assets/javascripts/admin/mixins/setting-object.js.es6 b/app/assets/javascripts/admin/mixins/setting-object.js.es6 new file mode 100644 index 0000000000..0b16ed5009 --- /dev/null +++ b/app/assets/javascripts/admin/mixins/setting-object.js.es6 @@ -0,0 +1,29 @@ +export default Ember.Mixin.create({ + overridden: function() { + let val = this.get('value'), + defaultVal = this.get('default'); + + if (val === null) val = ''; + if (defaultVal === null) defaultVal = ''; + + return val.toString() !== defaultVal.toString(); + }.property('value', 'default'), + + validValues: function() { + const vals = [], + translateNames = this.get('translate_names'); + + this.get('valid_values').forEach(v => { + if (v.name && v.name.length > 0 && translateNames) { + vals.addObject({ name: I18n.t(v.name), value: v.value }); + } else { + vals.addObject(v); + } + }); + return vals; + }.property('valid_values'), + + allowsNone: function() { + if ( _.indexOf(this.get('valid_values'), '') >= 0 ) return 'admin.settings.none'; + }.property('valid_values') +}); diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6 index e3f1530bad..99aec6aaf7 100644 --- a/app/assets/javascripts/admin/models/admin-user.js.es6 +++ b/app/assets/javascripts/admin/models/admin-user.js.es6 @@ -168,6 +168,14 @@ const AdminUser = Discourse.User.extend({ }).catch(popupAjaxError); }, + disableSecondFactor() { + return ajax(`/admin/users/${this.get('id')}/disable_second_factor`, { + type: 'PUT' + }).then(() => { + this.set('second_factor_enabled', false); + }).catch(popupAjaxError); + }, + refreshBrowsers() { return ajax("/admin/users/" + this.get('id') + "/refresh_browsers", { type: 'POST' diff --git a/app/assets/javascripts/admin/models/site-setting.js.es6 b/app/assets/javascripts/admin/models/site-setting.js.es6 index 6ed7878796..3fc3cf43f5 100644 --- a/app/assets/javascripts/admin/models/site-setting.js.es6 +++ b/app/assets/javascripts/admin/models/site-setting.js.es6 @@ -1,31 +1,7 @@ import { ajax } from 'discourse/lib/ajax'; -const SiteSetting = Discourse.Model.extend({ - overridden: function() { - let val = this.get('value'), - defaultVal = this.get('default'); +import Setting from 'admin/mixins/setting-object'; - if (val === null) val = ''; - if (defaultVal === null) defaultVal = ''; - - return val.toString() !== defaultVal.toString(); - }.property('value', 'default'), - - validValues: function() { - const vals = [], - translateNames = this.get('translate_names'); - - this.get('valid_values').forEach(function(v) { - if (v.name && v.name.length > 0) { - vals.addObject(translateNames ? {name: I18n.t(v.name), value: v.value} : v); - } - }); - return vals; - }.property('valid_values'), - - allowsNone: function() { - if ( _.indexOf(this.get('valid_values'), '') >= 0 ) return 'admin.site_settings.none'; - }.property('valid_values') -}); +const SiteSetting = Discourse.Model.extend(Setting, {}); SiteSetting.reopenClass({ findAll() { diff --git a/app/assets/javascripts/admin/models/theme-settings.js.es6 b/app/assets/javascripts/admin/models/theme-settings.js.es6 new file mode 100644 index 0000000000..b1e824cf49 --- /dev/null +++ b/app/assets/javascripts/admin/models/theme-settings.js.es6 @@ -0,0 +1,3 @@ +import Setting from 'admin/mixins/setting-object'; + +export default Discourse.Model.extend(Setting, {}); diff --git a/app/assets/javascripts/admin/models/theme.js.es6 b/app/assets/javascripts/admin/models/theme.js.es6 index 742d06317f..44e7f2ea24 100644 --- a/app/assets/javascripts/admin/models/theme.js.es6 +++ b/app/assets/javascripts/admin/models/theme.js.es6 @@ -2,6 +2,7 @@ import RestModel from 'discourse/models/rest'; import { default as computed } from 'ember-addons/ember-computed-decorators'; const THEME_UPLOAD_VAR = 2; +const FIELDS_IDS = [0, 1, 5]; const Theme = RestModel.extend({ @@ -14,13 +15,11 @@ const Theme = RestModel.extend({ } let hash = {}; - if (fields) { - fields.forEach(field=>{ - if (!field.type_id || field.type_id < THEME_UPLOAD_VAR) { - hash[this.getKey(field)] = field; - } - }); - } + fields.forEach(field => { + if (!field.type_id || FIELDS_IDS.includes(field.type_id)) { + hash[this.getKey(field)] = field; + } + }); return hash; }, @@ -29,11 +28,11 @@ const Theme = RestModel.extend({ if (!fields) { return []; } - return fields.filter((f)=> f.target === 'common' && f.type_id === THEME_UPLOAD_VAR); + return fields.filter(f => f.target === 'common' && f.type_id === THEME_UPLOAD_VAR); }, getKey(field){ - return field.target + " " + field.name; + return `${field.target} ${field.name}`; }, hasEdited(target, name){ @@ -151,6 +150,11 @@ const Theme = RestModel.extend({ .then(() => this.set("changed", false)); }, + saveSettings(name, value) { + const settings = {}; + settings[name] = value; + return this.save({ settings }); + } }); export default Theme; diff --git a/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6 index c1d3b225ff..d9e7e6249f 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6 @@ -18,6 +18,11 @@ export default Ember.Route.extend({ }, setupController(controller, wrapper) { + const fields = controller.fieldsForTarget(wrapper.target); + if (!fields.includes(wrapper.field_name)) { + this.transitionTo('adminCustomizeThemes.edit', wrapper.model.id, wrapper.target, fields[0]); + return; + } controller.set("model", wrapper.model); controller.setTargetName(wrapper.target || "common"); controller.set("fieldName", wrapper.field_name || "scss"); diff --git a/app/assets/javascripts/admin/templates/components/site-setting.hbs b/app/assets/javascripts/admin/templates/components/site-setting.hbs index 7fd80e4446..e91922f7a7 100644 --- a/app/assets/javascripts/admin/templates/components/site-setting.hbs +++ b/app/assets/javascripts/admin/templates/components/site-setting.hbs @@ -10,5 +10,5 @@ {{d-button class="cancel" action="cancel" icon="times"}} {{else if setting.overridden}} - {{d-button action="resetDefault" icon="undo" label="admin.site_settings.reset"}} + {{d-button action="resetDefault" icon="undo" label="admin.settings.reset"}} {{/if}} diff --git a/app/assets/javascripts/admin/templates/customize-colors-show.hbs b/app/assets/javascripts/admin/templates/customize-colors-show.hbs index e688e13316..08cbef2ddc 100644 --- a/app/assets/javascripts/admin/templates/customize-colors-show.hbs +++ b/app/assets/javascripts/admin/templates/customize-colors-show.hbs @@ -22,7 +22,7 @@ diff --git a/app/assets/javascripts/admin/templates/customize-themes-edit.hbs b/app/assets/javascripts/admin/templates/customize-themes-edit.hbs index 83eebb83d0..c798836b76 100644 --- a/app/assets/javascripts/admin/templates/customize-themes-edit.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes-edit.hbs @@ -3,39 +3,47 @@

{{i18n 'admin.customize.theme.edit_css_html'}} {{#link-to 'adminCustomizeThemes.show' model.id replace=true}}{{model.name}}{{/link-to}}

{{#if error}} -
{{error}}
+
{{error}}
{{/if}}
diff --git a/app/assets/javascripts/admin/templates/customize-themes-show.hbs b/app/assets/javascripts/admin/templates/customize-themes-show.hbs index 7f339d63d7..fcd933553c 100644 --- a/app/assets/javascripts/admin/templates/customize-themes-show.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes-show.hbs @@ -50,16 +50,16 @@

{{i18n "admin.customize.theme.css_html"}}

{{#if hasEditedFields}} -

{{i18n "admin.customize.theme.custom_sections"}}

- +

{{i18n "admin.customize.theme.custom_sections"}}

+ {{else}} -

- {{i18n "admin.customize.theme.edit_css_html_help"}} -

+

+ {{i18n "admin.customize.theme.edit_css_html_help"}} +

{{/if}}

{{#if model.remote_theme}} @@ -71,17 +71,17 @@ {{/if}} {{#d-button action="editTheme" class="btn edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}} {{#if model.remote_theme}} - - {{#if updatingRemote}} - {{i18n 'admin.customize.theme.updating'}} - {{else}} - {{#if model.remote_theme.commits_behind}} - {{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}} + + {{#if updatingRemote}} + {{i18n 'admin.customize.theme.updating'}} {{else}} - {{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}} + {{#if model.remote_theme.commits_behind}} + {{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}} + {{else}} + {{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}} + {{/if}} {{/if}} - {{/if}} - + {{/if}}

@@ -105,6 +105,15 @@ {{#d-button action="addUploadModal" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}}

+ {{#if hasSettings}} +

{{i18n "admin.customize.theme.theme_settings"}}

+ {{#d-section class="form-horizontal theme settings"}} + {{#each settings as |setting|}} + {{theme-setting setting=setting model=model class="theme-setting"}} + {{/each}} + {{/d-section}} + {{/if}} + {{#if availableChildThemes}}

{{i18n "admin.customize.theme.theme_components"}}

{{#unless model.childThemes.length}} diff --git a/app/assets/javascripts/admin/templates/group.hbs b/app/assets/javascripts/admin/templates/group.hbs index 0689bd30ce..c10ca20bb3 100644 --- a/app/assets/javascripts/admin/templates/group.hbs +++ b/app/assets/javascripts/admin/templates/group.hbs @@ -35,7 +35,7 @@ {{user-selector usernames=model.ownerUsernames - placeholderKey="admin.groups.selector_placeholder" + placeholderKey="groups.selector_placeholder" id="owner-selector"}} {{#if model.id}} diff --git a/app/assets/javascripts/admin/templates/logs/screened-ip-addresses.hbs b/app/assets/javascripts/admin/templates/logs/screened-ip-addresses.hbs index 00e67a9aa5..e8ee4d896d 100644 --- a/app/assets/javascripts/admin/templates/logs/screened-ip-addresses.hbs +++ b/app/assets/javascripts/admin/templates/logs/screened-ip-addresses.hbs @@ -58,7 +58,7 @@ {{#unless item.editing}} {{d-button action="destroy" actionParam=item icon="trash-o" class="btn-danger"}} {{d-button action="edit" actionParam=item icon="pencil"}} - {{#if isBlocked}} + {{#if item.isBlocked}} {{d-button action="allow" actionParam=item icon="check" label="admin.logs.screened_ips.actions.do_nothing"}} {{else}} {{d-button action="block" actionParam=item icon="ban" label="admin.logs.screened_ips.actions.block"}} diff --git a/app/assets/javascripts/admin/templates/site-settings-category.hbs b/app/assets/javascripts/admin/templates/site-settings-category.hbs index 2e44f80c38..1af19ed7b6 100644 --- a/app/assets/javascripts/admin/templates/site-settings-category.hbs +++ b/app/assets/javascripts/admin/templates/site-settings-category.hbs @@ -1,7 +1,7 @@ {{#if filteredContent}} {{#d-section class="form-horizontal settings"}} {{#each filteredContent as |setting|}} - {{site-setting setting=setting saveAction="saveSetting"}} + {{site-setting setting=setting}} {{/each}} {{/d-section}} {{else}} diff --git a/app/assets/javascripts/admin/templates/site-settings.hbs b/app/assets/javascripts/admin/templates/site-settings.hbs index 4561129eb8..e3d9f4d875 100644 --- a/app/assets/javascripts/admin/templates/site-settings.hbs +++ b/app/assets/javascripts/admin/templates/site-settings.hbs @@ -2,7 +2,7 @@
diff --git a/app/assets/javascripts/admin/templates/user-index.hbs b/app/assets/javascripts/admin/templates/user-index.hbs index 28b526da55..11f9671365 100644 --- a/app/assets/javascripts/admin/templates/user-index.hbs +++ b/app/assets/javascripts/admin/templates/user-index.hbs @@ -156,6 +156,22 @@
{{/if}} + +
+
{{i18n 'user.second_factor.title'}}
+
+ {{#if model.second_factor_enabled}} + {{i18n "yes_value"}} + {{else}} + {{i18n "no_value"}} + {{/if}} +
+
+ {{#if canDisableSecondFactor}} + {{d-button action="disableSecondFactor" icon="unlock-alt" label="user.second_factor.disable"}} + {{/if}} +
+
{{#if userFields}} diff --git a/app/assets/javascripts/admin/templates/users-list-show.hbs b/app/assets/javascripts/admin/templates/users-list-show.hbs index 0ccaee0251..7401077ee6 100644 --- a/app/assets/javascripts/admin/templates/users-list-show.hbs +++ b/app/assets/javascripts/admin/templates/users-list-show.hbs @@ -98,6 +98,10 @@ {{#if user.moderator}} {{d-icon "shield" title="admin.moderator" }} {{/if}} + + {{#if user.second_factor_enabled}} + {{d-icon "lock" title="admin.user.second_factor_enabled" }} + {{/if}} {{/each}} diff --git a/app/assets/javascripts/admin/templates/watched-words-action.hbs b/app/assets/javascripts/admin/templates/watched-words-action.hbs index 8377c28bd5..d58f1a9430 100644 --- a/app/assets/javascripts/admin/templates/watched-words-action.hbs +++ b/app/assets/javascripts/admin/templates/watched-words-action.hbs @@ -10,6 +10,12 @@ {{watched-word-uploader uploading=uploading actionKey=actionNameKey done="uploadComplete"}}
+
+ +
{{#if showWordsList}} {{#each filteredContent as |word| }} diff --git a/app/assets/javascripts/admin/templates/watched-words.hbs b/app/assets/javascripts/admin/templates/watched-words.hbs index 7bec3d8812..dccbdb29cc 100644 --- a/app/assets/javascripts/admin/templates/watched-words.hbs +++ b/app/assets/javascripts/admin/templates/watched-words.hbs @@ -1,10 +1,4 @@
-
{{text-field value=filter placeholderKey="admin.watched_words.search" class="no-blur"}} {{d-button action="clearFilter" label="admin.watched_words.clear_filter"}} diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c6ca0b0c90..f231c52bf8 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -11,6 +11,7 @@ // Stuff we need to load first //= require ./discourse/lib/utilities //= require ./discourse/lib/page-visible +//= require ./discourse/lib/logout //= require ./discourse/lib/ajax //= require ./discourse/lib/text //= require ./discourse/lib/hash diff --git a/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 b/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 index 260d13ce28..986ac0ff6c 100644 --- a/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 +++ b/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 @@ -61,5 +61,11 @@ export default Ember.Component.extend({ } return false; } - } + }, + + actions: { + showInserted() { + this.sendAction('showInserted'); + }, + }, }); diff --git a/app/assets/javascripts/discourse/components/categories-and-top-topics.js.es6 b/app/assets/javascripts/discourse/components/categories-and-top-topics.js.es6 new file mode 100644 index 0000000000..4f1c888692 --- /dev/null +++ b/app/assets/javascripts/discourse/components/categories-and-top-topics.js.es6 @@ -0,0 +1,3 @@ +export default Ember.Component.extend({ + classNames: ["categories-and-top"] +}); diff --git a/app/assets/javascripts/discourse/components/latest-topic-list.js.es6 b/app/assets/javascripts/discourse/components/category-title-before.js.es6 similarity index 55% rename from app/assets/javascripts/discourse/components/latest-topic-list.js.es6 rename to app/assets/javascripts/discourse/components/category-title-before.js.es6 index 664eb94c31..9250c1ae73 100644 --- a/app/assets/javascripts/discourse/components/latest-topic-list.js.es6 +++ b/app/assets/javascripts/discourse/components/category-title-before.js.es6 @@ -1,3 +1,3 @@ export default Ember.Component.extend({ - classNames: ['latest-topic-list'] + tagName: '' }); diff --git a/app/assets/javascripts/discourse/components/composer-action-title.js.es6 b/app/assets/javascripts/discourse/components/composer-action-title.js.es6 index 2bc6927300..5b4a148e4d 100644 --- a/app/assets/javascripts/discourse/components/composer-action-title.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-action-title.js.es6 @@ -38,11 +38,11 @@ export default Ember.Component.extend({ ${postLink.anchor} ${userAvatar} ${userLink.anchor} - ${iconHTML("mail-forward", { class: "reply-to-glyph" })} `; if (originalUser) { editTitle += ` + ${iconHTML("mail-forward", { class: "reply-to-glyph" })} ${originalUser.avatar} ${originalUser.username} `; diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6 index b0623158e4..86a9b02e50 100644 --- a/app/assets/javascripts/discourse/components/composer-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6 @@ -372,7 +372,13 @@ export default Ember.Component.extend({ post.set('refreshedPost', true); } - $oneboxes.each((_, o) => load({ elem: o, refresh, ajax, categoryId: this.get('composer.category.id') })); + $oneboxes.each((_, o) => load({ + elem: o, + refresh, + ajax, + categoryId: this.get('composer.category.id'), + topicId: this.get('composer.topic.id') + })); }, _warnMentionedGroups($preview) { diff --git a/app/assets/javascripts/discourse/components/composer-title.js.es6 b/app/assets/javascripts/discourse/components/composer-title.js.es6 index 86acf45c77..23d5443b0c 100644 --- a/app/assets/javascripts/discourse/components/composer-title.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-title.js.es6 @@ -86,6 +86,7 @@ export default Ember.Component.extend({ ajax, synchronous: true, categoryId: this.get('composer.category.id'), + topicId: this.get('composer.topic.id') }); if (loadOnebox && loadOnebox.then) { diff --git a/app/assets/javascripts/discourse/components/d-modal-body.js.es6 b/app/assets/javascripts/discourse/components/d-modal-body.js.es6 index 660456db7f..8de322600d 100644 --- a/app/assets/javascripts/discourse/components/d-modal-body.js.es6 +++ b/app/assets/javascripts/discourse/components/d-modal-body.js.es6 @@ -14,11 +14,13 @@ export default Ember.Component.extend({ Ember.run.scheduleOnce('afterRender', this, this._afterFirstRender); this.appEvents.on('modal-body:flash', msg => this._flash(msg)); + this.appEvents.on('modal-body:clearFlash', () => this._clearFlash()); }, willDestroyElement() { this._super(); this.appEvents.off('modal-body:flash'); + this.appEvents.off('modal-body:clearFlash'); }, _afterFirstRender() { @@ -45,10 +47,16 @@ export default Ember.Component.extend({ ); }, + _clearFlash() { + $('#modal-alert').hide().removeClass('alert-error', 'alert-success'); + }, + _flash(msg) { - $('#modal-alert').hide() - .removeClass('alert-error', 'alert-success') - .addClass(`alert alert-${msg.messageClass || 'success'}`).html(msg.text || '') - .fadeIn(); + this._clearFlash(); + + $('#modal-alert') + .addClass(`alert alert-${msg.messageClass || 'success'}`) + .html(msg.text || '') + .fadeIn(); }, }); diff --git a/app/assets/javascripts/discourse/components/emoji-picker.js.es6 b/app/assets/javascripts/discourse/components/emoji-picker.js.es6 index 4e1222a649..c9c8d607a3 100644 --- a/app/assets/javascripts/discourse/components/emoji-picker.js.es6 +++ b/app/assets/javascripts/discourse/components/emoji-picker.js.es6 @@ -2,8 +2,7 @@ import { on, observes } from "ember-addons/ember-computed-decorators"; import { findRawTemplate } from "discourse/lib/raw-templates"; import { emojiUrlFor } from "discourse/lib/text"; import KeyValueStore from "discourse/lib/key-value-store"; -import { emojis } from "pretty-text/emoji/data"; -import { extendedEmojiList, isSkinTonableEmoji } from "pretty-text/emoji"; +import { extendedEmojiList, isSkinTonableEmoji, emojiSearch } from "pretty-text/emoji"; const { run } = Ember; const keyValueStore = new KeyValueStore("discourse_emojis_"); @@ -205,10 +204,7 @@ export default Ember.Component.extend({ this.$list.css("visibility", "visible"); } else { const lowerCaseFilter = this.get("filter").toLowerCase(); - const filterableEmojis = emojis.concat(_.keys(extendedEmojiList())); - const filteredCodes = _.filter(filterableEmojis, code => { - return code.indexOf(lowerCaseFilter) > -1; - }).slice(0, 30); + const filteredCodes = emojiSearch(lowerCaseFilter, { maxResults: 30}); this.$results.empty().html( _.map(filteredCodes, (code) => { const hasDiversity = isSkinTonableEmoji(code); diff --git a/app/assets/javascripts/discourse/components/login-buttons.js.es6 b/app/assets/javascripts/discourse/components/login-buttons.js.es6 index 4c68d1740f..aa90e565c7 100644 --- a/app/assets/javascripts/discourse/components/login-buttons.js.es6 +++ b/app/assets/javascripts/discourse/components/login-buttons.js.es6 @@ -13,8 +13,12 @@ export default Ember.Component.extend({ }, actions: { - externalLogin: function(provider) { - this.sendAction('action', provider); + emailLogin() { + this.sendAction('emailLogin'); + }, + + externalLogin(provider) { + this.sendAction('externalLogin', provider); } } }); diff --git a/app/assets/javascripts/discourse/components/login-modal.js.es6 b/app/assets/javascripts/discourse/components/login-modal.js.es6 index e366392b34..c4d710966a 100644 --- a/app/assets/javascripts/discourse/components/login-modal.js.es6 +++ b/app/assets/javascripts/discourse/components/login-modal.js.es6 @@ -11,7 +11,7 @@ export default Ember.Component.extend({ } Ember.run.schedule('afterRender', () => { - $('#login-account-password, #login-account-name').keydown(e => { + $('#login-account-password, #login-account-name, #login-second-factor').keydown(e => { if (e.keyCode === 13) { this.sendAction(); } diff --git a/app/assets/javascripts/discourse/components/suggested-topics.js.es6 b/app/assets/javascripts/discourse/components/suggested-topics.js.es6 index 87ffd1865b..c9c298bd23 100644 --- a/app/assets/javascripts/discourse/components/suggested-topics.js.es6 +++ b/app/assets/javascripts/discourse/components/suggested-topics.js.es6 @@ -31,7 +31,7 @@ export default Ember.Component.extend({ } const unreadTopics = this.topicTrackingState.countUnread(); - const newTopics = this.topicTrackingState.countNew(); + const newTopics = this.currentUser ? this.topicTrackingState.countNew() : 0; if (newTopics + unreadTopics > 0) { const hasBoth = unreadTopics > 0 && newTopics > 0; diff --git a/app/assets/javascripts/discourse/components/tag-chooser.js.es6 b/app/assets/javascripts/discourse/components/tag-chooser.js.es6 deleted file mode 100644 index 9f962596e4..0000000000 --- a/app/assets/javascripts/discourse/components/tag-chooser.js.es6 +++ /dev/null @@ -1,142 +0,0 @@ -import renderTag from 'discourse/lib/render-tag'; - -function formatTag(t) { - return renderTag(t.id, {count: t.count, noHref: true}); -} - -export default Ember.TextField.extend({ - classNameBindings: [':tag-chooser'], - attributeBindings: ['tabIndex', 'placeholderKey', 'categoryId'], - - init() { - this._super(); - const tags = this.get('tags') || []; - this.set('value', tags.join(", ")); - - if (this.get('allowCreate') !== false) { - this.set('allowCreate', this.site.get('can_create_tag')); - } - - this.set('termMatchesForbidden', false); - }, - - _valueChanged: function() { - const tags = this.get('value').split(',').map(v => v.trim()).reject(v => v.length === 0).uniq(); - this.set('tags', tags); - }.observes('value'), - - _tagsChanged: function() { - const $tagChooser = this.$(), - val = this.get('value'); - - if ($tagChooser && val !== this.get('tags')) { - if (this.get('tags')) { - const data = this.get('tags').map((t) => {return {id: t, text: t};}); - $tagChooser.select2('data', data); - } else { - $tagChooser.select2('data', []); - } - } - }.observes('tags'), - - didInsertElement() { - this._super(); - - const self = this; - const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); - - let limit = this.siteSettings.max_tags_per_topic; - - if (this.get('unlimitedTagCount')) { - limit = null; - } else if (this.get('limit')) { - limit = parseInt(this.get('limit')); - } - - this.$().select2({ - tags: true, - placeholder: this.get('placeholder') === "" ? "" : I18n.t(this.get('placeholderKey') || 'tagging.choose_for_topic'), - maximumInputLength: this.siteSettings.max_tag_length, - maximumSelectionSize: limit, - width: this.get('width') || 'resolve', - initSelection(element, callback) { - const data = []; - - function splitVal(string, separator) { - var val, i, l; - if (string === null || string.length < 1) return []; - val = string.split(separator); - for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]); - return val; - } - - $(splitVal(element.val(), ",")).each(function () { - data.push({ - id: this, - text: this - }); - }); - - callback(data); - }, - createSearchChoice(term, data) { - term = term.replace(filterRegexp, '').trim().toLowerCase(); - - // No empty terms, make sure the user has permission to create the tag - if (!term.length || !self.get('allowCreate') || self.get('termMatchesForbidden')) return; - - if ($(data).filter(function() { - return this.text.localeCompare(term) === 0; - }).length === 0) { - return { id: term, text: term }; - } - }, - createSearchChoicePosition(list, item) { - // Search term goes on the bottom - list.push(item); - }, - formatSelection(data) { - return data ? renderTag(this.text(data), {noHref: true}) : undefined; - }, - formatSelectionCssClass() { - return "discourse-tag-select2"; - }, - formatResult: formatTag, - multiple: true, - ajax: { - quietMillis: 200, - cache: true, - url: Discourse.getURL("/tags/filter/search"), - dataType: 'json', - data: function (term) { - const selectedTags = self.get('tags'); - const d = { - q: term, - limit: self.siteSettings.max_tag_search_results, - categoryId: self.get('categoryId') - }; - if (selectedTags) { - d.selected_tags = selectedTags.slice(0,100); - } - if (!self.get('everyTag')) { - d.filterForInput = true; - } - return d; - }, - results: function (data) { - if (self.siteSettings.tags_sort_alphabetically) { - data.results = data.results.sort(function(a,b) { return a.id > b.id; }); - } - self.set('termMatchesForbidden', data.forbidden ? true : false); - return data; - } - }, - }); - }, - - willDestroyElement() { - this._super(); - this.$().select2('destroy'); - } - -}); diff --git a/app/assets/javascripts/discourse/components/tag-group-chooser.js.es6 b/app/assets/javascripts/discourse/components/tag-group-chooser.js.es6 deleted file mode 100644 index efe35db5bb..0000000000 --- a/app/assets/javascripts/discourse/components/tag-group-chooser.js.es6 +++ /dev/null @@ -1,86 +0,0 @@ -function renderTagGroup(tag) { - return "" + Handlebars.Utils.escapeExpression(tag.text ? tag.text : tag) + ""; -}; - -export default Ember.TextField.extend({ - classNameBindings: [':tag-chooser'], - attributeBindings: ['tabIndex', 'placeholderKey', 'categoryId'], - - _initValue: function() { - const names = this.get('tagGroups') || []; - this.set('value', names.join(",")); - }.on('init'), - - _valueChanged: function() { - const names = this.get('value').split(',').map(v => v.trim()).reject(v => v.length === 0).uniq(); - if ( this.get('tagGroups').join(',') !== this.get('value') ) { - this.set('tagGroups', names); - } - }.observes('value'), - - _tagGroupsChanged: function() { - const $chooser = this.$(), - val = this.get('value'); - - if ($chooser && val !== this.get('tagGroups')) { - if (this.get('tagGroups')) { - const data = this.get('tagGroups').map((t) => {return {id: t, text: t};}); - $chooser.select2('data', data); - } else { - $chooser.select2('data', []); - } - } - }.observes('tagGroups'), - - _initializeChooser: function() { - const self = this; - - this.$().select2({ - tags: true, - placeholder: this.get('placeholderKey') ? I18n.t(this.get('placeholderKey')) : null, - initSelection(element, callback) { - const data = []; - - function splitVal(string, separator) { - var val, i, l; - if (string === null || string.length < 1) return []; - val = string.split(separator); - for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]); - return val; - } - - $(splitVal(element.val(), ",")).each(function () { - data.push({ id: this, text: this }); - }); - - callback(data); - }, - formatSelection: function (data) { - return data ? renderTagGroup(this.text(data)) : undefined; - }, - formatSelectionCssClass: function(){ - return "discourse-tag-select2"; - }, - formatResult: renderTagGroup, - multiple: true, - ajax: { - quietMillis: 200, - cache: true, - url: Discourse.getURL("/tag_groups/filter/search"), - dataType: 'json', - data: function (term) { - return { q: term, limit: self.siteSettings.max_tag_search_results }; - }, - results: function (data) { - data.results = data.results.sort(function(a,b) { return a.text > b.text; }); - return data; - } - }, - }); - }.on('didInsertElement'), - - _destroyChooser: function() { - this.$().select2('destroy'); - }.on('willDestroyElement') - -}); diff --git a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 index af05e670fa..d2c110adfd 100644 --- a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 @@ -28,6 +28,11 @@ export default Ember.Component.extend({ return !this.site.mobileView && this.currentUser && this.currentUser.get('canManageTopic'); }, + showEditOnFooter: Ember.computed.and( + 'topic.isPrivateMessage', + 'site.can_tag_pms' + ), + @computed('topic.message_archived') archiveIcon: archived => archived ? '' : 'folder', diff --git a/app/assets/javascripts/discourse/components/topic-timeline.js.es6 b/app/assets/javascripts/discourse/components/topic-timeline.js.es6 index c693fd9c94..747bc97565 100644 --- a/app/assets/javascripts/discourse/components/topic-timeline.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-timeline.js.es6 @@ -62,13 +62,14 @@ export default MountWidget.extend(Docking, { this.dockBottom = false; if (posTop < topicTop) { - this.dockAt = topicTop; + this.dockAt = parseInt(topicTop, 10); } else if (pos > topicBottom + footerHeight) { - this.dockAt = (topicBottom - timelineHeight) + footerHeight; + this.dockAt = parseInt((topicBottom - timelineHeight) + footerHeight, 10); this.dockBottom = true; if (this.dockAt < 0) { this.dockAt = 0; } } else { this.dockAt = null; + this.fastDockAt = parseInt(topicBottom - timelineHeight + footerHeight - offsetTop, 10); } if (this.dockAt !== prev) { diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 68b5a75bac..9416e8df57 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -140,7 +140,8 @@ export default Ember.Controller.extend({ return !this.site.mobileView && this.site.get('can_tag_topics') && canEditTitle && - !creatingPrivateMessage; + !creatingPrivateMessage && + (!this.get('model.topic.isPrivateMessage') || this.site.get('can_tag_pms')); }, @computed('model.whisper', 'model.unlistTopic') diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index e955208acc..8d3f4b4b86 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -42,7 +42,7 @@ const controllerOpts = { const tracker = this.topicTrackingState; // Move inserted into topics - this.get('content').loadBefore(tracker.get('newIncoming')); + this.get('content').loadBefore(tracker.get('newIncoming'), true); tracker.resetTracking(); return false; }, diff --git a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 index 7e385fb302..a5dff79c90 100644 --- a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 +++ b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 @@ -29,45 +29,36 @@ export default Ember.Controller.extend(ModalFunctionality, { }, resetPassword() { - return this._submit('/session/forgot_password', 'forgot_password.complete'); - }, + if (this.get('submitDisabled')) return false; + this.set('disabled', true); - emailLogin() { - return this._submit('/u/email-login', 'email_login.complete'); + this.clearFlash(); + + ajax('/session/forgot_password', { + data: { login: this.get('accountEmailOrUsername').trim() }, + type: 'POST' + }).then(data => { + const accountEmailOrUsername = escapeExpression(this.get("accountEmailOrUsername")); + const isEmail = accountEmailOrUsername.match(/@/); + let key = `forgot_password.complete_${isEmail ? 'email' : 'username'}`; + if (data.user_found) { + this.set('offerHelp', I18n.t(`${key}_found`, { + email: accountEmailOrUsername, + username: accountEmailOrUsername + })); + } else { + this.flash(I18n.t(`${key}_not_found`, { + email: accountEmailOrUsername, + username: accountEmailOrUsername + }), 'error'); + } + }).catch(e => { + this.flash(extractError(e), 'error'); + }).finally(() => { + this.set('disabled', false); + }); + + return false; } }, - - _submit(route, translationKey) { - if (this.get('submitDisabled')) return false; - this.set('disabled', true); - - ajax(route, { - data: { login: this.get('accountEmailOrUsername').trim() }, - type: 'POST' - }).then(data => { - const escaped = escapeExpression(this.get('accountEmailOrUsername')); - const isEmail = this.get('accountEmailOrUsername').match(/@/); - let key = `${translationKey}_${isEmail ? 'email' : 'username'}`; - let extraClass; - - if (data.user_found === true) { - key += '_found'; - this.set('accountEmailOrUsername', ''); - this.set('offerHelp', I18n.t(key, { email: escaped, username: escaped })); - } else { - if (data.user_found === false) { - key += '_not_found'; - extraClass = 'error'; - } - - this.flash(I18n.t(key, { email: escaped, username: escaped }), extraClass); - } - }).catch(e => { - this.flash(extractError(e), 'error'); - }).finally(() => { - this.set('disabled', false); - }); - - return false; - }, }); diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index 6eb266d98f..76868ed983 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -4,6 +4,9 @@ import showModal from 'discourse/lib/show-modal'; import { setting } from 'discourse/lib/computed'; import { findAll } from 'discourse/models/login-method'; import { escape } from 'pretty-text/sanitizer'; +import { escapeExpression } from 'discourse/lib/utilities'; +import { extractError } from 'discourse/lib/ajax-error'; +import computed from 'ember-addons/ember-computed-decorators'; // This is happening outside of the app via popup const AuthErrors = [ @@ -23,14 +26,23 @@ export default Ember.Controller.extend(ModalFunctionality, { authenticate: null, loggingIn: false, loggedIn: false, + processingEmailLink: false, + showLoginButtons: true, canLoginLocal: setting('enable_local_logins'), + canLoginLocalWithEmail: setting('enable_local_logins_via_email'), loginRequired: Em.computed.alias('application.loginRequired'), resetForm: function() { - this.set('authenticate', null); - this.set('loggingIn', false); - this.set('loggedIn', false); + this.setProperties({ + 'authenticate': null, + 'loggingIn': false, + 'loggedIn': false, + 'secondFactorRequired': false, + 'showLoginButtons': true, + }); + $("#credentials").show(); + $("#second-factor").hide(); }, // Determines whether at least one login button is enabled @@ -38,9 +50,10 @@ export default Ember.Controller.extend(ModalFunctionality, { return findAll(this.siteSettings).length > 0; }.property(), - loginButtonText: function() { - return this.get('loggingIn') ? I18n.t('login.logging_in') : I18n.t('login.title'); - }.property('loggingIn'), + @computed('loggingIn') + loginButtonLabel(loggingIn) { + return loggingIn ? 'login.logging_in' : 'login.title'; + }, loginDisabled: Em.computed.or('loggingIn', 'loggedIn'), @@ -54,6 +67,11 @@ export default Ember.Controller.extend(ModalFunctionality, { return this.get('loggingIn') || this.get('authenticate'); }.property('loggingIn', 'authenticate'), + @computed('canLoginLocalWithEmail', 'loginName', 'processingEmailLink') + showLoginWithEmailLink(canLoginLocalWithEmail, loginName, processingEmailLink) { + return canLoginLocalWithEmail && !Ember.isEmpty(loginName) && !processingEmailLink; + }, + actions: { login() { const self = this; @@ -67,13 +85,28 @@ export default Ember.Controller.extend(ModalFunctionality, { this.set('loggingIn', true); ajax("/session", { - data: { login: this.get('loginName'), password: this.get('loginPassword') }, - type: 'POST' + type: 'POST', + data: { + login: this.get('loginName'), + password: this.get('loginPassword'), + second_factor_token: this.get('loginSecondFactor') + }, }).then(function (result) { // Successful login if (result && result.error) { self.set('loggingIn', false); - if (result.reason === 'not_activated') { + + if (result.reason === 'invalid_second_factor' && !self.get('secondFactorRequired')) { + $('#modal-alert').hide(); + self.setProperties({ + 'secondFactorRequired': true, + 'showLoginButtons': false, + }); + + $("#credentials").hide(); + $("#second-factor").show(); + return; + } else if (result.reason === 'not_activated') { self.send('showNotActivated', { username: self.get('loginName'), sentTo: escape(result.sent_to_email), @@ -182,6 +215,37 @@ export default Ember.Controller.extend(ModalFunctionality, { const forgotPasswordController = this.get('forgotPassword'); if (forgotPasswordController) { forgotPasswordController.set("accountEmailOrUsername", this.get("loginName")); } this.send("showForgotPassword"); + }, + + emailLogin() { + if (this.get('processingEmailLink')) { + return; + } + + if (Ember.isEmpty(this.get('loginName'))){ + this.flash(I18n.t('login.blank_username'), 'error'); + return; + } + + this.set('processingEmailLink', true); + + ajax('/u/email-login', { + data: { login: this.get('loginName').trim() }, + type: 'POST' + }).then(data => { + const loginName = escapeExpression(this.get('loginName')); + const isEmail = loginName.match(/@/); + let key = `email_login.complete_${isEmail ? 'email' : 'username'}`; + if (data.user_found) { + this.flash(I18n.t(`${key}_found`, { email: loginName, username: loginName })); + } else { + this.flash(I18n.t(`${key}_not_found`, { email: loginName, username: loginName }), 'error'); + } + }).catch(e => { + this.flash(extractError(e), 'error'); + }).finally(() => { + this.set('processingEmailLink', false); + }); } }, @@ -194,16 +258,28 @@ export default Ember.Controller.extend(ModalFunctionality, { }).property('authenticate'), authenticationComplete(options) { - const self = this; - function loginError(errorMsg, className) { + function loginError(errorMsg, className, callback) { showModal('login'); - Ember.run.next(function() { + + Ember.run.next(() => { + callback(); self.flash(errorMsg, className || 'success'); self.set('authenticate', null); }); } + if (options.omniauth_disallow_totp) { + return loginError(I18n.t('login.omniauth_disallow_totp'), 'error', () => { + this.setProperties({ + 'loginName': options.email, + 'showLoginButtons': false, + }); + + $('#login-account-password').focus(); + }); + } + for (let i=0; i { if (result.success) { @@ -45,10 +47,22 @@ export default Ember.Controller.extend(PasswordValidation, { DiscourseURL.redirectTo(result.redirect_to || '/'); } } else { - if (result.errors && result.errors.password && result.errors.password.length > 0) { + if (result.errors && result.errors.user_second_factor) { + this.setProperties({ + secondFactorRequired: true, + password: null, + errorMessage: result.message + }); + } else if (this.get('secondFactorRequired')) { + this.setProperties({ + secondFactorRequired: false, + errorMessage: null + }); + } else if (result.errors && result.errors.password && result.errors.password.length > 0) { this.get('rejectedPasswords').pushObject(this.get('accountPassword')); this.get('rejectedPasswordsMessages').set(this.get('accountPassword'), result.errors.password[0]); } + if (result.message) { this.set('errorMessage', result.message); } diff --git a/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 index 9dc6083f15..d8c54177d4 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 @@ -40,6 +40,11 @@ export default Ember.Controller.extend(CanCheckEmails, PreferencesTabController, return !this.siteSettings.enable_sso && this.siteSettings.enable_local_logins; }, + @computed("model.second_factor_enabled") + secondFactorStatusClass(secondFactorEnabled) { + return secondFactorEnabled ? 'tip good' : 'tip bad'; + }, + actions: { save() { this.set('saved', false); diff --git a/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 index f394acd3e1..6e27ea04e0 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 @@ -1,6 +1,6 @@ import PreferencesTabController from "discourse/mixins/preferences-tab-controller"; import { popupAjaxError } from 'discourse/lib/ajax-error'; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import computed from "ember-addons/ember-computed-decorators"; export default Ember.Controller.extend(PreferencesTabController, { saveAttrNames: [ @@ -12,7 +12,7 @@ export default Ember.Controller.extend(PreferencesTabController, { @computed("model.watchedCategories", "model.watchedFirstPostCategories", "model.trackedCategories", "model.mutedCategories") selectedCategories(watched, watchedFirst, tracked, muted) { - return [].concat(watched, watchedFirst, tracked, muted); + return [].concat(watched, watchedFirst, tracked, muted).filter(t => t); }, canSave: function() { diff --git a/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6 new file mode 100644 index 0000000000..f09263712d --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6 @@ -0,0 +1,99 @@ +import { default as computed } from 'ember-addons/ember-computed-decorators'; +import { default as DiscourseURL, userPath } from 'discourse/lib/url'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; +import { LOGIN_METHODS } from 'discourse/models/login-method'; + +export default Ember.Controller.extend({ + loading: false, + resetPasswordLoading: false, + resetPasswordProgress: '', + password: null, + secondFactorImage: null, + secondFactorKey: null, + showSecondFactorKey: false, + errorMessage: null, + newUsername: null, + + loaded: Ember.computed.and('secondFactorImage', 'secondFactorKey'), + + @computed('loading') + submitButtonText(loading) { + return loading ? 'loading' : 'submit'; + }, + + @computed + displayOAuthWarning() { + return LOGIN_METHODS.some(name => { + return this.siteSettings[`enable_${name}_logins`]; + }); + }, + + toggleSecondFactor(enable) { + if (!this.get('secondFactorToken')) return; + this.set('loading', true); + + this.get('content').toggleSecondFactor(this.get('secondFactorToken'), enable) + .then(response => { + if (response.error) { + this.set('errorMessage', response.error); + this.set('loading', false); + return; + } + + this.set('errorMessage',null); + DiscourseURL.redirectTo(userPath(`${this.get('content').username.toLowerCase()}/preferences`)); + }) + .catch(error => { + this.set('loading', false); + popupAjaxError(error); + }); + }, + + actions: { + confirmPassword() { + if (!this.get('password')) return; + this.set('loading', true); + + this.get('content').loadSecondFactorCodes(this.get('password')) + .then(response => { + if(response.error) { + this.set('errorMessage', response.error); + return; + } + + this.setProperties({ + errorMessage: null, + secondFactorKey: response.key, + secondFactorImage: response.qr, + }); + }) + .catch(popupAjaxError) + .finally(() => this.set('loading', false)); + }, + + resetPassword() { + this.setProperties({ + resetPasswordLoading: true, + resetPasswordProgress: '' + }); + + return this.get('model').changePassword().then(() => { + this.set('resetPasswordProgress', I18n.t('user.change_password.success')); + }) + .catch(popupAjaxError) + .finally(() => this.set('resetPasswordLoading', false)); + }, + + showSecondFactorKey() { + this.set('showSecondFactorKey', true); + }, + + enableSecondFactor() { + this.toggleSecondFactor(true); + }, + + disableSecondFactor() { + this.toggleSecondFactor(false); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/preferences/tags.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/tags.js.es6 index 6264d42b2d..b7e0ab97cf 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/tags.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/tags.js.es6 @@ -1,8 +1,8 @@ import PreferencesTabController from "discourse/mixins/preferences-tab-controller"; import { popupAjaxError } from 'discourse/lib/ajax-error'; +import computed from "ember-addons/ember-computed-decorators"; export default Ember.Controller.extend(PreferencesTabController, { - saveAttrNames: [ 'muted_tags', 'tracked_tags', @@ -10,6 +10,11 @@ export default Ember.Controller.extend(PreferencesTabController, { 'watching_first_post_tags' ], + @computed("model.watched_tags", "model.watching_first_post_tags", "model.tracked_tags", "model.muted_tags") + selectedTags(watched, watchedFirst, tracked, muted) { + return [].concat(watched, watchedFirst, tracked, muted).filter(t => t); + }, + actions: { save() { this.set('saved', false); diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 49110ce83b..dca8ddd0da 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -104,7 +104,7 @@ export default Ember.Controller.extend(BufferedContent, { @computed('model.isPrivateMessage') canEditTags(isPrivateMessage) { - return !isPrivateMessage && this.site.get('can_tag_topics'); + return this.site.get('can_tag_topics') && (!isPrivateMessage || this.site.get('can_tag_pms')); }, actions: { @@ -265,6 +265,23 @@ export default Ember.Controller.extend(BufferedContent, { } }, + editFirstPost() { + const postStream = this.get('model.postStream'); + let firstPost = postStream.get('posts.firstObject'); + + if (firstPost.get('post_number') !== 1) { + const postId = postStream.findPostIdForPostNumber(1); + // try loading from identity map first + firstPost = postStream.findLoadedPost(postId); + if (firstPost === undefined) { + return this.get('model.postStream').loadPost(postId).then(post => { + this.send("editPost", post); + }); + } + } + this.send("editPost", firstPost); + }, + // Post related methods replyToPost(post) { const composerController = this.get('composer'); diff --git a/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 index bb79db55d3..87d477ffd2 100644 --- a/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-private-messages.js.es6 @@ -33,7 +33,6 @@ export default Ember.Controller.extend({ return hasSelection && pmView !== "archive" && !archive; }, - bulkOperation(operation) { const selected = this.get('selected'); var params = {type: operation}; diff --git a/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 b/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 index 8e150fd289..9f877a1bb3 100644 --- a/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 @@ -1,18 +1,59 @@ +import computed from 'ember-addons/ember-computed-decorators'; + // Lists of topics on a user's page. export default Ember.Controller.extend({ application: Ember.inject.controller(), hideCategory: false, showPosters: false, + newIncoming: [], + incomingCount: 0, + channel: null, _showFooter: function() { this.set("application.showFooter", !this.get("model.canLoadMore")); }.observes("model.canLoadMore"), + @computed('incomingCount') + hasIncoming(incomingCount) { + return incomingCount > 0; + }, + + subscribe(channel) { + this.set('channel', channel); + + this.messageBus.subscribe(channel, data => { + if (this.get('newIncoming').indexOf(data.topic_id) === -1) { + this.get('newIncoming').push(data.topic_id); + this.incrementProperty('incomingCount'); + } + }); + }, + + unsubscribe() { + const channel = this.get('channel'); + if (channel) this.messageBus.unsubscribe(channel); + this._resetTracking(); + this.set('channel', null); + }, + + _resetTracking() { + this.setProperties({ + "newIncoming": [], + "incomingCount": 0 + }); + }, + actions: { loadMore: function() { this.get('model').loadMore(); - } + }, + + showInserted() { + this.get('model').loadBefore(this.get('newIncoming')); + this._resetTracking(); + return false; + }, }, }); diff --git a/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 b/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 index e20528bd42..dc685eee6e 100644 --- a/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 +++ b/app/assets/javascripts/discourse/helpers/user-avatar.js.es6 @@ -36,7 +36,7 @@ function renderAvatar(user, options) { if (!username || !avatarTemplate) { return ''; } - let formattedUsername = formatUsername(username); + let displayName = Ember.get(user, 'name') || formatUsername(username); let title = options.title; if (!title && !options.ignoreTitle) { @@ -49,7 +49,7 @@ function renderAvatar(user, options) { // if a description has been provided if (description && description.length > 0) { // preprend the username before the description - title = formattedUsername + " - " + description; + title = displayName + " - " + description; } } } @@ -57,7 +57,7 @@ function renderAvatar(user, options) { return avatarImg({ size: options.imageSize, extraClasses: Em.get(user, 'extras') || options.extraClasses, - title: title || formattedUsername, + title: title || displayName, avatarTemplate: avatarTemplate }); } else { diff --git a/app/assets/javascripts/discourse/initializers/logout.js.es6 b/app/assets/javascripts/discourse/initializers/logout.js.es6 index dc0ff59c81..db7d4de960 100644 --- a/app/assets/javascripts/discourse/initializers/logout.js.es6 +++ b/app/assets/javascripts/discourse/initializers/logout.js.es6 @@ -1,5 +1,7 @@ import logout from 'discourse/lib/logout'; +let _showingLogout = false; + // Subscribe to "logout" change events via the Message Bus export default { name: "logout", @@ -7,14 +9,22 @@ export default { initialize: function (container) { const messageBus = container.lookup('message-bus:main'); - const siteSettings = container.lookup('site-settings:main'); - const keyValueStore = container.lookup('key-value-store:main'); if (!messageBus) { return; } - const callback = () => logout(siteSettings, keyValueStore); messageBus.subscribe("/logout", function () { - bootbox.dialog(I18n.t("logout"), {label: I18n.t("refresh"), callback}, {onEscape: callback, backdrop: 'static'}); + if (!_showingLogout) { + + _showingLogout = true; + + bootbox.dialog(I18n.t("logout"), { + label: I18n.t("refresh"), + callback: logout + }, { + onEscape: logout, + backdrop: 'static' + }); + } }); } }; diff --git a/app/assets/javascripts/discourse/lib/ajax.js.es6 b/app/assets/javascripts/discourse/lib/ajax.js.es6 index 98e65ed9c0..863d6eae7b 100644 --- a/app/assets/javascripts/discourse/lib/ajax.js.es6 +++ b/app/assets/javascripts/discourse/lib/ajax.js.es6 @@ -1,7 +1,9 @@ import pageVisible from 'discourse/lib/page-visible'; +import logout from 'discourse/lib/logout'; let _trackView = false; let _transientHeader = null; +let _showingLogout = false; export function setTransientHeader(key, value) { _transientHeader = {key, value}; @@ -39,6 +41,10 @@ export function ajax() { args.headers = args.headers || {}; + if (Discourse.__container__.lookup('current-user:main')) { + args.headers['Discourse-Logged-In'] = "true"; + } + if (_transientHeader) { args.headers[_transientHeader.key] = _transientHeader.value; _transientHeader = null; @@ -54,7 +60,22 @@ export function ajax() { args.headers['Discourse-Visible'] = "true"; } + let handleLogoff = function(xhr) { + if (xhr.getResponseHeader('Discourse-Logged-Out') && !_showingLogout) { + _showingLogout = true; + bootbox.dialog( + I18n.t("logout"), {label: I18n.t("refresh"), callback: logout}, + { + onEscape: () => logout(), + backdrop: 'static' + } + ); + } + }; + args.success = (data, textStatus, xhr) => { + handleLogoff(xhr); + if (xhr.getResponseHeader('Discourse-Readonly')) { Ember.run(() => Discourse.Site.currentProp('isReadOnly', true)); } @@ -67,6 +88,8 @@ export function ajax() { }; args.error = (xhr, textStatus, errorThrown) => { + handleLogoff(xhr); + // note: for bad CSRF we don't loop an extra request right away. // this allows us to eliminate the possibility of having a loop. if (xhr.status === 403 && xhr.responseText === "[\"BAD CSRF\"]") { diff --git a/app/assets/javascripts/discourse/lib/click-track.js.es6 b/app/assets/javascripts/discourse/lib/click-track.js.es6 index 13845062a4..d4dc44f2cb 100644 --- a/app/assets/javascripts/discourse/lib/click-track.js.es6 +++ b/app/assets/javascripts/discourse/lib/click-track.js.es6 @@ -26,7 +26,7 @@ export default { } // don't track links in quotes or in elided part - let tracking = $link.parents('aside.quote,.elided').length === 0; + let tracking = $link.parents('aside.quote, .elided').length === 0; let href = $link.attr('href') || $link.data('href'); @@ -113,8 +113,10 @@ export default { return false; } + const isInternal = DiscourseURL.isInternal(href); + // If we're on the same site, use the router and track via AJAX - if (tracking && DiscourseURL.isInternal(href) && !$link.hasClass('attachment')) { + if (tracking && isInternal && !$link.hasClass('attachment')) { ajax("/clicks/track", { data: { url: href, @@ -128,9 +130,11 @@ export default { return false; } - // Otherwise, use a custom URL with a redirect - // consider CTRL+mouse-left-click / CMD+mouse-left-click or mouse-middle-click as well - if (Discourse.User.currentProp('external_links_in_new_tab') || ((e.ctrlKey || e.metaKey) && (e.which === 1)) || (e.which === 2)) { + const modifierLeftClicked = (e.ctrlKey || e.metaKey) && e.which === 1; + const middleClicked = e.which === 2; + const openExternalInNewTab = Discourse.User.currentProp('external_links_in_new_tab'); + + if (modifierLeftClicked || middleClicked || (!isInternal && openExternalInNewTab)) { window.open(destUrl, '_blank').focus(); } else { DiscourseURL.redirectTo(destUrl); diff --git a/app/assets/javascripts/discourse/lib/formatter.js.es6 b/app/assets/javascripts/discourse/lib/formatter.js.es6 index dcce95ca99..76e2ab44eb 100644 --- a/app/assets/javascripts/discourse/lib/formatter.js.es6 +++ b/app/assets/javascripts/discourse/lib/formatter.js.es6 @@ -311,7 +311,7 @@ export function number(val) { formattedNumber = I18n.toNumber(val / 1000000, {precision: 1}); return I18n.t("number.short.millions", {number: formattedNumber}); } else if (val > 99999) { - formattedNumber = I18n.toNumber(val / 1000, {precision: 0}); + formattedNumber = I18n.toNumber(Math.floor(val / 1000), {precision: 0}); return I18n.t("number.short.thousands", {number: formattedNumber}); } else if (val > 999) { formattedNumber = I18n.toNumber(val / 1000, {precision: 1}); diff --git a/app/assets/javascripts/discourse/lib/logout.js.es6 b/app/assets/javascripts/discourse/lib/logout.js.es6 index 2fe4078a69..51f397d186 100644 --- a/app/assets/javascripts/discourse/lib/logout.js.es6 +++ b/app/assets/javascripts/discourse/lib/logout.js.es6 @@ -1,4 +1,10 @@ export default function logout(siteSettings, keyValueStore) { + if (!siteSettings || !keyValueStore) { + const container = Discourse.__container__; + siteSettings = siteSettings || container.lookup('site-settings:main'); + keyValueStore = keyValueStore || container.lookup('key-value-store:main'); + } + keyValueStore.abandonLocal(); const redirect = siteSettings.logout_redirect; diff --git a/app/assets/javascripts/discourse/lib/render-tag.js.es6 b/app/assets/javascripts/discourse/lib/render-tag.js.es6 index 127dc5c9fe..70fa81179f 100644 --- a/app/assets/javascripts/discourse/lib/render-tag.js.es6 +++ b/app/assets/javascripts/discourse/lib/render-tag.js.es6 @@ -3,7 +3,12 @@ export default function renderTag(tag, params) { tag = Handlebars.Utils.escapeExpression(tag); const classes = ['tag-' + tag, 'discourse-tag']; const tagName = params.tagName || "a"; - const href = (tagName === "a" && !params.noHref) ? " href='" + Discourse.getURL("/tags/" + tag) + "' " : ""; + let path; + if (tagName === "a" && !params.noHref) { + const current_user = Discourse.User.current(); + path = params.isPrivateMessage ? `/u/${current_user.username}/messages/tag/${tag}` : `/tags/${tag}`; + } + const href = path ? ` href='${Discourse.getURL(path)}' ` : ""; if (Discourse.SiteSettings.tag_style || params.style) { classes.push(params.style || Discourse.SiteSettings.tag_style); diff --git a/app/assets/javascripts/discourse/lib/render-tags.js.es6 b/app/assets/javascripts/discourse/lib/render-tags.js.es6 index 6989eab57b..ef86b502d3 100644 --- a/app/assets/javascripts/discourse/lib/render-tags.js.es6 +++ b/app/assets/javascripts/discourse/lib/render-tags.js.es6 @@ -20,6 +20,7 @@ export function addTagsHtmlCallback(callback, options) { export default function(topic, params){ let tags = topic.tags; let buffer = ""; + const isPrivateMessage = topic.get('isPrivateMessage'); if (params && params.mode === "list") { tags = topic.get("visibleListTags"); @@ -43,7 +44,7 @@ export default function(topic, params){ buffer = "
"; if (tags) { for(let i=0; i ext.indexOf("*") === -1); +} + function extensions() { - return Discourse.SiteSettings.authorized_extensions - .toLowerCase() - .replace(/[\s\.]+/g, "") - .split("|") - .filter(ext => ext.indexOf("*") === -1); + return extensionsToArray(Discourse.SiteSettings.authorized_extensions); +} + +function staffExtensions() { + return extensionsToArray(Discourse.SiteSettings.authorized_extensions_for_staff); } function imagesExtensions() { - return extensions().filter(ext => IMAGES_EXTENSIONS_REGEX.test(ext)); + let exts = extensions().filter(ext => IMAGES_EXTENSIONS_REGEX.test(ext)); + if (Discourse.User.currentProp('staff')) { + const staffExts = staffExtensions().filter(ext => IMAGES_EXTENSIONS_REGEX.test(ext)); + exts = _.union(exts, staffExts); + } + return exts; } function extensionsRegex() { @@ -259,7 +271,14 @@ function imagesExtensionsRegex() { return new RegExp("\\.(" + imagesExtensions().join("|") + ")$", "i"); } +function staffExtensionsRegex() { + return new RegExp("\\.(" + staffExtensions().join("|") + ")$", "i"); +} + function isAuthorizedFile(fileName) { + if (Discourse.User.currentProp('staff') && staffExtensionsRegex().test(fileName)) { + return true; + } return extensionsRegex().test(fileName); } @@ -268,7 +287,8 @@ function isAuthorizedImage(fileName){ } export function authorizedExtensions() { - return authorizesAllExtensions() ? "*" : extensions().join(", "); + const exts = Discourse.User.currentProp('staff') ? [...extensions(), ...staffExtensions()] : extensions(); + return exts.filter(ext => ext.length > 0).join(", "); } export function authorizedImagesExtensions() { @@ -276,7 +296,9 @@ export function authorizedImagesExtensions() { } export function authorizesAllExtensions() { - return Discourse.SiteSettings.authorized_extensions.indexOf("*") >= 0; + return Discourse.SiteSettings.authorized_extensions.indexOf("*") >= 0 || ( + Discourse.SiteSettings.authorized_extensions_for_staff.indexOf("*") >= 0 && + Discourse.User.currentProp('staff')); } export function authorizesOneOrMoreExtensions() { @@ -322,7 +344,7 @@ export function allowsImages() { } export function allowsAttachments() { - return authorizesAllExtensions() || extensions().length > imagesExtensions().length; + return authorizesAllExtensions() || authorizedExtensions().split(", ").length > imagesExtensions().length; } export function uploadLocation(url) { diff --git a/app/assets/javascripts/discourse/mixins/can-check-emails.js.es6 b/app/assets/javascripts/discourse/mixins/can-check-emails.js.es6 index dc802c4d04..8e15c955c2 100644 --- a/app/assets/javascripts/discourse/mixins/can-check-emails.js.es6 +++ b/app/assets/javascripts/discourse/mixins/can-check-emails.js.es6 @@ -1,9 +1,9 @@ import { propertyEqual, setting } from 'discourse/lib/computed'; export default Ember.Mixin.create({ - isOwnEmail: propertyEqual("model.id", "currentUser.id"), + isCurrentUser: propertyEqual("model.id", "currentUser.id"), showEmailOnProfile: setting("show_email_on_profile"), canStaffCheckEmails: Em.computed.and("showEmailOnProfile", "currentUser.staff"), canAdminCheckEmails: Em.computed.alias("currentUser.admin"), - canCheckEmails: Em.computed.or("isOwnEmail", "canStaffCheckEmails", "canAdminCheckEmails"), + canCheckEmails: Em.computed.or("isCurrentUser", "canStaffCheckEmails", "canAdminCheckEmails"), }); diff --git a/app/assets/javascripts/discourse/mixins/modal-functionality.js.es6 b/app/assets/javascripts/discourse/mixins/modal-functionality.js.es6 index d78478dae6..b34b906bdc 100644 --- a/app/assets/javascripts/discourse/mixins/modal-functionality.js.es6 +++ b/app/assets/javascripts/discourse/mixins/modal-functionality.js.es6 @@ -5,6 +5,10 @@ export default Ember.Mixin.create({ this.appEvents.trigger('modal-body:flash', { text, messageClass }); }, + clearFlash() { + this.appEvents.trigger('modal-body:clearFlash'); + }, + showModal(...args) { return showModal(...args); }, diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6 index 52c3eb443c..1eca95047e 100644 --- a/app/assets/javascripts/discourse/models/category.js.es6 +++ b/app/assets/javascripts/discourse/models/category.js.es6 @@ -97,7 +97,7 @@ const Category = RestModel.extend({ allow_badges: this.get('allow_badges'), custom_fields: this.get('custom_fields'), topic_template: this.get('topic_template'), - suppress_from_homepage: this.get('suppress_from_homepage'), + suppress_from_latest: this.get('suppress_from_latest'), all_topics_wiki: this.get('all_topics_wiki'), allowed_tags: this.get('allowed_tags'), allowed_tag_groups: this.get('allowed_tag_groups'), diff --git a/app/assets/javascripts/discourse/models/login-method.js.es6 b/app/assets/javascripts/discourse/models/login-method.js.es6 index c125e7bd83..866c657f6b 100644 --- a/app/assets/javascripts/discourse/models/login-method.js.es6 +++ b/app/assets/javascripts/discourse/models/login-method.js.es6 @@ -22,12 +22,21 @@ const LoginMethod = Ember.Object.extend({ let methods; let preRegister; +export const LOGIN_METHODS = [ + "google_oauth2", + "facebook", + "twitter", + "yahoo", + "instagram", + "github" +]; + export function findAll(siteSettings, capabilities, isMobileDevice) { if (methods) { return methods; } methods = []; - [ "google_oauth2", "facebook", "cas", "twitter", "yahoo", "instagram", "github" ].forEach(name => { + LOGIN_METHODS.forEach(name => { if (siteSettings["enable_" + name + "_logins"]) { const params = { name }; if (name === "google_oauth2") { diff --git a/app/assets/javascripts/discourse/models/topic-list.js.es6 b/app/assets/javascripts/discourse/models/topic-list.js.es6 index 3e39ae5c89..61ee322a38 100644 --- a/app/assets/javascripts/discourse/models/topic-list.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-list.js.es6 @@ -70,17 +70,17 @@ const TopicList = RestModel.extend({ // loads topics with these ids "before" the current topics - loadBefore(topic_ids) { + loadBefore(topic_ids, storeInSession) { const topicList = this, topics = this.get('topics'); // refresh dupes topics.removeObjects(topics.filter(topic => topic_ids.indexOf(topic.get('id')) >= 0)); - const url = `${Discourse.getURL("/")}${this.get('filter')}?topic_ids=${topic_ids.join(",")}`; + const url = `${Discourse.getURL("/")}${this.get('filter')}.json?topic_ids=${topic_ids.join(",")}`; const store = this.store; - return ajax({ url }).then(result => { + return ajax({ url, data: this.get("params") }).then(result => { let i = 0; topicList.forEachNew(TopicList.topicsFrom(store, result), function(t) { // highlight the first of the new topics so we can get a visual feedback @@ -88,7 +88,7 @@ const TopicList = RestModel.extend({ topics.insertAt(i,t); i++; }); - Discourse.Session.currentProp('topicList', topicList); + if (storeInSession) Discourse.Session.currentProp('topicList', topicList); }); } }); diff --git a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 index d4c3a0354a..45fe3f6761 100644 --- a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 @@ -1,6 +1,5 @@ import { NotificationLevels } from 'discourse/lib/notification-levels'; -import computed from "ember-addons/ember-computed-decorators"; -import { on } from "ember-addons/ember-computed-decorators"; +import { default as computed, on } from "ember-addons/ember-computed-decorators"; import { defaultHomepage } from 'discourse/lib/utilities'; import PreloadStore from 'preload-store'; @@ -35,7 +34,7 @@ const TopicTrackingState = Discourse.Model.extend({ tracker.incrementMessageCount(); } - if (data.message_type === "new_topic" || data.message_type === "latest") { + if (["new_topic", "latest"].includes(data.message_type)) { const muted_category_ids = Discourse.User.currentProp("muted_category_ids"); if (_.include(muted_category_ids, data.payload.category_id)) { return; @@ -55,7 +54,7 @@ const TopicTrackingState = Discourse.Model.extend({ tracker.notify(data); } - if (data.message_type === "new_topic" || data.message_type === "unread" || data.message_type === "read") { + if (["new_topic", "unread", "read"].includes(data.message_type)) { tracker.notify(data); const old = tracker.states["t" + data.topic_id]; @@ -117,17 +116,17 @@ const TopicTrackingState = Discourse.Model.extend({ } if (filter === defaultHomepage()) { - const suppressed_from_homepage_category_ids = Discourse.Site.currentProp("suppressed_from_homepage_category_ids"); - if (_.include(suppressed_from_homepage_category_ids, data.payload.category_id)) { + const suppressed_from_latest_category_ids = Discourse.Site.currentProp("suppressed_from_latest_category_ids"); + if (_.include(suppressed_from_latest_category_ids, data.payload.category_id)) { return; } } - if ((filter === "all" || filter === "latest" || filter === "new") && data.message_type === "new_topic") { + if (["all", "latest", "new"].includes(filter) && data.message_type === "new_topic") { this.addIncoming(data.topic_id); } - if ((filter === "all" || filter === "unread") && data.message_type === "unread") { + if (["all", "unread"].includes(filter) && data.message_type === "unread") { const old = this.states["t" + data.topic_id]; if(!old || old.highest_post_number === old.last_read_post_number) { this.addIncoming(data.topic_id); diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 9925d4798a..e54caf2739 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -23,14 +23,16 @@ const User = RestModel.extend({ hasPMs: Em.computed.gt("private_messages_stats.all", 0), hasStartedPMs: Em.computed.gt("private_messages_stats.mine", 0), hasUnreadPMs: Em.computed.gt("private_messages_stats.unread", 0), - hasPosted: Em.computed.gt("post_count", 0), - hasNotPosted: Em.computed.not("hasPosted"), - canBeDeleted: Em.computed.and("can_be_deleted", "hasNotPosted"), redirected_to_top: { reason: null, }, + @computed("can_be_deleted", "post_count") + canBeDeleted(canBeDeleted, postCount) { + return canBeDeleted && postCount <= 5; + }, + @computed() stream() { return UserStream.create({ user: this }); @@ -304,6 +306,20 @@ const User = RestModel.extend({ }); }, + loadSecondFactorCodes(password) { + return ajax("/u/second_factors.json", { + data: { password }, + type: 'POST' + }); + }, + + toggleSecondFactor(token, enable) { + return ajax("/u/second_factor.json", { + data: { second_factor_token: token, enable }, + type: 'PUT' + }); + }, + loadUserAction(id) { const stream = this.get('stream'); return ajax(`/user_actions/${id}.json`, { cache: 'false' }).then(result => { diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 index 72dca540a1..0696f12ced 100644 --- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 +++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 @@ -96,6 +96,7 @@ export default function() { this.route('archive'); this.route('group', { path: 'group/:name'}); this.route('groupArchive', { path: 'group/:name/archive'}); + this.route('tag', { path: 'tag/:id'}); }); this.route('preferences', { resetNamespace: true }, function() { @@ -110,6 +111,7 @@ export default function() { this.route('username'); this.route('email'); + this.route('second-factor'); this.route('about', { path: '/about-me' }); this.route('badgeTitle', { path: '/badge_title' }); this.route('card-badge', { path: '/card-badge' }); diff --git a/app/assets/javascripts/discourse/routes/build-admin-user-posts-route.js.es6 b/app/assets/javascripts/discourse/routes/build-admin-user-posts-route.js.es6 index 1615cdaca8..4fbc3c1366 100644 --- a/app/assets/javascripts/discourse/routes/build-admin-user-posts-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-admin-user-posts-route.js.es6 @@ -1,3 +1,5 @@ +import { emojiUnescape } from 'discourse/lib/text'; + export default function (filter) { return Discourse.Route.extend({ actions: { @@ -20,6 +22,12 @@ export default function (filter) { // initialize "canLoadMore" model.set("canLoadMore", model.get("itemsLoaded") === 60); + model.get('content').forEach((item) => { + if (item.get('title')) { + item.set('title', emojiUnescape(Handlebars.Utils.escapeExpression(item.title))); + } + }); + this.controllerFor("user-posts").set("model", model); }, diff --git a/app/assets/javascripts/discourse/routes/build-private-messages-route.js.es6 b/app/assets/javascripts/discourse/routes/build-private-messages-route.js.es6 index b20e9e1b3d..3db308bbfc 100644 --- a/app/assets/javascripts/discourse/routes/build-private-messages-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-private-messages-route.js.es6 @@ -1,10 +1,15 @@ import UserTopicListRoute from "discourse/routes/user-topic-list"; // A helper to build a user topic list route -export default (viewName, path) => { +export default (viewName, path, channel) => { return UserTopicListRoute.extend({ userActionType: Discourse.UserAction.TYPES.messages_received, + titleToken() { + const key = viewName === "index" ? "inbox" : viewName; + return [I18n.t(`user.messages.${key}`), I18n.t("user.private_messages")]; + }, + actions: { didTransition() { this.controllerFor("user-topics-list")._showFooter(); @@ -19,6 +24,10 @@ export default (viewName, path) => { setupController() { this._super.apply(this, arguments); + if (channel) { + this.controllerFor("user-topics-list").subscribe(`/private-messages/${channel}`); + } + this.controllerFor("user-topics-list").setProperties({ hideCategory: true, showPosters: true, @@ -32,6 +41,8 @@ export default (viewName, path) => { }, deactivate() { + this.controllerFor('user-topics-list').unsubscribe(); + this.searchService.set( 'searchContext', this.controllerFor("user").get("model.searchContext") diff --git a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 index 14dd5e3d20..a7c1dc00ee 100644 --- a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 @@ -91,7 +91,7 @@ export default function(filter, extras) { const topicOpts = { model, category: null, - period: model.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''), + period: model.get('for_period') || (filter.indexOf('top/') >= 0 ? filter.split('/')[1] : ''), selected: [], expandGloballyPinned: true }; diff --git a/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 b/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 index 0ba2194e50..9753a776eb 100644 --- a/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 @@ -12,20 +12,24 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, { this.render("discovery/categories", { outlet: "list-container" }); }, - model() { - const style = !this.site.mobileView && this.siteSettings.desktop_category_page_style; - const parentCategory = this.get("model.parentCategory"); + findCategories() { + let style = !this.site.mobileView && + this.siteSettings.desktop_category_page_style; - let promise; + let parentCategory = this.get("model.parentCategory"); if (parentCategory) { - promise = CategoryList.listForParent(this.store, parentCategory); + return CategoryList.listForParent(this.store, parentCategory); } else if (style === "categories_and_latest_topics") { - promise = this._loadCategoriesAndLatestTopics(); - } else { - promise = CategoryList.list(this.store); + return this._findCategoriesAndTopics('latest'); + } else if (style === "categories_and_top_topics") { + return this._findCategoriesAndTopics('top'); } - return promise.then(model => { + return CategoryList.list(this.store); + }, + + model() { + return this.findCategories().then(model => { const tracking = this.topicTrackingState; if (tracking) { tracking.sync(model, "categories"); @@ -35,26 +39,31 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, { }); }, - _loadCategoriesAndLatestTopics() { - const wrappedCategoriesList = PreloadStore.getAndRemove("categories_list"); - const topicListLatest = PreloadStore.getAndRemove("topic_list_latest"); - const categoriesList = wrappedCategoriesList && wrappedCategoriesList.category_list; - if (categoriesList && topicListLatest) { - return new Ember.RSVP.Promise(resolve => { - const result = Ember.Object.create({ - categories: CategoryList.categoriesFrom(this.store, wrappedCategoriesList), - topics: TopicList.topicsFrom(this.store, topicListLatest), + _findCategoriesAndTopics(filter) { + return Ember.RSVP.hash({ + wrappedCategoriesList: PreloadStore.getAndRemove("categories_list"), + topicsList: PreloadStore.getAndRemove(`topic_list_${filter}`) + }).then(hash => { + let { wrappedCategoriesList, topicsList } = hash; + let categoriesList = wrappedCategoriesList && + wrappedCategoriesList.category_list; + + if (categoriesList && topicsList) { + return Ember.Object.create({ + categories: CategoryList.categoriesFrom( + this.store, + wrappedCategoriesList + ), + topics: TopicList.topicsFrom(this.store, topicsList), can_create_category: categoriesList.can_create_category, can_create_topic: categoriesList.can_create_topic, draft_key: categoriesList.draft_key, draft: categoriesList.draft, draft_sequence: categoriesList.draft_sequence }); - - resolve(result); - }); - } else { - return ajax("/categories_and_latest").then(result => { + } + // Otherwise, return the ajax result + return ajax(`/categories_and_${filter}`).then(result => { return Ember.Object.create({ categories: CategoryList.categoriesFrom(this.store, result), topics: TopicList.topicsFrom(this.store, result), @@ -65,7 +74,7 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, { draft_sequence: result.category_list.draft_sequence }); }); - } + }); }, titleToken() { diff --git a/app/assets/javascripts/discourse/routes/preferences-second-factor.js.es6 b/app/assets/javascripts/discourse/routes/preferences-second-factor.js.es6 new file mode 100644 index 0000000000..b688ec813b --- /dev/null +++ b/app/assets/javascripts/discourse/routes/preferences-second-factor.js.es6 @@ -0,0 +1,15 @@ +import RestrictedUserRoute from "discourse/routes/restricted-user"; + +export default RestrictedUserRoute.extend({ + model() { + return this.modelFor('user'); + }, + + renderTemplate() { + return this.render({ into: 'user' }); + }, + + setupController(controller, model) { + controller.setProperties({ model, newUsername: model.get('username') }); + } +}); diff --git a/app/assets/javascripts/discourse/routes/preferences.js.es6 b/app/assets/javascripts/discourse/routes/preferences.js.es6 index 1eb3d4b3e1..128a301731 100644 --- a/app/assets/javascripts/discourse/routes/preferences.js.es6 +++ b/app/assets/javascripts/discourse/routes/preferences.js.es6 @@ -15,6 +15,10 @@ export default RestrictedUserRoute.extend({ }, actions: { + showTwoFactorModal() { + showModal('second-factor-intro'); + }, + showAvatarSelector() { showModal('avatar-selector'); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-archive.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-archive.js.es6 index 7ebf279b29..ef304e9094 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages-archive.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages-archive.js.es6 @@ -1,3 +1,3 @@ import createPMRoute from "discourse/routes/build-private-messages-route"; -export default createPMRoute('archive', 'private-messages-archive'); +export default createPMRoute('archive', 'private-messages-archive', 'archive'); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-group-archive.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-group-archive.js.es6 index 19d84f9b7e..0594b9d112 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages-group-archive.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages-group-archive.js.es6 @@ -1,26 +1,41 @@ import createPMRoute from "discourse/routes/build-private-messages-route"; export default createPMRoute('groups', 'private-messages-groups').extend({ - model(params) { - const username = this.modelFor("user").get("username_lower"); - return this.store.findFiltered("topicList", { - filter: `topics/private-messages-group/${username}/${params.name}/archive` - }); - }, + groupName: null, - afterModel(model) { - const split = model.get("filter").split('/'); - const groupName = split[split.length-2]; - const groups = this.modelFor("user").get("groups"); - const group = _.first(groups.filterBy("name", groupName)); - this.controllerFor("user-private-messages").set("group", group); - }, + titleToken() { + const groupName = this.get('groupName'); - setupController(controller, model) { - this._super.apply(this, arguments); - const split = model.get("filter").split('/'); - const group = split[split.length-2]; - this.controllerFor("user-private-messages").set("groupFilter", group); - this.controllerFor("user-private-messages").set("archive", true); - } + if (groupName) { + return [ + `${groupName.capitalize()} ${I18n.t('user.messages.archive')}`, + I18n.t("user.private_messages") + ]; + }; + }, + + model(params) { + const username = this.modelFor("user").get("username_lower"); + return this.store.findFiltered("topicList", { + filter: `topics/private-messages-group/${username}/${params.name}/archive` + }); + }, + + afterModel(model) { + const split = model.get("filter").split('/'); + const groupName = split[split.length-2]; + this.set("groupName", groupName); + const groups = this.modelFor("user").get("groups"); + const group = _.first(groups.filterBy("name", groupName)); + this.controllerFor("user-private-messages").set("group", group); + }, + + setupController(controller, model) { + this._super.apply(this, arguments); + const split = model.get("filter").split('/'); + const group = split[split.length-2]; + this.controllerFor("user-private-messages").set("groupFilter", group); + this.controllerFor("user-private-messages").set("archive", true); + this.controllerFor("user-topics-list").subscribe(`/private-messages/group/${group}/archive`); + } }); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-group.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-group.js.es6 index 0b7f923f20..556f65fded 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages-group.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages-group.js.es6 @@ -1,24 +1,33 @@ import createPMRoute from "discourse/routes/build-private-messages-route"; export default createPMRoute('groups', 'private-messages-groups').extend({ - model(params) { - const username = this.modelFor("user").get("username_lower"); - return this.store.findFiltered("topicList", { - filter: `topics/private-messages-group/${username}/${params.name}` - }); - }, + groupName: null, - afterModel(model) { - const groupName = _.last(model.get("filter").split('/')); - const groups = this.modelFor("user").get("groups"); - const group = _.first(groups.filterBy("name", groupName)); - this.controllerFor("user-private-messages").set("group", group); - }, + titleToken() { + const groupName = this.get('groupName'); + if (groupName) return [groupName.capitalize(), I18n.t("user.private_messages")]; + }, - setupController(controller, model) { - this._super.apply(this, arguments); - const group = _.last(model.get("filter").split('/')); - this.controllerFor("user-private-messages").set("groupFilter", group); - this.controllerFor("user-private-messages").set("archive", false); - } + model(params) { + const username = this.modelFor("user").get("username_lower"); + return this.store.findFiltered("topicList", { + filter: `topics/private-messages-group/${username}/${params.name}` + }); + }, + + afterModel(model) { + const groupName = _.last(model.get("filter").split('/')); + this.set("groupName", groupName); + const groups = this.modelFor("user").get("groups"); + const group = _.first(groups.filterBy("name", groupName)); + this.controllerFor("user-private-messages").set("group", group); + }, + + setupController(controller, model) { + this._super.apply(this, arguments); + const group = _.last(model.get("filter").split('/')); + this.controllerFor("user-private-messages").set("groupFilter", group); + this.controllerFor("user-private-messages").set("archive", false); + this.controllerFor("user-topics-list").subscribe(`/private-messages/group/${group}`); + } }); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-index.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-index.js.es6 index d1e2bf3c27..127d64cfee 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages-index.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages-index.js.es6 @@ -1,3 +1,3 @@ import createPMRoute from "discourse/routes/build-private-messages-route"; -export default createPMRoute('index', 'private-messages'); +export default createPMRoute('index', 'private-messages', 'inbox'); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-sent.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-sent.js.es6 index b31c2f4c9f..2312be9179 100644 --- a/app/assets/javascripts/discourse/routes/user-private-messages-sent.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-private-messages-sent.js.es6 @@ -1,3 +1,3 @@ import createPMRoute from "discourse/routes/build-private-messages-route"; -export default createPMRoute('sent', 'private-messages-sent'); +export default createPMRoute('sent', 'private-messages-sent', 'sent'); diff --git a/app/assets/javascripts/discourse/routes/user-private-messages-tag.js.es6 b/app/assets/javascripts/discourse/routes/user-private-messages-tag.js.es6 new file mode 100644 index 0000000000..4545195332 --- /dev/null +++ b/app/assets/javascripts/discourse/routes/user-private-messages-tag.js.es6 @@ -0,0 +1,10 @@ +import createPMRoute from "discourse/routes/build-private-messages-route"; + +export default createPMRoute('tags', 'private-messages-tags').extend({ + model(params) { + const username = this.modelFor("user").get("username_lower"); + return this.store.findFiltered("topicList", { + filter: `topics/private-messages-tag/${username}/${params.id}` + }); + } +}); diff --git a/app/assets/javascripts/discourse/templates/badges/index.hbs b/app/assets/javascripts/discourse/templates/badges/index.hbs index 12adeb3d06..b0e2c030f9 100644 --- a/app/assets/javascripts/discourse/templates/badges/index.hbs +++ b/app/assets/javascripts/discourse/templates/badges/index.hbs @@ -8,10 +8,11 @@

{{bg.badgeGrouping.displayName}}

- - {{#each bg.badges as |b|}} - {{badge-card badge=b filterUser=b.has_badge username=currentUser.username}} - {{/each}} +
+ {{#each bg.badges as |b|}} + {{badge-card badge=b filterUser=b.has_badge username=currentUser.username}} + {{/each}} +
{{/each}}
diff --git a/app/assets/javascripts/discourse/templates/badges/show.hbs b/app/assets/javascripts/discourse/templates/badges/show.hbs index fab3e9a5aa..952bac8b4f 100644 --- a/app/assets/javascripts/discourse/templates/badges/show.hbs +++ b/app/assets/javascripts/discourse/templates/badges/show.hbs @@ -37,6 +37,7 @@ {{#if userBadges}}
{{#load-more selector=".badge-info" action="loadMore"}} +
{{#each userBadges as |ub|}} {{#user-info user=ub.user size="medium" class="badge-info" date=ub.granted_at}}
{{i18n 'badges.granted_on' date=(inline-date ub.granted_at)}}
@@ -45,6 +46,7 @@ {{/if}} {{/user-info}} {{/each}} +
{{/load-more}} {{#unless canLoadMore}} diff --git a/app/assets/javascripts/discourse/templates/bulk-tag.hbs b/app/assets/javascripts/discourse/templates/bulk-tag.hbs index 4ed48ddc2b..3a54edf758 100644 --- a/app/assets/javascripts/discourse/templates/bulk-tag.hbs +++ b/app/assets/javascripts/discourse/templates/bulk-tag.hbs @@ -1,5 +1,5 @@

{{i18n (concat "topics.bulk." title)}}

-

{{tag-chooser tags=tags categoryId=categoryId}}

+

{{tag-chooser filterPlaceholder=null tags=tags categoryId=categoryId}}

{{d-button action=action disabled=emptyTags label=(concat "topics.bulk." label)}} diff --git a/app/assets/javascripts/discourse/templates/components/badge-card.hbs b/app/assets/javascripts/discourse/templates/components/badge-card.hbs index 3c519d85dd..e211aaec06 100644 --- a/app/assets/javascripts/discourse/templates/components/badge-card.hbs +++ b/app/assets/javascripts/discourse/templates/components/badge-card.hbs @@ -5,13 +5,15 @@ {{d-icon "check"}} {{/if}}
-
- {{icon-or-image badge.icon}} -
- diff --git a/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs b/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs index c174d4a9b4..fbcad14b57 100644 --- a/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs +++ b/app/assets/javascripts/discourse/templates/components/basic-topic-list.hbs @@ -1,4 +1,13 @@ {{#conditional-loading-spinner condition=loading}} + {{#if hasIncoming}} +
+
+ {{count-i18n key="topic_count_" suffix="latest" count=incomingCount}} + {{i18n 'click_to_show'}} +
+
+ {{/if}} + {{#if topics}} {{topic-list showParticipants=showParticipants showPosters=showPosters diff --git a/app/assets/javascripts/discourse/templates/components/categories-and-latest-topics.hbs b/app/assets/javascripts/discourse/templates/components/categories-and-latest-topics.hbs index 1a32cea92d..be7ee3e4a3 100644 --- a/app/assets/javascripts/discourse/templates/components/categories-and-latest-topics.hbs +++ b/app/assets/javascripts/discourse/templates/components/categories-and-latest-topics.hbs @@ -3,5 +3,5 @@
- {{latest-topic-list topics=topics}} + {{categories-topic-list topics=topics filter="latest" class="latest-topic-list"}}
diff --git a/app/assets/javascripts/discourse/templates/components/categories-and-top-topics.hbs b/app/assets/javascripts/discourse/templates/components/categories-and-top-topics.hbs new file mode 100644 index 0000000000..4e84c4d631 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/categories-and-top-topics.hbs @@ -0,0 +1,7 @@ +
+ {{categories-only categories=categories}} +
+ +
+ {{categories-topic-list topics=topics filter="top" class="top-topic-list"}} +
diff --git a/app/assets/javascripts/discourse/templates/components/categories-boxes-with-topics.hbs b/app/assets/javascripts/discourse/templates/components/categories-boxes-with-topics.hbs index 434e18fe62..aae157c521 100644 --- a/app/assets/javascripts/discourse/templates/components/categories-boxes-with-topics.hbs +++ b/app/assets/javascripts/discourse/templates/components/categories-boxes-with-topics.hbs @@ -11,6 +11,7 @@ {{#if c.read_restricted}} {{d-icon 'lock'}} {{/if}} + {{category-title-before category=c}} {{c.name}} diff --git a/app/assets/javascripts/discourse/templates/components/categories-only.hbs b/app/assets/javascripts/discourse/templates/components/categories-only.hbs index 2fa528e3a9..282e5d520c 100644 --- a/app/assets/javascripts/discourse/templates/components/categories-only.hbs +++ b/app/assets/javascripts/discourse/templates/components/categories-only.hbs @@ -24,6 +24,7 @@
{{#each c.subcategories as |s|}} + {{category-title-before category=s}} {{category-link s hideParent="true"}} {{category-unread category=s}} diff --git a/app/assets/javascripts/discourse/templates/components/categories-topic-list.hbs b/app/assets/javascripts/discourse/templates/components/categories-topic-list.hbs new file mode 100644 index 0000000000..7761050a38 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/categories-topic-list.hbs @@ -0,0 +1,16 @@ +
+ {{i18n (concat "filters." filter ".title")}} +
+ +{{#if topics}} + {{#each topics as |t|}} + {{latest-topic-list-item topic=t}} + {{/each}} + +{{else}} +
+

{{i18n (concat "topics.none." filter)}}

+
+{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/category-title-before.hbs b/app/assets/javascripts/discourse/templates/components/category-title-before.hbs new file mode 100644 index 0000000000..af92a5ecd6 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/category-title-before.hbs @@ -0,0 +1 @@ +{{plugin-outlet name="category-title-before" noTags=true args=(hash category=category)}} diff --git a/app/assets/javascripts/discourse/templates/components/category-title-link.hbs b/app/assets/javascripts/discourse/templates/components/category-title-link.hbs index 3403e70d49..f27eac550c 100644 --- a/app/assets/javascripts/discourse/templates/components/category-title-link.hbs +++ b/app/assets/javascripts/discourse/templates/components/category-title-link.hbs @@ -1,10 +1,12 @@ - - {{#if category.read_restricted}} - {{d-icon 'lock'}} - {{/if}} - - {{dir-span category.name}} +{{category-title-before category=category}} + +
+ {{#if category.read_restricted}} + {{d-icon 'lock'}} + {{/if}} + {{dir-span category.name}} +
{{#if category.uploaded_logo.url}}
{{cdn-img src=category.uploaded_logo.url class="category-logo"}}
{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs b/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs index 6075400d90..0019b1d179 100644 --- a/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs +++ b/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs @@ -5,7 +5,8 @@ composerModel=model options=model.replyOptions canWhisper=canWhisper - action=model.action}} + action=model.action + tabindex=tabindex}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs index fa7b2a5653..2c9735d40c 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs @@ -21,8 +21,8 @@
diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs index df6544d770..c8ea053636 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs @@ -1,7 +1,11 @@

{{i18n 'category.tags_allowed_tags'}}

- {{tag-chooser placeholderKey="category.tags_placeholder" tags=category.allowed_tags everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + filterPlaceholder="category.tags_placeholder" + tags=category.allowed_tags + everyTag=true + unlimitedTagCount=true}}

{{i18n 'category.tags_allowed_tag_groups'}}

- {{tag-group-chooser placeholderKey="category.tag_groups_placeholder" tagGroups=category.allowed_tag_groups}} + {{tag-group-chooser tagGroups=category.allowed_tag_groups}}
diff --git a/app/assets/javascripts/discourse/templates/components/group-members-input.hbs b/app/assets/javascripts/discourse/templates/components/group-members-input.hbs index ad66499dbb..4f0ab683cc 100644 --- a/app/assets/javascripts/discourse/templates/components/group-members-input.hbs +++ b/app/assets/javascripts/discourse/templates/components/group-members-input.hbs @@ -16,7 +16,7 @@ {{#unless model.automatic}}
{{user-selector usernames=model.usernames - placeholderKey="admin.groups.selector_placeholder" + placeholderKey="groups.selector_placeholder" id="member-selector"}} {{#if addButton}} diff --git a/app/assets/javascripts/discourse/templates/components/latest-topic-list-contents.hbs b/app/assets/javascripts/discourse/templates/components/latest-topic-list-contents.hbs deleted file mode 100644 index 33b8ec95b9..0000000000 --- a/app/assets/javascripts/discourse/templates/components/latest-topic-list-contents.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#each topics as |t|}} - {{latest-topic-list-item topic=t}} -{{/each}} diff --git a/app/assets/javascripts/discourse/templates/components/latest-topic-list.hbs b/app/assets/javascripts/discourse/templates/components/latest-topic-list.hbs deleted file mode 100644 index 4b339c3f3f..0000000000 --- a/app/assets/javascripts/discourse/templates/components/latest-topic-list.hbs +++ /dev/null @@ -1,14 +0,0 @@ -
- {{i18n "filters.latest.title"}} -
- -{{#if topics}} - {{latest-topic-list-contents topics=topics tagName=""}} -
-{{else}} -
-

{{i18n "topics.none.latest"}}

-
-{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/login-buttons.hbs b/app/assets/javascripts/discourse/templates/components/login-buttons.hbs index 776ddff179..bdd430c4e7 100644 --- a/app/assets/javascripts/discourse/templates/components/login-buttons.hbs +++ b/app/assets/javascripts/discourse/templates/components/login-buttons.hbs @@ -1,3 +1,12 @@ {{#each buttons as |b|}} {{/each}} + +{{#if canLoginLocalWithEmail}} + {{d-button + action="emailLogin" + label="email_login.button_label" + disabled=processingEmailLink + icon="envelope-o" + class="login-with-email-button"}} +{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/queued-post.hbs b/app/assets/javascripts/discourse/templates/components/queued-post.hbs index de2709ca4a..99162937d1 100644 --- a/app/assets/javascripts/discourse/templates/components/queued-post.hbs +++ b/app/assets/javascripts/discourse/templates/components/queued-post.hbs @@ -52,7 +52,7 @@ {{/each}}
{{else if editTags}} - {{tag-chooser tags=buffered.tags categoryId=buffered.category_id width='100%'}} + {{tag-chooser tags=buffered.tags categoryId=buffered.category_id}} {{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs b/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs index f53a0e0c50..3ce9b3d84f 100644 --- a/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs +++ b/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs @@ -40,7 +40,12 @@
- {{tag-chooser tags=searchedTerms.tags blacklist=searchedTerms.tags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true" width="70%"}} + {{tag-chooser + tags=searchedTerms.tags + allowCreate=false + filterPlaceholder=null + everyTag=true + unlimitedTagCount=true}}
diff --git a/app/assets/javascripts/discourse/templates/components/second-factor-form.hbs b/app/assets/javascripts/discourse/templates/components/second-factor-form.hbs new file mode 100644 index 0000000000..a1d5e03340 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/second-factor-form.hbs @@ -0,0 +1,16 @@ + diff --git a/app/assets/javascripts/discourse/templates/components/second-factor-input.hbs b/app/assets/javascripts/discourse/templates/components/second-factor-input.hbs new file mode 100644 index 0000000000..6cd05a616b --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/second-factor-input.hbs @@ -0,0 +1,8 @@ +{{text-field value=value + type="tel" + pattern='[0-9]{6}' + maxlength='6' + id=inputId + autocorrect="off" + autocapitalize="off" + autofocus="autofocus"}} diff --git a/app/assets/javascripts/discourse/templates/components/topic-category.hbs b/app/assets/javascripts/discourse/templates/components/topic-category.hbs index edf11686ba..3c380e5af0 100644 --- a/app/assets/javascripts/discourse/templates/components/topic-category.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-category.hbs @@ -1,13 +1,13 @@ -{{#if topic.category.parentCategory}} - {{bound-category-link topic.category.parentCategory}} -{{/if}} -{{bound-category-link topic.category hideParent=true}} +{{#unless topic.isPrivateMessage}} + {{#if topic.category.parentCategory}} + {{bound-category-link topic.category.parentCategory}} + {{/if}} + {{bound-category-link topic.category hideParent=true}} +{{/unless}}
{{#if siteSettings.tagging_enabled}}
- {{#each topic.tags as |t|}} - {{discourse-tag t}} - {{/each}} + {{discourse-tags topic mode="list"}}
{{/if}} {{#if siteSettings.topic_featured_link_enabled}} diff --git a/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs b/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs index b38900977f..1e6a61eef5 100644 --- a/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs @@ -58,6 +58,14 @@ action=toggleArchiveMessage}} {{/if}} + {{#if showEditOnFooter}} + {{d-button class="edit-message" + title="topic.edit_message.help" + label="topic.edit_message.title" + icon="pencil" + action=editFirstPost}} + {{/if}} + {{plugin-outlet name="topic-footer-main-buttons-before-create" args=(hash topic=topic) tagName="" diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index 99745222cc..403a382f66 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -16,7 +16,7 @@ {{plugin-outlet name="composer-open" args=(hash model=model)}}
- {{composer-action-title model=model canWhisper=canWhisper}} + {{composer-action-title model=model canWhisper=canWhisper tabindex=8}} {{#unless site.mobileView}} {{#if whisperOrUnlistTopicText}} diff --git a/app/assets/javascripts/discourse/templates/emoji-picker.raw.hbs.erb b/app/assets/javascripts/discourse/templates/emoji-picker.raw.hbs.erb index c2ae260f77..b148675b34 100644 --- a/app/assets/javascripts/discourse/templates/emoji-picker.raw.hbs.erb +++ b/app/assets/javascripts/discourse/templates/emoji-picker.raw.hbs.erb @@ -5,7 +5,7 @@ <% JSON.parse(File.read("lib/emoji/groups.json")).each.with_index do |group, group_index| %>
- +
<% end %> diff --git a/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.hbs b/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.hbs index b0a3dbe21e..6fe4c5d48c 100644 --- a/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/components/basic-topic-list.hbs @@ -1,4 +1,13 @@ {{#conditional-loading-spinner condition=loading}} + {{#if hasIncoming}} +
+
+ {{count-i18n key="topic_count_" suffix="latest" count=incomingCount}} + {{i18n 'click_to_show'}} +
+
+ {{/if}} + {{#if topics}} diff --git a/app/assets/javascripts/discourse/templates/mobile/modal/login.hbs b/app/assets/javascripts/discourse/templates/mobile/modal/login.hbs index 0e191a6c8d..90d322ed68 100644 --- a/app/assets/javascripts/discourse/templates/mobile/modal/login.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/modal/login.hbs @@ -1,9 +1,16 @@ -{{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword action="login"}} +{{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword loginSecondFactor=loginSecondFactor action="login"}} {{#d-modal-body title="login.title" class="login-modal"}} - {{login-buttons action="externalLogin"}} + {{#if showLoginButtons}} + {{login-buttons + canLoginLocalWithEmail=canLoginLocalWithEmail + processingEmailLink=processingEmailLink + emailLogin='emailLogin' + externalLogin='externalLogin'}} + {{/if}} + {{#if canLoginLocal}} -
+
+ {{#if showLoginWithEmailLink}} + + + + + {{/if}} @@ -29,8 +46,9 @@
@@ -13,12 +20,22 @@ {{text-field value=loginName type="email" placeholderKey="login.email_placeholder" id="login-account-name" autocorrect="off" autocapitalize="off"}}
+ +
- + - {{text-field value=loginPassword type="password" id="login-account-password" maxlength="200"}}   + {{text-field value=loginPassword type="password" id="login-account-password" maxlength="200"}}  
- - + {{#second-factor-form}} + {{second-factor-input value=loginSecondFactor inputId='login-second-factor'}} + {{/second-factor-form}} {{/if}} {{authMessage}} @@ -43,11 +61,11 @@ {{/if}} {{#if canLoginLocal}} - + {{d-button action="login" + icon="unlock" + label=loginButtonLabel + disabled=loginDisabled + class='btn btn-large btn-primary'}} {{#if showSignupLink}} + {{d-button action="login" + icon="unlock" + label=loginButtonLabel + disabled=loginDisabled + class='btn btn-large btn-primary'}} {{#if showSignupLink}} + {{d-button action="submit" class='btn-primary' label='user.change_password.set_password'}} + {{/if}} {{#if errorMessage}}

diff --git a/app/assets/javascripts/discourse/templates/preferences-second-factor.hbs b/app/assets/javascripts/discourse/templates/preferences-second-factor.hbs new file mode 100644 index 0000000000..d8c58a6ff8 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/preferences-second-factor.hbs @@ -0,0 +1,119 @@ +
+ +
+
+

{{i18n 'user.second_factor.title'}}

+
+
+ + {{#if errorMessage}} +
+
+
{{errorMessage}}
+
+
+ {{/if}} + + {{#if model.second_factor_enabled}} + + +
+
+ {{second-factor-input value=secondFactorToken inputId='second-factor-token'}} +
+ +
+ {{i18n 'user.second_factor.disable_description'}} +
+
+ +
+
+ {{d-button action="disableSecondFactor" + class="btn btn-primary" + disabled=loading + label=submitButtonText}} +
+
+ {{else}} + {{#if loaded}} +
+
+ {{{i18n 'user.second_factor.enable_description'}}} + + {{#if displayOAuthWarning}} + {{i18n 'user.second_factor.oauth_enabled_warning'}} + {{/if}} +
+
+ +
+
+
+
+ {{{secondFactorImage}}} +
+
+ +

+ {{#if showSecondFactorKey}} + {{secondFactorKey}} + {{else}} + {{i18n 'user.second_factor.show_key_description'}} + {{/if}} +

+
+
+ +
+ + +
+ {{second-factor-input value=secondFactorToken inputId='second-factor-token'}} +
+
+ +
+
+ {{d-button action="enableSecondFactor" + class="btn btn-primary" + disabled=loading + label=submitButtonText}} +
+
+ {{else}} +
+ + +
+ {{text-field value=password + id="password" + type="password" + classNames="input-xxlarge" + autofocus="autofocus"}} +
+ +
+ {{i18n 'user.second_factor.confirm_password_description'}} +
+
+ +
+
+ {{d-button action="confirmPassword" + class="btn btn-primary" + disabled=loading + label=submitButtonText}} + + {{d-button action="resetPassword" + class="btn" + disabled=resetPasswordLoading + label='user.change_password.action'}} + + {{resetPasswordProgress}} +
+
+ {{/if}} + {{/if}} + +
diff --git a/app/assets/javascripts/discourse/templates/preferences/account.hbs b/app/assets/javascripts/discourse/templates/preferences/account.hbs index 38569dd9bd..60c3e58b42 100644 --- a/app/assets/javascripts/discourse/templates/preferences/account.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/account.hbs @@ -66,6 +66,32 @@ {{passwordProgress}}
+ +
+ + + + +
+ + {{#if model.second_factor_enabled}} + {{i18n 'user.second_factor.enabled_status'}} + {{d-icon 'check'}} + {{else}} + {{i18n 'user.second_factor.disabled_status'}} + {{d-icon 'times'}} + {{/if}} + + + {{#if isCurrentUser}} + {{#link-to "preferences.second-factor" class="btn btn-small btn-icon pad-left no-text"}} + {{d-icon "pencil"}} + {{/link-to}} + {{/if}} +
+
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/preferences/tags.hbs b/app/assets/javascripts/discourse/templates/preferences/tags.hbs index 1c24147a2f..af170058de 100644 --- a/app/assets/javascripts/discourse/templates/preferences/tags.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/tags.hbs @@ -5,25 +5,49 @@
- {{tag-chooser tags=model.watched_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.watched_tags + blacklist=selectedTags + filterPlaceholder=null + allowCreate=false + everyTag=true + unlimitedTagCount=true}}
{{i18n 'user.watched_tags_instructions'}}
- {{tag-chooser tags=model.tracked_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.tracked_tags + blacklist=selectedTags + filterPlaceholder=null + allowCreate=false + everyTag=true + unlimitedTagCount=true}}
{{i18n 'user.tracked_tags_instructions'}}
- {{tag-chooser tags=model.watching_first_post_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.watching_first_post_tags + blacklist=selectedTags + filterPlaceholder=null + allowCreate=false + everyTag=true + unlimitedTagCount=true}}
{{i18n 'user.watched_first_post_tags_instructions'}}
- {{tag-chooser tags=model.muted_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.muted_tags + blacklist=selectedTags + filterPlaceholder=null + allowCreate=false + everyTag=true + unlimitedTagCount=true}}
{{i18n 'user.muted_tags_instructions'}}
diff --git a/app/assets/javascripts/discourse/templates/tag-groups-show.hbs b/app/assets/javascripts/discourse/templates/tag-groups-show.hbs index 26fde8830f..dec3a0e3e9 100644 --- a/app/assets/javascripts/discourse/templates/tag-groups-show.hbs +++ b/app/assets/javascripts/discourse/templates/tag-groups-show.hbs @@ -3,12 +3,19 @@

- {{tag-chooser tags=model.tag_names everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.tag_names + everyTag=true + unlimitedTagCount=true}}
- {{tag-chooser tags=model.parent_tag_name everyTag="true" limit="1" placeholderKey="tagging.groups.parent_tag_placeholder"}} + {{tag-chooser + tags=model.parent_tag_name + everyTag=true + limit=1 + filterPlaceholder="tagging.groups.parent_tag_placeholder"}} {{i18n 'tagging.groups.parent_tag_description'}}
diff --git a/app/assets/javascripts/discourse/templates/tags/index.hbs b/app/assets/javascripts/discourse/templates/tags/index.hbs index 1036b77bc7..1cbccceaab 100644 --- a/app/assets/javascripts/discourse/templates/tags/index.hbs +++ b/app/assets/javascripts/discourse/templates/tags/index.hbs @@ -27,4 +27,6 @@ {{tag-list tags=tagGroup.tags sortProperties=sortProperties tagGroupName=tagGroup.name}} {{/each}} -{{tag-list tags=model sortProperties=sortProperties titleKey=otherTagsTitleKey}} +{{#if model}} + {{tag-list tags=model sortProperties=sortProperties titleKey=otherTagsTitleKey}} +{{/if}} diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index ef2d4912cc..4223c7cdd4 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -22,7 +22,7 @@ {{/if}} {{#if canEditTags}} - {{tag-chooser tags=buffered.tags categoryId=buffered.category_id}} + {{mini-tag-chooser filterable=true tags=buffered.tags categoryId=buffered.category_id}} {{/if}} {{plugin-outlet name="edit-topic" args=(hash model=model buffered=buffered)}} @@ -63,9 +63,7 @@ {{/if}} - {{#unless model.isPrivateMessage}} - {{topic-category topic=model class="topic-category"}} - {{/unless}} + {{topic-category topic=model class="topic-category"}} {{/if}} {{/topic-title}} {{/if}} @@ -253,6 +251,7 @@ showFlagTopic=(action "topicRouteAction" "showFlagTopic") showInvite=(action "topicRouteAction" "showInvite") toggleArchiveMessage=(action "toggleArchiveMessage") + editFirstPost=(action "editFirstPost") replyToPost=(action "replyToPost") }} {{else}} diff --git a/app/assets/javascripts/discourse/templates/user-topics-list.hbs b/app/assets/javascripts/discourse/templates/user-topics-list.hbs index 355e4aee77..0749c7c837 100644 --- a/app/assets/javascripts/discourse/templates/user-topics-list.hbs +++ b/app/assets/javascripts/discourse/templates/user-topics-list.hbs @@ -4,7 +4,10 @@ showParticipants=showParticipants showPosters=showPosters bulkSelectEnabled=bulkSelectEnabled - selected=selected}} + selected=selected + hasIncoming=hasIncoming + incomingCount=incomingCount + showInserted="showInserted"}} {{conditional-loading-spinner condition=model.loadingMore}} {{/load-more}} diff --git a/app/assets/javascripts/discourse/templates/user.hbs b/app/assets/javascripts/discourse/templates/user.hbs index a585892676..f501a8103a 100644 --- a/app/assets/javascripts/discourse/templates/user.hbs +++ b/app/assets/javascripts/discourse/templates/user.hbs @@ -53,7 +53,7 @@
    {{#if model.can_send_private_message_to_user}}
  • - + {{d-icon "envelope"}} {{i18n 'user.private_message'}} diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index 04fdd8902a..4d4a06c508 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -31,11 +31,17 @@ createWidget('header-notifications', { html(attrs) { const { user } = attrs; + let avatarAttrs = { + template: user.get('avatar_template'), + username: user.get('username') + }; + + if (this.siteSettings.enable_names) { + avatarAttrs.name = user.get('name'); + } + const contents = [ - avatarImg(this.settings.avatarSize, addExtraUserClasses(user, { - template: user.get('avatar_template'), - username: user.get('username') - })) + avatarImg(this.settings.avatarSize, addExtraUserClasses(user, avatarAttrs)) ]; const unreadNotifications = user.get('unread_notifications'); diff --git a/app/assets/javascripts/discourse/widgets/post.js.es6 b/app/assets/javascripts/discourse/widgets/post.js.es6 index 887493fd45..27f263c216 100644 --- a/app/assets/javascripts/discourse/widgets/post.js.es6 +++ b/app/assets/javascripts/discourse/widgets/post.js.es6 @@ -15,7 +15,7 @@ export function avatarImg(wanted, attrs) { // We won't render an invalid url if (!url || url.length === 0) { return; } - const title = formatUsername(attrs.username); + const title = attrs.name || formatUsername(attrs.username); let className = 'avatar' + ( attrs.extraClasses ? " " + attrs.extraClasses : "" @@ -123,6 +123,7 @@ createWidget('post-avatar', { body = avatarFor.call(this, this.settings.size, { template: attrs.avatar_template, username: attrs.username, + name: attrs.name, url: attrs.usernameUrl, className: 'main-avatar' }); diff --git a/app/assets/javascripts/discourse/widgets/topic-map.js.es6 b/app/assets/javascripts/discourse/widgets/topic-map.js.es6 index fbf6074e0a..d63b9169ce 100644 --- a/app/assets/javascripts/discourse/widgets/topic-map.js.es6 +++ b/app/assets/javascripts/discourse/widgets/topic-map.js.es6 @@ -37,7 +37,12 @@ createWidget('topic-participant', { }, html(attrs, state) { - const linkContents = [avatarImg('medium', { username: attrs.username, template: attrs.avatar_template })]; + const linkContents = [ + avatarImg('medium', { + username: attrs.username, + template: attrs.avatar_template, + name: attrs.name + })]; if (attrs.post_count > 2) { linkContents.push(h('span.post-count', attrs.post_count.toString())); @@ -67,7 +72,11 @@ createWidget('topic-map-summary', { [ h('h4', I18n.t('created_lowercase')), h('div.topic-map-post.created-at', [ - avatarFor('tiny', { username: attrs.createdByUsername, template: attrs.createdByAvatarTemplate }), + avatarFor('tiny', { + username: attrs.createdByUsername, + template: attrs.createdByAvatarTemplate, + name: attrs.createdByName + }), dateNode(attrs.topicCreatedAt) ]) ] @@ -76,7 +85,11 @@ createWidget('topic-map-summary', { h('a', { attributes: { href: attrs.lastPostUrl } }, [ h('h4', I18n.t('last_reply_lowercase')), h('div.topic-map-post.last-reply', [ - avatarFor('tiny', { username: attrs.lastPostUsername, template: attrs.lastPostAvatarTemplate }), + avatarFor('tiny', { + username: attrs.lastPostUsername, + template: attrs.lastPostAvatarTemplate, + name: attrs.lastPostName + }), dateNode(attrs.lastPostAt) ]) ]) diff --git a/app/assets/javascripts/locales/sl.js.erb b/app/assets/javascripts/locales/sl.js.erb new file mode 100644 index 0000000000..84c0777697 --- /dev/null +++ b/app/assets/javascripts/locales/sl.js.erb @@ -0,0 +1,3 @@ +//= depend_on 'client.sl.yml' +//= require locales/i18n +<%= JsLocaleHelper.output_locale(:sl) %> \ No newline at end of file diff --git a/app/assets/javascripts/pretty-text/censored-words.js.es6 b/app/assets/javascripts/pretty-text/censored-words.js.es6 index 9eac5ad68d..e2e52032d9 100644 --- a/app/assets/javascripts/pretty-text/censored-words.js.es6 +++ b/app/assets/javascripts/pretty-text/censored-words.js.es6 @@ -2,7 +2,7 @@ function escapeRegexp(text) { return text.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&').replace(/\*/g, "\S*"); } -export function censorFn(censoredWords, censoredPattern, replacementLetter, watchedWordsRegularExpressions) { +export function censorFn(censoredWords, replacementLetter, watchedWordsRegularExpressions) { let patterns = []; @@ -15,10 +15,6 @@ export function censorFn(censoredWords, censoredPattern, replacementLetter, watc } } - if (censoredPattern && censoredPattern.length > 0) { - patterns.push("(" + censoredPattern + ")"); - } - if (patterns.length) { let censorRegexp; @@ -64,6 +60,6 @@ export function censorFn(censoredWords, censoredPattern, replacementLetter, watc return function(t){ return t;}; } -export function censor(text, censoredWords, censoredPattern, replacementLetter) { - return censorFn(censoredWords, censoredPattern, replacementLetter)(text); +export function censor(text, censoredWords, replacementLetter) { + return censorFn(censoredWords, replacementLetter)(text); } diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-block.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-block.js.es6 index ddb92eefdb..ce9d468d48 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-block.js.es6 +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-block.js.es6 @@ -51,6 +51,8 @@ export function parseBBCodeTag(src, start, max, multiline) { return; } + tag = tag.toLowerCase(); + return {tag, length: tag.length+3, closing: true}; } return; diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/censored.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/censored.js.es6 index b9a993b8bf..e5587145d6 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/censored.js.es6 +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/censored.js.es6 @@ -24,17 +24,15 @@ function censorTree(state, censor) { export function setup(helper) { helper.registerOptions((opts, siteSettings) => { - opts.censoredPattern = siteSettings.censored_pattern; opts.watchedWordsRegularExpressions = siteSettings.watched_words_regular_expressions; }); helper.registerPlugin(md => { const words = md.options.discourse.censoredWords; - const patterns = md.options.discourse.censoredPattern; - if ((words && words.length > 0) || (patterns && patterns.length > 0)) { + if ((words && words.length > 0)) { const replacement = String.fromCharCode(9632); - const censor = censorFn(words, patterns, replacement, md.options.discourse.watchedWordsRegularExpressions); + const censor = censorFn(words, replacement, md.options.discourse.watchedWordsRegularExpressions); md.core.ruler.push('censored', state => censorTree(state, censor)); } }); diff --git a/app/assets/javascripts/pretty-text/oneboxer.js.es6 b/app/assets/javascripts/pretty-text/oneboxer.js.es6 index 90538994b7..aef317865d 100644 --- a/app/assets/javascripts/pretty-text/oneboxer.js.es6 +++ b/app/assets/javascripts/pretty-text/oneboxer.js.es6 @@ -43,12 +43,17 @@ function loadNext(ajax) { let timeoutMs = 150; let removeLoading = true; - const { url, refresh, $elem, categoryId } = loadingQueue.shift(); + const { url, refresh, $elem, categoryId, topicId } = loadingQueue.shift(); // Retrieve the onebox return ajax("/onebox", { dataType: 'html', - data: { url, refresh, category_id: categoryId }, + data: { + url, + refresh, + category_id: categoryId, + topic_id: topicId + }, cache: true }).then(html => { let $html = $(html); @@ -59,7 +64,7 @@ function loadNext(ajax) { if (result && result.jqXHR && result.jqXHR.status === 429) { timeoutMs = 2000; removeLoading = false; - loadingQueue.unshift({ url, refresh, $elem, categoryId }); + loadingQueue.unshift({ url, refresh, $elem, categoryId, topicId }); } else { failedCache[normalize(url)] = true; } @@ -74,7 +79,7 @@ function loadNext(ajax) { // Perform a lookup of a onebox based an anchor $element. // It will insert a loading indicator and remove it when the loading is complete or fails. -export function load({ elem , refresh = true, ajax, synchronous = false, categoryId }) { +export function load({ elem , refresh = true, ajax, synchronous = false, categoryId, topicId }) { const $elem = $(elem); // If the onebox has loaded or is loading, return @@ -98,7 +103,7 @@ export function load({ elem , refresh = true, ajax, synchronous = false, categor $elem.addClass('loading-onebox'); // Add to the loading queue - loadingQueue.push({ url, refresh, $elem, categoryId }); + loadingQueue.push({ url, refresh, $elem, categoryId, topicId }); // Load next url in queue if (synchronous) { diff --git a/app/assets/javascripts/pretty-text/white-lister.js.es6 b/app/assets/javascripts/pretty-text/white-lister.js.es6 index 6dd1523f53..8e00d76a7b 100644 --- a/app/assets/javascripts/pretty-text/white-lister.js.es6 +++ b/app/assets/javascripts/pretty-text/white-lister.js.es6 @@ -137,7 +137,12 @@ const DEFAULT_LIST = [ 'div.quote-controls', 'div.title', 'div[align]', - 'div[data-theme-*]', + 'div[data-*]', /* This may seem a bit much but polls does + it anyway and this is needed for themes, + special code in sanitizer handles data-* + nothing exists for data-theme-* and we + don't want to slow sanitize for this case + */ 'div[dir]', 'dl', 'dt', diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 index d28f7a1caa..23113a0a8f 100644 --- a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 @@ -1,22 +1,24 @@ import ComboBox from "select-kit/components/combo-box"; -import { ajax } from "discourse/lib/ajax"; -import { popupAjaxError } from 'discourse/lib/ajax-error'; +import Tags from "select-kit/mixins/tags"; import { default as computed } from "ember-addons/ember-computed-decorators"; import renderTag from "discourse/lib/render-tag"; -const { get, isEmpty, isPresent, run, makeArray } = Ember; +const { get, isEmpty, run, makeArray } = Ember; -export default ComboBox.extend({ +export default ComboBox.extend(Tags, { allowContentReplacement: true, + headerComponent: "mini-tag-chooser/mini-tag-chooser-header", pluginApiIdentifiers: ["mini-tag-chooser"], + attributeBindings: ["categoryId"], classNames: ["mini-tag-chooser"], classNameBindings: ["noTags"], verticalOffset: 3, filterable: true, - noTags: Ember.computed.empty("computedTags"), + noTags: Ember.computed.empty("selectedTags"), allowAny: true, - maximumSelectionSize: Ember.computed.alias("siteSettings.max_tags_per_topic"), caretUpIcon: Ember.computed.alias("caretIcon"), caretDownIcon: Ember.computed.alias("caretIcon"), + isAsync: true, + fullWidthOnMobile: true, init() { this._super(); @@ -30,17 +32,8 @@ export default ComboBox.extend({ noHref: true }); }); - }, - @computed("limitReached", "maximumSelectionSize") - maxContentRow(limitReached, count) { - if (limitReached) { - return I18n.t("select_kit.max_content_reached", { count }); - } - }, - - mutateAttributes() { - this.set("value", null); + this.set("limit", parseInt(this.get("limit") || this.get("siteSettings.max_tags_per_topic"))); }, @computed("limitReached") @@ -48,9 +41,9 @@ export default ComboBox.extend({ return limitReached ? null : "plus"; }, - @computed("computedTags.[]", "maximumSelectionSize") - limitReached(computedTags, maximumSelectionSize) { - if (computedTags.length >= maximumSelectionSize) { + @computed("selectedTags.[]", "limit") + limitReached(selectedTags, limit) { + if (selectedTags.length >= limit) { return true; } @@ -58,33 +51,10 @@ export default ComboBox.extend({ }, @computed("tags") - computedTags(tags) { + selectedTags(tags) { return makeArray(tags); }, - validateCreate(term) { - if (this.get("limitReached") || !this.site.get("can_create_tag")) { - return false; - } - - const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); - term = term.replace(filterRegexp, "").trim().toLowerCase(); - - if (!term.length || this.get("termMatchesForbidden")) { - return false; - } - - if (this.get("siteSettings.max_tag_length") < term.length) { - return false; - } - - return true; - }, - - validateSelect() { - return this.get("computedTags").length < this.get("siteSettings.max_tags_per_topic"); - }, - filterComputedContent(computedContent) { return computedContent; }, @@ -92,19 +62,22 @@ export default ComboBox.extend({ didRender() { this._super(); - this.$().on("click.mini-tag-chooser", ".selected-tag", (event) => { + $(".select-kit-body").on("click.mini-tag-chooser", ".selected-tag", (event) => { event.stopImmediatePropagation(); this.send("removeTag", $(event.target).attr("data-value")); }); + + $(".select-kit-header").on("focus.mini-tag-chooser", ".selected-name", (event) => { + event.stopImmediatePropagation(); + this.focus(event); + }); }, willDestroyElement() { this._super(); $(".select-kit-body").off("click.mini-tag-chooser"); - - const searchDebounce = this.get("searchDebounce"); - if (isPresent(searchDebounce)) { run.cancel(searchDebounce); } + $(".select-kit-header").off("focus.mini-tag-chooser"); }, didPressEscape(event) { @@ -122,6 +95,10 @@ export default ComboBox.extend({ this.didPressBackspace(event); }, + // we are relying on selectedTags and not on value + // to define the current selection + mutateValue() {}, + didPressBackspace() { if (!this.get("isExpanded")) { this.expand(); @@ -167,9 +144,9 @@ export default ComboBox.extend({ computeHeaderContent() { let content = this.baseHeaderComputedContent(); - const joinedTags = this.get("computedTags").join(", "); + const joinedTags = this.get("selectedTags").join(", "); - if (isEmpty(this.get("computedTags"))) { + if (isEmpty(this.get("selectedTags"))) { content.label = I18n.t("tagging.choose_for_topic"); } else { content.label = joinedTags; @@ -182,80 +159,61 @@ export default ComboBox.extend({ actions: { removeTag(tag) { - let tags = this.get("computedTags"); + let tags = this.get("selectedTags"); delete tags[tags.indexOf(tag)]; this.set("tags", tags.filter(t => t)); - this.set("content", []); - this.set("searchDebounce", run.debounce(this, this._searchTags, this.get("filter"), 250)); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); }, onExpand() { - if (isEmpty(this.get("content"))) { - this.set("searchDebounce", run.debounce(this, this._searchTags, this.get("filter"), 250)); + if (isEmpty(this.get("collectionComputedContent"))) { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); } }, onFilter(filter) { filter = isEmpty(filter) ? null : filter; - this.set("searchDebounce", run.debounce(this, this._searchTags, filter, 250)); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, filter, 200)); }, onSelect(tag) { - if (isEmpty(this.get("computedTags"))) { + if (isEmpty(this.get("selectedTags"))) { this.set("tags", makeArray(tag)); } else { - this.set("tags", this.get("computedTags").concat(tag)); + this.set("tags", this.get("selectedTags").concat(tag)); } - this.set("content", []); - this.set("searchDebounce", run.debounce(this, this._searchTags, this.get("filter"), 250)); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 50)); + + this.autoHighlight(); } }, - _searchTags(query) { - this.startLoading(); - - const self = this; - const selectedTags = makeArray(this.get("computedTags")).filter(t => t); - const sortTags = this.siteSettings.tags_sort_alphabetically; + prepareSearch(query) { const data = { q: query, - limit: this.siteSettings.max_tag_search_results, + limit: this.get("siteSettings.max_tag_search_results"), categoryId: this.get("categoryId") }; + if (this.get("selectedTags")) data.selected_tags = this.get("selectedTags").slice(0, 100); + if (!this.get("everyTag")) data.filterForInput = true; - if (selectedTags) { - data.selected_tags = selectedTags.slice(0, 100); + this.searchTags("/tags/filter/search", data, this._transformJson); + }, + + _transformJson(context, json) { + let results = json.results; + + context.set("termMatchesForbidden", json.forbidden ? true : false); + + if (context.get("siteSettings.tags_sort_alphabetically")) { + results = results.sort((a, b) => a.id > b.id); } - ajax(Discourse.getURL("/tags/filter/search"), { - quietMillis: 200, - cache: true, - dataType: "json", - data, - }).then(json => { - let results = json.results; + results = results.filter(r => !context.get("selectedTags").includes(r.id)); - self.set("termMatchesForbidden", json.forbidden ? true : false); - - if (sortTags) { - results = results.sort((a, b) => a.id > b.id); - } - - const content = results.map((result) => { - return { - id: result.text, - name: result.text, - count: result.count - }; - }).filter(c => !selectedTags.includes(c.id)); - - self.set("content", content); - self.stopLoading(); - this.autoHighlight(); - }).catch(error => { - self.stopLoading(); - popupAjaxError(error); - }); + return results.map(result => { + return { id: result.text, name: result.text, count: result.count }; + }); } }); diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser/mini-tag-chooser-header.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser/mini-tag-chooser-header.js.es6 new file mode 100644 index 0000000000..1efc5095a1 --- /dev/null +++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser/mini-tag-chooser-header.js.es6 @@ -0,0 +1,6 @@ +import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header"; + +export default SelectKitHeaderComponent.extend({ + layoutName: "select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header", + classNames: "mini-tag-chooser-header", +}); diff --git a/app/assets/javascripts/select-kit/components/multi-select.js.es6 b/app/assets/javascripts/select-kit/components/multi-select.js.es6 index e3e0ad9a13..da21f55b07 100644 --- a/app/assets/javascripts/select-kit/components/multi-select.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select.js.es6 @@ -8,14 +8,15 @@ import { export default SelectKitComponent.extend({ pluginApiIdentifiers: ["multi-select"], + layoutName: "select-kit/templates/components/multi-select", classNames: "multi-select", headerComponent: "multi-select/multi-select-header", - filterComponent: null, headerText: "select_kit.default_header_text", allowAny: true, allowInitialValueMutation: false, autoFilterable: true, selectedNameComponent: "multi-select/selected-name", + filterIcon: null, init() { this._super(); @@ -38,16 +39,22 @@ export default SelectKitComponent.extend({ _compute() { Ember.run.scheduleOnce("afterRender", () => { this.willComputeAttributes(); - let content = this.willComputeContent(this.get("content") || []); + let content = this.get("content") || []; + let asyncContent = this.get("asyncContent") || []; + content = this.willComputeContent(content); + asyncContent = this.willComputeAsyncContent(asyncContent); let values = this._beforeWillComputeValues(this.get("values")); content = this.computeContent(content); + asyncContent = this.computeAsyncContent(asyncContent); content = this._beforeDidComputeContent(content); + asyncContent = this._beforeDidComputeAsyncContent(asyncContent); values = this.willComputeValues(values); values = this.computeValues(values); values = this._beforeDidComputeValues(values); this._setHeaderComputedContent(); this._setCollectionHeaderComputedContent(); this.didComputeContent(content); + this.didComputeAsyncContent(asyncContent); this.didComputeValues(values); this.didComputeAttributes(); }); @@ -102,6 +109,19 @@ export default SelectKitComponent.extend({ }); }, + @computed("computedAsyncContent.[]", "computedValues.[]") + filteredAsyncComputedContent(computedAsyncContent, computedValues) { + computedAsyncContent = computedAsyncContent.filter(c => { + return !computedValues.includes(get(c, "value")); + }); + + if (this.get("limitMatches")) { + return computedAsyncContent.slice(0, this.get("limitMatches")); + } + + return computedAsyncContent; + }, + @computed("computedContent.[]", "computedValues.[]", "filter") filteredComputedContent(computedContent, computedValues, filter) { computedContent = computedContent.filter(c => { @@ -133,6 +153,16 @@ export default SelectKitComponent.extend({ }; }, + @computed("limit", "computedValues.[]") + limitReached(limit, computedValues) { + if (!limit) return false; + return computedValues.length >= limit; + }, + + validateSelect() { + return this._super() && !this.get("limitReached"); + }, + didPressBackspace(event) { this.expand(event); this.keyDown(event); @@ -178,16 +208,16 @@ export default SelectKitComponent.extend({ if ($lastSelectedValue.length === 0) { return; } if ($filterInput.not(":visible") && $lastSelectedValue.length > 0) { - $lastSelectedValue.click(); + $lastSelectedValue.trigger("backspace"); return false; } if ($filterInput.val() === "") { if ($filterInput.is(":focus")) { - if ($lastSelectedValue.length > 0) { $lastSelectedValue.click(); } + if ($lastSelectedValue.length > 0) { $lastSelectedValue.trigger("backspace"); } } else { if ($lastSelectedValue.length > 0) { - $lastSelectedValue.click(); + $lastSelectedValue.trigger("backspace"); } else { $filterInput.focus(); } @@ -217,20 +247,20 @@ export default SelectKitComponent.extend({ if (!this.get("renderedBodyOnce")) return; if (!isNone(this.get("highlightedValue"))) return; - if (isEmpty(this.get("filteredComputedContent"))) { + if (isEmpty(this.get("collectionComputedContent"))) { if (this.get("createRowComputedContent")) { this.send("highlight", this.get("createRowComputedContent")); } else if (this.get("noneRowComputedContent") && this.get("hasSelection")) { this.send("highlight", this.get("noneRowComputedContent")); } } else { - this.send("highlight", this.get("filteredComputedContent.firstObject")); + this.send("highlight", this.get("collectionComputedContent.firstObject")); } }); }, didSelect() { - this.focusFilterOrHeader(); + this.focus(); this.autoHighlight(); applyOnSelectPluginApiCallbacks( @@ -247,9 +277,10 @@ export default SelectKitComponent.extend({ this.set("highlightedValue", null); }, - didDeselect() { - this.focusFilterOrHeader(); + didDeselect(rowComputedContentItems) { + this.focus(); this.autoHighlight(); + this._boundaryActionHandler("onDeselect", rowComputedContentItems); }, actions: { diff --git a/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 index a6b672d50e..eff2b9716d 100644 --- a/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 @@ -28,6 +28,20 @@ export default Ember.Component.extend({ return null; }, + didInsertElement() { + this._super(); + + $(this.element).on("backspace.selected-name", () => { + this._handleBackspace(); + }); + }, + + willDestroyElement() { + this._super(); + + $(this.element).off("backspace.selected-name"); + }, + label: Ember.computed.or("computedContent.label", "title", "name"), name: Ember.computed.alias("computedContent.name"), @@ -39,8 +53,18 @@ export default Ember.Component.extend({ }), click() { - if (this.get("isLocked") === true) { return false; } - this.toggleProperty("isHighlighted"); + if (this.get("isLocked") === true) return false; + this.sendAction("deselect", [this.get("computedContent")]); return false; + }, + + _handleBackspace() { + if (this.get("isLocked") === true) return false; + + if (this.get("isHighlighted")) { + this.sendAction("deselect", [this.get("computedContent")]); + } else { + this.set("isHighlighted", true); + } } }); diff --git a/app/assets/javascripts/select-kit/components/select-kit.js.es6 b/app/assets/javascripts/select-kit/components/select-kit.js.es6 index b8bdf5c667..6f14d71b39 100644 --- a/app/assets/javascripts/select-kit/components/select-kit.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit.js.es6 @@ -32,6 +32,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi isFocused: false, isHidden: false, isLoading: false, + isAsync: false, renderedBodyOnce: false, renderedFilterOnce: false, tabindex: 0, @@ -69,8 +70,6 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi allowContentReplacement: false, collectionHeader: null, allowAutoSelectFirst: true, - maximumSelectionSize: null, - maxContentRow: null, init() { this._super(); @@ -81,7 +80,10 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi this.set("computedContent", []); if (this.site && this.site.isMobileDevice) { - this.setProperties({ filterable: false, autoFilterable: false }); + this.setProperties({ + filterable: isNone(this.get("filterable")) ? false : this.get("filterable"), + autoFilterable: isNone(this.get("autoFilterable")) ? false : this.get("filterable") + }); } if (this.get("nameChanges")) { @@ -91,11 +93,16 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi if (this.get("allowContentReplacement")) { this.addObserver(`content.[]`, this, this._compute); } + + if (this.get("isAsync")) { + this.addObserver(`asyncContent.[]`, this, this._compute); + } }, willDestroyElement() { this.removeObserver(`content.@each.${this.get("nameProperty")}`, this, this._compute); this.removeObserver(`content.[]`, this, this._compute); + this.removeObserver(`asyncContent.[]`, this, this._compute); }, willComputeAttributes() {}, @@ -114,6 +121,17 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi }, didComputeContent() {}, + willComputeAsyncContent(content) { return content; }, + computeAsyncContent(content) { return content; }, + _beforeDidComputeAsyncContent(content) { + content = applyContentPluginApiCallbacks(this.get("pluginApiIdentifiers"), content, this); + this.setProperties({ + computedAsyncContent: content.map(c => this.computeAsyncContentItem(c)) + }); + return content; + }, + didComputeAsyncContent() {}, + computeHeaderContent() { return this.baseHeaderComputedContent(); }, @@ -122,6 +140,15 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi return this.baseComputedContentItem(contentItem, options); }, + computeAsyncContentItem(contentItem, options) { + return this.computeContentItem(contentItem, options); + }, + + @computed("isAsync", "filteredAsyncComputedContent.[]", "filteredComputedContent.[]") + collectionComputedContent(isAsync, filteredAsyncComputedContent, filteredComputedContent) { + return isAsync ? filteredAsyncComputedContent : filteredComputedContent; + }, + validateCreate() { return true; }, validateSelect() { return true; }, @@ -155,13 +182,20 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi return false; }, - @computed("filter", "filteredComputedContent.[]") - noContentRow(filter, filteredComputedContent) { - if (filter.length > 0 && filteredComputedContent.length === 0) { + @computed("filter", "collectionComputedContent.[]") + noContentRow(filter, collectionComputedContent) { + if (filter.length > 0 && collectionComputedContent.length === 0) { return I18n.t("select_kit.no_content"); } }, + @computed("limitReached", "limit") + maxContentRow(limitReached, limit) { + if (limitReached) { + return I18n.t("select_kit.max_content_reached", { count: limit }); + } + }, + @computed("filter", "filterable", "autoFilterable", "renderedFilterOnce") shouldFilter(filter, filterable, autoFilterable, renderedFilterOnce) { if (renderedFilterOnce && filterable) return true; @@ -170,9 +204,10 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi return false; }, - @computed("filter", "computedContent") - shouldDisplayCreateRow(filter, computedContent) { - if (computedContent.map(c => c.value).includes(filter)) return false; + @computed("computedValue", "filter", "collectionComputedContent.[]", "limitReached") + shouldDisplayCreateRow(computedValue, filter, collectionComputedContent, limitReached) { + if (limitReached) return false; + if (collectionComputedContent.map(c => c.value).includes(filter)) return false; if (this.get("allowAny") && filter.length > 0 && this.validateCreate(filter)) return true; return false; }, diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 index fbfed55bc3..60e7ec9819 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 @@ -1,6 +1,15 @@ +import computed from "ember-addons/ember-computed-decorators"; +const { isEmpty } = Ember; + export default Ember.Component.extend({ layoutName: "select-kit/templates/components/select-kit/select-kit-filter", classNames: ["select-kit-filter"], classNameBindings: ["isFocused", "isHidden"], - isHidden: Ember.computed.not("shouldDisplayFilter") + isHidden: Ember.computed.not("shouldDisplayFilter"), + + @computed("placeholder", "hasSelection") + computedPlaceholder(placeholder, hasSelection) { + if (hasSelection) return ""; + return isEmpty(placeholder) ? "" : I18n.t(placeholder); + } }); diff --git a/app/assets/javascripts/select-kit/components/single-select.js.es6 b/app/assets/javascripts/select-kit/components/single-select.js.es6 index d442a9e2ae..a9e2d32979 100644 --- a/app/assets/javascripts/select-kit/components/single-select.js.es6 +++ b/app/assets/javascripts/select-kit/components/single-select.js.es6 @@ -4,6 +4,7 @@ const { get, isNone, isEmpty, isPresent, run } = Ember; export default SelectKitComponent.extend({ pluginApiIdentifiers: ["single-select"], + layoutName: "select-kit/templates/components/single-select", classNames: "single-select", computedValue: null, value: null, @@ -13,14 +14,20 @@ export default SelectKitComponent.extend({ _compute() { run.scheduleOnce("afterRender", () => { this.willComputeAttributes(); - let content = this.willComputeContent(this.get("content") || []); + let content = this.get("content") || []; + let asyncContent = this.get("asyncContent") || []; + content = this.willComputeContent(content); + asyncContent = this.willComputeAsyncContent(asyncContent); let value = this._beforeWillComputeValue(this.get("value")); content = this.computeContent(content); + asyncContent = this.computeAsyncContent(asyncContent); content = this._beforeDidComputeContent(content); + asyncContent = this._beforeDidComputeAsyncContent(asyncContent); value = this.willComputeValue(value); value = this.computeValue(value); value = this._beforeDidComputeValue(value); this.didComputeContent(content); + this.didComputeAsyncContent(asyncContent); this.didComputeValue(value); this.didComputeAttributes(); @@ -86,6 +93,19 @@ export default SelectKitComponent.extend({ }; }, + @computed("computedAsyncContent.[]", "computedValue") + filteredAsyncComputedContent(computedAsyncContent, computedValue) { + computedAsyncContent = computedAsyncContent.filter(c => { + return computedValue !== get(c, "value"); + }); + + if (this.get("limitMatches")) { + return computedAsyncContent.slice(0, this.get("limitMatches")); + } + + return computedAsyncContent; + }, + @computed("computedContent.[]", "computedValue", "filter", "shouldFilter") filteredComputedContent(computedContent, computedValue, filter, shouldFilter) { if (shouldFilter) { @@ -110,8 +130,8 @@ export default SelectKitComponent.extend({ !Ember.isNone(selectedComputedContent); }, - @computed("filter", "computedValue") - shouldDisplayCreateRow(filter, computedValue) { + @computed("computedValue", "filter", "collectionComputedContent.[]", "limitReached") + shouldDisplayCreateRow(computedValue, filter) { return this._super() && computedValue !== filter; }, diff --git a/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 new file mode 100644 index 0000000000..0f4b1f5e86 --- /dev/null +++ b/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 @@ -0,0 +1,107 @@ +import MultiSelectComponent from "select-kit/components/multi-select"; +import Tags from "select-kit/mixins/tags"; +import renderTag from "discourse/lib/render-tag"; +import computed from "ember-addons/ember-computed-decorators"; +const { get, isEmpty, run, makeArray } = Ember; + +export default MultiSelectComponent.extend(Tags, { + pluginApiIdentifiers: ["tag-chooser"], + classNames: "tag-chooser", + isAsync: true, + filterable: true, + filterPlaceholder: "tagging.choose_for_topic", + limit: null, + attributeBindings: ["categoryId"], + allowAny: Ember.computed.alias("allowCreate"), + + init() { + this._super(); + + if (this.get("allowCreate") !== false) { + this.set("allowCreate", this.site.get("can_create_tag")); + } + + this.set("termMatchesForbidden", false); + + this.set("templateForRow", (rowComponent) => { + const tag = rowComponent.get("computedContent"); + return renderTag(get(tag, "value"), { + count: get(tag, "originalContent.count"), + noHref: true + }); + }); + + if (!this.get("unlimitedTagCount")) { + this.set("limit", parseInt(this.get("limit") || this.get("siteSettings.max_tags_per_topic"))); + } + }, + + mutateValues(values) { + this.set("tags", values.filter(v => v)); + }, + + @computed("tags") + values(tags) { + return makeArray(tags); + }, + + @computed("tags") + content(tags) { + return makeArray(tags); + }, + + actions: { + onFilter(filter) { + this.expand(); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, filter, 200)); + }, + + onExpand() { + if (isEmpty(this.get("collectionComputedContent"))) { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); + } + }, + + onDeselect() { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); + }, + + onSelect() { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 50)); + } + }, + + prepareSearch(query) { + const selectedTags = makeArray(this.get("values")).filter(t => t); + + const data = { + q: query, + limit: this.get("siteSettings.max_tag_search_results"), + categoryId: this.get("categoryId") + }; + if (selectedTags) data.selected_tags = selectedTags.slice(0, 100); + if (!this.get("everyTag")) data.filterForInput = true; + + this.searchTags("/tags/filter/search", data, this._transformJson); + }, + + _transformJson(context, json) { + let results = json.results; + + context.set("termMatchesForbidden", json.forbidden ? true : false); + + if (context.get("blacklist")) { + results = results.filter(result => { + return !context.get("blacklist").includes(result.id); + }); + } + + if (context.get("siteSettings.tags_sort_alphabetically")) { + results = results.sort((a, b) => a.id > b.id); + } + + return results.map(result => { + return { id: result.text, name: result.text, count: result.count }; + }); + } +}); diff --git a/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 b/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 new file mode 100644 index 0000000000..07aa1a6d2b --- /dev/null +++ b/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 @@ -0,0 +1,79 @@ +import MultiSelectComponent from "select-kit/components/multi-select"; +import Tags from "select-kit/mixins/tags"; +import renderTag from "discourse/lib/render-tag"; +import computed from "ember-addons/ember-computed-decorators"; +const { get, isEmpty, run, makeArray } = Ember; + +export default MultiSelectComponent.extend(Tags, { + pluginApiIdentifiers: ["tag-group-chooser"], + classNames: ["tag-group-chooser", "tag-chooser"], + isAsync: true, + filterable: true, + filterPlaceholder: "category.tag_groups_placeholder", + limit: null, + allowAny: false, + + init() { + this._super(); + + this.set("templateForRow", (rowComponent) => { + const tag = rowComponent.get("computedContent"); + return renderTag(get(tag, "value"), { + count: get(tag, "originalContent.count"), + noHref: true + }); + }); + }, + + mutateValues(values) { + this.set("tagGroups", values.filter(v => v)); + }, + + @computed("tagGroups") + values(tagGroups) { + return makeArray(tagGroups); + }, + + @computed("tagGroups") + content(tagGroups) { + return makeArray(tagGroups); + }, + + actions: { + onFilter(filter) { + this.expand(); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, filter, 200)); + }, + + onExpand() { + if (isEmpty(this.get("collectionComputedContent"))) { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); + } + }, + + onDeselect() { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); + }, + + onSelect() { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 50)); + } + }, + + prepareSearch(query) { + const data = { + q: query, + limit: this.get("siteSettings.max_tag_search_results") + }; + + this.searchTags("/tag_groups/filter/search", data, this._transformJson); + }, + + _transformJson(context, json) { + let results = json.results.sort((a, b) => a.id > b.id); + + return results.map(result => { + return { id: result.text, name: result.text, count: result.count }; + }); + }, +}); diff --git a/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 b/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 index e3521cfdc0..b867011f35 100644 --- a/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 +++ b/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 @@ -58,15 +58,10 @@ export default Ember.Mixin.create({ this.setProperties({ isFocused: false }); }, - // force the component in a known default state - focus() { - Ember.run.schedule("afterRender", () => this.$header().focus()); - }, - // try to focus filter and fallback to header if not present - focusFilterOrHeader() { + focus() { Ember.run.schedule("afterRender", () => { - if ((this.site && this.site.isMobileDevice) || !this.$filterInput().is(":visible")) { + if ((!this.get("filterable")) || !this.$filterInput().is(":visible")) { this.$header().focus(); } else { this.$filterInput().focus(); @@ -77,10 +72,10 @@ export default Ember.Mixin.create({ expand() { if (this.get("isExpanded") === true) return; this.setProperties({ isExpanded: true, renderedBodyOnce: true, isFocused: true }); - this.focusFilterOrHeader(); - this.autoHighlight(); this._setCollectionHeaderComputedContent(); this._setHeaderComputedContent(); + this.focus(); + this.autoHighlight(); }, collapse() { @@ -111,11 +106,10 @@ export default Ember.Mixin.create({ const discourseHeader = $(".d-header")[0]; const discourseHeaderHeight = discourseHeader ? (discourseHeader.getBoundingClientRect().top + this._computedStyle(discourseHeader, "height")) : 0; const bodyHeight = this._computedStyle(this.$body()[0], "height"); - const windowWidth = $(window).width(); const componentHeight = this._computedStyle(this.get("element"), "height"); - const componentWidth = this._computedStyle(this.get("element"), "width"); const offsetTop = this.get("element").getBoundingClientRect().top; const offsetBottom = this.get("element").getBoundingClientRect().bottom; + const windowWidth = $(window).width(); if (this.get("fullWidthOnMobile") && (this.site && this.site.isMobileDevice)) { const margin = 10; @@ -124,28 +118,25 @@ export default Ember.Mixin.create({ options.width = windowWidth - margin * 2; options.maxWidth = options.minWidth = "unset"; } else { + const parentWidth = this.$scrollableParent().length ? this.$scrollableParent().width() : windowWidth; const bodyWidth = this._computedStyle(this.$body()[0], "width"); - if (this._isRTL()) { - const horizontalSpacing = this.get("element").getBoundingClientRect().right; - const hasHorizontalSpace = horizontalSpacing - (this.get("horizontalOffset") + bodyWidth) > 0; - if (hasHorizontalSpace) { - this.setProperties({ isLeftAligned: true, isRightAligned: false }); - options.left = bodyWidth + this.get("horizontalOffset"); - } else { - this.setProperties({ isLeftAligned: false, isRightAligned: true }); - options.right = - (bodyWidth - componentWidth + this.get("horizontalOffset")); - } + let marginToEdge; + if (this.$scrollableParent().length) { + marginToEdge = this.$().offset().left - this.$scrollableParent().offset().left; } else { - const horizontalSpacing = this.get("element").getBoundingClientRect().left; - const hasHorizontalSpace = (windowWidth - (this.get("horizontalOffset") + horizontalSpacing + bodyWidth) > 0); - if (hasHorizontalSpace) { - this.setProperties({ isLeftAligned: true, isRightAligned: false }); - options.left = this.get("horizontalOffset"); - } else { - this.setProperties({ isLeftAligned: false, isRightAligned: true }); - options.right = this.get("horizontalOffset"); - } + marginToEdge = this.get("element").getBoundingClientRect().left; + } + + const enoughMarginToOppositeEdge = (parentWidth - marginToEdge - bodyWidth + this.get("horizontalOffset")) > 0; + if (enoughMarginToOppositeEdge) { + this.setProperties({ isLeftAligned: true, isRightAligned: false }); + options.left = this.get("horizontalOffset"); + options.right = "unset"; + } else { + this.setProperties({ isLeftAligned: false, isRightAligned: true }); + options.left = "unset"; + options.right = this.get("horizontalOffset"); } } @@ -166,8 +157,8 @@ export default Ember.Mixin.create({ _applyFixedPosition() { if (this.get("isExpanded") !== true) return; - if (this.$fixedPlaceholder().length === 1) return; - if (this.$scrollableParent().length === 0) return; + if (this.$fixedPlaceholder().length) return; + if (!this.$scrollableParent().length) return; const width = this._computedStyle(this.get("element"), "width"); const height = this._computedStyle(this.get("element"), "height"); diff --git a/app/assets/javascripts/select-kit/mixins/tags.js.es6 b/app/assets/javascripts/select-kit/mixins/tags.js.es6 new file mode 100644 index 0000000000..ab5003616f --- /dev/null +++ b/app/assets/javascripts/select-kit/mixins/tags.js.es6 @@ -0,0 +1,56 @@ +const { run, get } = Ember; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + +export default Ember.Mixin.create({ + willDestroyElement() { + this._super(); + + const searchDebounce = this.get("searchDebounce"); + if (searchDebounce) run.cancel(searchDebounce); + }, + + searchTags(url, data, callback) { + const self = this; + + this.startLoading(); + + return ajax(Discourse.getURL(url), { + quietMillis: 200, + cache: true, + dataType: "json", + data + }).then(json => { + self.set("asyncContent", callback(self, json)); + }).catch(error => { + popupAjaxError(error); + }) + .finally(() => { + self.stopLoading(); + self.autoHighlight(); + }); + }, + + validateCreate(term) { + if (this.get("limitReached") || !this.site.get("can_create_tag")) { + return false; + } + + const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); + term = term.replace(filterRegexp, "").trim().toLowerCase(); + + if (!term.length || this.get("termMatchesForbidden")) { + return false; + } + + if (this.get("siteSettings.max_tag_length") < term.length) { + return false; + } + + if (this.get("asyncContent").map(c => get(c, "id")).includes(term)) { + return false; + } + + return true; + }, +}); diff --git a/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs b/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs new file mode 100644 index 0000000000..09799d3d57 --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header.hbs @@ -0,0 +1 @@ +{{input class="selected-name" value=label readonly="readonly"}} diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select.hbs new file mode 100644 index 0000000000..072a4bff86 --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/multi-select.hbs @@ -0,0 +1,50 @@ +{{#component headerComponent + tabindex=tabindex + isFocused=isFocused + isExpanded=isExpanded + computedContent=headerComputedContent + deselect=(action "deselect") + toggle=(action "toggle") + clearSelection=(action "clearSelection") + options=headerComponentOptions +}} + {{component filterComponent + icon=filterIcon + placeholder=filterPlaceholder + filter=filter + hasSelection=hasSelection + isLoading=isLoading + shouldDisplayFilter=shouldDisplayFilter + isFocused=isFocused + filterComputedContent=(action "filterComputedContent") + }} +{{/component}} + +
    + {{#if renderedBodyOnce}} + {{component collectionComponent + collectionHeaderComputedContent=collectionHeaderComputedContent + hasSelection=hasSelection + noneRowComputedContent=noneRowComputedContent + createRowComputedContent=createRowComputedContent + collectionComputedContent=collectionComputedContent + rowComponent=rowComponent + noneRowComponent=noneRowComponent + createRowComponent=createRowComponent + templateForRow=templateForRow + templateForNoneRow=templateForNoneRow + templateForCreateRow=templateForCreateRow + clearSelection=(action "clearSelection") + select=(action "select") + highlight=(action "highlight") + create=(action "create") + highlightedValue=highlightedValue + computedValue=computedValue + rowComponentOptions=rowComponentOptions + noContentRow=noContentRow + maxContentRow=maxContentRow + }} + {{/if}} +
    + +
    diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs index eb4aba3f06..71173acb30 100644 --- a/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs @@ -5,11 +5,6 @@ computedContent=selectedComputedContent}} {{/each}} - {{component "select-kit/select-kit-filter" - filterComputedContent=filterComputedContent - shouldDisplayFilter=shouldDisplayFilter - isFocused=isFocused - filter=filter - }} + {{yield}}
diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs index 6e2b1b362f..09b5adfa85 100644 --- a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs +++ b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs @@ -1,7 +1,3 @@
- - {{d-icon "times"}} - - {{badge}}
diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs index e4167b47b4..ffa6a8f3b9 100644 --- a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs +++ b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs @@ -1,16 +1,6 @@ {{#if headerContent}}
{{headerContent}}
{{/if}}
- {{#if isLocked}} - - {{d-icon "lock"}} - - {{else}} - - {{d-icon "times"}} - - {{/if}} - {{{label}}} diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs index 35d46789dd..12f5d05380 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs @@ -40,7 +40,7 @@ {{noContentRow}} {{else}} - {{#each filteredComputedContent as |computedContent|}} + {{#each collectionComputedContent as |computedContent|}} {{component rowComponent computedContent=computedContent highlightedValue=highlightedValue diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs index e7d1e96a7e..dfe505f5b1 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs @@ -1,7 +1,7 @@ {{input tabindex=-1 class="filter-input" - placeholder=placeholder + placeholder=computedPlaceholder key-up=filterComputedContent autocomplete="off" autocorrect="off" diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit.hbs b/app/assets/javascripts/select-kit/templates/components/single-select.hbs similarity index 86% rename from app/assets/javascripts/select-kit/templates/components/select-kit.hbs rename to app/assets/javascripts/select-kit/templates/components/single-select.hbs index cd92fb8312..8f67603603 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit.hbs +++ b/app/assets/javascripts/select-kit/templates/components/single-select.hbs @@ -1,13 +1,10 @@ {{component headerComponent tabindex=tabindex - shouldDisplayFilter=shouldDisplayFilter isFocused=isFocused isExpanded=isExpanded computedContent=headerComputedContent deselect=(action "deselect") toggle=(action "toggle") - isLoading=isLoading - filterComputedContent=(action "filterComputedContent") clearSelection=(action "clearSelection") options=headerComponentOptions }} @@ -17,8 +14,9 @@ filter=filter isLoading=isLoading icon=filterIcon + hasSelection=hasSelection shouldDisplayFilter=shouldDisplayFilter - placeholder=(i18n filterPlaceholder) + placeholder=filterPlaceholder isFocused=isFocused filterComputedContent=(action "filterComputedContent") }} @@ -29,7 +27,7 @@ hasSelection=hasSelection noneRowComputedContent=noneRowComputedContent createRowComputedContent=createRowComputedContent - filteredComputedContent=filteredComputedContent + collectionComputedContent=collectionComputedContent rowComponent=rowComponent noneRowComponent=noneRowComponent createRowComponent=createRowComponent diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js index 6a7bd345ec..03accd5982 100644 --- a/app/assets/javascripts/vendor.js +++ b/app/assets/javascripts/vendor.js @@ -14,7 +14,6 @@ //= require bootstrap-dropdown.js //= require bootstrap-modal.js //= require bootstrap-transition.js -//= require select2.js //= require div_resizer //= require caret_position //= require favcount.js diff --git a/app/assets/javascripts/wizard-vendor.js b/app/assets/javascripts/wizard-vendor.js index ecb8d2bd7e..4d07d0ceea 100644 --- a/app/assets/javascripts/wizard-vendor.js +++ b/app/assets/javascripts/wizard-vendor.js @@ -1,5 +1,4 @@ //= require template_include.js -//= require select2.js //= require jquery.ui.widget.js //= require jquery.fileupload.js //= require sweetalert.js diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 565e431744..9bf5830056 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -3,7 +3,6 @@ @import "vendor/pikaday"; @import "common/foundation/helpers"; @import "common/foundation/base"; -@import "vendor/select2"; @import "common/foundation/mixins"; @import "common/foundation/variables"; @import "common/select-kit/*"; diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 670d93d894..122a647925 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -205,7 +205,7 @@ $mobile-breakpoint: 700px; width: 460px; right: 0; z-index: z("dropdown"); - box-shadow: 0 2px 6px rgba(0,0,0, .8); + box-shadow: shadow("card"); margin-top: -2px; background-color: $secondary; padding: 12px 12px 5px; @@ -270,7 +270,7 @@ $mobile-breakpoint: 700px; @include clearfix; nav { float: left; - margin-left: 12px; + margin-left: 12px; } .nav.nav-pills { li.active { @@ -493,9 +493,6 @@ $mobile-breakpoint: 700px; width: 100% !important; // !important overrides hard-coded mobile width of 68px } } - .select2-container-multi .select2-choices { - border: none; - } } .setting-controls { float: left; @@ -519,16 +516,8 @@ $mobile-breakpoint: 700px; background-color: $secondary; border: 1px solid $primary-low; border-radius: 3px; - box-shadow: inset 0 1px 1px rgba(51, 51, 51, 0.3); transition: border linear 0.2s, box-shadow linear 0.2s; - li.select2-search-choice { - cursor: pointer; - .select2-search-choice-close { - content: "x" - } - } - li.sortable-placeholder { padding: 3px 5px 3px 18px; margin: 3px 0 3px 5px; @@ -832,14 +821,6 @@ section.details { .controls { margin-top: 10px; } - .select2-container { - width: 100%; - } - .select2-choices { - width: 100%; - border-color: dark-light-choose($primary-low-mid, $secondary-high); - } - .content-list { margin-right: 20px; } @@ -1765,6 +1746,10 @@ table#user-badges { label { font-size: $font-0; } + + &.content-list { + width: 100%; + } } .web-hook-events-listing { @@ -1797,7 +1782,7 @@ table#user-badges { } .watched-words-list { - margin-top: 40px; + margin-top: 20px; } .watched-word { display: inline-block; diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss index e3204d60f5..8ba862a92a 100644 --- a/app/assets/stylesheets/common/admin/customize.scss +++ b/app/assets/stylesheets/common/admin/customize.scss @@ -36,7 +36,7 @@ .admin-footer { margin-top: 20px; } - .select2-chosen, .color-schemes li { + .color-schemes li { .fa { margin-right: 6px; color: dark-light-choose($primary-medium, $secondary-medium); @@ -55,6 +55,21 @@ } } + .theme.settings { + .theme-setting { + padding-bottom: 0; + padding-top: 18px; + min-height: 35px; + } + .setting-label { + width: 25%; + h3 { + margin-top: 0; + margin-bottom: .5rem; + } + } + } + .current-style.maximized { position: fixed; top: 0; diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index 19e372f95c..6bc899d66f 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -17,6 +17,8 @@ } } + + .topic-list-item.visited, .latest-topic-list-item.visited, .category-topic-link.visited { @@ -111,6 +113,12 @@ padding-left: 5px; } + span.badge-category { + .category-name { + max-width: 150px; + } + } + .topic-excerpt { font-size: $font-down-1; margin-top: 5px; diff --git a/app/assets/stylesheets/common/base/about.scss b/app/assets/stylesheets/common/base/about.scss index 5debc06741..91bf2f1989 100644 --- a/app/assets/stylesheets/common/base/about.scss +++ b/app/assets/stylesheets/common/base/about.scss @@ -1,27 +1,20 @@ section.about { + margin-bottom: 40px; h3 { - margin-bottom: 10px; - margin-top: 10px; - } - - - p { - margin: 10px 0; + margin-bottom: 15px; } table { - margin-top: 20px; - width: 90%; + width: auto; th { text-align: left; } td, th { - padding: 10px 5px 5px 5px; - border-bottom: 1px solid lighten($primary, 70%); - line-height: $line-height-small; + padding: 10px; + border-bottom: 1px solid $primary-low; } td.title { diff --git a/app/assets/stylesheets/common/base/category-list.scss b/app/assets/stylesheets/common/base/category-list.scss index 62faa65cfe..ff00a12116 100644 --- a/app/assets/stylesheets/common/base/category-list.scss +++ b/app/assets/stylesheets/common/base/category-list.scss @@ -1,3 +1,19 @@ +.category-list { + table-layout: fixed; + .category-text-title { + display: flex; + align-items: baseline; + } + .category-name { + display: inline-block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: text-top; + line-height: $line-height-medium; + } +} + .category-boxes, .category-boxes-with-topics { display: flex; flex-wrap: wrap; @@ -91,6 +107,8 @@ line-height: $line-height-medium; text-align: center; color: $primary; + overflow: hidden; + text-overflow: ellipsis; } } @@ -104,6 +122,8 @@ h3 { font-size: $font-up-2; text-align: center; + overflow: hidden; + text-overflow: ellipsis; } .category-box-heading { diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss index 1a93bad72d..e050f69cef 100644 --- a/app/assets/stylesheets/common/base/compose.scss +++ b/app/assets/stylesheets/common/base/compose.scss @@ -19,7 +19,7 @@ z-index: z("composer","content"); transition: height 250ms ease, background 250ms ease, transform 250ms ease, max-width 250ms ease; background-color: $secondary; - box-shadow: 0 -1px 40px rgba(0,0,0, .12); + box-shadow: shadow("composer"); .reply-area { display: flex; @@ -189,7 +189,7 @@ .category-input { display: flex; flex: 1 0 35%; - margin: 0 0 5px 10px; + margin: 0 5px 5px 10px; @media screen and (max-width: 955px) { flex: 1 0 100%; margin-left: 0; @@ -223,16 +223,12 @@ .mini-tag-chooser { flex: 1 1 25%; - margin: 0 0 5px 5px; + margin: 0 0 5px 0; background: $secondary; @media all and (max-width: 900px) { margin: 0; flex: 1 1 100%; } - - &.select2-dropdown-open, &.select2-container-active { - border-color: $tertiary; - } } .wmd-controls { diff --git a/app/assets/stylesheets/common/base/crawler_layout.scss b/app/assets/stylesheets/common/base/crawler_layout.scss index 615d5b080f..9d072e7c70 100644 --- a/app/assets/stylesheets/common/base/crawler_layout.scss +++ b/app/assets/stylesheets/common/base/crawler_layout.scss @@ -6,7 +6,7 @@ body.crawler { z-index: z("max"); background-color: #fff; padding: 10px; - box-shadow: 0 2px 4px -1px rgba(0,0,0,0.25); + box-shadow: shadow("header"); } div.topic-list div[itemprop='itemListElement'] { padding: 10px 0; diff --git a/app/assets/stylesheets/common/base/directory.scss b/app/assets/stylesheets/common/base/directory.scss index 0a03b20871..65114a11b5 100644 --- a/app/assets/stylesheets/common/base/directory.scss +++ b/app/assets/stylesheets/common/base/directory.scss @@ -1,10 +1,10 @@ .directory { margin-bottom: 100px; - + .user-info { margin-bottom: 0; } - + .period-chooser { float: left; } @@ -18,16 +18,16 @@ .spinner { clear: both; } - + table { width: 100%; margin-bottom: 1em; - + td, th { padding: 0.5em; text-align: left; border-bottom: 1px solid $primary-low; - + .number, .time-read { font-size: $font-up-3; color: $primary-medium; @@ -36,21 +36,10 @@ white-space: nowrap; } } - - tr.me { - td { - background-color: dark-light-choose($highlight-low, $highlight-medium); - - .username a, .name, .title, .number, .time-read { - color: $primary-medium; - } - } - } - + th.sortable { cursor: pointer; white-space: nowrap; - width: 13%; .d-icon-heart { color: $love; @@ -59,10 +48,16 @@ .d-icon-chevron-down, .d-icon-chevron-up { margin-left: 0.5em; } - + &:hover { background-color: $primary-low; } } } + .me { + background-color: dark-light-choose($highlight-low, $highlight-medium); + .username a, .name, .title, .number, .time-read { + color: $primary-medium; + } + } } diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss index 613fa8301e..21060272c5 100644 --- a/app/assets/stylesheets/common/base/discourse.scss +++ b/app/assets/stylesheets/common/base/discourse.scss @@ -165,7 +165,7 @@ textarea { &:focus:required:invalid:focus { border-color: $danger; - box-shadow: 0 0 6px $danger; + box-shadow: shadow("focus-danger"); } } @@ -196,7 +196,7 @@ input { border-radius: 0; &:focus { border-color: $tertiary; - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); outline: 0; } } @@ -208,7 +208,7 @@ textarea { border: 1px solid $primary-medium; &:focus { border-color: $tertiary; - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); outline: 0; } } diff --git a/app/assets/stylesheets/common/base/emoji.scss b/app/assets/stylesheets/common/base/emoji.scss index f1a18dd556..7af43b61a7 100644 --- a/app/assets/stylesheets/common/base/emoji.scss +++ b/app/assets/stylesheets/common/base/emoji.scss @@ -5,14 +5,12 @@ img.emoji { } .emoji-picker { - box-shadow: 0 1px 5px rgba(0,0,0,0.4); background-clip: padding-box; z-index: z("modal","content"); position: fixed; display: none; flex-direction: row; height: 300px; - border-radius: 3px; color: dark-light-choose(darken($primary, 40%), blend-primary-secondary(90%)); background-color: $secondary; border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); diff --git a/app/assets/stylesheets/common/base/faqs.scss b/app/assets/stylesheets/common/base/faqs.scss index 5cdb93c116..c02f981538 100644 --- a/app/assets/stylesheets/common/base/faqs.scss +++ b/app/assets/stylesheets/common/base/faqs.scss @@ -1,32 +1,25 @@ .body-page { - - // Consistent vertical spacing - blockquote, - h1, - h2, - h3, - hr, - p, - pre, - ul, - ol, - table { - margin: 0 0 20px; - font-size: $font-up-1; - line-height: $line-height-large; + /* intended only for /about /faq, /guidelines, /tos, and /privacy */ + width: 100%; + max-width: 700px; + font-size: $font-up-1; + .mobile-view & { + font-size: $font-0; + margin-top: 20px; } + li { margin-bottom: 8px; } + .nav-pills { + margin-bottom: 20px; + } + ul:not(.nav-pills), ol:not(.nav-pills) { margin-left: 40px; } - /* intended only for /faq, /guidelines, /tos, and /privacy */ - width: 700px; - padding-left:20px; - } diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index b28652565f..b26186df6e 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -4,7 +4,7 @@ top: 0; z-index: z("header"); background-color: $header_background; - box-shadow: 0 2px 4px -1px rgba(0,0,0, .25); + box-shadow: shadow("header"); .docked & { position: fixed; @@ -33,11 +33,19 @@ .panel { float: right; position: relative; + display: flex; + align-items: center; + } + + .hamburger-panel, .user-menu { + width: 0; // Flexbox fix for Safari ≤ iOS11 + } + + .header-buttons { + margin-top: .2em; } .login-button, button.sign-up-button { - float: left; - margin-top: 7px; padding: 6px 10px; .fa { margin-right: 3px; } } @@ -46,14 +54,12 @@ margin-left: 7px; } - .d-header-icons { - float: right; - } } .header-dropdown-toggle, .drop-down, .panel-body { .flagged-posts, .queued-posts { background: $danger; + min-width: 6px; } } @@ -62,14 +68,17 @@ margin: 0 0 0 5px; list-style: none; - > li { float: left; } .icon { position: relative; - display: block; - padding: 3px; + display: flex; + align-items: center; + justify-content: center; + width: 2.2857em; + height: 2.2857em; + padding: .2143em; color: dark-light-choose(scale-color($header_primary, $lightness: 50%), $header_primary); text-decoration: none; cursor: pointer; @@ -77,11 +86,13 @@ border-left: 1px solid transparent; border-right: 1px solid transparent; transition: all linear .15s; - - + img.avatar { + width: 2.2857em; + height: 2.2857em; + } &:hover { color: $primary; - background-color: $primary-low; + background-color: $primary-low; border-top: 1px solid transparent; border-left: 1px solid transparent; border-right: 1px solid transparent; @@ -119,8 +130,7 @@ } .d-icon { - width: 32px; - height: 32px; + width: 100%; font-size: $font-up-4; line-height: $line-height-large; display: inline-block; diff --git a/app/assets/stylesheets/common/base/history.scss b/app/assets/stylesheets/common/base/history.scss index a1faeea65e..cb11152e06 100644 --- a/app/assets/stylesheets/common/base/history.scss +++ b/app/assets/stylesheets/common/base/history.scss @@ -24,6 +24,12 @@ } } + #revision-details { + padding: 5px; + margin-top: 10px; + border-bottom: 3px solid $primary-low; + } + #revisions .row:first-of-type { margin-top: 10px; } diff --git a/app/assets/stylesheets/common/base/login.scss b/app/assets/stylesheets/common/base/login.scss index 1c471da2f8..a5a68a84da 100644 --- a/app/assets/stylesheets/common/base/login.scss +++ b/app/assets/stylesheets/common/base/login.scss @@ -12,6 +12,18 @@ display: none; } +#login-form { + a { + color: dark-light-choose($primary-high, $secondary-low); + } + td { + padding-right: 5px; + } +} + +#new-account-link { + cursor: pointer; +} $label-width: 92px; $input-width: 220px; diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index ad27956a56..a398bd3002 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -1,6 +1,7 @@ .menu-panel.slide-in { position: fixed; right: 0; + box-shadow: shadow("header"); .panel-body { position: absolute; @@ -19,7 +20,7 @@ .menu-panel { border: 1px solid $primary-low; - box-shadow: 0 2px 2px rgba(0,0,0, .25); + box-shadow: shadow("menu-panel"); background-color: $secondary; z-index: z("header"); padding: 0.5em; @@ -44,7 +45,6 @@ overflow-y: auto; overflow-x: hidden; } - } .menu-links.columned { @@ -69,7 +69,6 @@ margin-left: 0.5em; color: dark-light-choose($primary-medium, $secondary-medium); } - } li.category-link { @@ -77,7 +76,7 @@ background-color: transparent; display: inline-flex; margin: 0.25em 0.5em; - width: 44%; + width: 43%; .badge-notification { color: dark-light-choose($primary-medium, $secondary-medium); background-color: transparent; @@ -89,8 +88,6 @@ .badge-wrapper { overflow: hidden; text-overflow: ellipsis; - display: inline-block; - max-width: 80%; &.bar, &.bullet { color: $primary; } @@ -100,7 +97,7 @@ padding-top: 2px; } span { - z-index: z("base") * -1; + z-index: z("base") * -1; } } } diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss index 2a5b765cd3..f4c70b4b19 100644 --- a/app/assets/stylesheets/common/base/modal.scss +++ b/app/assets/stylesheets/common/base/modal.scss @@ -17,7 +17,7 @@ overflow: auto; height: auto; background-color: $secondary; - box-shadow: 0 2px 2px rgba(0,0,0, .25); + box-shadow: shadow("card"); background-clip: padding-box; } @@ -27,7 +27,17 @@ } .modal-header { + display: flex; + align-items: center; + padding: 10px 15px; border-bottom: 1px solid $primary-low; + h3 { + margin-bottom: 0; + } + .modal-close { + order: 2; + margin-left: auto; + } } .modal-backdrop { @@ -45,8 +55,7 @@ .modal-backdrop, .modal-backdrop.fade.in { - -webkit-animation: fade .3s; - animation: fade .3s; + animation: fade .3s; opacity: .9; filter: alpha(opacity=90); } @@ -57,22 +66,12 @@ to { opacity: .9 } } -@-webkit-keyframes fade { - from { opacity: 0 } - to { opacity: .9 } -} - // slide in @keyframes slidein { from { transform: translateY(-20%); } to { transform: translateY(0); } } -@-webkit-keyframes slidein { - from { -webkit-transform: translateY(-20%); } - to { -webkit-transform: translateY(0); } -} - .modal-outer-container { display:table; table-layout: fixed; @@ -85,16 +84,20 @@ margin: 0 auto; background-color: $secondary; background-clip: padding-box; + box-shadow: shadow("modal"); .select-kit { width: 220px; + + &.tag-chooser { + width: 100%; + } } } .create-account.in .modal-inner-container, .login-modal.in .modal-inner-container { - -webkit-animation: slidein .3s; - animation: slidein .3s; + animation: slidein .3s; } @@ -112,6 +115,7 @@ z-index: z("modal","content"); overflow: auto; } + .modal-form { margin-bottom: 0; } @@ -157,6 +161,8 @@ .modal-body { box-sizing: border-box; width: 100%; + overflow-y: auto; + max-height: 400px; &.full-height-modal { max-height: calc(100vh - 150px); } @@ -293,11 +299,13 @@ } } -.invite-modal { +#invite-modal { overflow: visible; - .ember-text-field { - width: 550px; + + label { + margin-top: 7px; } + .optional { color: #9e9ea6; } @@ -411,3 +419,76 @@ margin-right: 0.5em; } } + +.change-timestamp { + + .date-picker { + width: 10em; + } + + #date-container { + .pika-single { + position: relative !important; // overriding another important + display: inline-block; + } + } + + input[type=time] { + width: 6em; + } + + form { + margin: 0; + } +} + +.flag-modal { + max-height: 450px; + .flag-action-type-details { + line-height: $line-height-large; + } +} + +.flag-message { + width: 95% !important; + margin: 0; +} + +.custom-message-length { + color: dark-light-choose($primary-low-mid, $secondary-high); + font-size: $font-down-1; +} + +.edit-category-modal { + .secure-category-options { + margin: 10px 0 0 16px; + .badge-list { + margin: 10px 0; + li { + margin: 0 4px 8px 0; + a { + color: dark-light-choose($primary-medium, $secondary-medium); + cursor: pointer; + } + a:hover { + color: dark-light-choose($primary-medium, $secondary-medium); + } + } + } + } +} + +.tabbed-modal { + .modal-body { + position: relative; + height: 350px; + } +} + + +.modal-tab { + position: absolute; + width: 95%; +} + + \ No newline at end of file diff --git a/app/assets/stylesheets/common/base/search.scss b/app/assets/stylesheets/common/base/search.scss index cf5b0e4b9a..46e6bb36f6 100644 --- a/app/assets/stylesheets/common/base/search.scss +++ b/app/assets/stylesheets/common/base/search.scss @@ -92,7 +92,7 @@ } .search-bar { - display: flex; + display: flex; margin-bottom: 10px; max-width: 780px; input { @@ -122,7 +122,7 @@ position: relative; margin: 10px 0 15px; max-width: 780px; - border-bottom: 3px solid $primary-low; + border-bottom: 3px solid $primary-low; width: 100%; .term { font-weight: bold; @@ -130,7 +130,7 @@ .result-count { float: left; - margin-bottom: 4px; + margin-bottom: 4px; span { line-height: $line-height-large; height: 28px; @@ -163,7 +163,7 @@ max-width: 780px; .search-advanced-options { border: 1px solid $primary-low; - padding: 0 20px; + padding: 0 20px; width: 100%; .date-picker-wrapper { vertical-align: top; @@ -183,6 +183,10 @@ font-weight: bold; } + .tag-chooser { + width: 70%; + } + .container { display: flex; flex: 1 1 100%; @@ -206,4 +210,4 @@ } } } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/common/base/share_link.scss b/app/assets/stylesheets/common/base/share_link.scss index 8a5f9145ca..2914ceb9a1 100644 --- a/app/assets/stylesheets/common/base/share_link.scss +++ b/app/assets/stylesheets/common/base/share_link.scss @@ -4,7 +4,7 @@ position: absolute; left: 20px; z-index: z("dropdown"); - box-shadow: 0 1px 5px rgba(0,0,0, .4); + box-shadow: shadow("card"); background-color: $secondary; padding: 6px 10px 10px 10px; width: 300px; diff --git a/app/assets/stylesheets/common/base/tagging.scss b/app/assets/stylesheets/common/base/tagging.scss index 11248496fc..eb93138ccf 100644 --- a/app/assets/stylesheets/common/base/tagging.scss +++ b/app/assets/stylesheets/common/base/tagging.scss @@ -25,8 +25,8 @@ .tag-count { font-size: $font-down-1; - vertical-align: middle; - line-height: $line-height-small; + vertical-align: middle; + line-height: $line-height-small; } } @@ -61,7 +61,7 @@ &.box, &.bullet { } - + &.box + .topic-header-extra, &.bullet + .topic-header-extra, &.bar + .topic-header-extra { @@ -69,25 +69,17 @@ } } -.add-tags .select2 { - margin: 0; -} - $tag-color: $primary-medium; .discourse-tag-count { font-size: $font-down-1; color: $tag-color; line-height: $line-height-small; - vertical-align: middle; -} - -.select2-result-label .discourse-tag { - margin-right: 0; + vertical-align: middle; } .discourse-tag { - max-width: 14em; + max-width: 14em; display: inline-block; white-space: nowrap; overflow: hidden; @@ -131,29 +123,13 @@ $tag-color: $primary-medium; } .d-header .topic-header-extra { - .discourse-tags { + .discourse-tags { display: inline-block; - font-size: $font-down-1; + font-size: $font-down-1; } .topic-featured-link { margin-left: 8px; } } -.select2-container-multi .select2-choices .select2-search-choice.discourse-tag-select2 { - -webkit-box-shadow: none; - box-shadow: none; - border: 0; - border-radius: 0; - background-color: transparent; - .discourse-tag { - padding: 4px; - &.box { - padding: 1px 8px; - margin: 3px 5px; - } - } -} - - .fps-result .add-full-page-tags { display: inline-block; } @@ -213,11 +189,6 @@ header .discourse-tag {color: $tag-color } width: 100%; max-width: 100%; margin: 5px 0; - ul.select2-choices { - max-height: 30px; - padding-left: 10px; - overflow-y: auto; - } } .title-wrapper .tag-chooser { @@ -278,11 +249,7 @@ header .discourse-tag {color: $tag-color } } } .group-tags-list .tag-chooser { - height: 250px !important; - ul.select2-choices { - height: 250px !important; // to fight with select2.scss's important - max-height: none; - } + width: 100%; } .btn {margin-left: 10px;} .saving { diff --git a/app/assets/stylesheets/common/base/topic-admin-menu.scss b/app/assets/stylesheets/common/base/topic-admin-menu.scss index 56f5ba16e6..a2ef7aaddd 100644 --- a/app/assets/stylesheets/common/base/topic-admin-menu.scss +++ b/app/assets/stylesheets/common/base/topic-admin-menu.scss @@ -10,10 +10,11 @@ .popup-menu { background-color: $secondary; - width: 205px; + width: 215px; padding: 10px; - border: 1px solid $primary-low; + border: 1px solid $primary-low; z-index: z("dropdown"); + box-shadow: shadow("card"); ul { list-style: none; @@ -25,7 +26,7 @@ } button { - width: 200px; + width: 100%; margin-bottom: 5px; i { width: 14px; diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index baee2ab77a..1ca8df2268 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -159,7 +159,9 @@ aside.quote { } .post-hidden { - opacity: 0.5; + opacity: 0.5; // opacity sets a new z-index context, + position: relative; // so the positioning is required, + z-index: z("base"); // otherwise post controls are stacked too low } .topic-post.staged { @@ -398,7 +400,11 @@ kbd background-color: $secondary; border: 1px solid $primary-low; border-radius: 3px; - box-shadow: 0 1px 0 rgba(0,0,0, .8); + box-shadow: shadow("kbd"); + background: dark-light-choose(#fafafa, #333); + border: 1px solid dark-light-choose(#ccc, #555); + border-bottom: medium none dark-light-choose(#fff, #000); + color: $primary; display: inline-block; font-size: $font-down-1; diff --git a/app/assets/stylesheets/common/base/topic.scss b/app/assets/stylesheets/common/base/topic.scss index 5b267622e3..0a76f198ab 100644 --- a/app/assets/stylesheets/common/base/topic.scss +++ b/app/assets/stylesheets/common/base/topic.scss @@ -35,13 +35,13 @@ .topic-admin-popup-menu.right-side { position: relative; - right: 35px; + right: 50px; } } #topic-progress-wrapper.docked { .topic-admin-popup-menu.right-side { - right: 40px; + right: 50px; } } @@ -105,15 +105,15 @@ a.badge-category { .private-message-glyph { margin: 5px 5px 0 0; } - .category-chooser, .tag-chooser { + .category-chooser, .mini-tag-chooser { flex: 1 1 49%; margin: 0 0 9px 0; @media all and (max-width: 500px) { - flex: 1 1 100%; + flex: 1 1 100%; } - + } - .tag-chooser { + .mini-tag-chooser { margin-left: 2%; @media all and (max-width: 500px) { margin-left: 0; @@ -122,6 +122,10 @@ a.badge-category { } } +.archetype-private_message #topic-title .edit-topic-title .tag-chooser { + margin-left: 19px; +} + .private_message { #topic-title { .edit-topic-title { @@ -200,7 +204,7 @@ a.badge-category { .post-links { margin-top: 1em; padding-top: 1em; - border-top: 1px solid $primary-low; + border-top: 1px solid $primary-low; li:last-of-type { margin-bottom: 1em; } diff --git a/app/assets/stylesheets/common/base/user-badges.scss b/app/assets/stylesheets/common/base/user-badges.scss index bc426d3e0a..63a31d2a15 100644 --- a/app/assets/stylesheets/common/base/user-badges.scss +++ b/app/assets/stylesheets/common/base/user-badges.scss @@ -121,11 +121,11 @@ .badge-card { position: relative; display: inline-block; - background-color: $primary-low; - margin-right: 5px; - margin-bottom: 10px; - box-shadow: 1px 1px 3px rgba(0.0, 0.0, 0.0, 0.2); - + background-color: $primary-very-low; + border: 1px solid $primary-low; + margin-bottom: 2vh; + transition: box-shadow .25s; + .check-display { position: absolute; left: 5px; @@ -143,17 +143,26 @@ .badge-contents { display: flex; - flex-direction: row; min-height: 128px; height: 100%; + + .badge-link { + display: flex; + flex: 1 1 auto; + padding: 0 10%; + @media screen and (max-width: 550px) { + padding: 0 5%; + } + } .badge-icon { - min-width: 90px; display: flex; + flex: 0 0 auto; + width: 1.23em; + margin-right: 5%; align-items: center; justify-content: center; - background-color: $primary-low; - font-size: 3em; + font-size: 3.5em; img { max-width: 80px; @@ -175,33 +184,75 @@ .badge-info { display: flex; + flex: 1 1 auto; align-items: center; - justify-content: center; - padding: 15px; + padding: 1em 1.5em 1em 0; color: $primary; + @media screen and (max-width: 600px) { + padding-right: 0; + } h3 { - margin-bottom: 0.4em; + margin-bottom: 0.25em; + font-size: $font-up-1; + @media screen and (min-width: 900px) { + font-size: $font-up-2; + } } } } + + &.medium { + vertical-align: top; + flex: 0 0 32%; + margin-right: calc(2% - 3px); + &:nth-of-type(3n) { + margin-right: 0; + } + @include small-width { + flex: 0 0 49%; + margin-right: 0; + } + @media screen and (max-width: 550px) { + flex: 0 0 100%; + } + &:hover { + box-shadow: shadow("card"); + } + &:active { + box-shadow: none; + } + @media all and (max-width: 320px) { + width: 100%; + } + } + &.large { + width: 100%; + align-self: flex-start; + @media screen and (min-width: 767px) { + max-width: calc(#{$large-width} / 2); + margin-right: 1.5em; + } + .badge-contents { + .badge-link { + padding: 0 5%; + width: 90%; + h3 { + font-size: $font-up-3; + } + } + } + } } -.badge-card.medium { - width: 350px; - vertical-align: top; -} - -@media all and (max-width: 320px) { - .badge-card.medium { - width: 100%; +.badges-granted { + display: flex; + flex-wrap: wrap; + @media screen and (max-width: $small-width) { + justify-content: space-between; } } -.badge-card.large { - width: 750px; -} - .badge-groups { margin: 20px 0; color: dark-light-choose($primary-medium, $secondary-medium); @@ -210,35 +261,65 @@ } } -.badge-grouping { +.badge-title { + .user-content { + padding: 0; + } +} + +.badge-group-list { margin-bottom: 1.5em; + display: flex; + flex-wrap: wrap; + @include small-width { + justify-content: space-between; + } + + .title { + width: 100%; + font-size: $font-up-1; + } } .show-badge-details { display: flex; + flex-wrap: wrap; flex-direction: row; margin-bottom: 2em; margin-top: 1em; .badge-grant-info { - display: flex; - align-items: center; - margin-left: 1em; &.hidden { display: none; } } + .grant-info-item { margin-bottom: 1em; color: dark-light-choose($primary-medium, $secondary-medium); + &:first-of-type { + margin-right: 10px; + } + } + + .badge-set-title { + padding: 1.5em; + border: 1px solid $primary-low; + + .user-content { + padding: 0; + .control-group { + margin-bottom: 1em; + } + } } .badge-title .form-horizontal .controls { - margin-left: 20px; + margin-left: 0; } - .close-btn { - margin: -20px 0 0 20px; + .form-horizontal { + margin-bottom: 0; } } diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index 8c39904e93..2eae6df77c 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -7,6 +7,14 @@ margin-right: 10px; } } + + .paginated-topics-list { + position: relative; + } + + .show-mores { + width: 100%; + } } .user-main { @@ -198,9 +206,9 @@ .staff-counters { background: $primary; - color: $secondary; + color: $secondary; display: flex; - padding: 10px; + padding: 10px; > div, > div a { display: flex; align-items: baseline; @@ -209,9 +217,9 @@ span { padding: 1px 6px; border-radius: 10px; - margin-right: 5px; + margin-right: 5px; } - } + } a { color: $secondary; @@ -384,11 +392,16 @@ .top-section, .top-sub-section { margin-bottom: 20px; + &.badges-section { + display: flex; + flex-wrap: wrap; + } } .stats-title { text-transform: uppercase; margin-bottom: 10px; + width: 100%; } .stats-section { @@ -404,7 +417,7 @@ &.linked-stat { // This makes the entire "box" (the li) clickable instead of a narrow area. padding: 0; a { - padding: 10px 14px; + padding: 10px 14px; width: 100%; height: 100%; display: block; @@ -519,15 +532,6 @@ .tag-notifications .tag-controls { margin-top: 24px; } - - .tags .select2-container-multi { - border: 1px solid $primary-low; - width: 540px; - border-radius: 0; - .select2-choices { - border: none; - } - } } .paginated-topics-list { @@ -565,3 +569,10 @@ } } +.qr-code-container { + display: flex; + .qr-code { + padding: 5px 5px 0 5px; + background-color: white; + } +} diff --git a/app/assets/stylesheets/common/components/badges.scss b/app/assets/stylesheets/common/components/badges.scss index d7123032ba..548ed6eeb0 100644 --- a/app/assets/stylesheets/common/components/badges.scss +++ b/app/assets/stylesheets/common/components/badges.scss @@ -20,8 +20,16 @@ font-weight: bold; white-space: nowrap; position: relative; + display: inline-flex; + align-items: baseline; .badge-category { + display: inline-flex; + align-items: baseline; + .category-name { + text-overflow: ellipsis; + overflow: hidden; + } .d-icon { margin-right: 3px; } @@ -98,7 +106,7 @@ span.badge-category { color: $primary; - padding: 3px; + padding: 1px 3px; overflow: hidden; text-overflow: ellipsis; @@ -211,11 +219,9 @@ .badge-group { @extend %badge; - padding: 4px 5px 2px 5px; + padding: 2px 5px; color: $primary; - text-shadow: 0 1px 0 rgba($primary, 0.1); background-color: $primary-low; border-color: $primary-low; font-size: $font-down-1; - box-shadow: inset 0 1px 0 rgba(0,0,0, 0.22); } diff --git a/app/assets/stylesheets/common/components/banner.scss b/app/assets/stylesheets/common/components/banner.scss index 47e09f43f3..c70a20bc97 100644 --- a/app/assets/stylesheets/common/components/banner.scss +++ b/app/assets/stylesheets/common/components/banner.scss @@ -5,7 +5,6 @@ #banner { padding: 10px; background: $tertiary-low; - box-shadow: 0 2px 4px -1px rgba(0,0,0, .25); color: $primary; z-index: z("base") + 1; overflow: auto; diff --git a/app/assets/stylesheets/common/components/buttons.scss b/app/assets/stylesheets/common/components/buttons.scss index 059f09d740..f0e2255a0d 100644 --- a/app/assets/stylesheets/common/components/buttons.scss +++ b/app/assets/stylesheets/common/components/buttons.scss @@ -119,8 +119,6 @@ .btn-social { color: #fff; - text-shadow: 0 1px 0 rgba($primary, 0.2); - box-shadow: inset 0 1px 0 rgba(0,0,0, 0.1); &:hover { color: #fff; } @@ -130,7 +128,7 @@ &:before { margin-right: 9px; font-family: FontAwesome; - font-size: 1.214em; + font-size: $font-0; } &.google, &.google_oauth2 { background: $google; diff --git a/app/assets/stylesheets/common/components/keyboard_shortcuts.scss b/app/assets/stylesheets/common/components/keyboard_shortcuts.scss index 13010953fe..643c314463 100644 --- a/app/assets/stylesheets/common/components/keyboard_shortcuts.scss +++ b/app/assets/stylesheets/common/components/keyboard_shortcuts.scss @@ -33,7 +33,7 @@ b { padding: 2px 6px; border-radius: 4px; - box-shadow: 0 2px 0 rgba(0,0,0,0.2),0 0 0 1px dark-light-choose(#fff,#000) inset; + box-shadow: shadow("kbd"); background: dark-light-choose(#fafafa, #333); border: 1px solid dark-light-choose(#ccc, #555); border-bottom: medium none dark-light-choose(#fff, #000); diff --git a/app/assets/stylesheets/common/components/user-info.scss b/app/assets/stylesheets/common/components/user-info.scss index c9884f5604..497c5f9439 100644 --- a/app/assets/stylesheets/common/components/user-info.scss +++ b/app/assets/stylesheets/common/components/user-info.scss @@ -13,7 +13,9 @@ float: left; width: 70%; padding-left: 5px; - font-size: $font-down-1; + @media screen and (max-width: 600px) { + font-size: $font-down-1; + } .name-line { white-space: nowrap; @@ -57,6 +59,7 @@ &.badge-info { min-height: 80px; + min-width: 250px; .granted-on { color: dark-light-choose($primary-medium, $secondary-medium); diff --git a/app/assets/stylesheets/common/foundation/variables.scss b/app/assets/stylesheets/common/foundation/variables.scss index 59d0dfb0bc..f028ab048d 100644 --- a/app/assets/stylesheets/common/foundation/variables.scss +++ b/app/assets/stylesheets/common/foundation/variables.scss @@ -103,6 +103,26 @@ $z-layers: ( @return map-deep-get($z-layers, $layers...); } + +// Box-shadow +// -------------------------------------------------- + +$box-shadow: ( + "modal": 0 8px 60px rgba(0, 0, 0, 0.6), + "composer": 0 -1px 40px rgba(0, 0, 0, 0.12), + "menu-panel": 0 6px 14px rgba(0, 0, 0, 0.15), + "card": 0 4px 14px rgba(0, 0, 0, 0.15), + "dropdown": 0 2px 3px 0 rgba(0, 0, 0, 0.2), + "header": 0 2px 4px -1px rgba(0, 0, 0, 0.25), + "kbd": (0 2px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px dark-light-choose(#fff, #000) inset), + "focus": 0 0 6px 0 $tertiary, + "focus-danger": 0 0 6px 0 $danger +); + +@function shadow($key) { + @return map-get($box-shadow, $key); +} + // Color utilities // -------------------------------------------------- diff --git a/app/assets/stylesheets/common/input_tip.scss b/app/assets/stylesheets/common/input_tip.scss index 0d5de0c8ab..7a57d21069 100644 --- a/app/assets/stylesheets/common/input_tip.scss +++ b/app/assets/stylesheets/common/input_tip.scss @@ -9,7 +9,7 @@ &.bad { background: $danger-medium; color: white; - box-shadow: 1px 1px 5px rgba(0,0,0, .7); + box-shadow: shadow("dropdown"); } &.hide, &.good { display: none; diff --git a/app/assets/stylesheets/common/select-kit/category-drop.scss b/app/assets/stylesheets/common/select-kit/category-drop.scss index 391f80d8ae..83f5efdfcf 100644 --- a/app/assets/stylesheets/common/select-kit/category-drop.scss +++ b/app/assets/stylesheets/common/select-kit/category-drop.scss @@ -17,11 +17,14 @@ } &.bar.has-selection .category-drop-header, - &.box.has-selection .category-drop-header, &.none.has-selection .category-drop-header { padding: 5px 10px; } + &.box.has-selection .category-drop-header { + padding: 2.5px 10px; + } + &.bullet.has-selection .category-drop-header { padding: 5px 10px; } @@ -49,8 +52,7 @@ &.is-expanded .category-drop-header { border: 1px solid $tertiary; - -webkit-box-shadow: $tertiary 0 0 6px 0px; - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); } .select-kit-collection { @@ -82,8 +84,7 @@ width: auto; min-width: 300px; border-radius: 0; - -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.4); - box-shadow: 0 2px 2px rgba(0,0,0,0.4); + box-shadow: shadow("dropdown"); } .select-kit-row { diff --git a/app/assets/stylesheets/common/select-kit/combo-box.scss b/app/assets/stylesheets/common/select-kit/combo-box.scss index e69bfdcdcd..b9ce024633 100644 --- a/app/assets/stylesheets/common/select-kit/combo-box.scss +++ b/app/assets/stylesheets/common/select-kit/combo-box.scss @@ -32,8 +32,7 @@ &.is-focused { border: 1px solid $tertiary; - -webkit-box-shadow: $tertiary 0 0 6px 0px; - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); } } @@ -47,8 +46,7 @@ &.is-highlighted { .select-kit-header { border: 1px solid $tertiary; - -webkit-box-shadow: $tertiary 0 0 6px 0px; - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); } } @@ -56,14 +54,12 @@ .select-kit-wrapper { display: block; border: 1px solid $tertiary; - -webkit-box-shadow: $tertiary 0 0 6px 0px; - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); } .select-kit-header { border-color: transparent; - -webkit-box-shadow: none; - box-shadow: none; + box-shadow: none; } .select-kit-body { diff --git a/app/assets/stylesheets/common/select-kit/composer-actions.scss b/app/assets/stylesheets/common/select-kit/composer-actions.scss index 7dfefaa08a..87b81f371f 100644 --- a/app/assets/stylesheets/common/select-kit/composer-actions.scss +++ b/app/assets/stylesheets/common/select-kit/composer-actions.scss @@ -14,7 +14,7 @@ margin: 0!important; } - &:hover { + &:hover, &:focus { background: $primary-low; } } diff --git a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss index 98f7173e36..c229b00422 100644 --- a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss +++ b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss @@ -16,8 +16,7 @@ .select-kit-body { border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); background-clip: padding-box; - -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.4); - box-shadow: 0 2px 2px rgba(0,0,0,0.4); + box-shadow: shadow("dropdown"); max-width: 300px; width: 300px; } diff --git a/app/assets/stylesheets/common/select-kit/legacy-combo-box.scss b/app/assets/stylesheets/common/select-kit/legacy-combo-box.scss deleted file mode 100644 index 3ba9f7ad94..0000000000 --- a/app/assets/stylesheets/common/select-kit/legacy-combo-box.scss +++ /dev/null @@ -1,88 +0,0 @@ -// DO NOT MODIFY -// TODO: remove when all select2 instances are gone -.select2-results .select2-highlighted { - background: $highlight-medium; - color: $primary; -} - -.select2-drop { - .badge-category { - display: inline-block; - } - .topic-count { - font-size: $font-down-2; - color: $primary; - display: inline-block; - } - .highlighted .topic-count, .select2-highlighted .category-desc { - color: $primary; - } - .category-desc { - color: $primary; - font-size: $font-down-1; - line-height: 16px; - } -} - -.select2-drop { - background: $secondary; - .d-icon { - color: dark-light-choose($primary-medium, $secondary-medium); - } -} - -.select2-search input { - background: image-url("select2.png") no-repeat 100% -22px, $secondary 0 0 -} - -.select2-container { - min-width: 200px; - - &.select2-dropdown-open { - border: 0; - margin-bottom: 2px; - } - &.select2-container-active { - border-color: $tertiary; - } - &.select2-container-disabled .select2-chosen { - color: blend-primary-secondary(50%); - } -} - -.select2-container-multi .select2-choices .select2-search-field input.select2-active { - background: $secondary image-url("select2-spinner.gif") no-repeat 100% !important; -} - -.select2-container-multi .select2-choices { - border: 1px solid $primary-medium; -} - -.select2-container a.select2-choice { - background: $secondary; - border-radius: 3px; - border-color: $secondary; - color: $primary; -} - -.select2-dropdown-open a.select2-choice { - box-shadow: none; - border-radius: 3px 3px 0 0; - border-color: $tertiary; -} -.select2-drop { - color: $primary; -} -.select2-drop-active { - border: 1px solid $tertiary; - border-top: 0; -} - -.select2-container-active { - box-shadow: $tertiary 0 0 6px 0px; -} - -.select2-results .select2-no-results, .select2-results .select2-searching, .select2-results .select2-selection-limit { - background: $secondary; - color: $primary; -} diff --git a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss index 956c9a8064..1d6b6dce6b 100644 --- a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss +++ b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss @@ -7,8 +7,6 @@ &.is-expanded { .select-kit-header { border: 1px solid $tertiary; - -webkit-box-shadow: $tertiary 0 0 6px 0px; - box-shadow: $tertiary 0 0 6px 0px; } } @@ -21,7 +19,19 @@ .select-kit-body { max-width: 500px; width: 500px; - border: 1px solid $primary-low; + border: 1px solid $tertiary; + box-shadow: shadow("focus") + } + + .select-kit-header { + .selected-name { + margin: 0; + border: 0; + padding: 0; + outline: none; + box-shadow: none; + cursor: pointer; + } } .select-kit-filter { @@ -59,7 +69,7 @@ } .selected-tag { - background: $primary-very-low; + background: $primary-low; padding: 2px 4px; margin: 2px; border: 0; diff --git a/app/assets/stylesheets/common/select-kit/multi-select.scss b/app/assets/stylesheets/common/select-kit/multi-select.scss index de86261025..91ef4c1539 100644 --- a/app/assets/stylesheets/common/select-kit/multi-select.scss +++ b/app/assets/stylesheets/common/select-kit/multi-select.scss @@ -17,6 +17,7 @@ .select-kit-filter { border: 0; + flex: 1; } .multi-select-header { @@ -24,7 +25,7 @@ border: 1px solid $primary-medium; &.is-focused { - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); border-radius: 0; } } @@ -40,7 +41,7 @@ .multi-select-header { border-radius: 0; border-bottom: 1px solid transparent; - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); } } @@ -48,7 +49,7 @@ .select-kit-wrapper { display: block; border: 1px solid $tertiary; - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); border-radius: 0; } @@ -79,7 +80,7 @@ justify-content: space-between; flex-wrap: wrap; flex-direction: row; - margin: 2.5px; + margin: 2px; } .filter { @@ -89,6 +90,7 @@ min-width: 50px; padding: 0; outline: none; + flex: 1; .filter-input, .filter-input:focus { border: none; @@ -101,7 +103,6 @@ margin: 0; outline: 0; border: 0; - -webkit-box-shadow: none; box-shadow: none; border-radius: 0; height: 21px; @@ -118,17 +119,36 @@ .color-preview { height: 5px; margin: 0 2px 2px 2px; - border-radius: 5px; display: flex; width: 100%; } } + .selected-category { + .badge-wrapper { + &.bullet { + margin-right: 2.5px; + } + + margin: auto 2.5px; + padding: 2px 4px; + line-height: $line-height-medium; + display: flex; + flex: 1; + align-items: center; + + &:after { + content: '\f00d'; + color: $primary-low-mid; + font-family: 'FontAwesome'; + font-size: $font-down-2; + margin-left: 5px; + } + } + } + .selected-name { color: $primary; - border: 1px solid $primary-medium; - border-radius: 3px; - box-shadow: 0 0 2px $secondary inset, 0 1px 0 rgba(0,0,0,0.05); background-clip: padding-box; -webkit-touch-callout: none; user-select: none; @@ -149,55 +169,32 @@ } .body { - width: 100%; - display: inline-flex; + display: flex; align-items: center; - - .locked-icon, .delete-icon { - justify-content: center; - align-items: center; - display: inline-flex; - height: 21px; - width: 21px; - - .d-icon { - color: $primary-medium; - cursor: pointer; - font-size: 1em; - margin: 0; - - &:hover { - color: $primary; - } - } - } + flex: 1; } .name { - padding: 0 5px; + padding: 2px 4px; line-height: $line-height-medium; + + &:after { + content: '\f00d'; + color: $primary-low-mid; + font-family: 'FontAwesome'; + font-size: $font-down-2; + } + + &:hover { + &:after { + color: $danger; + } + } } &.is-highlighted { box-shadow: 0 0 2px $danger, 0 1px 0 rgba(0,0,0,0.05); } - - .locked-icon, .delete-icon { - justify-content: center; - align-items: center; - width: 21px; - height: 21px; - display: inline-flex; - .d-icon { - color: $primary-medium; - cursor: pointer; - font-size: $font-0; - - &:hover { - color: $primary; - } - } - } } } } diff --git a/app/assets/stylesheets/common/select-kit/select-kit.scss b/app/assets/stylesheets/common/select-kit/select-kit.scss index b5c1597ef3..3f61e5e260 100644 --- a/app/assets/stylesheets/common/select-kit/select-kit.scss +++ b/app/assets/stylesheets/common/select-kit/select-kit.scss @@ -202,6 +202,7 @@ border-radius: inherit; -webkit-overflow-scrolling: touch; margin: 0; + padding: 0; max-height: 200px; .select-kit-collection { diff --git a/app/assets/stylesheets/common/select-kit/tag-chooser.scss b/app/assets/stylesheets/common/select-kit/tag-chooser.scss new file mode 100644 index 0000000000..cc51e23ef5 --- /dev/null +++ b/app/assets/stylesheets/common/select-kit/tag-chooser.scss @@ -0,0 +1,14 @@ +.select-kit { + &.multi-select { + &.tag-chooser { + .select-kit-row { + display: flex; + align-items: center; + + .discourse-tag-count { + margin-left: 5px; + } + } + } + } +} diff --git a/app/assets/stylesheets/common/select-kit/tag-drop.scss b/app/assets/stylesheets/common/select-kit/tag-drop.scss index cc647cf79b..84b6b7b1f0 100644 --- a/app/assets/stylesheets/common/select-kit/tag-drop.scss +++ b/app/assets/stylesheets/common/select-kit/tag-drop.scss @@ -19,8 +19,7 @@ &.is-expanded .tag-drop-header { border: 1px solid $tertiary; - -webkit-box-shadow: $tertiary 0 0 6px 0px; - box-shadow: $tertiary 0 0 6px 0px; + box-shadow: shadow("focus"); } .select-kit-collection { @@ -46,12 +45,15 @@ } } + .select-kit-filter .filter-input { + width: auto; + } + .select-kit-body { width: auto; min-width: 150px; border-radius: 0; - -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.4); - box-shadow: 0 2px 2px rgba(0,0,0,0.4); + box-shadow: shadow("dropdown"); } .select-kit-row { diff --git a/app/assets/stylesheets/common/topic-entrance.scss b/app/assets/stylesheets/common/topic-entrance.scss index 03caf20de5..1f20b3ac56 100644 --- a/app/assets/stylesheets/common/topic-entrance.scss +++ b/app/assets/stylesheets/common/topic-entrance.scss @@ -3,7 +3,7 @@ border: 1px solid $primary-low; padding: 5px; background: $secondary; - box-shadow: 0 0 2px rgba(0,0,0, .2); + box-shadow: shadow("card"); z-index: z("dropdown"); position: absolute; diff --git a/app/assets/stylesheets/common/topic-timeline.scss b/app/assets/stylesheets/common/topic-timeline.scss index 6a9899154d..f5e77393ca 100644 --- a/app/assets/stylesheets/common/topic-timeline.scss +++ b/app/assets/stylesheets/common/topic-timeline.scss @@ -52,7 +52,7 @@ left: 0; right: 0; border-top: 1px solid dark-light-choose($primary-low, $secondary-low); - box-shadow: 0px -2px 4px -1px rgba(0,0,0,.25); + box-shadow: shadow("composer"); padding-top: 20px; z-index: z("fullscreen"); @media screen and (max-height: 425px) { diff --git a/app/assets/stylesheets/desktop.scss b/app/assets/stylesheets/desktop.scss index 8534c2f783..e11252ac35 100644 --- a/app/assets/stylesheets/desktop.scss +++ b/app/assets/stylesheets/desktop.scss @@ -19,7 +19,6 @@ @import "desktop/user"; @import "desktop/history"; @import "desktop/queued-posts"; -@import "desktop/menu-panel"; @import "desktop/group"; // Import all component-specific files diff --git a/app/assets/stylesheets/desktop/category-list.scss b/app/assets/stylesheets/desktop/category-list.scss index 1ebf438378..f3263e9661 100644 --- a/app/assets/stylesheets/desktop/category-list.scss +++ b/app/assets/stylesheets/desktop/category-list.scss @@ -51,7 +51,7 @@ .subcategories { .badge-notification.new-posts { padding: 0; - margin: 0 5px 0 0; + margin: 0 10px 0 0; } } @@ -110,7 +110,7 @@ } } -.categories-and-latest { +.categories-and-latest, .categories-and-top { display: flex; flex-flow: row wrap; diff --git a/app/assets/stylesheets/desktop/components/user-info.scss b/app/assets/stylesheets/desktop/components/user-info.scss index f1b7260d19..ba13a6e182 100644 --- a/app/assets/stylesheets/desktop/components/user-info.scss +++ b/app/assets/stylesheets/desktop/components/user-info.scss @@ -1,14 +1,17 @@ .user-info { &.medium { - width: 480px; - + flex: 0 0 32%; + margin: 0 2% 4vh 0; + display: flex; + &:nth-of-type(3n) { + margin-right: 0; + } + @media screen and (max-width: $small-width) { + flex: 0 0 48%; + margin-right: 0; + } .user-image { width: 55px; } - - .user-detail { - width: 380px; - } - } } diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 00e4a9f85a..30c278b6dc 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -79,7 +79,7 @@ overflow-y: auto; z-index: z("composer","popover"); padding: 10px 10px 35px 10px; - box-shadow: 3px 3px 3px rgba(0,0,0, 0.34); + box-shadow: shadow("card"); background: $highlight-medium; &.urgent { diff --git a/app/assets/stylesheets/desktop/discourse.scss b/app/assets/stylesheets/desktop/discourse.scss index 8dce9693b5..273e967534 100644 --- a/app/assets/stylesheets/desktop/discourse.scss +++ b/app/assets/stylesheets/desktop/discourse.scss @@ -244,7 +244,7 @@ input { &:focus { border-color: scale-color($danger, $lightness: -30%); - box-shadow: 0 0 6px $danger; + box-shadow: shadow("focus-danger"); } } @@ -287,7 +287,7 @@ input { &:focus { border-color: $success; - box-shadow: 0 0 6px $success; + box-shadow: shadow("focus"); } } diff --git a/app/assets/stylesheets/desktop/header.scss b/app/assets/stylesheets/desktop/header.scss index ad83f6d2c2..4a6f406983 100644 --- a/app/assets/stylesheets/desktop/header.scss +++ b/app/assets/stylesheets/desktop/header.scss @@ -5,9 +5,9 @@ .d-header { left: 0; padding-top: 3px; - height: 60px; + height: 4.2857em; .d-icon-home { - padding:8px; + padding: 8px; font-size: $font-up-5; } @@ -17,9 +17,10 @@ } } -@media all -and (max-width : 570px) { - .extra-info-wrapper {display: none;} +@media all and (max-width: 570px) { + .extra-info-wrapper { + display: none; + } } #main { diff --git a/app/assets/stylesheets/desktop/history.scss b/app/assets/stylesheets/desktop/history.scss index 8481485d10..3192d0b9b2 100644 --- a/app/assets/stylesheets/desktop/history.scss +++ b/app/assets/stylesheets/desktop/history.scss @@ -7,9 +7,6 @@ max-width: 960px; } #revision-controls { - float: left; - padding-right: 5px; - .btn[disabled] { cursor: not-allowed; background-color: $primary-low; @@ -32,11 +29,7 @@ font-weight: bold; } } - #revision-details { - padding: 5px; - margin-top: 10px; - border-bottom: 3px solid $primary-low; - } + #revisions { word-wrap: break-word; table { diff --git a/app/assets/stylesheets/desktop/latest-topic-list.scss b/app/assets/stylesheets/desktop/latest-topic-list.scss index b19c016475..6cdb77df6b 100644 --- a/app/assets/stylesheets/desktop/latest-topic-list.scss +++ b/app/assets/stylesheets/desktop/latest-topic-list.scss @@ -1,4 +1,4 @@ -.latest-topic-list { +.latest-topic-list, .top-topic-list { @extend .topic-list-icons; .table-heading { @@ -23,8 +23,15 @@ } .main-link { @extend .topic-list-main-link; - flex: 15; + flex: 0 1 auto; + max-width: 65%; font-size: $font-0; + .badge-wrapper { + max-width: 100%; + .badge-category-bg { + flex-shrink: 0; + } + } .top-row { margin-bottom: 0.1em; diff --git a/app/assets/stylesheets/desktop/login.scss b/app/assets/stylesheets/desktop/login.scss index 3e7e61efcc..b7ce599cb8 100644 --- a/app/assets/stylesheets/desktop/login.scss +++ b/app/assets/stylesheets/desktop/login.scss @@ -12,21 +12,8 @@ margin-bottom: 20px; } -#login-form { - a { - color: dark-light-choose($primary-high, $secondary-low); - } - td { - padding-right: 5px; - } -} - // Create account -#new-account-link { - cursor: pointer; -} - .create-account { form { margin-bottom: 0; diff --git a/app/assets/stylesheets/desktop/menu-panel.scss b/app/assets/stylesheets/desktop/menu-panel.scss deleted file mode 100644 index 74266de4cf..0000000000 --- a/app/assets/stylesheets/desktop/menu-panel.scss +++ /dev/null @@ -1,12 +0,0 @@ -.docked #hamburger-menu { - position: fixed; -} - -#hamburger-menu { - position: absolute; - top: 63px; - // compensate on the other end for this top - .hamburger-body { - bottom: 100px; - } -} diff --git a/app/assets/stylesheets/desktop/modal.scss b/app/assets/stylesheets/desktop/modal.scss index d950497110..d52c0d4869 100644 --- a/app/assets/stylesheets/desktop/modal.scss +++ b/app/assets/stylesheets/desktop/modal.scss @@ -22,8 +22,6 @@ } .modal-body { - overflow-y: auto; - max-height: 400px; padding: 15px; } @@ -35,19 +33,12 @@ margin-left: -1px; } -.modal-close { - display: inline-block; - float: right; - margin: 7px; -} - .modal-header { h3 { - display: inline-block;; font-size: $font-up-3; - padding: 10px 15px 7px; } } + .close { font-size: $font-up-3; text-decoration: none; @@ -65,34 +56,9 @@ .category-combobox { width: 430px; - - .select2-drop { - left: -9000px; - width: 428px; - } - .select2-search input { - width: 378px; - } } } -.flag-modal { - max-height: 450px; - .flag-action-type-details { - line-height: $line-height-large; - } -} - -.custom-message-length { - color: dark-light-choose($primary-low-mid, $secondary-high); - font-size: $font-down-1; -} - -.flag-message { - width: 95%; - margin: 0; -} - .edit-category-modal { .modal-body { position: relative; @@ -100,22 +66,6 @@ max-height: 420px; padding-bottom: 0; } - .secure-category-options { - margin: 10px 0 0 16px; - .badge-list { - margin: 10px 0; - li { - margin: 0 4px 8px 0; - a { - color: dark-light-choose($primary-medium, $secondary-medium); - cursor: pointer; - } - a:hover { - color: dark-light-choose($primary-medium, $secondary-medium); - } - } - } - } .disable_info_wrap { margin-top: -70px; @@ -154,18 +104,6 @@ max-width: 23%; } } - -} -.tabbed-modal { - .modal-body { - position: relative; - height: 350px; - } -} - -.modal-tab { - position: absolute; - width: 95%; } .split-modal { @@ -211,19 +149,3 @@ max-height: 150px; margin-bottom: 10px; } - -.change-timestamp { - min-height: 300px; - - .date-picker { - width: 10em; - } - - input[type=time] { - width: 6em; - } - - form { - margin: 0; - } -} diff --git a/app/assets/stylesheets/desktop/queued-posts.scss b/app/assets/stylesheets/desktop/queued-posts.scss index 79cfb1f630..b4c5ef0888 100644 --- a/app/assets/stylesheets/desktop/queued-posts.scss +++ b/app/assets/stylesheets/desktop/queued-posts.scss @@ -24,6 +24,15 @@ } } + .tag-chooser { + width: 100%; + margin-bottom: .5em; + + .select-kit-collection { + padding: 0; + } + } + .queue-controls { button { float: left; diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index dd65265203..99d25b737e 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -126,7 +126,7 @@ nav.post-controls { border: none; margin-left: 3px; - &.d-hover { + &.d-hover, &:focus { background: $primary-low; color: $primary; } @@ -142,12 +142,12 @@ nav.post-controls { position: relative; } - &.delete.d-hover { + &.delete.d-hover, &.delete:focus { background: $danger; color: $secondary; } - &.like.d-hover { + &.like.d-hover, &.like:focus { color: $love; background: $love-low; } @@ -423,7 +423,6 @@ nav.post-controls { clear: left; padding: 20px 0 15px 0; table { - table-layout: fixed; margin-top: 10px; } @@ -663,7 +662,7 @@ $topic-avatar-width: 45px; list-style: none; background-color: $secondary; border: 1px solid $primary-low; - box-shadow: 0 1px 5px rgba(0,0,0, .4); + box-shadow: shadow("dropdown"); background-clip: padding-box; span { font-size: $font-down-1; @@ -778,13 +777,12 @@ $topic-avatar-width: 45px; } &:active { @include linear-gradient(darken($tertiary, 18%), darken($tertiary, 12%)); - box-shadow: inset 0 1px 3px rgba(0,0,0, 0.2); color: $secondary; } &[disabled] { text-shadow: 0 1px 0 rgba($primary, 0.2); @include linear-gradient($tertiary, darken($tertiary, 20%)); - @include box-shadow((inset 0 1px 0 rgba(0,0,0, 0.33), inset 0 -1px 2px rgba($primary, 0.2))); + @include box-shadow(inset 0 1px 0 rgba(0,0,0, 0.33)); } } } diff --git a/app/assets/stylesheets/desktop/topic.scss b/app/assets/stylesheets/desktop/topic.scss index b4d33b0958..db173a3681 100644 --- a/app/assets/stylesheets/desktop/topic.scss +++ b/app/assets/stylesheets/desktop/topic.scss @@ -28,15 +28,12 @@ font-size: $font-up-4; line-height: $line-height-medium; overflow: hidden; - width: 100%; + width: 100%; a {color: $primary;} } .topic-statuses { margin-top: -2px; } - .select2-container { - vertical-align: middle; - } .private-message-glyph { display: none; } .remove-featured-link { float: right; diff --git a/app/assets/stylesheets/desktop/user-card.scss b/app/assets/stylesheets/desktop/user-card.scss index 8f09dd9fc8..2450fd71dc 100644 --- a/app/assets/stylesheets/desktop/user-card.scss +++ b/app/assets/stylesheets/desktop/user-card.scss @@ -10,7 +10,7 @@ $user_card_background: $secondary; left: -9999px; top: -9999px; z-index: z("usercard"); - box-shadow: 1px 2px 6px rgba(0,0,0, .25); + box-shadow: shadow("card"); margin-top: -2px; color: $user_card_primary; background: $user_card_background center center; diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 0fb3e102f9..a7ac7803c3 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -8,6 +8,10 @@ margin-top: 10px; } } + + .show-mores { + position: absolute; + } } .profile-image { @@ -75,6 +79,11 @@ margin-bottom: 10px; box-sizing: border-box; + &.user-badges-list { + display: flex; + flex-wrap: wrap; + } + .btn.right { float: right } @@ -279,10 +288,12 @@ width: 530px; } + .category-selector, .tag-chooser { + width: 530px; + } + input { - &.category-selector, - &.user-selector, - &.tag-chooser { + &.user-selector { width: 530px; } diff --git a/app/assets/stylesheets/mobile.scss b/app/assets/stylesheets/mobile.scss index 08abaaf2ae..a95604a0cb 100644 --- a/app/assets/stylesheets/mobile.scss +++ b/app/assets/stylesheets/mobile.scss @@ -7,7 +7,6 @@ @import "mobile/banner"; @import "mobile/compose"; @import "mobile/discourse"; -@import "mobile/faqs"; @import "mobile/header"; @import "mobile/login"; @import "mobile/modal"; @@ -18,7 +17,6 @@ @import "mobile/user"; @import "mobile/history"; @import "mobile/directory"; -@import "mobile/menu-panel"; @import "mobile/search"; @import "mobile/emoji"; @import "mobile/ring"; diff --git a/app/assets/stylesheets/mobile/components/user-info.scss b/app/assets/stylesheets/mobile/components/user-info.scss deleted file mode 100644 index 8cf359ea18..0000000000 --- a/app/assets/stylesheets/mobile/components/user-info.scss +++ /dev/null @@ -1,14 +0,0 @@ -// Mobile styles for "user-info" component -.user-info { - &.medium { - width: 300px; - - .user-image { - width: auto; - } - - .user-detail { - width: 240px; - } - } -} diff --git a/app/assets/stylesheets/mobile/directory.scss b/app/assets/stylesheets/mobile/directory.scss index 472345a79e..15fdb95165 100644 --- a/app/assets/stylesheets/mobile/directory.scss +++ b/app/assets/stylesheets/mobile/directory.scss @@ -1,36 +1,18 @@ -.user-controls { - padding: 1em; -} - -.total-rows { - padding: 0.25em 0.5em; -} - .directory .user { border-top: 1px solid $primary-low; padding: 1em; - - &.me { - background-color: dark-light-choose($highlight-low, $highlight-medium); - - .username a, .name, .title, .number, .time-read, .user-stat .label { - color: scale-color($highlight, $lightness: -50%); - } - } .user-stat { margin-left: 55px; - .value { font-weight: bold; } .label { margin-left: 0.2em; - color: blend-primary-secondary(50%); + color: $primary-medium; } .d-icon-heart { color: $love; } } - margin-bottom: 1em; } diff --git a/app/assets/stylesheets/mobile/emoji.scss b/app/assets/stylesheets/mobile/emoji.scss index 34d67e4c01..3bd4ef1ae0 100644 --- a/app/assets/stylesheets/mobile/emoji.scss +++ b/app/assets/stylesheets/mobile/emoji.scss @@ -1,6 +1,4 @@ .emoji-picker { - box-shadow: none; height: 250px; - border-radius: 0; border: none; } diff --git a/app/assets/stylesheets/mobile/faqs.scss b/app/assets/stylesheets/mobile/faqs.scss deleted file mode 100644 index 4fdfda90ba..0000000000 --- a/app/assets/stylesheets/mobile/faqs.scss +++ /dev/null @@ -1,10 +0,0 @@ -// -------------------------------------------------- -// FAQs, About, etc -// -------------------------------------------------- - -.body-page { - margin-top: 20px; - margin-left: 15px; - width: 90%; - padding-left: 0; -} \ No newline at end of file diff --git a/app/assets/stylesheets/mobile/history.scss b/app/assets/stylesheets/mobile/history.scss index 93a2a29e7e..bae7149be0 100644 --- a/app/assets/stylesheets/mobile/history.scss +++ b/app/assets/stylesheets/mobile/history.scss @@ -8,11 +8,6 @@ #revision-numbers { line-height: $line-height-large; } - #revision-details { - background-color: $primary-low; - padding: 5px; - margin-top: 10px; - } img { max-width: 95%; height: auto; diff --git a/app/assets/stylesheets/mobile/login.scss b/app/assets/stylesheets/mobile/login.scss index 53d8e7b41a..e7b346f9ac 100644 --- a/app/assets/stylesheets/mobile/login.scss +++ b/app/assets/stylesheets/mobile/login.scss @@ -15,9 +15,7 @@ } #login-form { - a { - color: dark-light-choose($primary-high, $secondary-low); - } + label { float: left; display: block; } textarea, input, select { font-size: $font-up-1; @@ -31,12 +29,6 @@ a#new-account-link { white-space:nowrap; } // Create account -#new-account-link { - cursor: pointer; -} - -a#forgot-password-link {clear: left; float: left; } - .login-modal, .create-account { .btn-primary { margin-bottom: 10px; diff --git a/app/assets/stylesheets/mobile/menu-panel.scss b/app/assets/stylesheets/mobile/menu-panel.scss deleted file mode 100644 index 972ea30ab2..0000000000 --- a/app/assets/stylesheets/mobile/menu-panel.scss +++ /dev/null @@ -1,7 +0,0 @@ -.menu-panel { - span.badge-category { - max-width: 85px; - overflow: hidden; - text-overflow: ellipsis; - } -} diff --git a/app/assets/stylesheets/mobile/modal.scss b/app/assets/stylesheets/mobile/modal.scss index 2eb136e07f..7d2e19e7b3 100644 --- a/app/assets/stylesheets/mobile/modal.scss +++ b/app/assets/stylesheets/mobile/modal.scss @@ -22,8 +22,6 @@ top: 50%; } .modal-body { - overflow-y: auto; - max-height: 400px; padding: 10px; } @@ -33,20 +31,13 @@ padding: 15px 7px 10px 7px; } -.modal-close { - display: inline-block; - float: right; -} - .modal-header { - padding: 10px 0 10px 10px; - + padding: 10px; h3 { - display: inline; font-size: $font-up-2; - margin: 0; } } + .close { font-size: $font-up-4; padding: 10px 15px 5px 5px; @@ -77,27 +68,12 @@ } } -.flag-modal { - max-height: 450px; -} - @media only screen and (max-device-width: 568px) { .modal .flag-modal .flag-message { - height: 1.2em; + height: 3em; } } -.custom-message-length { - margin: -4px 0 10px 20px; - color: dark-light-choose($primary-high, $secondary-low); - font-size: $font-down-1; -} - -.flag-message { - margin-left: 20px; - width: 95% !important; -} - .edit-category-modal { .modal-body { box-sizing: border-box; @@ -107,19 +83,6 @@ &.small .modal-body { height: 310px; } - .secure-category-options { - margin: 10px 0 0 16px; - .badge-list { - margin: 10px 0; - li { - margin: 0 4px 8px 0; - a { - color: #888; - cursor: pointer; - } - } - } - } .disable_info_wrap .cannot_delete_reason { top: -114px; @@ -128,18 +91,6 @@ } } -.tabbed-modal { - .modal-body { - position: relative; - height: 350px; - } -} - - -.modal-tab { - position: absolute; -} - /* fixes for the new account confirm dialog on mobile */ .modal-inner-container { diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index 8e7022e8e5..53c9c98f03 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -232,6 +232,10 @@ tr.category-topic-link { } } + .category-name { + max-width: 80vw; + } + .category-topic-link .main-link, .subcategories-list td, .category-description td { padding-left: 10px; } @@ -345,7 +349,7 @@ tr.category-topic-link { background-color: $secondary; border: 1px solid dark-light-choose(rgba(0, 0, 0, 0.2), $primary); border-radius: 5px; - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: shadow("dropdown"); background-clip: padding-box; margin: 1px 0 20px; .title {font-weight: bold; display: block;} diff --git a/app/assets/stylesheets/mobile/topic.scss b/app/assets/stylesheets/mobile/topic.scss index dafbfd69e5..48e682e147 100644 --- a/app/assets/stylesheets/mobile/topic.scss +++ b/app/assets/stylesheets/mobile/topic.scss @@ -52,10 +52,6 @@ } } -.docked #topic-progress { - box-shadow: 0 0 3px rbga(0,0,0, .5); -} - #topic-progress-wrapper { position: fixed; width: 0; @@ -185,11 +181,6 @@ sup sup, sub sup, sup sub, sub sub { top: 0; } width: 100%; height: 32px; } - - .select2-container { - box-sizing: border-box; - width: 100% !important; - } .btn-small { padding: 6px 12px; margin: 6px 6px 0 0; diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss index 7a652d9904..09df789b5a 100644 --- a/app/assets/stylesheets/mobile/user.scss +++ b/app/assets/stylesheets/mobile/user.scss @@ -143,20 +143,20 @@ display: flex; flex: 1 1 auto; flex-wrap: wrap; - + } li { display: flex; - flex: 1 1 auto; - margin: 0 5px; + flex: 1 1 auto; + margin: 0 5px; } span { flex: 0 1 100%; - margin: 0 5px; + margin: 0 5px; } - + .user-profile-controls-outlet { margin: 0; li { @@ -165,7 +165,7 @@ } a { - flex: 1 1 auto; + flex: 1 1 auto; min-width: 120px; } } @@ -214,7 +214,7 @@ } .stats-section { - li { + li { margin: 0 15px 10px 0; padding: 0; &.linked-stat { @@ -277,7 +277,7 @@ .checkbox-label { display: flex; - overflow: auto; + overflow: auto; width: 100%; padding: 5px 0; input { diff --git a/app/assets/stylesheets/vendor/select2.scss b/app/assets/stylesheets/vendor/select2.scss deleted file mode 100644 index 0981475b45..0000000000 --- a/app/assets/stylesheets/vendor/select2.scss +++ /dev/null @@ -1,575 +0,0 @@ -/* -Version: @@ver@@ Timestamp: @@timestamp@@ -*/ -.select2-container { - margin: 0; - position: relative; - display: inline-block; - /* inline-block for ie7 */ - zoom: 1; - vertical-align: middle; -} - -.select2-container, -.select2-drop, -.select2-search, -.select2-search input { - /* - Force border-box so that % widths fit the parent - container without overlap because of margin/padding. - More Info : http://www.quirksmode.org/css/box.html - */ - -webkit-box-sizing: border-box; /* webkit */ - -moz-box-sizing: border-box; /* firefox */ - box-sizing: border-box; /* css3 */ -} - -.select2-container .select2-choice { - display: block; - height: 26px; - padding: 0 0 0 8px; - overflow: hidden; - position: relative; - - border: 1px solid #aaa; - white-space: nowrap; - line-height: 26px; - color: #444; - text-decoration: none; - - border-radius: 4px; - - background-clip: padding-box; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - background-color: #fff; -} - -.select2-container.select2-drop-above .select2-choice { - border-bottom-color: #aaa; - - border-radius: 0 0 4px 4px; - -} - -.select2-container.select2-allowclear .select2-choice .select2-chosen { - margin-right: 42px; -} - -.select2-container .select2-choice > .select2-chosen { - margin-right: 26px; - display: block; - overflow: hidden; - - white-space: nowrap; - - text-overflow: ellipsis; - float: none; - width: auto; -} - -.select2-container .select2-choice abbr { - display: none; - width: 12px; - height: 12px; - position: absolute; - right: 24px; - top: 8px; - - font-size: 1px; - text-decoration: none; - - border: 0; - background: asset-url('select2.png') right top no-repeat; - cursor: pointer; - outline: 0; -} - -.select2-container.select2-allowclear .select2-choice abbr { - display: inline-block; -} - -.select2-container .select2-choice abbr:hover { - background-position: right -11px; - cursor: pointer; -} - -.select2-drop-mask { - border: 0; - margin: 0; - padding: 0; - position: fixed; - left: 0; - top: 0; - min-height: 100%; - min-width: 100%; - height: auto; - width: auto; - opacity: 0; - z-index: 9998; - /* styles required for IE to work */ - background-color: #fff; - filter: alpha(opacity=0); -} - -.select2-drop { - width: 100%; - margin-top: -1px; - position: absolute; - z-index: 9999; - top: 100%; - - background: #fff; - color: #000; - border: 1px solid #aaa; - border-top: 0; - - - -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); - box-shadow: 0 4px 5px rgba(0, 0, 0, .15); -} - -.select2-drop.select2-drop-above { - margin-top: 1px; - border-top: 1px solid #aaa; - border-bottom: 0; - - - -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); - box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); -} - -.select2-drop-active { - border: 1px solid #5897fb; - border-top: none; -} - -.select2-drop.select2-drop-above.select2-drop-active { - border-top: 1px solid #5897fb; -} - -.select2-drop-auto-width { - border-top: 1px solid #aaa; - width: auto; -} - -.select2-drop-auto-width .select2-search { - padding-top: 4px; -} - -.select2-container .select2-choice .select2-arrow { - display: inline-block; - width: 18px; - height: 100%; - position: absolute; - right: 0; - top: 0; - - border-radius: 0 4px 4px 0; - - background-clip: padding-box; - -} - -.select2-container .select2-choice .select2-arrow b { - display: block; - width: 100%; - height: 100%; - background: asset-url('select2.png') no-repeat 0 1px; -} - -.select2-search { - display: inline-block; - width: 100%; - min-height: 26px; - margin: 0; - padding-left: 4px; - padding-right: 4px; - - position: relative; - z-index: 10000; - - white-space: nowrap; -} - -//noinspection CssOverwrittenProperties -.select2-search input { - width: 100%; - height: auto !important; - min-height: 26px; - padding: 4px 20px 4px 5px; - margin: 0; - - outline: 0; - font-family: sans-serif; - font-size: 1em; - - border: 1px solid #aaa; - border-radius: 0; - - -webkit-box-shadow: none; - box-shadow: none; - - background: #fff asset-url('select2.png') no-repeat 100% -22px; - background: asset-url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); - background: asset-url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); - background: asset-url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); - background: asset-url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; -} - -.select2-drop.select2-drop-above .select2-search input { - margin-top: 4px; -} - -//noinspection CssOverwrittenProperties -.select2-search input.select2-active { - background: #fff asset-url('select2-spinner.gif') no-repeat 100%; - background: asset-url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); - background: asset-url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); - background: asset-url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); - background: asset-url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; -} - -.select2-container-active .select2-choice, -.select2-container-active .select2-choices { - border: 1px solid #5897fb; - outline: none; - - -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); - box-shadow: 0 0 5px rgba(0, 0, 0, .3); -} - -.select2-dropdown-open .select2-choice { - border-bottom-color: transparent; - -webkit-box-shadow: 0 1px 0 #fff inset; - box-shadow: 0 1px 0 #fff inset; - - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - - background-color: #eee; -} - -.select2-dropdown-open.select2-drop-above .select2-choice, -.select2-dropdown-open.select2-drop-above .select2-choices { - border: 1px solid #5897fb; - border-top-color: transparent; -} - -.select2-dropdown-open .select2-choice .select2-arrow { - background: transparent; - border-left: none; - filter: none; -} -.select2-dropdown-open .select2-choice .select2-arrow b { - background-position: -18px 1px; -} - -.select2-hidden-accessible { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; -} - -/* results */ -.select2-results { - max-height: 200px; - padding: 0 0 0 4px; - margin: 4px 4px 4px 0; - position: relative; - overflow-x: hidden; - overflow-y: auto; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -.select2-results ul.select2-result-sub { - margin: 0; - padding-left: 0; -} - -.select2-results li { - list-style: none; - display: list-item; - background-image: none; -} - -.select2-results li.select2-result-with-children > .select2-result-label { - font-weight: bold; -} - -.select2-results .select2-result-label { - padding: 3px 7px 4px; - margin: 0; - cursor: pointer; - - min-height: 1em; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.select2-results-dept-1 .select2-result-label { padding-left: 20px } -.select2-results-dept-2 .select2-result-label { padding-left: 40px } -.select2-results-dept-3 .select2-result-label { padding-left: 60px } -.select2-results-dept-4 .select2-result-label { padding-left: 80px } -.select2-results-dept-5 .select2-result-label { padding-left: 100px } -.select2-results-dept-6 .select2-result-label { padding-left: 110px } -.select2-results-dept-7 .select2-result-label { padding-left: 120px } - -.select2-results li em { - background: #feffde; - font-style: normal; -} - -.select2-results .select2-highlighted em { - background: transparent; -} - -.select2-results .select2-highlighted ul { - background: #fff; - color: #000; -} - - -.select2-results .select2-no-results, -.select2-results .select2-searching, -.select2-results .select2-selection-limit { - background: #f4f4f4; - display: list-item; - padding-left: 5px; -} - -/* -disabled look for disabled choices in the results dropdown -*/ -.select2-results .select2-disabled.select2-highlighted { - color: #666; - background: #f4f4f4; - display: list-item; - cursor: default; -} -.select2-results .select2-disabled { - background: #f4f4f4; - display: list-item; - cursor: default; -} - -.select2-results .select2-selected { - display: none; -} - -.select2-more-results.select2-active { - background: #f4f4f4 asset-url('select2-spinner.gif') no-repeat 100%; -} - -.select2-more-results { - background: #f4f4f4; - display: list-item; -} - -/* disabled styles */ - -.select2-container.select2-container-disabled .select2-choice { - background: #f4f4f4 none; - border: 1px solid #ddd; - cursor: default; -} - -.select2-container.select2-container-disabled .select2-choice .select2-arrow { - background: #f4f4f4 none; - border-left: 0; -} - -.select2-container.select2-container-disabled .select2-choice abbr { - display: none; -} - - -/* multiselect */ - -.select2-container-multi .select2-choices { - height: auto !important; - margin: 0; - padding: 0 5px 0 0; - position: relative; - cursor: text; - overflow: hidden; -} - -.select2-locked { - padding: 3px 5px 3px 5px !important; -} - -.select2-container-multi .select2-choices { - min-height: 26px; -} - -.select2-container-multi.select2-container-active .select2-choices { - border: 1px solid #5897fb; - outline: none; - - -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); - box-shadow: 0 0 5px rgba(0, 0, 0, .3); -} -.select2-container-multi .select2-choices li { - float: left; - list-style: none; -} -html[dir="rtl"] .select2-container-multi .select2-choices li -{ - float: right; -} -.select2-container-multi .select2-choices .select2-search-field { - margin: 0; - padding: 0; - white-space: nowrap; -} - -.select2-container-multi .select2-choices .select2-search-field input { - padding-left: 0; - font-family: sans-serif; - font-size: 1em; - color: #666; - outline: 0; - border: 0; - margin-bottom: 0; - -webkit-box-shadow: none; - box-shadow: none; - background: transparent !important; -} - -.select2-container-multi .select2-choices .select2-search-field input.select2-active { - background: #fff asset-url('select2-spinner.gif') no-repeat 100% !important; -} - -.select2-default { - color: #999 !important; -} - -.select2-container-multi .select2-choices .select2-search-choice { - padding: 0 0 0 12px; - margin: 0; - position: relative; - - color: #333; - cursor: default; - border: 1px solid #aaaaaa; - - border-radius: 3px; - - -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); - box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); - - background-clip: padding-box; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - background-color: #e4e4e4; -} -html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice -{ - margin-left: 0; - margin-right: 5px; -} -.select2-container-multi .select2-choices .select2-search-choice .select2-chosen { - cursor: default; -} -.select2-container-multi .select2-choices .select2-search-choice-focus { - background: #d4d4d4; -} - -.select2-search-choice-close { - display: block; - width: 12px; - height: 13px; - position: absolute; - right: 3px; - top: 8px; - - font-size: 1px; - outline: none; - background: asset-url('select2.png') right top no-repeat; -} -html[dir="rtl"] .select2-search-choice-close { - right: auto; - left: 3px; -} - -.select2-container-multi .select2-search-choice-close { - left: 3px; -} - -.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { - background-position: right -11px; -} -.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { - background-position: right -11px; -} - -/* disabled styles */ -.select2-container-multi.select2-container-disabled .select2-choices { - background: #f4f4f4 none; - border: 1px solid #ddd; - cursor: default; -} - -.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { - padding: 3px 5px 3px 5px; - border: 1px solid #ddd; - background: #f4f4f4 none; -} - -.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none; - background: none; -} -/* end multiselect */ - - -.select2-result-selectable .select2-match, -.select2-result-unselectable .select2-match { - text-decoration: underline; -} - -.select2-offscreen, .select2-offscreen:focus { - clip: rect(0 0 0 0) !important; - width: 1px !important; - height: 1px !important; - border: 0 !important; - margin: 0 !important; - padding: 0 !important; - overflow: hidden !important; - position: absolute !important; - outline: 0 !important; - left: 0 !important; - top: 0 !important; -} - -.select2-display-none { - display: none; -} - -.select2-measure-scrollbar { - position: absolute; - top: -10000px; - left: -10000px; - width: 100px; - height: 100px; - overflow: scroll; -} diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index 821c8e8689..0fa4c95008 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -1,6 +1,5 @@ @import "vendor/normalize"; @import "vendor/font_awesome/font-awesome"; -@import "vendor/select2"; @import "vendor/sweetalert"; @import "common/foundation/colors"; @import "common/foundation/variables"; @@ -63,9 +62,6 @@ body.wizard { .select { width: 400px; } -.select2-results .select2-highlighted { - background: #ff9; -} .wizard-canvas { position: absolute; @@ -484,7 +480,6 @@ body.wizard { .wizard-column { margin: auto !important; } .wizard-step-contents { min-height: auto !important; } .wizard-step-banner { width: 100% !important; margin-bottom: 1em !important; } - .select2-container { width: 100% !important; } .wizard-step-footer { display: block !important; } .wizard-progress { margin-bottom: 10px !important; } .wizard-buttons { text-align: right !important; } diff --git a/app/controllers/admin/themes_controller.rb b/app/controllers/admin/themes_controller.rb index b0f8d21f39..3013c1c284 100644 --- a/app/controllers/admin/themes_controller.rb +++ b/app/controllers/admin/themes_controller.rb @@ -138,6 +138,7 @@ class Admin::ThemesController < Admin::AdminController end set_fields + update_settings save_remote = false if params[:theme][:remote_check] @@ -158,7 +159,7 @@ class Admin::ThemesController < Admin::AdminController update_default_theme log_theme_change(original_json, @theme) - format.json { render json: @theme, status: :created } + format.json { render json: @theme, status: :ok } else format.json { @@ -193,7 +194,7 @@ class Admin::ThemesController < Admin::AdminController response.headers['Content-Disposition'] = "attachment; filename=#{@theme.name.parameterize}.dcstyle.json" response.sending_file = true - render json: ThemeWithEmbeddedUploadsSerializer.new(@theme, root: 'theme') + render json: ::ThemeWithEmbeddedUploadsSerializer.new(@theme, root: 'theme') end end @@ -223,6 +224,7 @@ class Admin::ThemesController < Admin::AdminController :color_scheme_id, :default, :user_selectable, + settings: {}, theme_fields: [:name, :target, :value, :upload_id, :type_id], child_theme_ids: [] ) @@ -243,6 +245,14 @@ class Admin::ThemesController < Admin::AdminController end end + def update_settings + return unless target_settings = theme_params[:settings] + + target_settings.each_pair do |setting_name, new_value| + @theme.update_setting(setting_name.to_sym, new_value) + end + end + def log_theme_change(old_record, new_record) StaffActionLogger.new(current_user).log_theme_change(old_record, new_record) end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index d948d5f5f0..298161a88d 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -25,7 +25,8 @@ class Admin::UsersController < Admin::AdminController :generate_api_key, :revoke_api_key, :anonymize, - :reset_bounce_score] + :reset_bounce_score, + :disable_second_factor] def index users = ::AdminUserIndexQuery.new(params).find_users @@ -340,6 +341,23 @@ class Admin::UsersController < Admin::AdminController } end + def disable_second_factor + guardian.ensure_can_disable_second_factor!(@user) + user_second_factor = @user.user_second_factor + raise Discourse::InvalidParameters unless user_second_factor + + user_second_factor.destroy! + StaffActionLogger.new(current_user).log_disable_second_factor_auth(@user) + + Jobs.enqueue( + :critical_user_email, + type: :account_second_factor_disabled, + user_id: @user.id + ) + + render json: success_json + end + def destroy user = User.find_by(id: params[:id].to_i) guardian.ensure_can_delete_user!(user) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 653ba952fc..566c79be63 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -460,6 +460,7 @@ class ApplicationController < ActionController::Base def preload_anonymous_data store_preloaded("site", Site.json_for(guardian)) store_preloaded("siteSettings", SiteSetting.client_settings_json) + store_preloaded("themeSettings", Theme.settings_for_client(@theme_key)) store_preloaded("customHTML", custom_html_json) store_preloaded("banner", banner_json) store_preloaded("customEmoji", custom_emoji) @@ -658,6 +659,10 @@ class ApplicationController < ActionController::Base render_to_string status: status, layout: layout, formats: [:html], template: '/exceptions/not_found' end + def is_asset_path + request.env['DISCOURSE_IS_ASSET_PATH'] = 1 + end + protected def render_post_json(post, add_raw = true) diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 58a95610c4..aaaf1ec7bd 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -2,11 +2,11 @@ require_dependency 'category_serializer' class CategoriesController < ApplicationController - requires_login except: [:index, :categories_and_latest, :show, :redirect, :find_by_slug] + requires_login except: [:index, :categories_and_latest, :categories_and_top, :show, :redirect, :find_by_slug] before_action :fetch_category, only: [:show, :update, :destroy] before_action :initialize_staff_action_logger, only: [:create, :update, :destroy] - skip_before_action :check_xhr, only: [:index, :categories_and_latest, :redirect] + skip_before_action :check_xhr, only: [:index, :categories_and_latest, :categories_and_top, :redirect] def redirect redirect_to path("/c/#{params[:path]}") @@ -27,7 +27,10 @@ class CategoriesController < ApplicationController @category_list = CategoryList.new(guardian, category_options) @category_list.draft_key = Draft::NEW_TOPIC - @category_list.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC) + @category_list.draft_sequence = DraftSequence.current( + current_user, + Draft::NEW_TOPIC + ) @category_list.draft = Draft.get(current_user, Draft::NEW_TOPIC, @category_list.draft_sequence) if current_user @title = "#{I18n.t('js.filters.categories.title')} - #{SiteSetting.title}" unless category_options[:is_homepage] @@ -36,10 +39,23 @@ class CategoriesController < ApplicationController format.html do store_preloaded(@category_list.preload_key, MultiJson.dump(CategoryListSerializer.new(@category_list, scope: guardian))) - if SiteSetting.desktop_category_page_style == "categories_and_latest_topics".freeze - topic_options = { per_page: SiteSetting.categories_topics, no_definitions: true } - topic_list = TopicQuery.new(current_user, topic_options).list_latest - store_preloaded(topic_list.preload_key, MultiJson.dump(TopicListSerializer.new(topic_list, scope: guardian))) + style = SiteSetting.desktop_category_page_style + topic_options = { + per_page: SiteSetting.categories_topics, + no_definitions: true + } + + if style == "categories_and_latest_topics".freeze + @topic_list = TopicQuery.new(current_user, topic_options).list_latest + elsif style == "categories_and_top_topics".freeze + @topic_list = TopicQuery.new(nil, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym) + end + + if @topic_list.present? + store_preloaded( + @topic_list.preload_key, + MultiJson.dump(TopicListSerializer.new(@topic_list, scope: guardian)) + ) end render @@ -50,35 +66,11 @@ class CategoriesController < ApplicationController end def categories_and_latest - discourse_expires_in 1.minute + categories_and_topics(:latest) + end - category_options = { - is_homepage: current_homepage == "categories".freeze, - parent_category_id: params[:parent_category_id], - include_topics: false - } - - topic_options = { - per_page: SiteSetting.categories_topics, - no_definitions: true, - exclude_category_ids: Category.where(suppress_from_homepage: true).pluck(:id) - } - - result = CategoryAndTopicLists.new - result.category_list = CategoryList.new(guardian, category_options) - result.topic_list = TopicQuery.new(current_user, topic_options).list_latest - - draft_key = Draft::NEW_TOPIC - draft_sequence = DraftSequence.current(current_user, draft_key) - draft = Draft.get(current_user, draft_key, draft_sequence) if current_user - - %w{category topic}.each do |type| - result.send(:"#{type}_list").draft = draft - result.send(:"#{type}_list").draft_key = draft_key - result.send(:"#{type}_list").draft_sequence = draft_sequence - end - - render_serialized(result, CategoryAndTopicListsSerializer, root: false) + def categories_and_top + categories_and_topics(:top) end def move @@ -208,6 +200,42 @@ class CategoriesController < ApplicationController end private + def categories_and_topics(topics_filter) + discourse_expires_in 1.minute + + category_options = { + is_homepage: current_homepage == "categories".freeze, + parent_category_id: params[:parent_category_id], + include_topics: false + } + + topic_options = { + per_page: SiteSetting.categories_topics, + no_definitions: true, + exclude_category_ids: Category.where(suppress_from_latest: true).pluck(:id) + } + + result = CategoryAndTopicLists.new + result.category_list = CategoryList.new(guardian, category_options) + + if topics_filter == :latest + result.topic_list = TopicQuery.new(current_user, topic_options).list_latest + elsif topics_filter == :top + result.topic_list = TopicQuery.new(nil, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym) + end + + draft_key = Draft::NEW_TOPIC + draft_sequence = DraftSequence.current(current_user, draft_key) + draft = Draft.get(current_user, draft_key, draft_sequence) if current_user + + %w{category topic}.each do |type| + result.send(:"#{type}_list").draft = draft + result.send(:"#{type}_list").draft_key = draft_key + result.send(:"#{type}_list").draft_sequence = draft_sequence + end + + render_serialized(result, CategoryAndTopicListsSerializer, root: false) + end def required_param_keys [:name, :color, :text_color] @@ -235,7 +263,7 @@ class CategoriesController < ApplicationController :email_in, :email_in_allow_strangers, :mailinglist_mirror, - :suppress_from_homepage, + :suppress_from_latest, :all_topics_wiki, :parent_category_id, :auto_close_hours, @@ -269,9 +297,11 @@ class CategoriesController < ApplicationController end def include_topics(parent_category = nil) + style = SiteSetting.desktop_category_page_style view_context.mobile_view? || - params[:include_topics] || - (parent_category && parent_category.subcategory_list_includes_topics?) || - SiteSetting.desktop_category_page_style == "categories_with_featured_topics".freeze + params[:include_topics] || + (parent_category && parent_category.subcategory_list_includes_topics?) || + style == "categories_with_featured_topics".freeze || + style == "categories_with_top_topics".freeze end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 74f5479d26..7ba55917fe 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -12,6 +12,7 @@ class GroupsController < ApplicationController ] skip_before_action :preload_json, :check_xhr, only: [:posts_feed, :mentions_feed] + skip_before_action :check_xhr, only: [:show] def index unless SiteSetting.enable_group_directory? @@ -48,7 +49,19 @@ class GroupsController < ApplicationController end def show - render_serialized(find_group(:id), GroupShowSerializer, root: 'basic_group') + respond_to do |format| + group = find_group(:id) + + format.html do + @title = group.full_name.present? ? group.full_name.capitalize : group.name + @description_meta = group.bio_cooked.present? ? PrettyText.excerpt(group.bio_cooked, 300) : @title + render :show + end + + format.json do + render_serialized(group, GroupShowSerializer, root: 'basic_group') + end + end end def edit diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index 905f0077cb..ba7946b1d3 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -63,7 +63,7 @@ class ListController < ApplicationController if filter == :latest list_opts[:no_definitions] = true end - if filter.to_s == current_homepage + if [:latest, :categories].include?(filter) list_opts[:exclude_category_ids] = get_excluded_category_ids(list_opts[:category]) end end @@ -151,6 +151,7 @@ class ListController < ApplicationController private_messages_archive private_messages_group private_messages_group_archive + private_messages_tag }.each do |action| generate_message_route(action) end @@ -293,10 +294,11 @@ class ListController < ApplicationController def page_params(opts = nil) opts ||= {} route_params = { format: 'json' } - route_params[:category] = @category.slug_for_url if @category - route_params[:parent_category] = @category.parent_category.slug_for_url if @category && @category.parent_category - route_params[:order] = opts[:order] if opts[:order].present? - route_params[:ascending] = opts[:ascending] if opts[:ascending].present? + route_params[:category] = @category.slug_for_url if @category + route_params[:parent_category] = @category.parent_category.slug_for_url if @category && @category.parent_category + route_params[:order] = opts[:order] if opts[:order].present? + route_params[:ascending] = opts[:ascending] if opts[:ascending].present? + route_params[:username] = UrlHelper.escape_uri(params[:username]) if params[:username].present? route_params end @@ -332,6 +334,7 @@ class ListController < ApplicationController def build_topic_list_options options = {} params[:page] = params[:page].to_i rescue 1 + params[:tags] = [params[:tag_id]] if params[:tag_id].present? && guardian.can_tag_pms? TopicQuery.public_valid_options.each do |key| options[key] = params[key] @@ -368,7 +371,7 @@ class ListController < ApplicationController end def get_excluded_category_ids(current_category = nil) - exclude_category_ids = Category.where(suppress_from_homepage: true) + exclude_category_ids = Category.where(suppress_from_latest: true) exclude_category_ids = exclude_category_ids.where.not(id: current_category) if current_category exclude_category_ids.pluck(:id) end diff --git a/app/controllers/onebox_controller.rb b/app/controllers/onebox_controller.rb index dea29ce536..f093342c77 100644 --- a/app/controllers/onebox_controller.rb +++ b/app/controllers/onebox_controller.rb @@ -15,6 +15,7 @@ class OneboxController < ApplicationController user_id = current_user.id category_id = params[:category_id].to_i + topic_id = params[:topic_id].to_i invalidate = params[:refresh] == 'true' url = params[:url] @@ -24,7 +25,8 @@ class OneboxController < ApplicationController preview = Oneboxer.preview(url, invalidate_oneboxes: invalidate, user_id: user_id, - category_id: category_id + category_id: category_id, + topic_id: topic_id ) preview.strip! if preview.present? diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index 8b0e0f243c..2421ebbcae 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -188,6 +188,10 @@ class SessionController < ApplicationController end def create + unless params[:second_factor_token].blank? + RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed! + end + params.require(:login) params.require(:password) @@ -221,28 +225,50 @@ class SessionController < ApplicationController if payload = login_error_check(user) render json: payload else + if user.totp_enabled? && !user.authenticate_totp(params[:second_factor_token]) + return render json: failed_json.merge( + error: I18n.t("login.invalid_second_factor_code"), + reason: "invalid_second_factor" + ) + end + (user.active && user.email_confirmed?) ? login(user) : not_activated(user) end end def email_login raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email + second_factor_token = params[:second_factor_token] + token = params[:token] + valid_token = !!EmailToken.valid_token_format?(token) + user = EmailToken.confirmable(token)&.user - if EmailToken.valid_token_format?(params[:token]) && (user = EmailToken.confirm(params[:token])) + if valid_token && user&.totp_enabled? + RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed! + + if !second_factor_token.present? + @second_factor_required = true + return render layout: 'no_ember' + elsif !user.authenticate_totp(second_factor_token) + @error = I18n.t('login.invalid_second_factor_code') + return render layout: 'no_ember' + end + end + + if user = EmailToken.confirm(token) if login_not_approved_for?(user) @error = login_not_approved[:error] - return render layout: 'no_ember' elsif payload = login_error_check(user) @error = payload[:error] - return render layout: 'no_ember' else log_on_user(user) - redirect_to path("/") + return redirect_to path("/") end else @error = I18n.t('email_login.invalid_token') - return render layout: 'no_ember' end + + render layout: 'no_ember' end def forgot_password diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb index 069562e3e3..76a2085883 100644 --- a/app/controllers/static_controller.rb +++ b/app/controllers/static_controller.rb @@ -94,6 +94,8 @@ class StaticController < ApplicationController redirect_to destination end + FAVICON ||= -"favicon" + # We need to be able to draw our favicon on a canvas # and pull it off the canvas into a data uri # This can work by ensuring people set all the right CORS @@ -101,16 +103,18 @@ class StaticController < ApplicationController # instead we cache the favicon in redis and serve it out real quick with # a huge expiry, we also cache these assets in nginx so it bypassed if needed def favicon + is_asset_path hijack do - data = DistributedMemoizer.memoize('favicon' + SiteSetting.favicon_url, 60 * 30) do + data = DistributedMemoizer.memoize(FAVICON + SiteSetting.favicon_url, 60 * 30) do begin file = FileHelper.download( SiteSetting.favicon_url, max_file_size: 50.kilobytes, - tmp_file_name: "favicon.png", + tmp_file_name: FAVICON, follow_redirect: true ) + file ||= Tempfile.new([FAVICON, ".png"]) data = file.read file.unlink data @@ -136,16 +140,22 @@ class StaticController < ApplicationController end def brotli_asset + is_asset_path + serve_asset(".br") do response.headers["Content-Encoding"] = 'br' end end def cdn_asset + is_asset_path + serve_asset end def service_worker_asset + is_asset_path + respond_to do |format| format.js do # https://github.com/w3c/ServiceWorker/blob/master/explainer.md#updating-a-service-worker diff --git a/app/controllers/stylesheets_controller.rb b/app/controllers/stylesheets_controller.rb index 32b1f8be8e..ef1837eab9 100644 --- a/app/controllers/stylesheets_controller.rb +++ b/app/controllers/stylesheets_controller.rb @@ -6,6 +6,8 @@ class StylesheetsController < ApplicationController end def show + is_asset_path + show_resource end diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 2e1fb88246..505882f67b 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -386,19 +386,19 @@ class TopicsController < ApplicationController .where('topic_allowed_groups.group_id IN (?)', group_ids).pluck(:id) allowed_groups.each do |id| if archive - GroupArchivedMessage.archive!(id, topic.id) + GroupArchivedMessage.archive!(id, topic) group_id = id else - GroupArchivedMessage.move_to_inbox!(id, topic.id) + GroupArchivedMessage.move_to_inbox!(id, topic) end end end if topic.allowed_users.include?(current_user) if archive - UserArchivedMessage.archive!(current_user.id, topic.id) + UserArchivedMessage.archive!(current_user.id, topic) else - UserArchivedMessage.move_to_inbox!(current_user.id, topic.id) + UserArchivedMessage.move_to_inbox!(current_user.id, topic) end end @@ -486,9 +486,10 @@ class TopicsController < ApplicationController end def invite - username_or_email = params[:user] ? fetch_username : fetch_email - topic = Topic.find_by(id: params[:topic_id]) + raise Discourse::InvalidParameters.new unless topic + + username_or_email = params[:user] ? fetch_username : fetch_email groups = Group.lookup_groups( group_ids: params[:group_ids], @@ -501,6 +502,7 @@ class TopicsController < ApplicationController begin if topic.invite(current_user, username_or_email, group_ids, params[:custom_message]) user = User.find_by_username_or_email(username_or_email) + if user render_json_dump BasicUserSerializer.new(user, scope: guardian, root: 'user') else diff --git a/app/controllers/user_avatars_controller.rb b/app/controllers/user_avatars_controller.rb index dc868d4805..03e9e03471 100644 --- a/app/controllers/user_avatars_controller.rb +++ b/app/controllers/user_avatars_controller.rb @@ -33,6 +33,8 @@ class UserAvatarsController < ApplicationController end def show_proxy_letter + is_asset_path + if SiteSetting.external_system_avatars_url !~ /^\/letter_avatar_proxy/ raise Discourse::NotFound end @@ -56,6 +58,8 @@ class UserAvatarsController < ApplicationController end def show_letter + is_asset_path + params.require(:username) params.require(:version) params.require(:size) @@ -75,6 +79,8 @@ class UserAvatarsController < ApplicationController end def show + is_asset_path + # we need multisite support to keep a single origin pull for CDNs RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do hijack do diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index afebf58eb8..768898585a 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -114,11 +114,22 @@ class Users::OmniauthCallbacksController < ApplicationController end def user_found(user) + if user.totp_enabled? + @auth_result.omniauth_disallow_totp = true + return + end + # automatically activate/unstage any account if a provider marked the email valid if @auth_result.email_valid && @auth_result.email == user.email user.update!(staged: false) + # ensure there is an active email token - user.email_tokens.create(email: user.email) unless EmailToken.where(email: user.email, confirmed: true).present? || user.email_tokens.active.where(email: user.email).exists? + unless EmailToken.where(email: user.email, confirmed: true).exists? || + user.email_tokens.active.where(email: user.email).exists? + + user.email_tokens.create!(email: user.email) + end + user.activate user.update!(registration_ip_address: request.remote_ip) if user.registration_ip_address.blank? end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4d0726d865..d8c0ab7043 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -12,7 +12,7 @@ class UsersController < ApplicationController requires_login only: [ :username, :update, :user_preferences_redirect, :upload_user_image, :pick_avatar, :destroy_user_image, :destroy, :check_emails, :topic_tracking_state, - :preferences + :preferences, :create_second_factor, :update_second_factor ] skip_before_action :check_xhr, only: [ @@ -470,12 +470,24 @@ class UsersController < ApplicationController end end + totp_enabled = @user&.totp_enabled? + + if !totp_enabled || @user.authenticate_totp(params[:second_factor_token]) + secure_session["second-factor-#{token}"] = "true" + end + + valid_second_factor = secure_session["second-factor-#{token}"] == "true" + if !@user @error = I18n.t('password_reset.no_token') elsif request.put? @invalid_password = params[:password].blank? || params[:password].length > User.max_password_length - if @invalid_password + if !valid_second_factor + RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed! + @user.errors.add(:user_second_factor, :invalid) + @error = I18n.t('login.invalid_second_factor_code') + elsif @invalid_password @user.errors.add(:password, :invalid) else @user.password = params[:password] @@ -484,6 +496,7 @@ class UsersController < ApplicationController if @user.save Invite.invalidate_for_email(@user.email) # invite link can't be used to log in anymore secure_session["password-#{token}"] = nil + secure_session["second-factor-#{token}"] = nil logon_after_password_reset end end @@ -496,9 +509,14 @@ class UsersController < ApplicationController else store_preloaded( "password_reset", - MultiJson.dump(is_developer: UsernameCheckerService.is_developer?(@user.email), admin: @user.admin?) + MultiJson.dump( + is_developer: UsernameCheckerService.is_developer?(@user.email), + admin: @user.admin?, + second_factor_required: !valid_second_factor + ) ) end + return redirect_to(wizard_path) if request.put? && Wizard.user_requires_completion?(@user) end @@ -521,7 +539,11 @@ class UsersController < ApplicationController } end else - render json: { is_developer: UsernameCheckerService.is_developer?(@user.email), admin: @user.admin? } + render json: { + is_developer: UsernameCheckerService.is_developer?(@user.email), + admin: @user.admin?, + second_factor_required: !valid_second_factor + } end end end @@ -550,7 +572,7 @@ class UsersController < ApplicationController def admin_login return redirect_to(path("/")) if current_user - if request.put? + if request.put? && params[:email].present? RateLimiter.new(nil, "admin-login-hr-#{request.remote_ip}", 6, 1.hour).performed! RateLimiter.new(nil, "admin-login-min-#{request.remote_ip}", 3, 1.minute).performed! @@ -561,15 +583,45 @@ class UsersController < ApplicationController else @message = I18n.t("admin_login.errors.unknown_email_address") end - elsif params[:token].present? - if EmailToken.valid_token_format?(params[:token]) - @user = EmailToken.confirm(params[:token]) + elsif (token = params[:token]).present? + valid_token = EmailToken.valid_token_format?(token) - if @user&.admin? - log_on_user(@user) - return redirect_to path("/") - else - @message = I18n.t("admin_login.errors.unknown_email_address") + if valid_token + if params[:second_factor_token].present? + RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed! + end + + email_token_user = EmailToken.confirmable(token)&.user + totp_enabled = email_token_user.totp_enabled? + second_factor_token = params[:second_factor_token] + confirm_email = false + + confirm_email = + if totp_enabled + @second_factor_required = true + @message = I18n.t("login.second_factor_title") + + if second_factor_token.present? + if email_token_user.authenticate_totp(second_factor_token) + true + else + @error = I18n.t("login.invalid_second_factor_code") + false + end + end + else + true + end + + if confirm_email + @user = EmailToken.confirm(token) + + if @user && @user.admin? + log_on_user(@user) + return redirect_to path("/") + else + @message = I18n.t("admin_login.errors.unknown_email_address") + end end else @message = I18n.t("admin_login.errors.invalid_token") @@ -899,6 +951,61 @@ class UsersController < ApplicationController render layout: 'no_ember' end + def create_second_factor + raise Discourse::NotFound if SiteSetting.enable_sso || !SiteSetting.enable_local_logins + RateLimiter.new(nil, "login-hr-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_hour, 1.hour).performed! + RateLimiter.new(nil, "login-min-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_minute, 1.minute).performed! + + unless current_user.confirm_password?(params[:password]) + return render json: failed_json.merge( + error: I18n.t("login.incorrect_password") + ) + end + + qrcode_svg = RQRCode::QRCode.new(current_user.totp_provisioning_uri).as_svg( + offset: 0, + color: '000', + shape_rendering: 'crispEdges', + module_size: 4 + ) + + render json: success_json.merge( + key: current_user.user_second_factor.data, + qr: qrcode_svg + ) + end + + def update_second_factor + params.require(:second_factor_token) + + [request.remote_ip, current_user.id].each do |key| + RateLimiter.new(nil, "second-factor-min-#{key}", 3, 1.minute).performed! + end + + user_second_factor = current_user.user_second_factor + raise Discourse::InvalidParameters unless user_second_factor + + unless current_user.authenticate_totp(params[:second_factor_token]) + return render json: failed_json.merge( + error: I18n.t("login.invalid_second_factor_code") + ) + end + + if params[:enable] == "true" + user_second_factor.update!(enabled: true) + else + user_second_factor.destroy! + + Jobs.enqueue( + :critical_user_email, + type: :account_second_factor_disabled, + user_id: current_user.id + ) + end + + render json: success_json + end + private def honeypot_value @@ -975,7 +1082,12 @@ class UsersController < ApplicationController result.merge!(params.permit(:active, :staged, :approved)) end - result + modify_user_params(result) + end + + # Plugins can use this to modify user parameters + def modify_user_params(attrs) + attrs end def user_locale diff --git a/app/controllers/users_email_controller.rb b/app/controllers/users_email_controller.rb index e408a84f2a..2fd289edb0 100644 --- a/app/controllers/users_email_controller.rb +++ b/app/controllers/users_email_controller.rb @@ -33,12 +33,32 @@ class UsersEmailController < ApplicationController def confirm expires_now - updater = EmailUpdater.new - @update_result = updater.confirm(params[:token]) - if @update_result == :complete - updater.user.user_stat.reset_bounce_score! - log_on_user(updater.user) + token = EmailToken.confirmable(params[:token]) + user = token&.user + + change_request = + if user + user.email_change_requests.where(new_email_token_id: token.id).first + end + + if change_request&.change_state == EmailChangeRequest.states[:authorizing_new] && + user.totp_enabled? && !user.authenticate_totp(params[:second_factor_token]) + + @update_result = :invalid_second_factor + + if params[:second_factor_token].present? + RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed! + @show_invalid_second_factor_error = true + end + else + updater = EmailUpdater.new + @update_result = updater.confirm(params[:token]) + + if @update_result == :complete + updater.user.user_stat.reset_bounce_score! + log_on_user(updater.user) + end end render layout: 'no_ember' diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb index 5eca7dd91b..0a0899e572 100644 --- a/app/jobs/regular/export_csv_file.rb +++ b/app/jobs/regular/export_csv_file.rb @@ -146,8 +146,14 @@ module Jobs @extra[:end_date] = @extra[:end_date].to_date if @extra[:end_date].is_a?(String) @extra[:category_id] = @extra[:category_id].present? ? @extra[:category_id].to_i : nil @extra[:group_id] = @extra[:group_id].present? ? @extra[:group_id].to_i : nil + + report_hash = {} Report.find(@extra[:name], @extra).data.each do |row| - yield [row[:x].to_s(:db), row[:y].to_s(:db)] + report_hash[row[:x].to_s(:db)] = row[:y].to_s(:db) + end + + (@extra[:start_date]..@extra[:end_date]).each do |date| + yield [date.to_s(:db), report_hash.fetch(date.to_s(:db), 0)] end end diff --git a/app/jobs/regular/toggle_topic_closed.rb b/app/jobs/regular/toggle_topic_closed.rb index dab5d65190..7a77f02489 100644 --- a/app/jobs/regular/toggle_topic_closed.rb +++ b/app/jobs/regular/toggle_topic_closed.rb @@ -16,6 +16,7 @@ module Jobs if Guardian.new(user).can_close?(topic) topic.update_status('autoclosed', state, user) + topic.inherit_auto_close_from_category if state == false end end end diff --git a/app/jobs/scheduled/reindex_search.rb b/app/jobs/scheduled/reindex_search.rb index e354b36d00..0e9768d292 100644 --- a/app/jobs/scheduled/reindex_search.rb +++ b/app/jobs/scheduled/reindex_search.rb @@ -1,7 +1,7 @@ module Jobs # if locale changes or search algorithm changes we may want to reindex stuff class ReindexSearch < Jobs::Scheduled - every 1.day + every 2.hours def execute(args) rebuild_problem_topics @@ -38,13 +38,14 @@ module Jobs end end - def rebuild_problem_posts(limit = 10000) + def rebuild_problem_posts(limit = 20000) post_ids = load_problem_post_ids(limit) post_ids.each do |id| - post = Post.find_by(id: id) # could be deleted while iterating through batch - SearchIndexer.index(post, force: true) if post + if post = Post.find_by(id: id) + SearchIndexer.index(post, force: true) + end end end @@ -67,6 +68,7 @@ module Jobs WHERE pd.post_id IS NULL )', SiteSetting.default_locale, Search::INDEX_VERSION) .limit(limit) + .order('posts.id DESC') .pluck(:id) end diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index f5d8e537ec..c9cd1a032d 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -120,6 +120,15 @@ class UserNotifications < ActionMailer::Base ) end + def account_second_factor_disabled(user, opts = {}) + build_email( + user.email, + template: 'user_notifications.account_second_factor_disabled', + locale: user_locale(user), + email: user.email + ) + end + def short_date(dt) if dt.year == Time.now.year I18n.l(dt, format: :short_no_year) @@ -258,6 +267,7 @@ class UserNotifications < ActionMailer::Base opts[:use_site_subject] = true opts[:add_re_to_subject] = true opts[:show_category_in_subject] = false + opts[:show_group_in_subject] = true if SiteSetting.group_in_subject # We use the 'user_posted' event when you are emailed a post in a PM. opts[:notification_type] = 'posted' @@ -372,6 +382,7 @@ class UserNotifications < ActionMailer::Base use_site_subject: opts[:use_site_subject], add_re_to_subject: opts[:add_re_to_subject], show_category_in_subject: opts[:show_category_in_subject], + show_group_in_subject: opts[:show_group_in_subject], notification_type: notification_type, use_invite_template: opts[:use_invite_template], user: user @@ -422,6 +433,21 @@ class UserNotifications < ActionMailer::Base show_category_in_subject = nil end + if post.topic.private_message? + subject_pm = + if opts[:show_group_in_subject] + if group = post.topic.allowed_groups&.first + if group.full_name + "[#{group.full_name}] " + else + "[#{group.name}] " + end + end + else + I18n.t('subject_pm') + end + end + if SiteSetting.private_email? title = I18n.t("system_messages.private_topic_title", id: post.topic_id) end @@ -523,6 +549,7 @@ class UserNotifications < ActionMailer::Base add_re_to_subject: add_re_to_subject, show_category_in_subject: show_category_in_subject, private_reply: post.topic.private_message?, + subject_pm: subject_pm, include_respond_instructions: !(user.suspended? || user.staged?), template: template, site_description: SiteSetting.site_description, diff --git a/app/models/badge.rb b/app/models/badge.rb index abb618cc78..60da562949 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -240,7 +240,7 @@ end # Table name: badges # # id :integer not null, primary key -# name :string(255) not null +# name :string not null # description :text # badge_type_id :integer not null # grant_count :integer default(0), not null @@ -248,7 +248,7 @@ end # updated_at :datetime not null # allow_title :boolean default(FALSE), not null # multiple_grant :boolean default(FALSE), not null -# icon :string(255) default("fa-certificate") +# icon :string default("fa-certificate") # listable :boolean default(TRUE) # target_posts :boolean default(FALSE) # query :text @@ -263,5 +263,6 @@ end # # Indexes # -# index_badges_on_name (name) UNIQUE +# index_badges_on_badge_type_id (badge_type_id) +# index_badges_on_name (name) UNIQUE # diff --git a/app/models/badge_grouping.rb b/app/models/badge_grouping.rb index f1201e7214..4fdcef66e3 100644 --- a/app/models/badge_grouping.rb +++ b/app/models/badge_grouping.rb @@ -22,7 +22,7 @@ end # Table name: badge_groupings # # id :integer not null, primary key -# name :string(255) not null +# name :string not null # description :text # position :integer not null # created_at :datetime not null diff --git a/app/models/badge_type.rb b/app/models/badge_type.rb index 4648e3ad58..b1d6e91edd 100644 --- a/app/models/badge_type.rb +++ b/app/models/badge_type.rb @@ -12,7 +12,7 @@ end # Table name: badge_types # # id :integer not null, primary key -# name :string(255) not null +# name :string not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/category.rb b/app/models/category.rb index 2589903c2c..9f8cd2870f 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -23,9 +23,6 @@ class Category < ActiveRecord::Base has_many :category_featured_topics has_many :featured_topics, through: :category_featured_topics, source: :topic - has_many :category_featured_users - has_many :featured_users, through: :category_featured_users, source: :user - has_many :category_groups, dependent: :destroy has_many :groups, through: :category_groups @@ -199,7 +196,7 @@ SQL t.delete_topic_timer(TopicTimer.types[:close]) t.save!(validate: false) update_column(:topic_id, t.id) - t.posts.create(raw: post_template, user: user) + t.posts.create(raw: description || post_template, user: user) end def topic_url @@ -405,8 +402,8 @@ SQL end def self.query_category(slug_or_id, parent_category_id) - self.where(slug: slug_or_id, parent_category_id: parent_category_id).includes(:featured_users).first || - self.where(id: slug_or_id.to_i, parent_category_id: parent_category_id).includes(:featured_users).first + self.where(slug: slug_or_id, parent_category_id: parent_category_id).first || + self.where(id: slug_or_id.to_i, parent_category_id: parent_category_id).first end def self.find_by_email(email) @@ -521,7 +518,7 @@ end # topics_year :integer default(0) # topics_month :integer default(0) # topics_week :integer default(0) -# slug :string(255) not null +# slug :string not null # description :text # text_color :string(6) default("FFFFFF"), not null # read_restricted :boolean default(FALSE), not null @@ -534,7 +531,7 @@ end # posts_year :integer default(0) # posts_month :integer default(0) # posts_week :integer default(0) -# email_in :string(255) +# email_in :string # email_in_allow_strangers :boolean default(FALSE) # topics_day :integer default(0) # posts_day :integer default(0) @@ -542,7 +539,6 @@ end # name_lower :string(50) not null # auto_close_based_on_last_post :boolean default(FALSE) # topic_template :text -# suppress_from_homepage :boolean default(FALSE) # contains_messages :boolean # sort_order :string # sort_ascending :boolean @@ -556,10 +552,11 @@ end # subcategory_list_style :string(50) default("rows_with_featured_topics") # default_top_period :string(20) default("all") # mailinglist_mirror :boolean default(FALSE), not null +# suppress_from_latest :boolean default(FALSE) # # Indexes # -# index_categories_on_email_in (email_in) UNIQUE -# index_categories_on_forum_thread_count (topic_count) -# unique_index_categories_on_name ((COALESCE(parent_category_id, '-1'::integer)), name) UNIQUE +# index_categories_on_email_in (email_in) UNIQUE +# index_categories_on_topic_count (topic_count) +# unique_index_categories_on_name ((COALESCE(parent_category_id, '-1'::integer)), name) UNIQUE # diff --git a/app/models/category_featured_user.rb b/app/models/category_featured_user.rb deleted file mode 100644 index 638af79213..0000000000 --- a/app/models/category_featured_user.rb +++ /dev/null @@ -1,63 +0,0 @@ -class CategoryFeaturedUser < ActiveRecord::Base - belongs_to :category - belongs_to :user - - def self.max_featured_users - 5 - end - - def self.feature_users_in(category_or_category_id) - category_id = - if category_or_category_id.is_a?(Integer) - category_or_category_id - else - category_or_category_id.id - end - - # Figure out most recent posters in the category - most_recent_user_ids = exec_sql " - SELECT x.user_id - FROM ( - SELECT DISTINCT ON (p.user_id) p.user_id AS user_id, - p.created_at AS created_at - FROM posts AS p - INNER JOIN topics AS ft ON ft.id = p.topic_id - WHERE ft.category_id = :category_id - AND p.user_id IS NOT NULL - ORDER BY p.user_id, p.created_at DESC - ) AS x - ORDER BY x.created_at DESC - LIMIT :max_featured_users; - ", category_id: category_id, max_featured_users: max_featured_users - - user_ids = most_recent_user_ids.map { |uc| uc['user_id'].to_i } - current = CategoryFeaturedUser.where(category_id: category_id).order(:id).pluck(:user_id) - - return if current == user_ids - - transaction do - CategoryFeaturedUser.where(category_id: category_id).delete_all - - user_ids.each do |user_id| - create(category_id: category_id, user_id: user_id) - end - end - - end - -end - -# == Schema Information -# -# Table name: category_featured_users -# -# id :integer not null, primary key -# category_id :integer -# user_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# -# Indexes -# -# index_category_featured_users_on_category_id_and_user_id (category_id,user_id) UNIQUE -# diff --git a/app/models/category_list.rb b/app/models/category_list.rb index ccc23d88b6..76511d5b8b 100644 --- a/app/models/category_list.rb +++ b/app/models/category_list.rb @@ -72,7 +72,6 @@ class CategoryList subcategories: [:topic_only_relative_url] ).secured(@guardian) - @categories = @categories.where(suppress_from_homepage: false) if @options[:is_homepage] @categories = @categories.where("categories.parent_category_id = ?", @options[:parent_category_id].to_i) if @options[:parent_category_id].present? if SiteSetting.fixed_category_positions diff --git a/app/models/category_page_style.rb b/app/models/category_page_style.rb index fe82ea95db..d45376ad1f 100644 --- a/app/models/category_page_style.rb +++ b/app/models/category_page_style.rb @@ -11,6 +11,7 @@ class CategoryPageStyle < EnumSiteSetting { name: 'category_page_style.categories_only', value: 'categories_only' }, { name: 'category_page_style.categories_with_featured_topics', value: 'categories_with_featured_topics' }, { name: 'category_page_style.categories_and_latest_topics', value: 'categories_and_latest_topics' }, + { name: 'category_page_style.categories_and_top_topics', value: 'categories_and_top_topics' }, ] end diff --git a/app/models/category_tag.rb b/app/models/category_tag.rb index 20ab52d7bc..507d13e384 100644 --- a/app/models/category_tag.rb +++ b/app/models/category_tag.rb @@ -10,8 +10,8 @@ end # id :integer not null, primary key # category_id :integer not null # tag_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/category_tag_group.rb b/app/models/category_tag_group.rb index 3c642d3964..c262539b96 100644 --- a/app/models/category_tag_group.rb +++ b/app/models/category_tag_group.rb @@ -10,8 +10,8 @@ end # id :integer not null, primary key # category_id :integer not null # tag_group_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/category_tag_stat.rb b/app/models/category_tag_stat.rb index f201de08cd..8aff0447c8 100644 --- a/app/models/category_tag_stat.rb +++ b/app/models/category_tag_stat.rb @@ -61,3 +61,20 @@ class CategoryTagStat < ActiveRecord::Base SQL end end + +# == Schema Information +# +# Table name: category_tag_stats +# +# id :integer not null, primary key +# category_id :integer not null +# tag_id :integer not null +# topic_count :integer default(0), not null +# +# Indexes +# +# index_category_tag_stats_on_category_id (category_id) +# index_category_tag_stats_on_category_id_and_tag_id (category_id,tag_id) UNIQUE +# index_category_tag_stats_on_category_id_and_topic_count (category_id,topic_count) +# index_category_tag_stats_on_tag_id (tag_id) +# diff --git a/app/models/child_theme.rb b/app/models/child_theme.rb index 6e101bd8aa..e4eb2d0ef7 100644 --- a/app/models/child_theme.rb +++ b/app/models/child_theme.rb @@ -10,8 +10,8 @@ end # id :integer not null, primary key # parent_theme_id :integer # child_theme_id :integer -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/color_scheme.rb b/app/models/color_scheme.rb index dbe60b1c76..0e1f09c12c 100644 --- a/app/models/color_scheme.rb +++ b/app/models/color_scheme.rb @@ -187,7 +187,7 @@ end # Table name: color_schemes # # id :integer not null, primary key -# name :string(255) not null +# name :string not null # version :integer default(1), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/color_scheme_color.rb b/app/models/color_scheme_color.rb index 39a7b51eec..51e0c4ae8d 100644 --- a/app/models/color_scheme_color.rb +++ b/app/models/color_scheme_color.rb @@ -9,8 +9,8 @@ end # Table name: color_scheme_colors # # id :integer not null, primary key -# name :string(255) not null -# hex :string(255) not null +# name :string not null +# hex :string not null # color_scheme_id :integer not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/concerns/has_custom_fields.rb b/app/models/concerns/has_custom_fields.rb index 519f8deb51..b33b4fa213 100644 --- a/app/models/concerns/has_custom_fields.rb +++ b/app/models/concerns/has_custom_fields.rb @@ -155,6 +155,20 @@ module HasCustomFields !@custom_fields || @custom_fields_orig == @custom_fields end + # `upsert_custom_fields` will only insert/update existing fields, and will not + # delete anything. It is safer under concurrency and is recommended when + # you just want to attach fields to things without maintaining a specific + # set of fields. + def upsert_custom_fields(fields) + fields.each do |k, v| + row_count = _custom_fields.where(name: k).update_all(value: v) + if row_count == 0 + _custom_fields.create!(name: k, value: v) + end + custom_fields[k] = v + end + end + def save_custom_fields(force = false) if force || !custom_fields_clean? dup = @custom_fields.dup diff --git a/app/models/concerns/second_factor_manager.rb b/app/models/concerns/second_factor_manager.rb new file mode 100644 index 0000000000..096fc00305 --- /dev/null +++ b/app/models/concerns/second_factor_manager.rb @@ -0,0 +1,40 @@ +module SecondFactorManager + extend ActiveSupport::Concern + + def totp + self.create_totp + ROTP::TOTP.new(self.user_second_factor.data, issuer: SiteSetting.title) + end + + def create_totp(opts = {}) + if !self.user_second_factor + self.create_user_second_factor!({ + method: UserSecondFactor.methods[:totp], + data: ROTP::Base32.random_base32 + }.merge(opts)) + end + end + + def totp_provisioning_uri + self.totp.provisioning_uri(self.email) + end + + def authenticate_totp(token) + totp = self.totp + last_used = 0 + + if self.user_second_factor.last_used + last_used = self.user_second_factor.last_used.to_i + end + + authenticated = !token.blank? && totp.verify_with_drift_and_prior(token, 0, last_used) + self.user_second_factor.update!(last_used: DateTime.now) if authenticated + !!authenticated + end + + def totp_enabled? + !!(self&.user_second_factor&.enabled?) && + !SiteSetting.enable_sso && + SiteSetting.enable_local_logins + end +end diff --git a/app/models/discourse_single_sign_on.rb b/app/models/discourse_single_sign_on.rb index 3b976a57b3..16496c8e80 100644 --- a/app/models/discourse_single_sign_on.rb +++ b/app/models/discourse_single_sign_on.rb @@ -144,7 +144,7 @@ class DiscourseSingleSignOn < SingleSignOn user = User.create!(user_params) if SiteSetting.verbose_sso_logging - Rails.logger.warn("Verbose SSO log: New User (user_id: #{user.id}) Created with #{user_params}") + Rails.logger.warn("Verbose SSO log: New User (user_id: #{user.id}) Params: #{user_params} User Params: #{user.attributes} User Errors: #{user.errors.full_messages} Email: #{user.primary_email.attributes} Email Error: #{user.primary_email.errors.full_messages}") end end diff --git a/app/models/draft.rb b/app/models/draft.rb index 48bd38ce13..be412cc72a 100644 --- a/app/models/draft.rb +++ b/app/models/draft.rb @@ -59,7 +59,7 @@ end # # id :integer not null, primary key # user_id :integer not null -# draft_key :string(255) not null +# draft_key :string not null # data :text not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/draft_sequence.rb b/app/models/draft_sequence.rb index 863b00aa70..460556d004 100644 --- a/app/models/draft_sequence.rb +++ b/app/models/draft_sequence.rb @@ -33,7 +33,7 @@ end # # id :integer not null, primary key # user_id :integer not null -# draft_key :string(255) not null +# draft_key :string not null # sequence :integer not null # # Indexes diff --git a/app/models/email_log.rb b/app/models/email_log.rb index c17878c93d..0fafa1bcc0 100644 --- a/app/models/email_log.rb +++ b/app/models/email_log.rb @@ -74,8 +74,8 @@ end # Table name: email_logs # # id :integer not null, primary key -# to_address :string(255) not null -# email_type :string(255) not null +# to_address :string not null +# email_type :string not null # user_id :integer # created_at :datetime not null # updated_at :datetime not null @@ -83,16 +83,19 @@ end # post_id :integer # topic_id :integer # skipped :boolean default(FALSE) -# skipped_reason :string(255) +# skipped_reason :string # bounce_key :string # bounced :boolean default(FALSE), not null # message_id :string # # Indexes # +# idx_email_logs_user_created_filtered (user_id,created_at) # index_email_logs_on_created_at (created_at) # index_email_logs_on_message_id (message_id) +# index_email_logs_on_post_id (post_id) # index_email_logs_on_reply_key (reply_key) # index_email_logs_on_skipped_and_created_at (skipped,created_at) +# index_email_logs_on_topic_id (topic_id) # index_email_logs_on_user_id_and_created_at (user_id,created_at) # diff --git a/app/models/email_token.rb b/app/models/email_token.rb index 2338108ac4..2a10dd4e23 100644 --- a/app/models/email_token.rb +++ b/app/models/email_token.rb @@ -94,8 +94,8 @@ end # # id :integer not null, primary key # user_id :integer not null -# email :string(255) not null -# token :string(255) not null +# email :string not null +# token :string not null # confirmed :boolean default(FALSE), not null # expired :boolean default(FALSE), not null # created_at :datetime not null diff --git a/app/models/embeddable_host.rb b/app/models/embeddable_host.rb index b76a6725b8..2fb3e4ed15 100644 --- a/app/models/embeddable_host.rb +++ b/app/models/embeddable_host.rb @@ -60,10 +60,10 @@ end # Table name: embeddable_hosts # # id :integer not null, primary key -# host :string(255) not null +# host :string not null # category_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # path_whitelist :string # class_name :string # diff --git a/app/models/facebook_user_info.rb b/app/models/facebook_user_info.rb index 7b099e03bb..eb99611310 100644 --- a/app/models/facebook_user_info.rb +++ b/app/models/facebook_user_info.rb @@ -9,13 +9,13 @@ end # id :integer not null, primary key # user_id :integer not null # facebook_user_id :integer not null -# username :string(255) -# first_name :string(255) -# last_name :string(255) -# email :string(255) -# gender :string(255) -# name :string(255) -# link :string(255) +# username :string +# first_name :string +# last_name :string +# email :string +# gender :string +# name :string +# link :string # created_at :datetime not null # updated_at :datetime not null # avatar_url :string diff --git a/app/models/github_user_info.rb b/app/models/github_user_info.rb index c79a3b0e91..8e776f33ab 100644 --- a/app/models/github_user_info.rb +++ b/app/models/github_user_info.rb @@ -8,7 +8,7 @@ end # # id :integer not null, primary key # user_id :integer not null -# screen_name :string(255) not null +# screen_name :string not null # github_user_id :integer not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/google_user_info.rb b/app/models/google_user_info.rb index 26f3dda50d..343fe9945f 100644 --- a/app/models/google_user_info.rb +++ b/app/models/google_user_info.rb @@ -8,15 +8,15 @@ end # # id :integer not null, primary key # user_id :integer not null -# google_user_id :string(255) not null -# first_name :string(255) -# last_name :string(255) -# email :string(255) -# gender :string(255) -# name :string(255) -# link :string(255) -# profile_link :string(255) -# picture :string(255) +# google_user_id :string not null +# first_name :string +# last_name :string +# email :string +# gender :string +# name :string +# link :string +# profile_link :string +# picture :string # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/group.rb b/app/models/group.rb index 61f7e79f13..6c542b9b6c 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -192,6 +192,7 @@ class Group < ActiveRecord::Base .references(:posts, :topics, :category) .where(user_id: user_ids) .where('topics.archetype <> ?', Archetype.private_message) + .where('topics.visible') .where(post_type: Post.types[:regular]) if opts[:category_id].present? @@ -534,6 +535,16 @@ class Group < ActiveRecord::Base if user_attributes.present? User.where(id: user_ids).update_all(user_attributes) end + + # update group user count + Group.exec_sql <<-SQL.squish + UPDATE groups g + SET user_count = + (SELECT COUNT(gu.user_id) + FROM group_users gu + WHERE gu.group_id = g.id) + WHERE g.id = #{self.id}; + SQL end if self.grant_trust_level.present? @@ -661,7 +672,7 @@ end # Table name: groups # # id :integer not null, primary key -# name :string(255) not null +# name :string not null # created_at :datetime not null # updated_at :datetime not null # automatic :boolean default(FALSE), not null @@ -669,7 +680,7 @@ end # automatic_membership_email_domains :text # automatic_membership_retroactive :boolean default(FALSE) # primary_group :boolean default(FALSE), not null -# title :string(255) +# title :string # grant_trust_level :integer # incoming_email :string # has_messages :boolean default(FALSE), not null diff --git a/app/models/group_archived_message.rb b/app/models/group_archived_message.rb index 91d2062cae..778a3944c1 100644 --- a/app/models/group_archived_message.rb +++ b/app/models/group_archived_message.rb @@ -2,17 +2,21 @@ class GroupArchivedMessage < ActiveRecord::Base belongs_to :user belongs_to :topic - def self.move_to_inbox!(group_id, topic_id) + def self.move_to_inbox!(group_id, topic) + topic_id = topic.id GroupArchivedMessage.where(group_id: group_id, topic_id: topic_id).destroy_all trigger(:move_to_inbox, group_id, topic_id) MessageBus.publish("/topic/#{topic_id}", { type: "move_to_inbox" }, group_ids: [group_id]) + publish_topic_tracking_state(topic) end - def self.archive!(group_id, topic_id) + def self.archive!(group_id, topic) + topic_id = topic.id GroupArchivedMessage.where(group_id: group_id, topic_id: topic_id).destroy_all GroupArchivedMessage.create!(group_id: group_id, topic_id: topic_id) trigger(:archive_message, group_id, topic_id) MessageBus.publish("/topic/#{topic_id}", { type: "archived" }, group_ids: [group_id]) + publish_topic_tracking_state(topic) end def self.trigger(event, group_id, topic_id) @@ -23,6 +27,13 @@ class GroupArchivedMessage < ActiveRecord::Base end end + private + + def self.publish_topic_tracking_state(topic) + TopicTrackingState.publish_private_message( + topic, group_archive: true + ) + end end # == Schema Information @@ -32,8 +43,8 @@ end # id :integer not null, primary key # group_id :integer not null # topic_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/group_mention.rb b/app/models/group_mention.rb index 30eb647ebc..3cef10dd73 100644 --- a/app/models/group_mention.rb +++ b/app/models/group_mention.rb @@ -10,8 +10,8 @@ end # id :integer not null, primary key # post_id :integer # group_id :integer -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/invite.rb b/app/models/invite.rb index 5a57084a07..6b63855f01 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -262,7 +262,7 @@ end # # id :integer not null, primary key # invite_key :string(32) not null -# email :string(255) +# email :string # invited_by_id :integer not null # user_id :integer # redeemed_at :datetime diff --git a/app/models/muted_user.rb b/app/models/muted_user.rb index 59f8089c6f..1ba464105d 100644 --- a/app/models/muted_user.rb +++ b/app/models/muted_user.rb @@ -10,8 +10,8 @@ end # id :integer not null, primary key # user_id :integer not null # muted_user_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/oauth2_user_info.rb b/app/models/oauth2_user_info.rb index 7ce0cace41..5b27d0cf71 100644 --- a/app/models/oauth2_user_info.rb +++ b/app/models/oauth2_user_info.rb @@ -9,10 +9,10 @@ end # # id :integer not null, primary key # user_id :integer not null -# uid :string(255) not null -# provider :string(255) not null -# email :string(255) -# name :string(255) +# uid :string not null +# provider :string not null +# email :string +# name :string # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index 82b6479725..54c8d6c490 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -321,7 +321,7 @@ end # width :integer not null # height :integer not null # upload_id :integer not null -# url :string(255) not null +# url :string not null # # Indexes # diff --git a/app/models/permalink.rb b/app/models/permalink.rb index 4bfaa5a9cd..62e3ef84f5 100644 --- a/app/models/permalink.rb +++ b/app/models/permalink.rb @@ -101,8 +101,8 @@ end # topic_id :integer # post_id :integer # category_id :integer -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # external_url :string(1000) # # Indexes diff --git a/app/models/plugin_store_row.rb b/app/models/plugin_store_row.rb index db1d9d3fda..d9bb1c05db 100644 --- a/app/models/plugin_store_row.rb +++ b/app/models/plugin_store_row.rb @@ -6,9 +6,9 @@ end # Table name: plugin_store_rows # # id :integer not null, primary key -# plugin_name :string(255) not null -# key :string(255) not null -# type_name :string(255) not null +# plugin_name :string not null +# key :string not null +# type_name :string not null # value :text # # Indexes diff --git a/app/models/post.rb b/app/models/post.rb index 5863cc49c1..78551d1f81 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -164,7 +164,7 @@ class Post < ActiveRecord::Base }.merge(options) if Topic.visible_post_types.include?(post_type) - if topic.archetype == Archetype.private_message + if topic.private_message? user_ids = User.where('admin or moderator').pluck(:id) user_ids |= topic.allowed_users.pluck(:id) MessageBus.publish(channel, msg, user_ids: user_ids) @@ -536,7 +536,7 @@ class Post < ActiveRecord::Base revise( actor, { raw: self.raw, user_id: new_user.id, edit_reason: edit_reason }, - bypass_bump: true, skip_revision: skip_revision + bypass_bump: true, skip_revision: skip_revision, skip_validations: true ) if post_number == topic.highest_post_number @@ -814,7 +814,7 @@ end # notify_user_count :integer default(0), not null # like_score :integer default(0), not null # deleted_by_id :integer -# edit_reason :string(255) +# edit_reason :string # word_count :integer # version :integer default(1), not null # cook_method :integer default(1), not null @@ -827,8 +827,9 @@ end # via_email :boolean default(FALSE), not null # raw_email :text # public_version :integer default(1), not null -# action_code :string(255) +# action_code :string # image_url :string +# locked_by_id :integer # # Indexes # diff --git a/app/models/post_action.rb b/app/models/post_action.rb index 8bcb64482b..471cebe9ab 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -154,6 +154,7 @@ SQL end DiscourseEvent.trigger(:confirmed_spam_post, post) if trigger_spam + DiscourseEvent.trigger(:flag_reviewed, post) update_flagged_posts_count end @@ -186,6 +187,7 @@ SQL end Post.with_deleted.where(id: post.id).update_all(cached) + DiscourseEvent.trigger(:flag_reviewed, post) update_flagged_posts_count end @@ -203,6 +205,7 @@ SQL action.add_moderator_post_if_needed(moderator, :deferred, delete_post) end + DiscourseEvent.trigger(:flag_reviewed, post) update_flagged_posts_count end @@ -302,7 +305,8 @@ SQL BadgeGranter.queue_badge_grant(Badge::Trigger::PostAction, post_action: post_action) end end - GivenDailyLike.increment_for(user.id) + + GivenDailyLike.increment_for(user.id) if post_action_type_id == PostActionType.types[:like] # agree with other flags if staff_took_action @@ -339,7 +343,7 @@ SQL if action = finder.first action.remove_act!(user) action.post.unhide! if action.staff_took_action - GivenDailyLike.decrement_for(user.id) + GivenDailyLike.decrement_for(user.id) if post_action_type_id == PostActionType.types[:like] end end @@ -360,7 +364,7 @@ SQL end def is_flag? - !!PostActionType.flag_types[post_action_type_id] + !!PostActionType.notify_flag_types[post_action_type_id] end def is_private_message? @@ -392,7 +396,7 @@ SQL end before_create do - post_action_type_ids = is_flag? ? PostActionType.flag_types_without_custom.values : post_action_type_id + post_action_type_ids = is_flag? ? PostActionType.notify_flag_types.values : post_action_type_id raise AlreadyActed if PostAction.where(user_id: user_id) .where(post_id: post_id) .where(post_action_type_id: post_action_type_ids) diff --git a/app/models/post_analyzer.rb b/app/models/post_analyzer.rb index ddcfa2fd0c..ab8ec1903d 100644 --- a/app/models/post_analyzer.rb +++ b/app/models/post_analyzer.rb @@ -130,7 +130,7 @@ class PostAnalyzer def cooked_stripped @cooked_stripped ||= begin doc = Nokogiri::HTML.fragment(cook(@raw, topic_id: @topic_id)) - doc.css("pre, code, aside.quote, .onebox, .elided").remove + doc.css("pre .mention, aside.quote > .title, aside.quote .mention, .onebox, .elided").remove doc end end diff --git a/app/models/post_detail.rb b/app/models/post_detail.rb index 8f88851673..c43219749c 100644 --- a/app/models/post_detail.rb +++ b/app/models/post_detail.rb @@ -11,8 +11,8 @@ end # # id :integer not null, primary key # post_id :integer -# key :string(255) -# value :string(255) +# key :string +# value :string # extra :text # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/post_search_data.rb b/app/models/post_search_data.rb index e890e87547..58940e693b 100644 --- a/app/models/post_search_data.rb +++ b/app/models/post_search_data.rb @@ -9,7 +9,7 @@ end # post_id :integer not null, primary key # search_data :tsvector # raw_data :text -# locale :string(255) +# locale :string # version :integer default(0) # # Indexes diff --git a/app/models/post_stat.rb b/app/models/post_stat.rb index ec293b6663..9de208b538 100644 --- a/app/models/post_stat.rb +++ b/app/models/post_stat.rb @@ -11,8 +11,8 @@ end # drafts_saved :integer # typing_duration_msecs :integer # composer_open_duration_msecs :integer -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/queued_post.rb b/app/models/queued_post.rb index 50f2997541..e8f0bdcca4 100644 --- a/app/models/queued_post.rb +++ b/app/models/queued_post.rb @@ -121,7 +121,7 @@ end # Table name: queued_posts # # id :integer not null, primary key -# queue :string(255) not null +# queue :string not null # state :integer not null # user_id :integer not null # raw :text not null @@ -131,8 +131,8 @@ end # approved_at :datetime # rejected_by_id :integer # rejected_at :datetime -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/remote_theme.rb b/app/models/remote_theme.rb index 3030b4aa22..9d11932744 100644 --- a/app/models/remote_theme.rb +++ b/app/models/remote_theme.rb @@ -76,6 +76,8 @@ class RemoteTheme < ActiveRecord::Base end Theme.targets.keys.each do |target| + next if target == :settings + ALLOWED_FIELDS.each do |field| lookup = if field == "scss" @@ -91,6 +93,9 @@ class RemoteTheme < ActiveRecord::Base end end + settings_yaml = importer["settings.yaml"] || importer["settings.yml"] + theme.set_field(target: :settings, name: "yaml", value: settings_yaml) + self.license_url ||= theme_info["license_url"] self.about_url ||= theme_info["about_url"] self.remote_updated_at = Time.zone.now @@ -155,6 +160,6 @@ end # license_url :string # commits_behind :integer # remote_updated_at :datetime -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # diff --git a/app/models/screened_email.rb b/app/models/screened_email.rb index 74c0c9593e..a0dec7f12f 100644 --- a/app/models/screened_email.rb +++ b/app/models/screened_email.rb @@ -67,7 +67,7 @@ end # Table name: screened_emails # # id :integer not null, primary key -# email :string(255) not null +# email :string not null # action_type :integer not null # match_count :integer default(0), not null # last_match_at :datetime @@ -77,6 +77,6 @@ end # # Indexes # -# index_blocked_emails_on_email (email) UNIQUE -# index_blocked_emails_on_last_match_at (last_match_at) +# index_screened_emails_on_email (email) UNIQUE +# index_screened_emails_on_last_match_at (last_match_at) # diff --git a/app/models/screened_url.rb b/app/models/screened_url.rb index be97e0d842..47e6e949db 100644 --- a/app/models/screened_url.rb +++ b/app/models/screened_url.rb @@ -42,8 +42,8 @@ end # Table name: screened_urls # # id :integer not null, primary key -# url :string(255) not null -# domain :string(255) not null +# url :string not null +# domain :string not null # action_type :integer not null # match_count :integer default(0), not null # last_match_at :datetime diff --git a/app/models/single_sign_on_record.rb b/app/models/single_sign_on_record.rb index f6acfa9ddc..d556a208bc 100644 --- a/app/models/single_sign_on_record.rb +++ b/app/models/single_sign_on_record.rb @@ -10,13 +10,13 @@ end # # id :integer not null, primary key # user_id :integer not null -# external_id :string(255) not null +# external_id :string not null # last_payload :text not null # created_at :datetime not null # updated_at :datetime not null -# external_username :string(255) -# external_email :string(255) -# external_name :string(255) +# external_username :string +# external_email :string +# external_name :string # external_avatar_url :string(1000) # # Indexes diff --git a/app/models/site.rb b/app/models/site.rb index 9d90b2c1bc..b3001e49d9 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -71,8 +71,8 @@ class Site end end - def suppressed_from_homepage_category_ids - categories.select { |c| c.suppress_from_homepage == true }.map(&:id) + def suppressed_from_latest_category_ids + categories.select { |c| c.suppress_from_latest == true }.map(&:id) end def archetypes diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index d0ce4465eb..481ec0eb7f 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -157,7 +157,7 @@ end # Table name: site_settings # # id :integer not null, primary key -# name :string(255) not null +# name :string not null # data_type :integer not null # value :text # created_at :datetime not null diff --git a/app/models/stylesheet_cache.rb b/app/models/stylesheet_cache.rb index 0d318ce0db..08b98c3085 100644 --- a/app/models/stylesheet_cache.rb +++ b/app/models/stylesheet_cache.rb @@ -43,11 +43,11 @@ end # Table name: stylesheet_cache # # id :integer not null, primary key -# target :string(255) not null -# digest :string(255) not null +# target :string not null +# digest :string not null # content :text not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # theme_id :integer default(-1), not null # source_map :text # diff --git a/app/models/tag.rb b/app/models/tag.rb index 45d900d422..f26c793b39 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -16,21 +16,6 @@ class Tag < ActiveRecord::Base after_save :index_search - COUNT_ARG = "topics.id" - - # Apply more activerecord filters to the tags_by_count_query, and then - # fetch the result with .count(Tag::COUNT_ARG). - # - # e.g., Tag.tags_by_count_query.where("topics.category_id = ?", category.id).count(Tag::COUNT_ARG) - def self.tags_by_count_query(opts = {}) - q = Tag.joins("LEFT JOIN topic_tags ON tags.id = topic_tags.tag_id") - .joins("LEFT JOIN topics ON topics.id = topic_tags.topic_id AND topics.deleted_at IS NULL") - .group("tags.id, tags.name") - .order('count_topics_id DESC') - q = q.limit(opts[:limit]) if opts[:limit] - q - end - def self.ensure_consistency! update_topic_counts # topic_count counter cache can miscount end @@ -43,7 +28,7 @@ class Tag < ActiveRecord::Base SELECT COUNT(topics.id) AS topic_count, tags.id AS tag_id FROM tags LEFT JOIN topic_tags ON tags.id = topic_tags.tag_id - LEFT JOIN topics ON topics.id = topic_tags.topic_id AND topics.deleted_at IS NULL + LEFT JOIN topics ON topics.id = topic_tags.topic_id AND topics.deleted_at IS NULL AND topics.archetype != 'private_message' GROUP BY tags.id ) x WHERE x.tag_id = t.id @@ -94,8 +79,8 @@ end # id :integer not null, primary key # name :string not null # topic_count :integer default(0), not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/tag_group.rb b/app/models/tag_group.rb index ccc97798d0..b1c7088c70 100644 --- a/app/models/tag_group.rb +++ b/app/models/tag_group.rb @@ -42,8 +42,8 @@ end # # id :integer not null, primary key # name :string not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # parent_tag_id :integer # one_per_topic :boolean default(FALSE) # diff --git a/app/models/tag_group_membership.rb b/app/models/tag_group_membership.rb index 76e9be22c3..3bc5610e27 100644 --- a/app/models/tag_group_membership.rb +++ b/app/models/tag_group_membership.rb @@ -10,8 +10,8 @@ end # id :integer not null, primary key # tag_id :integer not null # tag_group_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/tag_user.rb b/app/models/tag_user.rb index 52db737cb5..d54b787c40 100644 --- a/app/models/tag_user.rb +++ b/app/models/tag_user.rb @@ -161,8 +161,8 @@ end # tag_id :integer not null # user_id :integer not null # notification_level :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/theme.rb b/app/models/theme.rb index 5fc90241d9..8e6c152163 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -1,6 +1,8 @@ require_dependency 'distributed_cache' require_dependency 'stylesheet/compiler' require_dependency 'stylesheet/manager' +require_dependency 'theme_settings_parser' +require_dependency 'theme_settings_manager' class Theme < ActiveRecord::Base @@ -8,6 +10,7 @@ class Theme < ActiveRecord::Base belongs_to :color_scheme has_many :theme_fields, dependent: :destroy + has_many :theme_settings, dependent: :destroy has_many :child_theme_relation, class_name: 'ChildTheme', foreign_key: 'parent_theme_id', dependent: :destroy has_many :child_themes, through: :child_theme_relation, source: :child_theme has_many :color_schemes @@ -34,11 +37,13 @@ class Theme < ActiveRecord::Base @included_themes = nil remove_from_cache! + clear_cached_settings! notify_scheme_change if saved_change_to_color_scheme_id? end after_destroy do remove_from_cache! + clear_cached_settings! if SiteSetting.default_theme_key == self.key Theme.clear_default! end @@ -72,7 +77,7 @@ class Theme < ActiveRecord::Base if keys = @cache["user_theme_keys"] return keys end - @cache["theme_keys"] = Set.new( + @cache["user_theme_keys"] = Set.new( Theme .where('user_selectable OR key = ?', SiteSetting.default_theme_key) .pluck(:key) @@ -122,7 +127,11 @@ class Theme < ActiveRecord::Base end def self.targets - @targets ||= Enum.new(common: 0, desktop: 1, mobile: 2) + @targets ||= Enum.new(common: 0, desktop: 1, mobile: 2, settings: 3) + end + + def self.lookup_target(target_id) + self.targets.invert[target_id] end def notify_scheme_change(clear_manager_cache = true) @@ -288,6 +297,56 @@ class Theme < ActiveRecord::Base child_themes.reload save! end + + def settings + field = theme_fields.where(target_id: Theme.targets[:settings], name: "yaml").first + return [] unless field && field.error.nil? + + settings = [] + ThemeSettingsParser.new(field).load do |name, default, type, opts| + settings << ThemeSettingsManager.create(name, default, type, self, opts) + end + settings + end + + def cached_settings + Rails.cache.fetch("settings_for_theme_#{self.key}", expires_in: 30.minutes) do + hash = {} + self.settings.each do |setting| + hash[setting.name] = setting.value + end + hash + end + end + + def clear_cached_settings! + Rails.cache.delete("settings_for_theme_#{self.key}") + end + + def included_settings + hash = {} + + self.included_themes.each do |theme| + hash.merge!(theme.cached_settings) + end + + hash.merge!(self.cached_settings) + hash + end + + def self.settings_for_client(key) + theme = Theme.find_by(key: key) + return {}.to_json unless theme + + theme.included_settings.to_json + end + + def update_setting(setting_name, new_value) + target_setting = settings.find { |setting| setting.name == setting_name } + raise Discourse::NotFound unless target_setting + + target_setting.value = new_value + end end # == Schema Information @@ -295,9 +354,9 @@ end # Table name: themes # # id :integer not null, primary key -# name :string(255) not null +# name :string not null # user_id :integer not null -# key :string(255) not null +# key :string not null # created_at :datetime not null # updated_at :datetime not null # compiler_version :integer default(0), not null diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb index 386b0fb903..1eeb40831f 100644 --- a/app/models/theme_field.rb +++ b/app/models/theme_field.rb @@ -1,3 +1,5 @@ +require_dependency 'theme_settings_parser' + class ThemeField < ActiveRecord::Base belongs_to :upload @@ -7,7 +9,8 @@ class ThemeField < ActiveRecord::Base scss: 1, theme_upload_var: 2, theme_color_var: 3, - theme_var: 4) + theme_var: 4, + yaml: 5) end def self.theme_var_type_ids @@ -21,10 +24,28 @@ class ThemeField < ActiveRecord::Base belongs_to :theme + def settings(source) + + settings = {} + + theme.cached_settings.each do |k, v| + if source.include?("settings.#{k}") + settings[k] = v + end + end + + if settings.length > 0 + "let settings = #{settings.to_json};" + else + "" + end + end + def transpile(es6_source, version) template = Tilt::ES6ModuleTranspilerTemplate.new {} wrapped = < { + #{settings(es6_source)} #{es6_source} }); PLUGIN_API_JS @@ -77,11 +98,54 @@ COMPILED [doc.to_s, errors&.join("\n")] end + def validate_yaml! + return unless self.name == "yaml" + + errors = [] + begin + ThemeSettingsParser.new(self).load do |name, default, type, opts| + setting = ThemeSetting.new(name: name, data_type: type, theme: theme) + translation_key = "themes.settings_errors" + + if setting.invalid? + setting.errors.details.each_pair do |attribute, _errors| + _errors.each do |hash| + errors << I18n.t("#{translation_key}.#{attribute}_#{hash[:error]}", name: name) + end + end + end + + if default.nil? + errors << I18n.t("#{translation_key}.default_value_missing", name: name) + end + + if (min = opts[:min]) && (max = opts[:max]) + unless ThemeSetting.value_in_range?(default, (min..max), type) + errors << I18n.t("#{translation_key}.default_out_range", name: name) + end + end + + unless ThemeSetting.acceptable_value_for_type?(default, type) + errors << I18n.t("#{translation_key}.default_not_match_type", name: name) + end + end + rescue ThemeSettingsParser::InvalidYaml => e + errors << e.message + end + + self.error = errors.join("\n").presence unless self.destroyed? + if will_save_change_to_error? + update_columns(error: self.error) + end + end + def self.guess_type(name) if html_fields.include?(name.to_s) types[:html] elsif scss_fields.include?(name.to_s) types[:scss] + elsif name.to_s === "yaml" + types[:yaml] end end @@ -121,7 +185,7 @@ COMPILED ) self.error = nil unless error.nil? rescue SassC::SyntaxError => e - self.error = e.message + self.error = e.message unless self.destroyed? end if will_save_change_to_error? @@ -143,6 +207,8 @@ COMPILED after_commit do ensure_baked! ensure_scss_compiles! + validate_yaml! + theme.clear_cached_settings! Stylesheet::Manager.clear_theme_cache! if self.name.include?("scss") @@ -162,8 +228,8 @@ end # name :string(30) not null # value :text not null # value_baked :text -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # compiler_version :integer default(0), not null # error :string # upload_id :integer diff --git a/app/models/theme_setting.rb b/app/models/theme_setting.rb new file mode 100644 index 0000000000..782d214864 --- /dev/null +++ b/app/models/theme_setting.rb @@ -0,0 +1,66 @@ +class ThemeSetting < ActiveRecord::Base + belongs_to :theme + + validates_presence_of :name, :theme + validates :data_type, numericality: { only_integer: true } + validates :name, length: { maximum: 255 } + + after_save do + theme.clear_cached_settings! + theme.remove_from_cache! + theme.theme_fields.update_all(value_baked: nil) + end + + def self.types + @types ||= Enum.new(integer: 0, float: 1, string: 2, bool: 3, list: 4, enum: 5) + end + + def self.acceptable_value_for_type?(value, type) + case type + when self.types[:integer] + value.is_a?(Integer) + when self.types[:float] + value.is_a?(Integer) || value.is_a?(Float) + when self.types[:bool] + value.is_a?(TrueClass) || value.is_a?(FalseClass) + when self.types[:list] + value.is_a?(String) + else + true + end + end + + def self.value_in_range?(value, range, type) + if type == self.types[:integer] || type == self.types[:float] + range.include? value + elsif type == self.types[:string] + range.include? value.to_s.length + end + end + + def self.guess_type(value) + case value + when Integer + types[:integer] + when Float + types[:float] + when String + types[:string] + when TrueClass, FalseClass + types[:bool] + end + end +end + +# == Schema Information +# +# Table name: theme_settings +# +# id :integer not null, primary key +# name :string(255) not null +# data_type :integer not null +# value :text +# theme_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# diff --git a/app/models/topic.rb b/app/models/topic.rb index 3b3c74b956..45d0726fca 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -84,6 +84,7 @@ class Topic < ActiveRecord::Base topic_title_length: true, censored_words: true, quality_title: { unless: :private_message? }, + max_emojis: true, unique_among: { unless: Proc.new { |t| (SiteSetting.allow_duplicate_topic_titles? || t.private_message?) }, message: :has_already_been_used, allow_blank: true, @@ -98,7 +99,7 @@ class Topic < ActiveRecord::Base if: Proc.new { |t| (t.new_record? || t.category_id_changed?) && !SiteSetting.allow_uncategorized_topics && - (t.archetype.nil? || t.archetype == Archetype.default) && + (t.archetype.nil? || t.regular?) && (!t.user_id || !t.user.staff?) } @@ -222,10 +223,15 @@ class Topic < ActiveRecord::Base ApplicationController.banner_json_cache.clear end - if tags_changed - TagUser.auto_watch(topic_id: id) - TagUser.auto_track(topic_id: id) - self.tags_changed = false + if tags_changed || saved_change_to_attribute?(:category_id) + + SearchIndexer.queue_post_reindex(self.id) + + if tags_changed + TagUser.auto_watch(topic_id: id) + TagUser.auto_track(topic_id: id) + self.tags_changed = false + end end SearchIndexer.index(self) @@ -259,7 +265,7 @@ class Topic < ActiveRecord::Base end def advance_draft_sequence - if archetype == Archetype.private_message + if self.private_message? DraftSequence.next!(user, Draft::NEW_PRIVATE_MESSAGE) else DraftSequence.next!(user, Draft::NEW_TOPIC) @@ -267,7 +273,7 @@ class Topic < ActiveRecord::Base end def ensure_topic_has_a_category - if category_id.nil? && (archetype.nil? || archetype == Archetype.default) + if category_id.nil? && (archetype.nil? || self.regular?) self.category_id = SiteSetting.uncategorized_category_id end end @@ -447,14 +453,6 @@ class Topic < ActiveRecord::Base ((Time.zone.now - created_at) / 1.minute).round end - def has_meta_data_boolean?(key) - meta_data_string(key) == 'true' - end - - def meta_data_string(key) - custom_fields[key.to_s] - end - def self.listable_count_per_day(start_date, end_date, category_id = nil) result = listable_topics.where('created_at >= ? and created_at <= ?', start_date, end_date) result = result.where(category_id: category_id) if category_id @@ -465,6 +463,10 @@ class Topic < ActiveRecord::Base archetype == Archetype.private_message end + def regular? + self.archetype == Archetype.default + end + MAX_SIMILAR_BODY_LENGTH ||= 200 def self.similar_to(title, raw, user = nil) @@ -473,7 +475,7 @@ class Topic < ActiveRecord::Base search_data = "#{title} #{raw[0...MAX_SIMILAR_BODY_LENGTH]}".strip filter_words = Search.prepare_data(search_data) - ts_query = Search.ts_query(filter_words, nil, "|") + ts_query = Search.ts_query(term: filter_words, joiner: "|") candidates = Topic .visible @@ -509,7 +511,7 @@ class Topic < ActiveRecord::Base def update_status(status, enabled, user, opts = {}) TopicStatusUpdater.new(self, user).update!(status, enabled, opts) - DiscourseEvent.trigger(:topic_status_updated, self.id, status, enabled) + DiscourseEvent.trigger(:topic_status_updated, self, status, enabled) end # Atomically creates the next post number @@ -767,7 +769,8 @@ SQL group_id = group.id group.users.where( - "group_users.notification_level > ?", NotificationLevels.all[:muted] + "group_users.notification_level > ? AND user_id != ?", + NotificationLevels.all[:muted], user.id ).find_each do |u| u.notifications.create!( @@ -786,62 +789,52 @@ SQL true end - # Invite a user to the topic by username or email. Returns success/failure def invite(invited_by, username_or_email, group_ids = nil, custom_message = nil) - user = User.find_by_username_or_email(username_or_email) + target_user = User.find_by_username_or_email(username_or_email) - if private_message? - # If the user exists, add them to the message. - raise UserExists.new I18n.t("topic_invite.user_exists") if user.present? && topic_allowed_users.where(user_id: user.id).exists? - - if user && topic_allowed_users.create!(user_id: user.id) - # Create a small action message - add_small_action(invited_by, "invited_user", user.username) - - # Notify the user they've been invited - user.notifications.create(notification_type: Notification.types[:invited_to_private_message], - topic_id: id, - post_number: 1, - data: { topic_title: title, - display_username: invited_by.username }.to_json) - return true - end + if target_user && topic_allowed_users.where(user_id: target_user.id).exists? + raise UserExists.new(I18n.t("topic_invite.user_exists")) end - if username_or_email =~ /^.+@.+$/ && Guardian.new(invited_by).can_invite_via_email?(self) - RateLimiter.new(invited_by, "topic-invitations-per-day", SiteSetting.max_topic_invitations_per_day, 1.day.to_i).performed! + return true if target_user && invite_existing_muted?(target_user, invited_by) - if user.present? - # add existing users - Invite.extend_permissions(self, user, invited_by) + if target_user && private_message? && topic_allowed_users.create!(user_id: target_user.id) + add_small_action(invited_by, "invited_user", target_user.username) - # Notify the user they've been invited - user.notifications.create(notification_type: Notification.types[:invited_to_topic], - topic_id: id, - post_number: 1, - data: { topic_title: title, - display_username: invited_by.username }.to_json) - return true + create_invite_notification!( + target_user, + Notification.types[:invited_to_private_message], + invited_by.username + ) + + true + elsif username_or_email =~ /^.+@.+$/ && Guardian.new(invited_by).can_invite_via_email?(self) + rate_limit_topic_invitation(invited_by) + + if target_user + Invite.extend_permissions(self, target_user, invited_by) + + create_invite_notification!( + target_user, + Notification.types[:invited_to_topic], + invited_by.username + ) else - # NOTE callers expect an invite object if an invite was sent via email invite_by_email(invited_by, username_or_email, group_ids, custom_message) end - else - raise UserExists.new I18n.t("topic_invite.user_exists") if user.present? && topic_allowed_users.where(user_id: user.id).exists? - if user && topic_allowed_users.create!(user_id: user.id) - RateLimiter.new(invited_by, "topic-invitations-per-day", SiteSetting.max_topic_invitations_per_day, 1.day.to_i).performed! + true + elsif target_user && + rate_limit_topic_invitation(invited_by) && + topic_allowed_users.create!(user_id: target_user.id) - # Notify the user they've been invited - user.notifications.create(notification_type: Notification.types[:invited_to_topic], - topic_id: id, - post_number: 1, - data: { topic_title: title, - display_username: invited_by.username }.to_json) - return true - else - false - end + create_invite_notification!( + target_user, + Notification.types[:invited_to_topic], + invited_by.username + ) + + true end end @@ -849,6 +842,26 @@ SQL Invite.invite_by_email(email, invited_by, self, group_ids, custom_message) end + def invite_existing_muted?(target_user, invited_by) + if invited_by.id && + MutedUser.where(user_id: target_user.id, muted_user_id: invited_by.id) + .joins(:muted_user) + .where('NOT admin AND NOT moderator') + .exists? + return true + end + + if TopicUser.where( + topic: self, + user: target_user, + notification_level: TopicUser.notification_levels[:muted] + ).exists? + return true + end + + false + end + def email_already_exists_for?(invite) invite.email_already_exists && private_message? end @@ -1304,6 +1317,28 @@ SQL RateLimiter.new(user, "#{key}-per-day", SiteSetting.send(method_name), 1.day.to_i) end + def create_invite_notification!(target_user, notification_type, username) + target_user.notifications.create!( + notification_type: notification_type, + topic_id: self.id, + post_number: 1, + data: { + topic_title: self.title, + display_username: username + }.to_json + ) + end + + def rate_limit_topic_invitation(invited_by) + RateLimiter.new( + invited_by, + "topic-invitations-per-day", + SiteSetting.max_topic_invitations_per_day, + 1.day.to_i + ).performed! + + true + end end # == Schema Information @@ -1311,7 +1346,7 @@ end # Table name: topics # # id :integer not null, primary key -# title :string(255) not null +# title :string not null # last_posted_at :datetime # created_at :datetime not null # updated_at :datetime not null @@ -1326,7 +1361,7 @@ end # avg_time :integer # deleted_at :datetime # highest_post_number :integer default(0), not null -# image_url :string(255) +# image_url :string # like_count :integer default(0), not null # incoming_link_count :integer default(0), not null # category_id :integer @@ -1337,15 +1372,15 @@ end # bumped_at :datetime not null # has_summary :boolean default(FALSE), not null # vote_count :integer default(0), not null -# archetype :string(255) default("regular"), not null +# archetype :string default("regular"), not null # featured_user4_id :integer # notify_moderators_count :integer default(0), not null # spam_count :integer default(0), not null # pinned_at :datetime # score :float # percent_rank :float default(1.0), not null -# subtype :string(255) -# slug :string(255) +# subtype :string +# slug :string # deleted_by_id :integer # participant_count :integer default(1) # word_count :integer @@ -1361,7 +1396,7 @@ end # idx_topics_front_page (deleted_at,visible,archetype,category_id,id) # idx_topics_user_id_deleted_at (user_id) # idxtopicslug (slug) -# index_forum_threads_on_bumped_at (bumped_at) +# index_topics_on_bumped_at (bumped_at) # index_topics_on_created_at_and_visible (created_at,visible) # index_topics_on_id_and_deleted_at (id,deleted_at) # index_topics_on_lower_title (lower((title)::text)) diff --git a/app/models/topic_link.rb b/app/models/topic_link.rb index a55cd5ca48..45bf194b87 100644 --- a/app/models/topic_link.rb +++ b/app/models/topic_link.rb @@ -284,16 +284,16 @@ end # reflection :boolean default(FALSE) # clicks :integer default(0), not null # link_post_id :integer -# title :string(255) +# title :string # crawled_at :datetime # quote :boolean default(FALSE), not null # extension :string(10) # # Indexes # -# index_forum_thread_links_on_forum_thread_id (topic_id) -# index_forum_thread_links_on_forum_thread_id_and_post_id_and_url (topic_id,post_id,url) UNIQUE -# index_topic_links_on_extension (extension) -# index_topic_links_on_link_post_id_and_reflection (link_post_id,reflection) -# index_topic_links_on_post_id (post_id) +# index_topic_links_on_extension (extension) +# index_topic_links_on_link_post_id_and_reflection (link_post_id,reflection) +# index_topic_links_on_post_id (post_id) +# index_topic_links_on_topic_id (topic_id) +# unique_post_links (topic_id,post_id,url) UNIQUE # diff --git a/app/models/topic_link_click.rb b/app/models/topic_link_click.rb index 3afce0bfbd..90cddefe2f 100644 --- a/app/models/topic_link_click.rb +++ b/app/models/topic_link_click.rb @@ -118,5 +118,5 @@ end # # Indexes # -# index_forum_thread_link_clicks_on_forum_thread_link_id (topic_link_id) +# by_link (topic_link_id) # diff --git a/app/models/topic_search_data.rb b/app/models/topic_search_data.rb index 641d79246c..ddf065327b 100644 --- a/app/models/topic_search_data.rb +++ b/app/models/topic_search_data.rb @@ -8,7 +8,7 @@ end # # topic_id :integer not null, primary key # raw_data :text -# locale :string(255) not null +# locale :string not null # search_data :tsvector # version :integer default(0) # diff --git a/app/models/topic_tag.rb b/app/models/topic_tag.rb index 2dc203290b..8c0a8df501 100644 --- a/app/models/topic_tag.rb +++ b/app/models/topic_tag.rb @@ -1,22 +1,28 @@ class TopicTag < ActiveRecord::Base belongs_to :topic - belongs_to :tag, counter_cache: "topic_count" + belongs_to :tag after_create do - if topic.category_id - if stat = CategoryTagStat.where(tag_id: tag_id, category_id: topic.category_id).first - stat.increment!(:topic_count) - else - CategoryTagStat.create(tag_id: tag_id, category_id: topic.category_id, topic_count: 1) + if topic && topic.archetype != Archetype.private_message + tag.increment!(:topic_count) + + if topic.category_id + if stat = CategoryTagStat.where(tag_id: tag_id, category_id: topic.category_id).first + stat.increment!(:topic_count) + else + CategoryTagStat.create(tag_id: tag_id, category_id: topic.category_id, topic_count: 1) + end end end end after_destroy do - if topic.category_id - if stat = CategoryTagStat.where(tag_id: tag_id, category: topic.category_id).first + if topic && topic.archetype != Archetype.private_message + if topic.category_id && stat = CategoryTagStat.where(tag_id: tag_id, category: topic.category_id).first stat.topic_count == 1 ? stat.destroy : stat.decrement!(:topic_count) end + + tag.decrement!(:topic_count) end end end @@ -28,8 +34,8 @@ end # id :integer not null, primary key # topic_id :integer not null # tag_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/topic_timer.rb b/app/models/topic_timer.rb index 160f6c40a7..39f882dbaf 100644 --- a/app/models/topic_timer.rb +++ b/app/models/topic_timer.rb @@ -157,8 +157,8 @@ end # based_on_last_post :boolean default(FALSE), not null # deleted_at :datetime # deleted_by_id :integer -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # category_id :integer # public_type :boolean default(TRUE) # diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb index 0e144d991e..5256a99d4a 100644 --- a/app/models/topic_tracking_state.rb +++ b/app/models/topic_tracking_state.rb @@ -10,6 +10,8 @@ class TopicTrackingState include ActiveModel::SerializerSupport CHANNEL = "/user-tracking" + UNREAD_MESSAGE_TYPE = "unread".freeze + LATEST_MESSAGE_TYPE = "latest".freeze attr_accessor :user_id, :topic_id, @@ -20,6 +22,7 @@ class TopicTrackingState :notification_level def self.publish_new(topic) + return unless topic.regular? message = { topic_id: topic.id, @@ -41,14 +44,13 @@ class TopicTrackingState end def self.publish_latest(topic, staff_only = false) - return unless topic.archetype == "regular" + return unless topic.regular? message = { topic_id: topic.id, - message_type: "latest", + message_type: LATEST_MESSAGE_TYPE, payload: { bumped_at: topic.bumped_at, - topic_id: topic.id, category_id: topic.category_id, archetype: topic.archetype } @@ -63,7 +65,12 @@ class TopicTrackingState MessageBus.publish("/latest", message.as_json, group_ids: group_ids) end + def self.unread_channel_key(user_id) + "/unread/#{user_id}" + end + def self.publish_unread(post) + return unless post.topic.regular? # TODO at high scale we are going to have to defer this, # perhaps cut down to users that are around in the last 7 days as well @@ -81,19 +88,18 @@ class TopicTrackingState message = { topic_id: post.topic_id, - message_type: "unread", + message_type: UNREAD_MESSAGE_TYPE, payload: { last_read_post_number: tu.last_read_post_number, highest_post_number: post.post_number, created_at: post.created_at, - topic_id: post.topic_id, category_id: post.topic.category_id, notification_level: tu.notification_level, archetype: post.topic.archetype } } - MessageBus.publish("/unread/#{tu.user_id}", message.as_json, group_ids: group_ids) + MessageBus.publish(self.unread_channel_key(tu.user_id), message.as_json, group_ids: group_ids) end end @@ -103,10 +109,7 @@ class TopicTrackingState message = { topic_id: topic.id, - message_type: "recover", - payload: { - topic_id: topic.id, - } + message_type: "recover" } MessageBus.publish("/recover", message.as_json, group_ids: group_ids) @@ -118,17 +121,13 @@ class TopicTrackingState message = { topic_id: topic.id, - message_type: "delete", - payload: { - topic_id: topic.id, - } + message_type: "delete" } MessageBus.publish("/delete", message.as_json, group_ids: group_ids) end def self.publish_read(topic_id, last_read_post_number, user_id, notification_level = nil) - highest_post_number = Topic.where(id: topic_id).pluck(:highest_post_number).first message = { @@ -142,8 +141,7 @@ class TopicTrackingState } } - MessageBus.publish("/unread/#{user_id}", message.as_json, user_ids: [user_id]) - + MessageBus.publish(self.unread_channel_key(user_id), message.as_json, user_ids: [user_id]) end def self.treat_as_new_topic_clause @@ -248,4 +246,47 @@ SQL sql end + def self.publish_private_message(topic, user_id: user_id, user_archive: false, post: nil, group_archive: false) + return unless topic.private_message? + channels = {} + + allowed_user_ids = topic.allowed_users.pluck(:id) + + if post && allowed_user_ids.include?(post.user_id) + channels["/private-messages/sent"] = [post.user_id] + elsif user_archive + user_ids = [user_id] + + [ + "/private-messages/archive", + "/private-messages/inbox", + "/private-messages/sent", + ].each do |channel| + channels[channel] = user_ids + end + else + topic.allowed_groups.each do |group| + group_channels = [] + group_channels << "/private-messages/group/#{group.name.downcase}" + group_channels << "#{group_channels.first}/archive" if group_archive + group_channels.each { |channel| channels[channel] = group.users.pluck(:id) } + end + end + + if channels.except("/private-messages/sent").blank? + channels["/private-messages/inbox"] = allowed_user_ids + end + + message = { + topic_id: topic.id + } + + channels.each do |channel, user_ids| + MessageBus.publish( + channel, + message.as_json, + user_ids: user_ids + ) + end + end end diff --git a/app/models/topic_user.rb b/app/models/topic_user.rb index 1d5618fbaf..a6cff083de 100644 --- a/app/models/topic_user.rb +++ b/app/models/topic_user.rb @@ -187,15 +187,41 @@ SQL end unless attrs[:notification_level] - auto_track_after = UserOption.where(user_id: user_id).pluck(:auto_track_topics_after_msecs).first - auto_track_after ||= SiteSetting.default_other_auto_track_topics_after_msecs + if Topic.private_messages.where(id: topic_id).exists? && + Notification.where( + user_id: user_id, + topic_id: topic_id, + notification_type: Notification.types[:invited_to_private_message] + ).exists? - if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed].to_i || 0) - attrs[:notification_level] ||= notification_levels[:tracking] + group_notification_level = Group + .joins("LEFT OUTER JOIN group_users gu ON gu.group_id = groups.id AND gu.user_id = #{user_id}") + .joins("LEFT OUTER JOIN topic_allowed_groups tag ON tag.topic_id = #{topic_id}") + .where("gu.id IS NOT NULL AND tag.id IS NOT NULL") + .pluck(:default_notification_level) + .first + + if group_notification_level.present? + attrs[:notification_level] = group_notification_level + else + attrs[:notification_level] = notification_levels[:watching] + end + else + auto_track_after = UserOption.where(user_id: user_id).pluck(:auto_track_topics_after_msecs).first + auto_track_after ||= SiteSetting.default_other_auto_track_topics_after_msecs + + if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed].to_i || 0) + attrs[:notification_level] ||= notification_levels[:tracking] + end end end - TopicUser.create(attrs.merge!(user_id: user_id, topic_id: topic_id, first_visited_at: now , last_visited_at: now)) + TopicUser.create!(attrs.merge!( + user_id: user_id, + topic_id: topic_id, + first_visited_at: now , + last_visited_at: now + )) end def track_visit!(topic_id, user_id) @@ -474,6 +500,6 @@ end # # Indexes # -# index_forum_thread_users_on_forum_thread_id_and_user_id (topic_id,user_id) UNIQUE -# index_topic_users_on_user_id_and_topic_id (user_id,topic_id) UNIQUE +# index_topic_users_on_topic_id_and_user_id (topic_id,user_id) UNIQUE +# index_topic_users_on_user_id_and_topic_id (user_id,topic_id) UNIQUE # diff --git a/app/models/twitter_user_info.rb b/app/models/twitter_user_info.rb index 42d4f4d83c..a57608a468 100644 --- a/app/models/twitter_user_info.rb +++ b/app/models/twitter_user_info.rb @@ -8,10 +8,11 @@ end # # id :integer not null, primary key # user_id :integer not null -# screen_name :string(255) not null +# screen_name :string not null # twitter_user_id :integer not null # created_at :datetime not null # updated_at :datetime not null +# email :string(1000) # # Indexes # diff --git a/app/models/unsubscribe_key.rb b/app/models/unsubscribe_key.rb index 6cc8b7f25f..967d8f34a1 100644 --- a/app/models/unsubscribe_key.rb +++ b/app/models/unsubscribe_key.rb @@ -30,8 +30,8 @@ end # # key :string(64) not null, primary key # user_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # unsubscribe_key_type :string # topic_id :integer # post_id :integer diff --git a/app/models/upload.rb b/app/models/upload.rb index 0e776d5267..6c683d1873 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -156,11 +156,11 @@ end # # id :integer not null, primary key # user_id :integer not null -# original_filename :string(255) not null +# original_filename :string not null # filesize :integer not null # width :integer # height :integer -# url :string(255) not null +# url :string not null # created_at :datetime not null # updated_at :datetime not null # sha1 :string(40) diff --git a/app/models/user.rb b/app/models/user.rb index db69ef9f96..f25099c27a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -18,6 +18,7 @@ class User < ActiveRecord::Base include Searchable include Roleable include HasCustomFields + include SecondFactorManager # TODO: Remove this after 7th Jan 2018 self.ignored_columns = %w{email} @@ -60,6 +61,8 @@ class User < ActiveRecord::Base has_one :github_user_info, dependent: :destroy has_one :google_user_info, dependent: :destroy has_one :oauth2_user_info, dependent: :destroy + has_one :instagram_user_info, dependent: :destroy + has_one :user_second_factor, dependent: :destroy has_one :user_stat, dependent: :destroy has_one :user_profile, dependent: :destroy, inverse_of: :user has_one :single_sign_on_record, dependent: :destroy @@ -885,7 +888,8 @@ class User < ActiveRecord::Base result << "Twitter(#{twitter_user_info.screen_name})" if twitter_user_info result << "Facebook(#{facebook_user_info.username})" if facebook_user_info result << "Google(#{google_user_info.email})" if google_user_info - result << "Github(#{github_user_info.screen_name})" if github_user_info + result << "GitHub(#{github_user_info.screen_name})" if github_user_info + result << "Instagram(#{instagram_user_info.screen_name})" if instagram_user_info result << "#{oauth2_user_info.provider}(#{oauth2_user_info.email})" if oauth2_user_info user_open_ids.each do |oid| @@ -988,11 +992,11 @@ class User < ActiveRecord::Base primary_email.email end - def email=(email) + def email=(new_email) if primary_email - new_record? ? primary_email.email = email : primary_email.update(email: email) + new_record? ? primary_email.email = new_email : primary_email.update(email: new_email) else - build_primary_email(email: email) + build_primary_email(email: new_email) end end @@ -1175,7 +1179,7 @@ end # username :string(60) not null # created_at :datetime not null # updated_at :datetime not null -# name :string(255) +# name :string # seen_notification_id :integer default(0), not null # last_posted_at :datetime # password_hash :string(64) @@ -1197,10 +1201,10 @@ end # flag_level :integer default(0), not null # ip_address :inet # moderator :boolean default(FALSE) -# title :string(255) +# title :string # uploaded_avatar_id :integer -# primary_group_id :integer # locale :string(10) +# primary_group_id :integer # registration_ip_address :inet # staged :boolean default(FALSE), not null # first_seen_at :datetime diff --git a/app/models/user_action.rb b/app/models/user_action.rb index 02123a1d44..073e2ec08a 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -428,9 +428,9 @@ end # # Indexes # -# idx_unique_rows (action_type,user_id,target_topic_id,target_post_id,acting_user_id) UNIQUE -# idx_user_actions_speed_up_user_all (user_id,created_at,action_type) -# index_actions_on_acting_user_id (acting_user_id) -# index_actions_on_user_id_and_action_type (user_id,action_type) -# index_user_actions_on_target_post_id (target_post_id) +# idx_unique_rows (action_type,user_id,target_topic_id,target_post_id,acting_user_id) UNIQUE +# idx_user_actions_speed_up_user_all (user_id,created_at,action_type) +# index_user_actions_on_acting_user_id (acting_user_id) +# index_user_actions_on_target_post_id (target_post_id) +# index_user_actions_on_user_id_and_action_type (user_id,action_type) # diff --git a/app/models/user_api_key.rb b/app/models/user_api_key.rb index 39f8d4a80f..dc1edca2d6 100644 --- a/app/models/user_api_key.rb +++ b/app/models/user_api_key.rb @@ -70,8 +70,8 @@ end # key :string not null # application_name :string not null # push_url :string -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # revoked_at :datetime # scopes :text default([]), not null, is an Array # diff --git a/app/models/user_archived_message.rb b/app/models/user_archived_message.rb index 36c3ccd1ac..c5932b4a7b 100644 --- a/app/models/user_archived_message.rb +++ b/app/models/user_archived_message.rb @@ -2,7 +2,9 @@ class UserArchivedMessage < ActiveRecord::Base belongs_to :user belongs_to :topic - def self.move_to_inbox!(user_id, topic_id) + def self.move_to_inbox!(user_id, topic) + topic_id = topic.id + return if (TopicUser.where( user_id: user_id, topic_id: topic_id, @@ -12,13 +14,16 @@ class UserArchivedMessage < ActiveRecord::Base UserArchivedMessage.where(user_id: user_id, topic_id: topic_id).destroy_all trigger(:move_to_inbox, user_id, topic_id) MessageBus.publish("/topic/#{topic_id}", { type: "move_to_inbox" }, user_ids: [user_id]) + publish_topic_tracking_state(topic, user_id) end - def self.archive!(user_id, topic_id) + def self.archive!(user_id, topic) + topic_id = topic.id UserArchivedMessage.where(user_id: user_id, topic_id: topic_id).destroy_all UserArchivedMessage.create!(user_id: user_id, topic_id: topic_id) trigger(:archive_message, user_id, topic_id) MessageBus.publish("/topic/#{topic_id}", { type: "archived" }, user_ids: [user_id]) + publish_topic_tracking_state(topic, user_id) end def self.trigger(event, user_id, topic_id) @@ -28,6 +33,14 @@ class UserArchivedMessage < ActiveRecord::Base DiscourseEvent.trigger(event, user: user, topic: topic) end end + + private + + def self.publish_topic_tracking_state(topic, user_id) + TopicTrackingState.publish_private_message( + topic, user_id: user_id, user_archive: true + ) + end end # == Schema Information @@ -37,8 +50,8 @@ end # id :integer not null, primary key # user_id :integer not null # topic_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/user_auth_token.rb b/app/models/user_auth_token.rb index 5597d52367..045fc2f9d4 100644 --- a/app/models/user_auth_token.rb +++ b/app/models/user_auth_token.rb @@ -183,8 +183,8 @@ end # legacy :boolean default(FALSE), not null # client_ip :inet # rotated_at :datetime not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # seen_at :datetime # # Indexes diff --git a/app/models/user_badge.rb b/app/models/user_badge.rb index 71a065e0e0..53979ec9cf 100644 --- a/app/models/user_badge.rb +++ b/app/models/user_badge.rb @@ -49,4 +49,5 @@ end # index_user_badges_on_badge_id_and_user_id (badge_id,user_id) # index_user_badges_on_badge_id_and_user_id_and_post_id (badge_id,user_id,post_id) UNIQUE # index_user_badges_on_badge_id_and_user_id_and_seq (badge_id,user_id,seq) UNIQUE +# index_user_badges_on_user_id (user_id) # diff --git a/app/models/user_email.rb b/app/models/user_email.rb index 7ddbf73602..1b53341031 100644 --- a/app/models/user_email.rb +++ b/app/models/user_email.rb @@ -47,8 +47,8 @@ end # user_id :integer not null # email :string(513) not null # primary :boolean default(FALSE), not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/user_export.rb b/app/models/user_export.rb index 20269d9b92..34d8fa7ed9 100644 --- a/app/models/user_export.rb +++ b/app/models/user_export.rb @@ -26,8 +26,8 @@ end # Table name: user_exports # # id :integer not null, primary key -# file_name :string(255) not null +# file_name :string not null # user_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # diff --git a/app/models/user_field.rb b/app/models/user_field.rb index 6183f63401..77c1f3383d 100644 --- a/app/models/user_field.rb +++ b/app/models/user_field.rb @@ -16,12 +16,12 @@ end # Table name: user_fields # # id :integer not null, primary key -# name :string(255) not null -# field_type :string(255) not null -# created_at :datetime -# updated_at :datetime +# name :string not null +# field_type :string not null +# created_at :datetime not null +# updated_at :datetime not null # editable :boolean default(FALSE), not null -# description :string(255) not null +# description :string not null # required :boolean default(TRUE), not null # show_on_profile :boolean default(FALSE), not null # position :integer default(0) diff --git a/app/models/user_field_option.rb b/app/models/user_field_option.rb index 2b3036fa40..a441255342 100644 --- a/app/models/user_field_option.rb +++ b/app/models/user_field_option.rb @@ -7,7 +7,7 @@ end # # id :integer not null, primary key # user_field_id :integer not null -# value :string(255) not null -# created_at :datetime -# updated_at :datetime +# value :string not null +# created_at :datetime not null +# updated_at :datetime not null # diff --git a/app/models/user_history.rb b/app/models/user_history.rb index 7bd0f68587..5e809af46e 100644 --- a/app/models/user_history.rb +++ b/app/models/user_history.rb @@ -66,7 +66,8 @@ class UserHistory < ActiveRecord::Base change_name: 48, post_locked: 49, post_unlocked: 50, - check_personal_message: 51) + check_personal_message: 51, + disabled_second_factor: 52) end # Staff actions is a subset of all actions, used to audit actions taken by staff users. @@ -110,7 +111,8 @@ class UserHistory < ActiveRecord::Base :backup_destroy, :post_locked, :post_unlocked, - :check_personal_message] + :check_personal_message, + :disabled_second_factor] end def self.staff_action_ids @@ -182,23 +184,23 @@ end # details :text # created_at :datetime not null # updated_at :datetime not null -# context :string(255) -# ip_address :string(255) -# email :string(255) +# context :string +# ip_address :string +# email :string # subject :text # previous_value :text # new_value :text # topic_id :integer # admin_only :boolean default(FALSE) # post_id :integer -# custom_type :string(255) +# custom_type :string # category_id :integer # # Indexes # -# index_staff_action_logs_on_action_and_id (action,id) -# index_staff_action_logs_on_subject_and_id (subject,id) -# index_staff_action_logs_on_target_user_id_and_id (target_user_id,id) # index_user_histories_on_acting_user_id_and_action_and_id (acting_user_id,action,id) +# index_user_histories_on_action_and_id (action,id) # index_user_histories_on_category_id (category_id) +# index_user_histories_on_subject_and_id (subject,id) +# index_user_histories_on_target_user_id_and_id (target_user_id,id) # diff --git a/app/models/user_open_id.rb b/app/models/user_open_id.rb index 183f241482..188a04ef15 100644 --- a/app/models/user_open_id.rb +++ b/app/models/user_open_id.rb @@ -11,8 +11,8 @@ end # # id :integer not null, primary key # user_id :integer not null -# email :string(255) not null -# url :string(255) not null +# email :string not null +# url :string not null # created_at :datetime not null # updated_at :datetime not null # active :boolean not null diff --git a/app/models/user_profile.rb b/app/models/user_profile.rb index d10b63f034..c320be9218 100644 --- a/app/models/user_profile.rb +++ b/app/models/user_profile.rb @@ -117,12 +117,12 @@ end # Table name: user_profiles # # user_id :integer not null, primary key -# location :string(255) -# website :string(255) +# location :string +# website :string # bio_raw :text # bio_cooked :text -# dismissed_banner_key :integer # profile_background :string(255) +# dismissed_banner_key :integer # bio_cooked_version :integer # badge_granted_title :boolean default(FALSE) # card_background :string(255) diff --git a/app/models/user_search.rb b/app/models/user_search.rb index 8a2013b5b5..a9eceda1e9 100644 --- a/app/models/user_search.rb +++ b/app/models/user_search.rb @@ -49,7 +49,7 @@ class UserSearch if @term.present? if SiteSetting.enable_names? && @term !~ /[_\.-]/ - query = Search.ts_query(@term, "simple") + query = Search.ts_query(term: @term, ts_config: "simple") users = users.includes(:user_search_data) .references(:user_search_data) diff --git a/app/models/user_second_factor.rb b/app/models/user_second_factor.rb new file mode 100644 index 0000000000..0ffa9717d5 --- /dev/null +++ b/app/models/user_second_factor.rb @@ -0,0 +1,23 @@ +class UserSecondFactor < ActiveRecord::Base + belongs_to :user + + def self.methods + @methods ||= Enum.new( + totp: 1, + ) + end +end + +# == Schema Information +# +# Table name: user_second_factors +# +# id :integer not null, primary key +# user_id :integer not null +# method :integer not null +# data :string not null +# enabled :boolean default(FALSE), not null +# last_used :datetime +# created_at :datetime not null +# updated_at :datetime not null +# diff --git a/app/models/user_warning.rb b/app/models/user_warning.rb index dd89c7f995..612406ac0e 100644 --- a/app/models/user_warning.rb +++ b/app/models/user_warning.rb @@ -12,8 +12,8 @@ end # topic_id :integer not null # user_id :integer not null # created_by_id :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/watched_word.rb b/app/models/watched_word.rb index 0861fb3a22..73a3f66116 100644 --- a/app/models/watched_word.rb +++ b/app/models/watched_word.rb @@ -60,8 +60,8 @@ end # id :integer not null, primary key # word :string not null # action :integer not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index de0db8bbf9..dd8df00b67 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -69,6 +69,6 @@ end # wildcard_web_hook :boolean default(FALSE), not null # verify_certificate :boolean default(TRUE), not null # active :boolean default(FALSE), not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # diff --git a/app/models/web_hook_event.rb b/app/models/web_hook_event.rb index dc3e56d28b..204efba8aa 100644 --- a/app/models/web_hook_event.rb +++ b/app/models/web_hook_event.rb @@ -36,8 +36,8 @@ end # response_headers :string # response_body :text # duration :integer default(0) -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb index 8cc9316cb5..e0ad2abcfc 100644 --- a/app/serializers/admin_detailed_user_serializer.rb +++ b/app/serializers/admin_detailed_user_serializer.rb @@ -25,7 +25,9 @@ class AdminDetailedUserSerializer < AdminUserSerializer :user_fields, :bounce_score, :reset_bounce_score_after, - :can_view_action_logs + :can_view_action_logs, + :second_factor_enabled, + :can_disable_second_factor has_one :approved_by, serializer: BasicUserSerializer, embed: :objects has_one :api_key, serializer: ApiKeySerializer, embed: :objects @@ -34,6 +36,14 @@ class AdminDetailedUserSerializer < AdminUserSerializer has_one :tl3_requirements, serializer: TrustLevel3RequirementsSerializer, embed: :objects has_many :groups, embed: :object, serializer: BasicGroupSerializer + def second_factor_enabled + object.totp_enabled? + end + + def can_disable_second_factor + object&.id != scope.user.id + end + def can_revoke_admin scope.can_revoke_admin?(object) end diff --git a/app/serializers/admin_user_list_serializer.rb b/app/serializers/admin_user_list_serializer.rb index e4a95b3efb..32021dad74 100644 --- a/app/serializers/admin_user_list_serializer.rb +++ b/app/serializers/admin_user_list_serializer.rb @@ -25,7 +25,8 @@ class AdminUserListSerializer < BasicUserSerializer :silenced, :silenced_till, :time_read, - :staged + :staged, + :second_factor_enabled [:days_visited, :posts_read_count, :topics_entered, :post_count].each do |sym| attributes sym @@ -115,4 +116,12 @@ class AdminUserListSerializer < BasicUserSerializer SiteSetting.must_approve_users end + def include_second_factor_enabled? + object.totp_enabled? + end + + def second_factor_enabled + true + end + end diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb index 3c5f98a97b..d9d37497a2 100644 --- a/app/serializers/category_serializer.rb +++ b/app/serializers/category_serializer.rb @@ -9,7 +9,7 @@ class CategorySerializer < BasicCategorySerializer :email_in, :email_in_allow_strangers, :mailinglist_mirror, - :suppress_from_homepage, + :suppress_from_latest, :all_topics_wiki, :can_delete, :cannot_delete_reason, @@ -72,7 +72,7 @@ class CategorySerializer < BasicCategorySerializer scope && scope.can_edit?(object) end - def include_suppress_from_homepage? + def include_suppress_from_latest? scope && scope.can_edit?(object) end diff --git a/app/serializers/concerns/topic_tags_mixin.rb b/app/serializers/concerns/topic_tags_mixin.rb new file mode 100644 index 0000000000..6ad88b47ea --- /dev/null +++ b/app/serializers/concerns/topic_tags_mixin.rb @@ -0,0 +1,18 @@ +module TopicTagsMixin + def self.included(klass) + klass.attributes :tags + end + + def include_tags? + scope.can_see_tags?(topic) + end + + def tags + # Calling method `pluck` along with `includes` causing N+1 queries + topic.tags.map(&:name) + end + + def topic + object.is_a?(Topic) ? object : object.topic + end +end diff --git a/app/serializers/post_revision_serializer.rb b/app/serializers/post_revision_serializer.rb index 11b13677f4..b86fda2379 100644 --- a/app/serializers/post_revision_serializer.rb +++ b/app/serializers/post_revision_serializer.rb @@ -164,7 +164,7 @@ class PostRevisionSerializer < ApplicationSerializer end def include_tags_changes? - SiteSetting.tagging_enabled && previous["tags"] != current["tags"] + scope.can_see_tags?(topic) && previous["tags"] != current["tags"] end protected @@ -197,18 +197,11 @@ class PostRevisionSerializer < ApplicationSerializer # Retrieve any `tracked_topic_fields` PostRevisor.tracked_topic_fields.each_key do |field| - if topic.respond_to?(field) - latest_modifications[field.to_s] = [topic.send(field)] - end + latest_modifications[field.to_s] = [topic.send(field)] if topic.respond_to?(field) end - if SiteSetting.topic_featured_link_enabled - latest_modifications["featured_link"] = [post.topic.featured_link] - end - - if SiteSetting.tagging_enabled - latest_modifications["tags"] = [post.topic.tags.map(&:name)] - end + latest_modifications["featured_link"] = [post.topic.featured_link] if SiteSetting.topic_featured_link_enabled + latest_modifications["tags"] = [topic.tags.pluck(:name)] if scope.can_see_tags?(topic) post_revisions << PostRevision.new( number: post_revisions.last.number + 1, diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index 3f1b2a7254..f12ad4fcca 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -82,7 +82,7 @@ class PostSerializer < BasicPostSerializer end def topic_slug - object.topic && object.topic.slug + topic&.slug end def include_topic_title? @@ -98,15 +98,15 @@ class PostSerializer < BasicPostSerializer end def topic_title - object.topic.title + topic&.title end def topic_html_title - object.topic.fancy_title + topic&.fancy_title end def category_id - object.topic.category_id + topic&.category_id end def moderator? @@ -376,6 +376,12 @@ class PostSerializer < BasicPostSerializer private + def topic + @topic = object.topic + @topic ||= Topic.with_deleted.find(object.topic_id) if scope.is_staff? + @topic + end + def post_actions @post_actions ||= (@topic_view&.all_post_actions || {})[object.id] end diff --git a/app/serializers/search_topic_list_item_serializer.rb b/app/serializers/search_topic_list_item_serializer.rb index abe002899a..29640c13d0 100644 --- a/app/serializers/search_topic_list_item_serializer.rb +++ b/app/serializers/search_topic_list_item_serializer.rb @@ -1,12 +1,5 @@ class SearchTopicListItemSerializer < ListableTopicSerializer - attributes :tags, - :category_id + include TopicTagsMixin - def include_tags? - SiteSetting.tagging_enabled - end - - def tags - object.tags.map(&:name) - end + attributes :category_id end diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb index 850a8dd8dd..9c3ab5a570 100644 --- a/app/serializers/site_serializer.rb +++ b/app/serializers/site_serializer.rb @@ -16,11 +16,12 @@ class SiteSerializer < ApplicationSerializer :is_readonly, :disabled_plugins, :user_field_max_length, - :suppressed_from_homepage_category_ids, + :suppressed_from_latest_category_ids, :post_action_types, :topic_flag_types, :can_create_tag, :can_tag_topics, + :can_tag_pms, :tags_filter_regexp, :top_tags, :wizard_required, @@ -106,11 +107,15 @@ class SiteSerializer < ApplicationSerializer end def can_create_tag - SiteSetting.tagging_enabled && scope.can_create_tag? + scope.can_create_tag? end def can_tag_topics - SiteSetting.tagging_enabled && scope.can_tag_topics? + scope.can_tag_topics? + end + + def can_tag_pms + scope.can_tag_pms? end def include_tags_filter_regexp? diff --git a/app/serializers/suggested_topic_serializer.rb b/app/serializers/suggested_topic_serializer.rb index 85eeea9499..a2dcdef6b1 100644 --- a/app/serializers/suggested_topic_serializer.rb +++ b/app/serializers/suggested_topic_serializer.rb @@ -1,4 +1,5 @@ class SuggestedTopicSerializer < ListableTopicSerializer + include TopicTagsMixin # need to embed so we have users # front page json gets away without embedding @@ -7,21 +8,13 @@ class SuggestedTopicSerializer < ListableTopicSerializer has_one :user, serializer: BasicUserSerializer, embed: :objects end - attributes :archetype, :like_count, :views, :category_id, :tags, :featured_link, :featured_link_root_domain + attributes :archetype, :like_count, :views, :category_id, :featured_link, :featured_link_root_domain has_many :posters, serializer: SuggestedPosterSerializer, embed: :objects def posters object.posters || [] end - def include_tags? - SiteSetting.tagging_enabled - end - - def tags - object.tags.map(&:name) - end - def include_featured_link? SiteSetting.topic_featured_link_enabled end diff --git a/app/serializers/theme_serializer.rb b/app/serializers/theme_serializer.rb index 78532e399a..8cf183b4a8 100644 --- a/app/serializers/theme_serializer.rb +++ b/app/serializers/theme_serializer.rb @@ -24,11 +24,7 @@ class ThemeFieldSerializer < ApplicationSerializer end def target - case object.target_id - when 0 then "common" - when 1 then "desktop" - when 2 then "mobile" - end + Theme.lookup_target(object.target_id)&.to_s end def include_error? @@ -60,7 +56,7 @@ class RemoteThemeSerializer < ApplicationSerializer end class ThemeSerializer < ChildThemeSerializer - attributes :color_scheme, :color_scheme_id, :user_selectable, :remote_theme_id + attributes :color_scheme, :color_scheme_id, :user_selectable, :remote_theme_id, :settings has_many :theme_fields, serializer: ThemeFieldSerializer, embed: :objects has_many :child_themes, serializer: ChildThemeSerializer, embed: :objects @@ -69,6 +65,10 @@ class ThemeSerializer < ChildThemeSerializer def child_themes object.child_themes.order(:name) end + + def settings + object.settings.map { |setting| ThemeSettingsSerializer.new(setting, root: false) } + end end class ThemeFieldWithEmbeddedUploadsSerializer < ThemeFieldSerializer @@ -94,4 +94,8 @@ end class ThemeWithEmbeddedUploadsSerializer < ThemeSerializer has_many :theme_fields, serializer: ThemeFieldWithEmbeddedUploadsSerializer, embed: :objects + + def include_settings? + false + end end diff --git a/app/serializers/theme_settings_serializer.rb b/app/serializers/theme_settings_serializer.rb new file mode 100644 index 0000000000..cb3aa20794 --- /dev/null +++ b/app/serializers/theme_settings_serializer.rb @@ -0,0 +1,35 @@ +class ThemeSettingsSerializer < ApplicationSerializer + attributes :setting, :type, :default, :value, :description, :valid_values + + def setting + object.name + end + + def type + object.type_name + end + + def default + object.default + end + + def value + object.value + end + + def description + object.description + end + + def valid_values + object.choices + end + + def include_valid_values? + object.type == ThemeSetting.types[:enum] + end + + def include_description? + object.description.present? + end +end diff --git a/app/serializers/topic_list_item_serializer.rb b/app/serializers/topic_list_item_serializer.rb index 0ce4f4cd50..66092c6246 100644 --- a/app/serializers/topic_list_item_serializer.rb +++ b/app/serializers/topic_list_item_serializer.rb @@ -1,4 +1,5 @@ class TopicListItemSerializer < ListableTopicSerializer + include TopicTagsMixin attributes :views, :like_count, @@ -10,7 +11,6 @@ class TopicListItemSerializer < ListableTopicSerializer :pinned_globally, :bookmarked_post_numbers, :liked_post_numbers, - :tags, :featured_link, :featured_link_root_domain @@ -66,14 +66,6 @@ class TopicListItemSerializer < ListableTopicSerializer object.association(:first_post).loaded? end - def include_tags? - SiteSetting.tagging_enabled - end - - def tags - object.tags.map(&:name) - end - def include_featured_link? SiteSetting.topic_featured_link_enabled end diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index 839a931134..fc98d32aea 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -4,6 +4,7 @@ require_dependency 'new_post_manager' class TopicViewSerializer < ApplicationSerializer include PostStreamSerializerMixin include SuggestedTopicsMixin + include TopicTagsMixin include ApplicationHelper def self.attributes_from_topic(*list) @@ -60,7 +61,6 @@ class TopicViewSerializer < ApplicationSerializer :chunk_size, :bookmarked, :message_archived, - :tags, :topic_timer, :private_topic_timer, :unicode_title, @@ -238,10 +238,6 @@ class TopicViewSerializer < ApplicationSerializer scope.is_staff? && NewPostManager.queue_enabled? end - def include_tags? - SiteSetting.tagging_enabled - end - def topic_timer TopicTimerSerializer.new(object.topic.public_topic_timer, root: false) end @@ -255,10 +251,6 @@ class TopicViewSerializer < ApplicationSerializer TopicTimerSerializer.new(timer, root: false) end - def tags - object.topic.tags.map(&:name) - end - def include_featured_link? SiteSetting.topic_featured_link_enabled end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 30bb1038c6..c54147747e 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -72,7 +72,9 @@ class UserSerializer < BasicUserSerializer :primary_group_flair_url, :primary_group_flair_bg_color, :primary_group_flair_color, - :staged + :staged, + :second_factor_enabled, + :external_id has_one :invited_by, embed: :object, serializer: BasicUserSerializer has_many :groups, embed: :object, serializer: BasicGroupSerializer @@ -145,6 +147,14 @@ class UserSerializer < BasicUserSerializer (scope.is_staff? && object.staged?) end + def include_second_factor_enabled? + (object&.id == scope.user&.id) || scope.is_staff? + end + + def second_factor_enabled + object.totp_enabled? + end + def can_change_bio !(SiteSetting.enable_sso && SiteSetting.sso_overrides_bio) end @@ -278,6 +288,14 @@ class UserSerializer < BasicUserSerializer object.try(:primary_group).try(:flair_color) end + def external_id + object&.single_sign_on_record&.external_id + end + + def include_external_id? + SiteSetting.enable_sso + end + ### ### STAFF ATTRIBUTES ### diff --git a/app/serializers/web_hook_topic_view_serializer.rb b/app/serializers/web_hook_topic_view_serializer.rb index 75c99aeb14..2beca4f3ee 100644 --- a/app/serializers/web_hook_topic_view_serializer.rb +++ b/app/serializers/web_hook_topic_view_serializer.rb @@ -1,11 +1,18 @@ require_dependency 'pinned_check' class WebHookTopicViewSerializer < TopicViewSerializer - def include_post_stream? - false - end - def include_timeline_lookup? - false + %i{ + post_stream + timeline_lookup + pm_with_non_human_user + draft + draft_key + draft_sequence + message_bus_last_id + }.each do |attr| + define_method("include_#{attr}?") do + false + end end end diff --git a/app/services/anonymous_shadow_creator.rb b/app/services/anonymous_shadow_creator.rb index fc602b8255..d28f89e210 100644 --- a/app/services/anonymous_shadow_creator.rb +++ b/app/services/anonymous_shadow_creator.rb @@ -36,6 +36,7 @@ class AnonymousShadowCreator shadow = User.create!( password: SecureRandom.hex, email: "#{SecureRandom.hex}@anon.#{Discourse.current_hostname}", + skip_email_validation: true, name: username, # prevents error when names are required username: username, active: true, diff --git a/app/services/post_owner_changer.rb b/app/services/post_owner_changer.rb index f419289faa..4e2d894cbe 100644 --- a/app/services/post_owner_changer.rb +++ b/app/services/post_owner_changer.rb @@ -11,19 +11,17 @@ class PostOwnerChanger end def change_owner! - ActiveRecord::Base.transaction do - @post_ids.each do |post_id| - post = Post.with_deleted.where(id: post_id, topic_id: @topic.id).first - next if post.blank? - @topic.user = @new_owner if post.is_first_post? + @post_ids.each do |post_id| + post = Post.with_deleted.where(id: post_id, topic_id: @topic.id).first + next if post.blank? + @topic.user = @new_owner if post.is_first_post? - if post.user == nil - @topic.recover! if post.is_first_post? - end - post.topic = @topic - post.set_owner(@new_owner, @acting_user, @skip_revision) - PostAction.remove_act(@new_owner, post, PostActionType.types[:like]) + if post.user == nil + @topic.recover! if post.is_first_post? end + post.topic = @topic + post.set_owner(@new_owner, @acting_user, @skip_revision) + PostAction.remove_act(@new_owner, post, PostActionType.types[:like]) @topic.update_statistics @@ -31,7 +29,7 @@ class PostOwnerChanger first_post_created_at: @new_owner.reload.posts.order('created_at ASC').first&.created_at ) - @topic.save! + @topic.save!(validate: false) end end end diff --git a/app/services/search_indexer.rb b/app/services/search_indexer.rb index fdf6b50669..645866db8b 100644 --- a/app/services/search_indexer.rb +++ b/app/services/search_indexer.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require_dependency 'search' class SearchIndexer @@ -14,111 +15,152 @@ class SearchIndexer HtmlScrubber.scrub(html) end - def self.update_index(table, id, raw_data) - raw_data = Search.prepare_data(raw_data, :index) - - table_name = "#{table}_search_data" - foreign_key = "#{table}_id" - + def self.inject_extra_terms(raw) # insert some extra words for I.am.a.word so "word" is tokenized # I.am.a.word becomes I.am.a.word am a word - search_data = raw_data.gsub(/[^[:space:]]*[\.]+[^[:space:]]*/) do |with_dot| + raw.gsub(/[^[:space:]]*[\.]+[^[:space:]]*/) do |with_dot| split = with_dot.split(".") if split.length > 1 - with_dot + (" " << split[1..-1].join(" ")) + with_dot + ((+" ") << split[1..-1].join(" ")) else with_dot end end + end + + def self.update_index(table: , id: , raw_data:) + search_data = raw_data.map do |data| + inject_extra_terms(Search.prepare_data(data || "", :index)) + end + + table_name = "#{table}_search_data" + foreign_key = "#{table}_id" # for user login and name use "simple" lowercase stemmer stemmer = table == "user" ? "simple" : Search.ts_config + ranked_index = <<~SQL + setweight(to_tsvector('#{stemmer}', coalesce(:a,'')), 'A') || + setweight(to_tsvector('#{stemmer}', coalesce(:b,'')), 'B') || + setweight(to_tsvector('#{stemmer}', coalesce(:c,'')), 'C') || + setweight(to_tsvector('#{stemmer}', coalesce(:d,'')), 'D') + SQL + + indexed_data = search_data.select { |d| d.length > 0 }.join(' ') + + params = { + a: search_data[0], + b: search_data[1], + c: search_data[2], + d: search_data[3], + raw_data: indexed_data, + id: id, + locale: SiteSetting.default_locale, + version: Search::INDEX_VERSION + } + # Would be nice to use AR here but not sure how to execut Postgres functions # when inserting data like this. - rows = Post.exec_sql_row_count("UPDATE #{table_name} - SET - raw_data = :raw_data, - locale = :locale, - search_data = TO_TSVECTOR('#{stemmer}', :search_data), - version = :version - WHERE #{foreign_key} = :id", - raw_data: raw_data, - search_data: search_data, - id: id, - locale: SiteSetting.default_locale, - version: Search::INDEX_VERSION) + rows = Post.exec_sql_row_count(<<~SQL, params) + UPDATE #{table_name} + SET + raw_data = :raw_data, + locale = :locale, + search_data = #{ranked_index}, + version = :version + WHERE #{foreign_key} = :id + SQL + if rows == 0 - Post.exec_sql("INSERT INTO #{table_name} - (#{foreign_key}, search_data, locale, raw_data, version) - VALUES (:id, TO_TSVECTOR('#{stemmer}', :search_data), :locale, :raw_data, :version)", - raw_data: raw_data, - search_data: search_data, - id: id, - locale: SiteSetting.default_locale, - version: Search::INDEX_VERSION) + Post.exec_sql(<<~SQL, params) + INSERT INTO #{table_name} + (#{foreign_key}, search_data, locale, raw_data, version) + VALUES (:id, #{ranked_index}, :locale, :raw_data, :version) + SQL end rescue - # don't allow concurrency to mess up saving a post + # TODO is there any way we can safely avoid this? + # best way is probably pushing search indexer into a dedicated process so it no longer happens on save + # instead in the post processor end def self.update_topics_index(topic_id, title, cooked) - search_data = title.dup << " " << scrub_html_for_search(cooked)[0...Topic::MAX_SIMILAR_BODY_LENGTH] - update_index('topic', topic_id, search_data) + scrubbed_cooked = scrub_html_for_search(cooked)[0...Topic::MAX_SIMILAR_BODY_LENGTH] + + # a bit inconsitent that we use title as A and body as B when in + # the post index body is C + update_index(table: 'topic', id: topic_id, raw_data: [title, scrubbed_cooked]) end - def self.update_posts_index(post_id, cooked, title, category) - search_data = scrub_html_for_search(cooked) << " " << title.dup.force_encoding('UTF-8') - search_data << " " << category if category - update_index('post', post_id, search_data) + def self.update_posts_index(post_id, title, category, tags, cooked) + update_index(table: 'post', id: post_id, raw_data: [title, category, tags, scrub_html_for_search(cooked)]) end def self.update_users_index(user_id, username, name) - search_data = username.dup << " " << (name || "") - update_index('user', user_id, search_data) + update_index(table: 'user', id: user_id, raw_data: [username, name]) end def self.update_categories_index(category_id, name) - update_index('category', category_id, name) + update_index(table: 'category', id: category_id, raw_data: [name]) end def self.update_tags_index(tag_id, name) - update_index('tag', tag_id, name) + update_index(table: 'tag', id: tag_id, raw_data: [name]) + end + + def self.queue_post_reindex(topic_id) + return if @disabled + + ActiveRecord::Base.exec_sql(<<~SQL, topic_id: topic_id) + UPDATE post_search_data + SET version = 0 + WHERE post_id IN (SELECT id FROM posts WHERE topic_id = :topic_id) + SQL end def self.index(obj, force: false) return if @disabled - if obj.class == Post && (obj.saved_change_to_cooked? || force) - if obj.topic - category_name = obj.topic.category.name if obj.topic.category - SearchIndexer.update_posts_index(obj.id, obj.cooked, obj.topic.title, category_name) - SearchIndexer.update_topics_index(obj.topic_id, obj.topic.title, obj.cooked) if obj.is_first_post? + category_name, tag_names = nil + topic = nil + + if Topic === obj + topic = obj + elsif Post === obj + topic = obj.topic + end + + category_name = topic.category&.name if topic + tag_names = topic.tags.pluck(:name).join(' ') if topic + + if Post === obj && (obj.saved_change_to_cooked? || force) + if topic + SearchIndexer.update_posts_index(obj.id, topic.title, category_name, tag_names, obj.cooked) + SearchIndexer.update_topics_index(topic.id, topic.title, obj.cooked) if obj.is_first_post? else Rails.logger.warn("Orphan post skipped in search_indexer, topic_id: #{obj.topic_id} post_id: #{obj.id} raw: #{obj.raw}") end end - if obj.class == User && (obj.saved_change_to_username? || obj.saved_change_to_name? || force) + if User === obj && (obj.saved_change_to_username? || obj.saved_change_to_name? || force) SearchIndexer.update_users_index(obj.id, obj.username_lower || '', obj.name ? obj.name.downcase : '') end - if obj.class == Topic && (obj.saved_change_to_title? || force) + if Topic === obj && (obj.saved_change_to_title? || force) if obj.posts post = obj.posts.find_by(post_number: 1) if post - category_name = obj.category.name if obj.category - SearchIndexer.update_posts_index(post.id, post.cooked, obj.title, category_name) + SearchIndexer.update_posts_index(post.id, obj.title, category_name, tag_names, post.cooked) SearchIndexer.update_topics_index(obj.id, obj.title, post.cooked) end end end - if obj.class == Category && (obj.saved_change_to_name? || force) + if Category === obj && (obj.saved_change_to_name? || force) SearchIndexer.update_categories_index(obj.id, obj.name) end - if obj.class == Tag && (obj.saved_change_to_name? || force) + if Tag === obj && (obj.saved_change_to_name? || force) SearchIndexer.update_tags_index(obj.id, obj.name) end end @@ -127,14 +169,14 @@ class SearchIndexer attr_reader :scrubbed def initialize - @scrubbed = "" + @scrubbed = +"" end def self.scrub(html) me = new parser = Nokogiri::HTML::SAX::Parser.new(me) begin - copy = "
" + copy = +"
" copy << html unless html.nil? copy << "
" parser.parse(html) unless html.nil? diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb index b5a64c3f2f..6058c1b584 100644 --- a/app/services/staff_action_logger.rb +++ b/app/services/staff_action_logger.rb @@ -305,6 +305,12 @@ class StaffActionLogger target_user_id: user.id)) end + def log_disable_second_factor_auth(user, opts = {}) + raise Discourse::InvalidParameters.new(:user) unless user + UserHistory.create(params(opts).merge(action: UserHistory.actions[:disabled_second_factor], + target_user_id: user.id)) + end + def log_grant_admin(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user UserHistory.create(params(opts).merge(action: UserHistory.actions[:grant_admin], diff --git a/app/services/user_action_creator.rb b/app/services/user_action_creator.rb index 7b1c92562b..6d4b917bb8 100644 --- a/app/services/user_action_creator.rb +++ b/app/services/user_action_creator.rb @@ -85,7 +85,7 @@ class UserActionCreator return unless model.user_id row = { - action_type: model.archetype == Archetype.private_message ? UserAction::NEW_PRIVATE_MESSAGE : UserAction::NEW_TOPIC, + action_type: model.private_message? ? UserAction::NEW_PRIVATE_MESSAGE : UserAction::NEW_TOPIC, user_id: model.user_id, acting_user_id: model.user_id, target_topic_id: model.id, @@ -94,7 +94,7 @@ class UserActionCreator } UserAction.remove_action!(row.merge( - action_type: model.archetype == Archetype.private_message ? UserAction::NEW_TOPIC : UserAction::NEW_PRIVATE_MESSAGE + action_type: model.private_message? ? UserAction::NEW_TOPIC : UserAction::NEW_PRIVATE_MESSAGE )) rows = [row] diff --git a/app/services/user_anonymizer.rb b/app/services/user_anonymizer.rb index dbd58624bf..a74dce5a1f 100644 --- a/app/services/user_anonymizer.rb +++ b/app/services/user_anonymizer.rb @@ -1,7 +1,11 @@ class UserAnonymizer + + attr_reader :user_history + def initialize(user, actor = nil) @user = user @actor = actor + @user_history = nil end def self.make_anonymous(user, actor = nil) @@ -45,14 +49,22 @@ class UserAnonymizer @user.facebook_user_info.try(:destroy) @user.single_sign_on_record.try(:destroy) @user.oauth2_user_info.try(:destroy) + @user.instagram_user_info.try(:destroy) @user.user_open_ids.find_each { |x| x.destroy } @user.api_key.try(:destroy) - UserHistory.create(action: UserHistory.actions[:anonymize_user], - target_user_id: @user.id, - acting_user_id: @actor ? @actor.id : @user.id, - email: prev_email, - details: "username: #{prev_username}") + history_details = { + action: UserHistory.actions[:anonymize_user], + target_user_id: @user.id, + acting_user_id: @actor ? @actor.id : @user.id, + } + + if SiteSetting.log_anonymizer_details? + history_details[:email] = prev_email + history_details[:details] = "username: #{prev_username}" + end + + @user_history = UserHistory.create(history_details) end @user end diff --git a/app/services/user_merger.rb b/app/services/user_merger.rb new file mode 100644 index 0000000000..600189565d --- /dev/null +++ b/app/services/user_merger.rb @@ -0,0 +1,434 @@ +class UserMerger + def initialize(source_user, target_user) + @source_user = source_user + @target_user = target_user + end + + def merge! + update_notifications + move_posts + update_user_ids + merge_given_daily_likes + merge_post_timings + merge_user_visits + update_site_settings + merge_user_attributes + + DiscourseEvent.trigger(:merging_users, @source_user, @target_user) + update_user_stats + + delete_source_user + delete_source_user_references + end + + protected + + def update_notifications + params = { + source_user_id: @source_user.id, + source_username: @source_user.username, + target_username: @target_user.username, + notification_types_with_correct_user_id: [ + Notification.types[:granted_badge], + Notification.types[:group_message_summary] + ], + invitee_accepted_notification_type: Notification.types[:invitee_accepted] + } + + Notification.exec_sql(<<~SQL, params) + UPDATE notifications AS n + SET data = (data :: JSONB || + jsonb_strip_nulls( + jsonb_build_object( + 'original_username', CASE data :: JSONB ->> 'original_username' + WHEN :source_username + THEN :target_username + ELSE NULL END, + 'display_username', CASE data :: JSONB ->> 'display_username' + WHEN :source_username + THEN :target_username + ELSE NULL END, + 'username', CASE data :: JSONB ->> 'username' + WHEN :source_username + THEN :target_username + ELSE NULL END + ) + )) :: JSON + WHERE EXISTS( + SELECT 1 + FROM posts AS p + WHERE p.topic_id = n.topic_id + AND p.post_number = n.post_number + AND p.user_id = :source_user_id) + OR (n.notification_type IN (:notification_types_with_correct_user_id) AND n.user_id = :source_user_id) + OR (n.notification_type = :invitee_accepted_notification_type + AND EXISTS( + SELECT 1 + FROM invites i + WHERE i.user_id = :source_user_id AND n.user_id = i.invited_by_id + ) + ) + SQL + end + + def move_posts + posts = Post.with_deleted + .where(user_id: @source_user.id) + .order(:topic_id, :post_number) + .pluck(:topic_id, :id) + + last_topic_id = nil + post_ids = [] + + posts.each do |current_topic_id, current_post_id| + if last_topic_id != current_topic_id && post_ids.any? + change_post_owner(last_topic_id, post_ids) + post_ids = [] + end + + last_topic_id = current_topic_id + post_ids << current_post_id + end + + change_post_owner(last_topic_id, post_ids) if post_ids.any? + end + + def change_post_owner(topic_id, post_ids) + PostOwnerChanger.new( + topic_id: topic_id, + post_ids: post_ids, + new_owner: @target_user, + acting_user: Discourse.system_user, + skip_revision: true + ).change_owner! + end + + def merge_given_daily_likes + sql = <<~SQL + INSERT INTO given_daily_likes AS g (user_id, likes_given, given_date, limit_reached) + SELECT + :target_user_id AS user_id, + COUNT(1) AS likes_given, + a.created_at::DATE AS given_date, + COUNT(1) >= :max_likes_per_day AS limit_reached + FROM post_actions AS a + WHERE a.user_id = :target_user_id + AND a.deleted_at IS NULL + AND EXISTS( + SELECT 1 + FROM given_daily_likes AS g + WHERE g.user_id = :source_user_id AND a.created_at::DATE = g.given_date + ) + GROUP BY given_date + ON CONFLICT (user_id, given_date) + DO UPDATE + SET likes_given = EXCLUDED.likes_given, + limit_reached = EXCLUDED.limit_reached + SQL + + GivenDailyLike.exec_sql(sql, + source_user_id: @source_user.id, + target_user_id: @target_user.id, + max_likes_per_day: SiteSetting.max_likes_per_day, + action_type_id: PostActionType.types[:like]) + end + + def merge_post_timings + update_user_id(:post_timings, conditions: ["x.topic_id = y.topic_id", + "x.post_number = y.post_number"]) + sql = <<~SQL + UPDATE post_timings AS t + SET msecs = t.msecs + s.msecs + FROM post_timings AS s + WHERE t.user_id = :target_user_id AND s.user_id = :source_user_id + AND t.topic_id = s.topic_id AND t.post_number = s.post_number + SQL + + PostTiming.exec_sql(sql, source_user_id: @source_user.id, target_user_id: @target_user.id) + end + + def merge_user_visits + update_user_id(:user_visits, conditions: "x.visited_at = y.visited_at") + + sql = <<~SQL + UPDATE user_visits AS t + SET posts_read = t.posts_read + s.posts_read, + mobile = t.mobile OR s.mobile, + time_read = t.time_read + s.time_read + FROM user_visits AS s + WHERE t.user_id = :target_user_id AND s.user_id = :source_user_id + AND t.visited_at = s.visited_at + SQL + + UserVisit.exec_sql(sql, source_user_id: @source_user.id, target_user_id: @target_user.id) + end + + def update_site_settings + SiteSetting.all_settings(true).each do |setting| + if setting[:type] == "username" && setting[:value] == @source_user.username + SiteSetting.set_and_log(setting[:setting], @target_user.username) + end + end + end + + def update_user_stats + # topics_entered + UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id) + UPDATE user_stats + SET topics_entered = ( + SELECT COUNT(topic_id) + FROM topic_views + WHERE user_id = :target_user_id + ) + WHERE user_id = :target_user_id + SQL + + # time_read and days_visited + UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id) + UPDATE user_stats + SET time_read = COALESCE(x.time_read, 0), + days_visited = COALESCE(x.days_visited, 0) + FROM ( + SELECT + SUM(time_read) AS time_read, + COUNT(1) AS days_visited + FROM user_visits + WHERE user_id = :target_user_id + ) AS x + WHERE user_id = :target_user_id + SQL + + # posts_read_count + UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id) + UPDATE user_stats + SET posts_read_count = ( + SELECT COUNT(1) + FROM post_timings AS pt + WHERE pt.user_id = :target_user_id AND EXISTS( + SELECT 1 + FROM topics AS t + WHERE t.archetype = 'regular' AND t.deleted_at IS NULL + )) + WHERE user_id = :target_user_id + SQL + + # likes_given, likes_received, new_since, read_faq, first_post_created_at + UserStat.exec_sql(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) + UPDATE user_stats AS t + SET likes_given = t.likes_given + s.likes_given, + likes_received = t.likes_received + s.likes_received, + new_since = LEAST(t.new_since, s.new_since), + read_faq = LEAST(t.read_faq, s.read_faq), + first_post_created_at = LEAST(t.first_post_created_at, s.first_post_created_at) + FROM user_stats AS s + WHERE t.user_id = :target_user_id AND s.user_id = :source_user_id + SQL + end + + def merge_user_attributes + User.exec_sql(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) + UPDATE users AS t + SET created_at = LEAST(t.created_at, s.created_at), + updated_at = LEAST(t.updated_at, s.updated_at), + seen_notification_id = GREATEST(t.seen_notification_id, s.seen_notification_id), + last_posted_at = GREATEST(t.last_seen_at, s.last_seen_at), + last_seen_at = GREATEST(t.last_seen_at, s.last_seen_at), + admin = t.admin OR s.admin, + last_emailed_at = GREATEST(t.last_emailed_at, s.last_emailed_at), + trust_level = GREATEST(t.trust_level, s.trust_level), + previous_visit_at = GREATEST(t.previous_visit_at, s.previous_visit_at), + date_of_birth = COALESCE(t.date_of_birth, s.date_of_birth), + ip_address = COALESCE(t.ip_address, s.ip_address), + moderator = t.moderator OR s.moderator, + title = COALESCE(t.title, s.title), + primary_group_id = COALESCE(t.primary_group_id, s.primary_group_id), + registration_ip_address = COALESCE(t.registration_ip_address, s.registration_ip_address), + first_seen_at = LEAST(t.first_seen_at, s.first_seen_at), + group_locked_trust_level = GREATEST(t.group_locked_trust_level, s.group_locked_trust_level), + manual_locked_trust_level = GREATEST(t.manual_locked_trust_level, s.manual_locked_trust_level) + FROM users AS s + WHERE t.id = :target_user_id AND s.id = :source_user_id + SQL + + UserProfile.exec_sql(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) + UPDATE user_profiles AS t + SET location = COALESCE(t.location, s.location), + website = COALESCE(t.website, s.website), + bio_raw = COALESCE(t.bio_raw, s.bio_raw), + bio_cooked = COALESCE(t.bio_cooked, s.bio_cooked), + bio_cooked_version = COALESCE(t.bio_cooked_version, s.bio_cooked_version), + profile_background = COALESCE(t.profile_background, s.profile_background), + dismissed_banner_key = COALESCE(t.dismissed_banner_key, s.dismissed_banner_key), + badge_granted_title = t.badge_granted_title OR s.badge_granted_title, + card_background = COALESCE(t.card_background, s.card_background), + card_image_badge_id = COALESCE(t.card_image_badge_id, s.card_image_badge_id), + views = t.views + s.views + FROM user_profiles AS s + WHERE t.user_id = :target_user_id AND s.user_id = :source_user_id + SQL + end + + def update_user_ids + Category.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + update_user_id(:category_users, conditions: ["x.category_id = y.category_id"]) + + update_user_id(:developers) + + update_user_id(:draft_sequences, conditions: "x.draft_key = y.draft_key") + update_user_id(:drafts, conditions: "x.draft_key = y.draft_key") + + EmailLog.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + GroupHistory.where(acting_user_id: @source_user.id).update_all(acting_user_id: @target_user.id) + GroupHistory.where(target_user_id: @source_user.id).update_all(target_user_id: @target_user.id) + + update_user_id(:group_users, conditions: "x.group_id = y.group_id") + + IncomingEmail.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + IncomingLink.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + IncomingLink.where(current_user_id: @source_user.id).update_all(current_user_id: @target_user.id) + + Invite.with_deleted.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + Invite.with_deleted.where(invited_by_id: @source_user.id).update_all(invited_by_id: @target_user.id) + Invite.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + + update_user_id(:muted_users, conditions: "x.muted_user_id = y.muted_user_id") + update_user_id(:muted_users, user_id_column_name: "muted_user_id", conditions: "x.user_id = y.user_id") + + Notification.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + update_user_id(:post_actions, conditions: ["x.post_id = y.post_id", + "x.post_action_type_id = y.post_action_type_id", + "x.targets_topic = y.targets_topic"]) + + PostAction.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + PostAction.where(deferred_by_id: @source_user.id).update_all(deferred_by_id: @target_user.id) + PostAction.where(agreed_by_id: @source_user.id).update_all(agreed_by_id: @target_user.id) + PostAction.where(disagreed_by_id: @source_user.id).update_all(disagreed_by_id: @target_user.id) + + PostRevision.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + Post.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + Post.with_deleted.where(last_editor_id: @source_user.id).update_all(last_editor_id: @target_user.id) + Post.with_deleted.where(locked_by_id: @source_user.id).update_all(locked_by_id: @target_user.id) + Post.with_deleted.where(reply_to_user_id: @source_user.id).update_all(reply_to_user_id: @target_user.id) + + QueuedPost.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + QueuedPost.where(approved_by_id: @source_user.id).update_all(approved_by_id: @target_user.id) + QueuedPost.where(rejected_by_id: @source_user.id).update_all(rejected_by_id: @target_user.id) + + SearchLog.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + update_user_id(:tag_users, conditions: "x.tag_id = y.tag_id") + + Theme.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + update_user_id(:topic_allowed_users, conditions: "x.topic_id = y.topic_id") + + TopicEmbed.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + + TopicLink.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + TopicLinkClick.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + TopicTimer.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + + update_user_id(:topic_timers, conditions: ["x.status_type = y.status_type", + "x.topic_id = y.topic_id", + "y.deleted_at IS NULL"]) + + update_user_id(:topic_users, conditions: "x.topic_id = y.topic_id") + + update_user_id(:topic_views, conditions: "x.topic_id = y.topic_id") + + Topic.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + + UnsubscribeKey.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + Upload.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + update_user_id(:user_archived_messages, conditions: "x.topic_id = y.topic_id") + + update_user_id(:user_actions, + user_id_column_name: "user_id", + conditions: ["x.action_type = y.action_type", + "x.target_topic_id IS NOT DISTINCT FROM y.target_topic_id", + "x.target_post_id IS NOT DISTINCT FROM y.target_post_id", + "x.acting_user_id IN (:source_user_id, :target_user_id)"]) + update_user_id(:user_actions, + user_id_column_name: "acting_user_id", + conditions: ["x.action_type = y.action_type", + "x.user_id = y.user_id", + "x.target_topic_id IS NOT DISTINCT FROM y.target_topic_id", + "x.target_post_id IS NOT DISTINCT FROM y.target_post_id"]) + + update_user_id(:user_badges, conditions: ["x.badge_id = y.badge_id", + "x.seq = y.seq", + "x.post_id IS NOT DISTINCT FROM y.post_id"]) + + UserBadge.where(granted_by_id: @source_user.id).update_all(granted_by_id: @target_user.id) + + update_user_id(:user_custom_fields, conditions: "x.name = y.name") + + update_user_id(:user_emails, conditions: "x.email = y.email OR y.primary = false", updates: '"primary" = false') + + UserExport.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + UserHistory.where(target_user_id: @source_user.id).update_all(target_user_id: @target_user.id) + UserHistory.where(acting_user_id: @source_user.id).update_all(acting_user_id: @target_user.id) + + UserProfileView.where(user_profile_id: @source_user.id).update_all(user_profile_id: @target_user.id) + UserProfileView.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + + UserWarning.where(user_id: @source_user.id).update_all(user_id: @target_user.id) + UserWarning.where(created_by_id: @source_user.id).update_all(created_by_id: @target_user.id) + + User.where(approved_by_id: @source_user.id).update_all(approved_by_id: @target_user.id) + end + + def delete_source_user + @source_user.reload + @source_user.update_attribute(:admin, false) + UserDestroyer.new(Discourse.system_user).destroy(@source_user) + end + + def delete_source_user_references + Developer.where(user_id: @source_user.id).delete_all + DraftSequence.where(user_id: @source_user.id).delete_all + GivenDailyLike.where(user_id: @source_user.id).delete_all + MutedUser.where(user_id: @source_user.id).or(MutedUser.where(muted_user_id: @source_user.id)).delete_all + UserAuthTokenLog.where(user_id: @source_user.id).delete_all + UserAvatar.where(user_id: @source_user.id).delete_all + UserAction.where(acting_user_id: @source_user.id).delete_all + end + + def update_user_id(table_name, opts = {}) + builder = update_user_id_sql_builder(table_name, opts) + builder.exec(source_user_id: @source_user.id, target_user_id: @target_user.id) + end + + def update_user_id_sql_builder(table_name, opts = {}) + user_id_column_name = opts[:user_id_column_name] || :user_id + conditions = Array.wrap(opts[:conditions]) + updates = Array.wrap(opts[:updates]) + + builder = SqlBuilder.new(<<~SQL) + UPDATE #{table_name} AS x + /*set*/ + WHERE x.#{user_id_column_name} = :source_user_id AND NOT EXISTS( + SELECT 1 + FROM #{table_name} AS y + /*where*/ + ) + SQL + + builder.set("#{user_id_column_name} = :target_user_id") + updates.each { |u| builder.set(u) } + + builder.where("y.#{user_id_column_name} = :target_user_id") + conditions.each { |c| builder.where(c) } + + builder + end +end diff --git a/app/views/common/_discourse_javascript.html.erb b/app/views/common/_discourse_javascript.html.erb index 3211308db2..dac6b19fbe 100644 --- a/app/views/common/_discourse_javascript.html.erb +++ b/app/views/common/_discourse_javascript.html.erb @@ -42,6 +42,7 @@ Discourse.BaseUri = '<%= Discourse::base_uri %>'; Discourse.Environment = '<%= Rails.env %>'; Discourse.SiteSettings = ps.get('siteSettings'); + Discourse.ThemeSettings = ps.get('themeSettings'); Discourse.LetterAvatarVersion = '<%= LetterAvatar.version %>'; Discourse.MarkdownItURL = '<%= asset_url('markdown-it-bundle.js') %>'; Discourse.ServiceWorkerURL = '<%= Rails.application.assets_manifest.assets['service-worker.js'] %>' diff --git a/app/views/common/_second_factor_text_field.html.erb b/app/views/common/_second_factor_text_field.html.erb new file mode 100644 index 0000000000..ad6d61c397 --- /dev/null +++ b/app/views/common/_second_factor_text_field.html.erb @@ -0,0 +1 @@ +<%= text_field_tag(:second_factor_token, nil, autofocus: true, pattern: '[0-9]{6}', maxlength: 6, type: 'tel') %> diff --git a/app/views/finish_installation/register.html.erb b/app/views/finish_installation/register.html.erb index 8fa60e3e38..cf6457da3d 100644 --- a/app/views/finish_installation/register.html.erb +++ b/app/views/finish_installation/register.html.erb @@ -51,9 +51,3 @@ <%- else -%>

<%= raw(t 'finish_installation.register.no_emails') %>

<%- end %> - - diff --git a/app/views/groups/show.html.erb b/app/views/groups/show.html.erb new file mode 100644 index 0000000000..cc250d4684 --- /dev/null +++ b/app/views/groups/show.html.erb @@ -0,0 +1,3 @@ +<% content_for :head do %> + <%= raw crawlable_meta_data(title: @title, description: @description_meta) %> +<% end %> diff --git a/app/views/robots_txt/index.erb b/app/views/robots_txt/index.erb index 0b6c8922fe..730032d637 100644 --- a/app/views/robots_txt/index.erb +++ b/app/views/robots_txt/index.erb @@ -25,5 +25,7 @@ Disallow: /user-api-key Disallow: /user-api-key/ Disallow: /*?api_key* Disallow: /*?*api_key* +Disallow: /groups +Disallow: /groups/ <%= server_plugin_outlet "robots_txt_index" %> diff --git a/app/views/session/email_login.html.erb b/app/views/session/email_login.html.erb index 7bd6cf03fd..a0f390df78 100644 --- a/app/views/session/email_login.html.erb +++ b/app/views/session/email_login.html.erb @@ -4,6 +4,19 @@
<%end%> +<%if @second_factor_required%> +
+
+ <%= form_tag(method: "post") do%> +

<%=t "login.second_factor_title" %>

+ <%= label_tag(:second_factor_token, t("login.second_factor_description")) %> +
<%= render 'common/second_factor_text_field' %>
+ <%= submit_tag(t("submit"), class: "btn btn-large btn-primary") %> + <%end%> +
+
+<%end%> + <% content_for :title do %><%=t "email_login.title" %><% end %> <%- content_for(:no_ember_head) do %> diff --git a/app/views/users/admin_login.html.erb b/app/views/users/admin_login.html.erb index 9b40e951ec..2304944071 100644 --- a/app/views/users/admin_login.html.erb +++ b/app/views/users/admin_login.html.erb @@ -5,6 +5,15 @@ <% if @message %> <%= @message %> + <% if @error %>

<%= @error %>

<% end %> + + <% if @second_factor_required %> + <%=form_tag({}, method: :put) do %> + <%= label_tag(:second_factor_token, t('login.second_factor_description')) %> + <%= render 'common/second_factor_text_field' %>

+ <%= submit_tag t('submit')%> + <% end %> + <% end %> <% else %> <%=form_tag({}, method: :put) do %> <%= label_tag(:email, t('admin_login.email_input')) %> diff --git a/app/views/users_email/confirm.html.erb b/app/views/users_email/confirm.html.erb index 0538ddfaac..35877cd95d 100644 --- a/app/views/users_email/confirm.html.erb +++ b/app/views/users_email/confirm.html.erb @@ -7,6 +7,17 @@

<%= t 'change_email.confirmed' %>


<%= t('change_email.please_continue', site_name: SiteSetting.title) %> + <% elsif @update_result == :invalid_second_factor%> +

<%= t('login.second_factor_title') %>

+
+ <%=form_tag({}, method: :put) do %> + <%= label_tag(:second_factor_token, t('login.second_factor_description')) %> + <%= text_field_tag(:second_factor_token, nil, autofocus: true) %>
+ <% if @show_invalid_second_factor_error %> +
<%= t('login.invalid_second_factor_code') %>
+ <% end %> + <%= submit_tag t('submit'), class: "btn btn-primary" %> + <% end %> <% else %>
<%=t 'change_email.already_done' %> diff --git a/bin/docker/reset_db b/bin/docker/reset_db index 038cfd236e..22ee77d1f2 100755 --- a/bin/docker/reset_db +++ b/bin/docker/reset_db @@ -1,8 +1,11 @@ #!/bin/bash -SCRIPTPATH=$(cd "$(dirname "$0")"; pwd -P) +SCRIPTPATH=$(cd "$(dirname "$0")" >/dev/null; pwd -P) SOURCE_DIR=$(cd "$SCRIPTPATH" && cd ../.. && pwd -P) DATA_DIR=$SOURCE_DIR/data/postgres # Should this also run /etc/runit/1.d/ensure_database, or will that -# happen automatically? +# happen automatically? -- It will happen, but restarting the container is required docker run -it -v $DATA_DIR:/shared/postgres_data discourse/dev /bin/bash -c "rm -fr /shared/postgres_data/*" +docker restart discourse_dev +echo "Creating admin user..." +"${SCRIPTPATH}/rake" admin:create diff --git a/config/application.rb b/config/application.rb index eb2b23be4d..bb98916354 100644 --- a/config/application.rb +++ b/config/application.rb @@ -129,13 +129,14 @@ module Discourse # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [ - :password, - :pop3_polling_password, - :api_key, - :s3_secret_access_key, - :twitter_consumer_secret, - :facebook_app_secret, - :github_client_secret + :password, + :pop3_polling_password, + :api_key, + :s3_secret_access_key, + :twitter_consumer_secret, + :facebook_app_secret, + :github_client_secret, + :second_factor_token, ] # Enable the asset pipeline diff --git a/config/boot.rb b/config/boot.rb index 929f1a62fe..ba9f61acee 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -10,15 +10,21 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) -if ENV['RAILS_ENV'] != 'production' - require 'bootsnap' +if ENV['RAILS_ENV'] != 'production' && ENV['RAILS_ENV'] != 'profile' + begin + require 'bootsnap' + rescue LoadError + # not a strong requirement + end - Bootsnap.setup( - cache_dir: 'tmp/cache', # Path to your cache - load_path_cache: true, # Should we optimize the LOAD_PATH with a cache? - autoload_paths_cache: true, # Should we optimize ActiveSupport autoloads with cache? - disable_trace: false, # Sets `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }` - compile_cache_iseq: true, # Should compile Ruby code into ISeq cache? - compile_cache_yaml: false # Skip YAML cache for now, cause we were seeing issues with it - ) + if defined? Bootsnap + Bootsnap.setup( + cache_dir: 'tmp/cache', # Path to your cache + load_path_cache: true, # Should we optimize the LOAD_PATH with a cache? + autoload_paths_cache: true, # Should we optimize ActiveSupport autoloads with cache? + disable_trace: false, # Sets `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }` + compile_cache_iseq: true, # Should compile Ruby code into ISeq cache? + compile_cache_yaml: false # Skip YAML cache for now, cause we were seeing issues with it + ) + end end diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf index fe949ee810..6eb579aca8 100644 --- a/config/discourse_defaults.conf +++ b/config/discourse_defaults.conf @@ -178,6 +178,10 @@ max_admin_api_reqs_per_key_per_minute = 60 max_reqs_per_ip_per_minute = 200 max_reqs_per_ip_per_10_seconds = 50 + +# applies to asset type routes (avatars/css and so on) +max_asset_reqs_per_ip_per_10_seconds = 200 + # global rate limiter will simply warn if the limit is exceeded, can be warn+block, warn, block or none max_reqs_per_ip_mode = none diff --git a/config/initializers/004-message_bus.rb b/config/initializers/004-message_bus.rb index fabada48a0..d9adc7a8c1 100644 --- a/config/initializers/004-message_bus.rb +++ b/config/initializers/004-message_bus.rb @@ -22,13 +22,18 @@ def setup_message_bus_env(env) user.groups.pluck('groups.id') end + extra_headers = { + "Access-Control-Allow-Origin" => Discourse.base_url_no_prefix, + "Access-Control-Allow-Methods" => "GET, POST", + "Access-Control-Allow-Headers" => "X-SILENCE-LOGGER, X-Shared-Session-Key, Dont-Chunk, Discourse-Visible" + } + + if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN] + extra_headers['Discourse-Logged-Out'] = '1' + end + hash = { - extra_headers: - { - "Access-Control-Allow-Origin" => Discourse.base_url_no_prefix, - "Access-Control-Allow-Methods" => "GET, POST", - "Access-Control-Allow-Headers" => "X-SILENCE-LOGGER, X-Shared-Session-Key, Dont-Chunk, Discourse-Visible" - }, + extra_headers: extra_headers, user_id: user_id, group_ids: group_ids, is_admin: is_admin, diff --git a/config/initializers/012-web_hook_events.rb b/config/initializers/012-web_hook_events.rb index ad5d94dcce..7e29c2d301 100644 --- a/config/initializers/012-web_hook_events.rb +++ b/config/initializers/012-web_hook_events.rb @@ -4,6 +4,10 @@ end end +DiscourseEvent.on(:topic_status_updated) do |topic, status| + WebHook.enqueue_topic_hooks("topic_#{status}_status_updated", topic) +end + DiscourseEvent.on(:topic_created) do |topic, _, user| WebHook.enqueue_topic_hooks(:topic_created, topic, user) end @@ -18,8 +22,13 @@ end end DiscourseEvent.on(:post_edited) do |post, topic_changed| - WebHook.enqueue_post_hooks(:post_edited, post) - WebHook.enqueue_topic_hooks(:topic_edited, post.topic) if post.is_first_post? && topic_changed + if post.topic + WebHook.enqueue_post_hooks(:post_edited, post) + + if post.is_first_post? && topic_changed + WebHook.enqueue_topic_hooks(:topic_edited, post.topic) + end + end end %i( diff --git a/config/initializers/100-lograge.rb b/config/initializers/100-lograge.rb index a1089a7b27..6ab44c6abe 100644 --- a/config/initializers/100-lograge.rb +++ b/config/initializers/100-lograge.rb @@ -8,6 +8,13 @@ if (Rails.env.production? && SiteSetting.logging_provider == 'lograge') || ENV[" Rails.application.configure do config.lograge.enabled = true + Lograge.ignore(lambda do |event| + # this is our hijack magic status, + # no point logging this cause we log again + # direct from hijack + event.payload[:status] == 418 + end) + config.lograge.custom_payload do |controller| begin username = @@ -46,7 +53,7 @@ if (Rails.env.production? && SiteSetting.logging_provider == 'lograge') || ENV[" database: RailsMultisite::ConnectionManagement.current_db, } - if data = Thread.current[:_method_profiler] + if data = (Thread.current[:_method_profiler] || event.payload[:timings]) sql = data[:sql] if sql @@ -60,6 +67,13 @@ if (Rails.env.production? && SiteSetting.logging_provider == 'lograge') || ENV[" output[:redis] = redis[:duration] * 1000 output[:redis_calls] = redis[:calls] end + + net = data[:net] + + if net + output[:net] = net[:duration] * 1000 + output[:net_calls] = net[:calls] + end end output diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index fcbc996ac8..1e87fbeba1 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -239,6 +239,7 @@ ar: not_implemented: "لم تُنجز هذه الخاصية بعد، عذرا." no_value: "لا" yes_value: "نعم" + submit: "أرسل" generic_error: "نأسŮŘŚ Ř­ŘŻŘ« عطل ما." generic_error_with_reason: "Ř­ŘŻŘ« عطل ما: %{error}" sign_up: "أنشأ حسابا" @@ -350,6 +351,7 @@ ar: uploading: "يرŮŘą..." uploading_filename: "يرŮŘą {{filename}}..." uploaded: "رُŮŘą!" + pasting: "جاري النسخ" enable: "Ůعّل" disable: "عطّل" undo: "تراجع" @@ -484,7 +486,7 @@ ar: name: "الاسم" user_count: "عدد اﻷعضاء" bio: "عن المجمŮعة" - selector_placeholder: "أض٠أعضاء" + selector_placeholder: "أدخل اسم المستخدم" owner: "المالŮ" index: title: "المجمŮعات" @@ -739,6 +741,9 @@ ar: set_password: " إعادة تعين Ůلمة المرŮر" choose_new: "اختر Ůلمة المرŮر الجديدة" choose: "اختر Ůلمة المرŮر" + second_factor: + enabled_status: "الحالة : متاج" + disabled_status: "الحالة : غير متاج" change_about: title: "تعديل عني" error: "Ř­ŘŻŘ« عطل أثناء تغيير هذه القيمة." @@ -1508,6 +1513,8 @@ ar: move_to_inbox: title: 'انقل إلى البريد الŮارد' help: 'انقل الرسالة للبريد الŮارد' + edit_message: + title: 'تحرير رسالة' list: 'المŮضŮعات' new: 'Ů…ŮضŮŘą جديد' unread: 'غير مقرŮء' @@ -1848,12 +1855,12 @@ ar: select_all: Ř­ŘŻŘŻ الŮŮ„ deselect_all: أزل ŘŞŘ­ŘŻŮŠŘŻ الŮŮ„ description: - zero: لم تحدّد ŘŁŮŠ منشŮرات. - one: لقد حدّدت منشŮرŮاحد. - two: لقد حدّدت منشŮرين. - few: لقد حدّدت {{count}} منشŮر. - many: لقد حدّدت {{count}} منشŮر. - other: لقد حدّدت {{count}} منشŮر. + zero: "لم تحدّد ŘŁŮŠ منشŮرات." + one: "لقد حدّدت منشŮرŮاحد." + two: "لقد حدّدت منشŮرين." + few: "لقد حدّدت {{count}} منشŮر." + many: "لقد حدّدت {{count}} منشŮر." + other: "لقد حدّدت {{count}} منشŮر." post: quote_reply: "اقتبس" edit_reason: "السبب:" @@ -2185,7 +2192,6 @@ ar: email_in_allow_strangers: "قبŮŮ„ بريد ŘĄŮ„ŮترŮني من زŮار لا ŮŠŮ…Ů„ŮŮن حسابات" email_in_disabled: "عُطّل إرسال المشارŮات عبر البريد الإلŮترŮنيّ من إعدادات المŮقع. لتŮعيل نشر المشارŮات الجديدة عبر البريد،" email_in_disabled_click: 'قم بتŮعيل خيار "email in" ŮŮŠ الإعدادات' - suppress_from_homepage: "امنع هذا القسم من الظهŮر ŮŮŠ الصŮŘ­Ř© الرئيسية." show_subcategory_list: "أظهر الأقسام الŮرعية ŮŮŮ‚ المŮضŮعات من هذا القسم." num_featured_topics: "عدد المŮضŮعات المعرŮضة ŮŮŠ صŮŘ­Ř© الأقسام:" subcategory_num_featured_topics: "عدد المŮضŮعات المُميزة ŮŮŠ صŮŘ­Ř© القسم الرئيسي." @@ -2785,7 +2791,6 @@ ar: edit: "تعديل المجمŮعة" refresh: "ŘŞŘ­ŘŻŮŠŘ«" new: "جديد" - selector_placeholder: "أدخل اسم المستخدم" about: "عدل عضŮŮŠŘ© المجمŮعة Ůأسماءها هنا" group_members: "أعضاء المجمŮعة" delete: "احذŮ" @@ -3531,10 +3536,7 @@ ar: recommended: "نŮصي٠بتخصيص النص التالي ليلائم احتياجاتŮ:" show_overriden: 'اظهر التجاŮزات Ůقط' site_settings: - show_overriden: 'اظهر Ůقط الاعدادات المعدلة' title: 'الإعدادات' - reset: 'إعادة تعيين' - none: 'لا شيء' no_results: "لا نتائج." clear_filter: "مسح" add_url: "اضاŮŘ© رابط" @@ -3556,6 +3558,7 @@ ar: developer: 'المطŮرŮن' embedding: "تضمين" legal: "الأمŮر القانŮنية" + api: 'API' user_api: 'API المستخدمين' uncategorized: 'أخرى' backups: "النُسخ الإحتياطية" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index 713b0cd14e..efdbf7c77f 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -368,7 +368,6 @@ bs_BA: name: "Ime" user_count: "Broj ÄŤlanova" bio: "O grupi" - selector_placeholder: "Dodaj ÄŤlanove" owner: "vlasnik" index: title: "Grupe" @@ -1705,7 +1704,6 @@ bs_BA: edit: "Edit Groups" refresh: "Refresh" new: "New" - selector_placeholder: "add users" about: "Edit your group membership and names here" group_members: "Group members" delete: "Delete" @@ -2108,10 +2106,7 @@ bs_BA: site_text: title: 'Text Content' site_settings: - show_overriden: 'Only show overridden' title: 'Settings' - reset: 'reset' - none: 'none' no_results: "No results found." clear_filter: "Clear" categories: diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml index 936df7f7c8..8e2706c3ef 100644 --- a/config/locales/client.ca.yml +++ b/config/locales/client.ca.yml @@ -380,7 +380,6 @@ ca: name: "Nom" user_count: "Quantitat de membres" bio: "Sobre el Grup" - selector_placeholder: "Afegeix membres" owner: "propietari" index: title: "Grups" @@ -446,6 +445,7 @@ ca: '14': "Pendents" categories: all: "totes les categories" + all_subcategories: "Tot a %{categoryName}" no_subcategory: "cap" category: "Categoria" category_list: "Mostra la llista de categories" @@ -561,6 +561,7 @@ ca: watched_first_post_tags_instructions: "Et notificarem la primera publicaciĂł per cada nou tema a aquestes etqieuetes." muted_categories: "Silenciades" muted_categories_instructions: "No et notificarem res sobre nous temes amb aquestes categories i no apareixeran a les darreres.." + no_category_access: "Com a moderador/a tens accĂ©s limitat a la categoria. Desar estĂ  inhabilitat." delete_account: "Esborra el meu compte" delete_account_confirm: "Segur que vols esborrar el teu compte permanentment? Aquesta acciĂł no es pot desfer!" deleted_yourself: "El teu compte ha estat esborrat amb èxit." @@ -1092,6 +1093,10 @@ ca: body: "Ara mateix aquest missatge nomĂ©s s'estĂ  enviant a la teva bĂşstia!" admin_options_title: "Configuracions opcionals de l'equip per a aquest tema" notifications: + tooltip: + regular: + one: "1 notificaciĂł no vista" + other: "{{count}} notificacions no vistes" title: "notificacions de mencions via @nom, respostes a les teves entrades, missatges, etc" none: "Impossible carregar ara mateix les notificacions" empty: "No s'hi han trobat notificacions." @@ -1105,7 +1110,7 @@ ca: replied: "Respost" posted: "Publicat per" edited: "Edita la teva publicaciĂł per" - liked: "Ha agradat la teva publicaciĂł" + liked: "Els ha agradat la teva entrada" invited_to_topic: "Ha convidat a un tema de" invitee_accepted: "InvitaciĂł acceptada per" moved_post: "La teva publicaciĂł ha estat moguda per" @@ -1118,7 +1123,7 @@ ca: quoted: '{{username}} t''ha citat a "{{topic}}" - {{site_title}}' replied: '{{username}} t''ha respost a "{{topic}}" - {{site_title}}' posted: '{{username}} publicat a "{{topic}}" - {{site_title}}' - linked: 'A {{username}} li ha agradat la teva publicaciĂł de "{{topic}}" - {{site_title}}' + linked: '{{username}} ha enllaçat la teva entrada en "{{topic}}" - {{site_title}}' upload_selector: title: "Afegeix una imatge" title_with_attachments: "Afegeix una imatge o arxiu" @@ -1196,7 +1201,7 @@ ca: select_all: "Selecciona-ho tot" clear_all: "Neteja-ho tot" unlist_topics: "Desllista temes" - reset_read: "Reinicia la la lectura" + reset_read: "Restableix llegit" delete: "Esborra temes" dismiss: "Descarta-ho" dismiss_read: "Descarta tots els llegits" @@ -1206,14 +1211,16 @@ ca: dismiss_new: "Descarta'n els nous" toggle: "canvia la selecciĂł massiva de temes" actions: "Accions massives" + change_category: "Defineix la Categoria" close_topics: "Tanca temes" archive_topics: "Arxiva temes" + notification_level: "Notificacions" choose_new_category: "Tria la nova categoria per als temes:" selected: one: "Has triat 1 tema." other: "Has triat {{count}} temes." - change_tags: "Reemplaçar Etiquetes" - append_tags: "Annexar Etiquetes" + change_tags: "Reemplaça Etiquetes" + append_tags: "Annexa Etiquetes" choose_new_tags: "Tria noves etiquetes per a aquests temes:" choose_append_tags: "Escollir noves etiquetes per annexar a aquest topics:" changed_tags: "Les etiquetes d'aquests temes han estat canviades." @@ -1308,6 +1315,8 @@ ca: three_months: "Tres mesos" six_months: "Sis mesos" one_year: "Un any" + status_update_notice: + auto_publish_to_category: "Aquest tema serĂ  publicat a la categoria #%{categoryName}%{timeLeft}" auto_close_title: 'ConfiguraciĂł del tancament automĂ tic' auto_close_immediate: one: "La darrera publicaciĂł al tema ja tĂ© 1 hora, per això el tema es tancarĂ  immediatament." @@ -1383,7 +1392,7 @@ ca: archive: "Arxiva tema" invisible: "Desllista" visible: "Fes llistats" - reset_read: "Reinicia la lectura de dades" + reset_read: "Restableix dades de lectura" make_public: "Fes pĂşblic el tema" feature: pin: "Clava tema" @@ -1508,8 +1517,8 @@ ca: select_all: selecciona-ho tot deselect_all: desmarca-ho tot description: - one: Has seleccionat 1 publicaciĂł - other: Has seleccionat {{count}} publicacions. + one: "Has seleccionat 1 publicaciĂł" + other: "Has seleccionat {{count}} publicacions." post: quote_reply: "Cita" edit_reason: "Motiu:" @@ -1697,6 +1706,8 @@ ca: settings: 'Configuracions' topic_template: "Plantilla de tema" tags: "Etiquetes" + tags_allowed_tags: "Permet l'Ăşs de nomĂ©s aquestes etiquetes en aquesta categoria:" + tags_allowed_tag_groups: "Permet nomĂ©s l'Ăşs d'etiquetes d'aquests grups en aquesta categoria:" tags_placeholder: "Llista (opcional) d'etiquetes permeses" tag_groups_placeholder: "Llista (opcional) d'etiquetes de grup permeses" topic_featured_link_allowed: "Permet enllaços destacats dins aquesta categoria" @@ -1731,9 +1742,11 @@ ca: email_in_allow_strangers: "Accepta correus des de persones usuĂ ries anònimes sense compte" email_in_disabled: "Les publicacions des del correu electrònic estan desactivades a les preferències del lloc. Per activar les publicacions des del correu electrònic, " email_in_disabled_click: 'activa l''opciĂł "email in".' - suppress_from_homepage: "Suprimeix aquesta categoria des de la pĂ gina principal." + mailinglist_mirror: "La categoria replica una llista de correu" show_subcategory_list: "Mostrar les publicacions de les subcategories llistades a continuaciĂł d'aquesta categoria." + subcategory_num_featured_topics: "Nombre de temes destacats en la pĂ gina mare de la categoria:" all_topics_wiki: "Fes nou temes de wiki per defecte." + subcategory_list_style: "Estil de llista de la subcategoria:" allow_badges_label: "Permet concedir distintius dins aquesta categoria" edit_permissions: "Edita permisos" add_permission: "Afegeix permisos" @@ -2213,7 +2226,6 @@ ca: edit: "Edita grups" refresh: "Actualitza" new: "Nou" - selector_placeholder: "escriu nom de persona usuĂ ria" about: "Edita aquĂ­ la teva pertinença de grup i noms" group_members: "Membres de grup" delete: "Esborra" @@ -2840,10 +2852,7 @@ ca: recommended: "Recomanem personalitzar el segĂĽent text segons les teves necessitats:" show_overriden: 'NomĂ©s mostra anul·lat' site_settings: - show_overriden: 'NomĂ©s mostra anul·lat' title: 'ConfiguraciĂł' - reset: 'restableix' - none: 'cap' no_results: "Sense resultats" clear_filter: "Neteja" add_url: "afegeix URL" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index f6d67fd699..183ff8e30e 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -67,6 +67,10 @@ cs: one: "1d" few: "%{count}d" other: "%{count}d" + x_months: + one: "1 mÄ›sĂ­c" + few: "%{count} mÄ›sĂ­ce" + other: "%{count} mÄ›sĂ­cĹŻ" about_x_years: one: "1r" few: "%{count}r" @@ -138,6 +142,7 @@ cs: split_topic: "rozdÄ›lil toto tĂ©ma %{when}" invited_user: "%{who} pozván %{when}" invited_group: "%{who} pozvána %{when}" + user_left: "%{who} se odstranil z tĂ©to zpráy %{when}" removed_user: "%{who} smazán %{when}" removed_group: "%{who} smazána %{when}" autoclosed: @@ -160,7 +165,9 @@ cs: disabled: 'neuvedeno %{when}' banner: enabled: 'vytvoĹ™il(a) tento banner %{when}. Bude se zobrazovat nahoĹ™e na kaĹľdĂ© stránce, dokud nebude uĹľivatelem odmĂ­tnut.' + disabled: 'odstranil(a) tento banner %{when}. UĹľ se nebude zobrazovat na kaĹľdĂ© stránce nahoĹ™e.' topic_admin_menu: "akce administrátora tĂ©matu" + wizard_required: "VĂ­tejte ve vašem novĂ©m Discourse! ZaÄŤnÄ›te s nastavenĂ­m pomocĂ­ prĹŻvodce ✨" emails_are_disabled: "Všechny odchozĂ­ emaily byly administrátorem vypnuty. ŽádnĂ© odchozĂ­ emaily nebudou odeslány." bootstrap_mode_enabled: "Aby se váš web jednodušeji rozjel, nacházĂ­ se v reĹľimu bootstrap. Všichni novĂ­ uĹľivatelĂ© zaÄŤĂ­najĂ­ s dĹŻveryhodnostĂ­ 1 a majĂ­ povolenĂ© dennĂ­ odesĂ­lánĂ­ souhrnnĂ˝ch emailĹŻ. Tento reĹľim se automaticky vypne, jakmile poÄŤet registrovanĂ˝ch uĹľivatelĹŻ pĹ™ekroÄŤĂ­ %{min_users}." bootstrap_mode_disabled: "ReĹľim bootstrap bude deaktivován v následujĂ­cĂ­ch 24 hodinách." @@ -179,6 +186,7 @@ cs: eu_west_2: "EU (LondĂ˝n)" sa_east_1: "JiĹľnĂ­ Amerika (Sao Paulo)" us_east_1: "VĂ˝chod USA (S. Virginie)" + us_east_2: "VĂ˝chod USA (Ohio)" us_gov_west_1: "AWS GovCloud (USA)" us_west_1: "Západ USA (S. Kalifornie)" us_west_2: "Západ USA (Oregon)" @@ -186,6 +194,7 @@ cs: not_implemented: "Tato funkce ještÄ› nebyla naprogramována, omlouváme se." no_value: "Ne" yes_value: "Ano" + submit: "Odeslat" generic_error: "BohuĹľel nastala chyba." generic_error_with_reason: "Nastala chyba: %{error}" sign_up: "Registrace" @@ -282,6 +291,7 @@ cs: uploading: "Nahrávám..." uploading_filename: "NahrávánĂ­ {{filename}}..." uploaded: "Nahráno!" + pasting: "Vkládám..." enable: "Zapnout" disable: "Vypnout" undo: "ZpÄ›t" @@ -379,6 +389,15 @@ cs: add_members: "PĹ™idat ÄŤleny" delete_member_confirm: "Odstranit '%{username}' ze skupiny '%{group}'?" name_placeholder: "Název skupiny, bez mezer, stejná pravidla jako pro uĹľivatelská jmĂ©na" + public_admission: "Povolit uĹľivatelĹŻm volnĂ˝ přístup do skupiny (skupina musĂ­ bĂ˝t veĹ™ejnÄ› viditelná)" + public_exit: "Povolit uĹľivatelĹŻm volnÄ› opustit skupinu" + empty: + posts: "V tĂ©to skupinÄ› nenĂ­ od žádnĂ©ho ÄŤlena ani jeden příspÄ›vek." + members: "V tĂ©to skupinÄ› nenĂ­ žádny uĹľivatel." + mentions: "O tĂ©to skupinÄ› nepadla žádná zmĂ­nka." + messages: "Pro tuto skupinu nenĂ­ žádná zpráva." + topics: "V tĂ©to skupinÄ› nenĂ­ od žádnĂ©ho ÄŤlena ani jedno tĂ©ma." + logs: "Pro tuto skupinu neexistujĂ­ žádnĂ© logy." add: "PĹ™idat" join: "PĹ™idat se ke skupinÄ›" leave: "Opustit skupinu" @@ -386,27 +405,42 @@ cs: message: "Zpráva" automatic_group: Automatická skupina closed_group: UzavĹ™ená skupina - is_group_user: "Jsi ÄŤlenem tĂ©to skupiny" + is_group_user: "Jste ÄŤlenem tĂ©to skupiny" + allow_membership_requests: "Povolit uĹľivatelĹŻm odeslat žádost o ÄŤlenstvĂ­ vlastnĂ­kĹŻm skupin" + membership_request_template: "Ĺ ablona, která se uĹľivatelĹŻm zobrazĂ­ pĹ™i odesĂ­lánĂ­ žádosti o ÄŤlenstvĂ­" + membership_request: + submit: "Odeslat poĹľadavek" + title: "Žádost o ÄŤlenstvĂ­ v @%{group_name}" + reason: "Informovat vlastnĂ­ky skupiny proÄŤ patříte do tĂ©to skupiny" membership: "ÄŚlenstvĂ­" name: "Název" user_count: "PoÄŤet ÄŤlenĹŻ" bio: "O skupinÄ›" - selector_placeholder: "PĹ™idat ÄŤleny" + selector_placeholder: "zadejte uĹľivatelskĂ© jmĂ©no" owner: "VlastnĂ­k" index: title: "Skupiny" empty: "ŽádnĂ© viditelnĂ© skupiny." + title: + one: "Skupina" + few: "Skupiny" + other: "Skupin" activity: "Aktivita" members: "ÄŚlenovĂ©" topics: "TĂ©mata" posts: "OdpovÄ›di" mentions: "ZmĂ­nÄ›nĂ­" messages: "Zprávy" + notification_level: "VĂ˝chozĂ­ ĂşroveĹ upozornÄ›nĂ­ pro zprávy ze skupiny" visibility_levels: title: "Kdo vidĂ­ tuto skupinu?" public: "Všichni" members: "VlastnĂ­ci skupiny, ÄŤlenovĂ© a správci" + staff: "VlastnĂ­ci a redaktoĹ™i skupiny" + owners: "VlastnĂ­ci a správci skupiny" alias_levels: + mentionable: "Kdo mĹŻĹľe @zmiĹovat tuto skupinu?" + messageable: "Kdo mĹŻĹľe psát zprávy do tĂ©to skupinu?" nobody: "Nikdo" only_admins: "Pouze správci" mods_and_admins: "Pouze moderátoĹ™i a správci" @@ -431,8 +465,15 @@ cs: muted: title: "ZtišenĂ˝" description: "Nikdy nedostanete oznámenĂ­ o novĂ˝ch tĂ©matech v tĂ©to skupinÄ›." + flair_url: "Avatar Flair obrázek" + flair_url_placeholder: "(VolitelnĂ©) URL obrázku nebo třída Font Awesome " + flair_bg_color: "Avatar Flair barva pozadĂ­" + flair_bg_color_placeholder: "(VolitelnĂ©) Hex kĂłd barvy" + flair_color: "Avatar Flair barva" + flair_color_placeholder: "(VolitelnĂ©) Hex kĂłd barvy" flair_preview_icon: "Náhled ikony" flair_preview_image: "Náhled obrázku" + flair_note: "Poznámka: Avatar se zobrazĂ­ pouze pro primárnĂ­ skupinu uĹľivatele." user_action_groups: '1': "RozdanĂ˝ch 'lĂ­bĂ­ se'" '2': "ObdrĹľenĂ˝ch 'lĂ­bĂ­ se'" @@ -448,6 +489,7 @@ cs: '14': "ÄŚeká na schválenĂ­" categories: all: "všechny kategorie" + all_subcategories: "všechny v %{categoryName}" no_subcategory: "žádnĂ©" category: "Kategorie" category_list: "Zobrazit seznam kategoriĂ­" @@ -488,6 +530,7 @@ cs: topics_entered: "tĂ©mat zadáno" post_count: "poÄŤet příspÄ›vkĹŻ" confirm_delete_other_accounts: "UrÄŤitÄ› chcete smazat tyto účty?" + powered_by: "běží na ipinfo.io" user_fields: none: "(zvolit moĹľnost)" user: @@ -527,6 +570,8 @@ cs: first_notification: "Vaše prvnĂ­ notifikace! KliknÄ›te zde a zaÄŤnÄ›te." disable_jump_reply: "Po odpovÄ›di nepĹ™eskakovat na novĂ˝ příspÄ›vek" dynamic_favicon: "Zobrazit poÄŤet novĂ˝ch tĂ©mat v ikonÄ› prohlĂ­ĹľeÄŤe" + theme_default_on_all_devices: "Nastavit toto tĂ©ma jako vĂ˝chozĂ­ na všech mĂ˝ch zařízenĂ­ch" + allow_private_messages: "Povolit ostatnĂ­m uĹľivatelĹŻm, aby mi mohli zasĂ­lat osobnĂ­ zprávy" external_links_in_new_tab: "OtevĂ­rat všechny externĂ­ odkazy do novĂ© záloĹľky" enable_quoting: "Povolit odpověď s citacĂ­ z oznaÄŤenĂ©ho textu" change: "zmÄ›nit" @@ -567,6 +612,7 @@ cs: watched_first_post_tags_instructions: "Budete informováni o prvnĂ­m novĂ©m příspÄ›vku v kaĹľdĂ©m novĂ©m tĂ©matu s tÄ›mito štĂ­tky." muted_categories: "ZtišenĂ©" muted_categories_instructions: "Budeš pĹ™ijĂ­mat upornÄ›nĂ­ na nová tĂ©mata v tÄ›chto kategoriĂ­ch a ty se neobjevĂ­ v aktuálnĂ­ch." + no_category_access: "Jako moderátor máte omezenĂ˝ přístup ke kategorii bez moĹľnosti ukládat zmÄ›ny." delete_account: "Smazat mĹŻj účet" delete_account_confirm: "Jste si jisti, Ĺľe chcete trvale odstranit svĹŻj účet? Tuto akci nelze vrátit zpÄ›t!" deleted_yourself: "Váš účet byl ĂşspěšnÄ› odstranÄ›n." @@ -578,10 +624,14 @@ cs: muted_users_instructions: "UmlÄŤet všechny notifikace od tÄ›chto uĹľivatelĹŻ." muted_topics_link: "Ukázat utlumená tĂ©mata" watched_topics_link: "Ukázat hlĂ­daná tĂ©mata" + tracked_topics_link: "Ukázat sledovaná tĂ©mata" automatically_unpin_topics: "Atomaticky odepni tĂ©ma jakmile se dostanu na konec." apps: "Aplikace" revoke_access: "Odebrat přístup" + undo_revoke_access: "Zrušit odebránĂ­ přístupu" api_approved: "Schváleno:" + theme: "TĂ©ma" + home: "VĂ˝chozĂ­ domovská stránka" staff_counters: flags_given: "uĹľiteÄŤná nahlášenĂ­" flagged_posts: "nahlášenĂ˝ch příspÄ›vkĹŻ" @@ -616,6 +666,13 @@ cs: set_password: "Nastavit heslo" choose_new: "Zvolte si novĂ© heslo" choose: "Vyber si heslo" + second_factor: + title: "DvoufázovĂ© pĹ™ihlašovánĂ­" + disable: "Vypnout dvoufázovĂ© pĹ™ihlašovánĂ­" + enabled_status: "Stav: Zapnuto" + disabled_status: "Stav: Vypnuto" + confirm_password_description: "PotvrÄŹte svĂ© heslo, abyste mohli pokraÄŤovat v aktivovánĂ­ dvoufázovĂ©ho pĹ™ihlašovánĂ­" + info_prompt: "Co je dvoufázovĂ© pĹ™ihlašovánĂ­?" change_about: title: "ZmÄ›na o mnÄ›" error: "Došlo k chybÄ› pĹ™i pokusu zmÄ›nit tuto hodnotu." @@ -629,18 +686,20 @@ cs: taken: "Tato emailová adresa nenĂ­ k dispozici." error: "Nastala chyba pĹ™i zmÄ›nÄ› emailovĂ© adresy. NenĂ­ tato adresa jiĹľ používaná?" success: "Na zadanou adresu jsme zaslali email. Následujte, prosĂ­m, instrukce v tomto emailu." + success_staff: "Na vaši aktuálnĂ­ adresu jsme zaslali email. Následujte prosĂ­m instrukce v tomto emailu." change_avatar: title: "ZmÄ›Ĺte si svĹŻj profilovĂ˝ obrázek" gravatar: "ZaloĹľeno na Gravataru" gravatar_title: "ZmÄ›Ĺte si avatar na webovĂ˝ch stránkách Gravatar" + gravatar_failed: "Nelze naÄŤĂ­st Gravatar. Je nÄ›jakĂ˝ Gravatar pĹ™iĹ™azen k tĂ©to e-mailovĂ© adrese?" refresh_gravatar_title: "Obnovit Gravatar" letter_based: "SystĂ©mem pĹ™idÄ›lenĂ˝ profilovĂ˝ obrázek" uploaded_avatar: "VlastnĂ­ obrázek" uploaded_avatar_empty: "PĹ™idat vlastnĂ­ obrázek" upload_title: "Nahrát obrázek" upload_picture: "Nahrát obrázek" - image_is_not_a_square: "VarovánĂ­: Ořízli jsme váš avatar; šířka a dĂ©lka nebyla stejná." - cache_notice: "ĂšspěšnÄ› jsi si vymÄ›nil profilovou fotku, ale chvĂ­li mĹŻĹľe trvat, neĹľ se zobrazĂ­ kvĹŻli ukládánĂ­ v mezipamÄ›ti prohlĂ­ĹľeÄŤe. " + image_is_not_a_square: "VarovánĂ­: Ořízli jsme váš obrázek; šířka a dĂ©lka nebyla stejná." + cache_notice: "ĂšspěšnÄ› jste zmÄ›nili váš profilovĂ˝ obrázek, ale chvĂ­li mĹŻĹľe trvat, neĹľ se zobrazĂ­ kvĹŻli ukládánĂ­ v mezipamÄ›ti prohlĂ­ĹľeÄŤe. " change_profile_background: title: "PozadĂ­ profilu" instructions: "PozadĂ­ profilu je zarovnáno doprostĹ™ed a má vĂ˝chozĂ­ šířku 850px." @@ -743,6 +802,7 @@ cs: title: "Pozvánky" user: "PozvanĂ˝ uĹľivatel" sent: "OdeslanĂ©" + none: "Nejsou žádnĂ© pozvánky k zobrazenĂ­." truncated: one: "Zobrazena prvnĂ­ pozvánka." few: "Zobrazeno prvnĂ­ch {{count}} pozvánek." @@ -761,8 +821,10 @@ cs: rescinded: "Pozvánka odstranÄ›na" rescind_all: "Odstranit všechny pozvánky" rescinded_all: "Všechny pozvánky odstranÄ›ny!" + rescind_all_confirm: "Opravdu chcete odstranit všechny pozvánky?" reinvite: "Znovu poslat pozvánku" reinvite_all: "Znovu poslat všechny pozvánky" + reinvite_all_confirm: "Opravdu chcete znovu poslat všechny pozvánky?" reinvited: "Pozvánka byla opÄ›tovnÄ› odeslána." reinvited_all: "Všechny pozvánky byly opÄ›tovnÄ› odeslány!" time_read: "ÄŚas ÄŤtenĂ­" @@ -770,10 +832,13 @@ cs: account_age_days: "Stáří účtu ve dnech" create: "Poslat pozvánku" generate_link: "ZkopĂ­rovat odkaz na pozvánku" + link_generated: "Odkaz pozvánky byl ĂşspěšnÄ› vygenerován!" + valid_for: "Odkaz pozvánky je platnĂ˝ pouze pro tuto emailovou adresu: %{email}" bulk_invite: - none: "Nikoho jsi zatĂ­m nepozval. Odešli individuálnĂ­ pozvánky, případnÄ› nahrej CSV soubor a pozvi tak nÄ›kolik lidĂ­ najednou." + none: "Nikoho jste zatĂ­m nepozval. MĹŻĹľete poslat individuálnĂ­ pozvánky, případnÄ› nahrát CSV soubor a pozvat tak nÄ›kolik lidĂ­ najednou." text: "HromadnĂ© pozvánĂ­ s pomocĂ­ souboru" success: "NahránĂ­ souboru probÄ›hlo ĂşspěšnÄ›. O dokonÄŤenĂ­ celĂ©ho procesu budete informovánĂ­ pomocĂ­ zprávy." + error: "Omlouváme se, soubor by mÄ›l bĂ˝t ve formátu CSV." password: title: "Heslo" too_short: "Vaše heslo je příliš krátkĂ©." @@ -786,6 +851,7 @@ cs: title: "Souhrn" stats: "Statistiky" time_read: "naÄŤteno" + recent_time_read: "poslednĂ­ ÄŤas ÄŤtenĂ­" topic_count: one: "tĂ©ma vytvoĹ™eno" few: "tĂ©mata vytvoĹ™ena" @@ -794,10 +860,22 @@ cs: one: "příspÄ›vek vytvoĹ™en" few: "příspÄ›vky vytvoĹ™eny" other: "příspÄ›vkĹŻ vytvoĹ™eno" + likes_given: + one: "dán" + few: "dány" + other: "dáno" + likes_received: + one: "obdrĹľen" + few: "obdrĹľeny" + other: "obdrĹľeno" days_visited: one: "den navštĂ­ven" few: "dny navštĂ­veno" other: "dnĂ­ navštĂ­veno" + topics_entered: + one: "zobrazeno tĂ©ma" + few: "zobrazeny tĂ©mata" + other: "zobrazeno tĂ©mat" posts_read: one: "pĹ™eÄŤten příspÄ›vek" few: "pĹ™eÄŤteny příspÄ›vky" @@ -866,6 +944,9 @@ cs: enabled: "Tato stránka je v reĹľimu jen pro ÄŤtenĂ­. Prosim pokraÄŤujte v prohlĂ­ĹľenĂ­, ale odpovĂ­dánĂ­ a ostatnĂ­ operace jsou momentálnÄ› vypnutĂ©." login_disabled: "PĹ™ihlášenĂ­ je zakázáno jelikoĹľ je stránka v reĹľimu jen pro ÄŤtenĂ­." logout_disabled: "OdhlášenĂ­ je zakázáno zatĂ­mco je stránka v reĹľimu jen pro ÄŤtenĂ­." + too_few_topics_and_posts_notice: "ZaÄŤnÄ›me tuto diskusi! V souÄŤasnĂ© dobÄ› tu je %{currentTopics} / %{requiredTopics} tĂ©mat a %{currentPosts} / %{requiredPosts} příspÄ›vkĹŻ. NovĂ­ návštÄ›vnĂ­ci potĹ™ebujĂ­ ÄŤĂ­st nÄ›jakĂ© příspÄ›vky a reagovat na nÄ›." + too_few_topics_notice: "ZaÄŤnÄ›me tuto diskusi! V souÄŤasnĂ© dobÄ› tu je %{currentTopics} / %{requiredTopics} tĂ©mat. NovĂ­ návštÄ›vnĂ­ci potĹ™ebujĂ­ ÄŤĂ­st nÄ›jakĂ© příspÄ›vky a reagovat na nÄ›." + too_few_posts_notice: "ZaÄŤnÄ›me tuto diskusi! V souÄŤasnĂ© dobÄ› tu je %{currentPosts}/%{requiredPosts} příspÄ›vkĹŻ. NovĂ­ návštÄ›vnĂ­ci potĹ™ebujĂ­ ÄŤĂ­st nÄ›jakĂ© příspÄ›vky a reagovat na nÄ›." logs_error_rate_notice: reached: "%{relativeAge} – %{rate} dosáhlo limitu stránky, kterĂ˝ je %{siteSettingRate}." exceeded: "%{relativeAge} – %{rate} pĹ™esahuje limit stránky, kterĂ˝ je %{siteSettingRate}." @@ -886,8 +967,11 @@ cs: first_post: PrvnĂ­ příspÄ›vek mute: Ignorovat unmute: Zrušit ignorovánĂ­ - last_post: PĹ™idáno + last_post: PĹ™ispÄ›l time_read: PĹ™eÄŤteno + time_read_recently: '%{time_read}nedávno' + time_read_tooltip: '%{time_read}celkovĂ˝ ÄŤas ÄŤtenĂ­' + time_read_recently_tooltip: '%{time_read}celkovĂ˝ ÄŤas ÄŤtenĂ­ (%{recent_time_read}za poslednĂ­ch 60 dnĹŻ)' last_reply_lowercase: poslednĂ­ odpověď replies_lowercase: one: odpověď @@ -913,7 +997,7 @@ cs: disable: "Zobrazit smazanĂ© příspÄ›vky" private_message_info: title: "Zpráva" - invite: "pozvat účastnĂ­ka" + invite: "Pozvat další..." leave_message: "Opravdu chcete opustit tuto zprávu?" remove_allowed_user: "UrÄŤitÄ› chcete odstranit {{name}} z tĂ©to zprávy?" remove_allowed_group: "Opravdu chcete odstranit {{name}} z tĂ©to zprávy?" @@ -939,21 +1023,35 @@ cs: complete_email_found: "Byl nalezen účet odpovĂ­dajĂ­cĂ­ emailu %{email}. Za chvilku obdržíte email s instrukcemi jak pĹ™enastavit vaše heslo." complete_username_not_found: "Nebyl nalezen účet s uĹľivatelskĂ˝m jmĂ©nem %{username}" complete_email_not_found: "Nebyl nalezen účet s odpovĂ­dajĂ­cĂ­ emailu %{email}" + help: "Nepříšel email? NezapomeĹte nejprve zkontrolovat sloĹľku spam.

Nevíte, kterou e-mailovou adresu jste použili? Zadejte e-mailovou adresu a my vás budeme informovat, pokud zde existuje.

Pokud již nemáte přístup k e-mailové adrese ve vašem účtu, kontaktujte prosím naši ochotnou redakci

." button_ok: "OK" button_help: "NápovÄ›da" + email_login: + link_label: "Zašlete mi jednorázovĂ˝ odkaz" + button_label: "pĹ™es email" + complete_username: "Pokud nÄ›jakĂ˝ účet odpovĂ­dá uĹľivatelskĂ©mu jmĂ©nu %{username}, obdržíte záhy email s pĹ™ihlašovacĂ­m odkazem." + complete_email: "Pokud nÄ›jakĂ˝ účet odpovĂ­dá emailu %{email}, obdržíte záhy email s pĹ™ihlašovacĂ­m odkazem." + complete_username_found: "Byl nalezen účet s uĹľivatelskĂ˝m jmĂ©nem %{username}. Za chvilku obdržíte email s pĹ™ihlašovacĂ­m odkazem." + complete_email_found: "Byl nalezen účet odpovĂ­dajĂ­cĂ­ emailu %{email}. Za chvilku obdržíte email s pĹ™ihlašovacĂ­m odkazem." + complete_username_not_found: "Nebyl nalezen účet s uĹľivatelskĂ˝m jmĂ©nem %{username}" + complete_email_not_found: "Nebyl nalezen účet s emailem %{email}" login: title: "PĹ™ihlásit se" username: "UĹľivatel" password: "Heslo" + second_factor_title: "VyĹľadováno dvoufázovĂ© pĹ™ihlašovánĂ­" + second_factor_label: "KĂłd" email_placeholder: "emailová adresa nebo uĹľivatelskĂ© jmĂ©no" caps_lock_warning: "zapnutĂ˝ Caps Lock" error: "Neznámá chyba" rate_limit: "PoÄŤkejte pĹ™ed dalším pokusem se pĹ™ihlásit." + blank_username: "Zadejte vaši emailovou adresu a uĹľivatelskĂ© jmĂ©no." blank_username_or_password: "VyplĹte prosĂ­m email nebo uĹľivatelskĂ© jmĂ©no, a heslo." reset_password: 'Resetovat heslo' logging_in: "PĹ™ihlašuji..." or: "Nebo" authenticating: "Autorizuji..." + awaiting_activation: "Váš účet nynĂ­ ÄŤeká na aktivaci, pouĹľijte odkaz pro zapomenutĂ© heslo, jestli chcete, abychom vám zaslali další aktivaÄŤnĂ­ email." awaiting_approval: "Váš účet zatĂ­m nebyl schválen ÄŤlenem redakce. AĹľ se tak stane, budeme vás informovat emailem." requires_invite: "PromiĹte, toto fĂłrum je pouze pro zvanĂ©." not_activated: "JeštÄ› se nemĹŻĹľete pĹ™ihlásit. Zaslali jsme vám aktivaÄŤnĂ­ email v {{sentTo}}. ProsĂ­m následujte instrukce v tomto emailu, abychom mohli váš účet aktivovat." @@ -962,11 +1060,13 @@ cs: resend_activation_email: "KliknÄ›te sem pro zaslánĂ­ aktivaÄŤnĂ­ho emailu." resend_title: "Znovu odeslat aktivaÄŤnĂ­ email" change_email: "ZmÄ›nit emailovou adresu" + provide_new_email: "Zadejte novou adresu na kterou chcete zaslat potvrzovacĂ­ email." submit_new_email: "Aktualizovat emailovou adresu" sent_activation_email_again: "Zaslali jsme vám další aktivaÄŤnĂ­ email na {{currentEmail}}. MĹŻĹľe trvat nÄ›kolik minut, neĹľ vám dorazĂ­. Zkontrolujte takĂ© vaši sloĹľku s nevyžádanou pošlou." to_continue: "PĹ™ihlaš se, prosĂ­m" preferences: "Pro to, aby jsi mohl mÄ›nit svĂ© uĹľivatelskĂ© nastavenĂ­, se musíš pĹ™ihlásit." forgot: "Nevybavuju si podrobnosti svĂ©ho účtu" + not_approved: "Váš účet zatĂ­m nebyl schválen. AĹľ se tak stane, obdržíte oznámenĂ­ emailem a budete se moci pĹ™ihlásit." google: title: "pĹ™es Google" message: "Autentizuji pĹ™es Google (ujistÄ›te se, Ĺľe nemáte zablokovaná popup okna)" @@ -992,6 +1092,8 @@ cs: accept_title: "PozvánĂ­" welcome_to: "VĂ­tejte na %{site_name}!" invited_by: "PozvánĂ­ od:" + social_login_available: "Budete se moci takĂ© pĹ™ihlásit pomocĂ­ libovolnĂ© sociálnĂ­ sĂ­tÄ› používajĂ­cĂ­ tento email." + your_email: "Emailová adresa vašeho účtu je %{email}." accept_invite: "PĹ™ijmout pozvánĂ­" success: "Váš účet byl vytvoĹ™en a nynĂ­ jste pĹ™ihlášeni." name_label: "JmĂ©no" @@ -1005,6 +1107,7 @@ cs: twitter: "Twitter" emoji_one: "Emoji One" win10: "Win10" + google_classic: "Google Classic" facebook_messenger: "Facebook Messenger" category_page_style: categories_only: "Pouze kategorie" @@ -1015,11 +1118,28 @@ cs: ctrl: 'Ctrl' alt: 'Alt' select_kit: + default_header_text: Vybrat… + no_content: Nebyly nalezeny žádnĂ© vĂ˝sledky filter_placeholder: Hledat... + create: "VytvoĹ™it: '{{content}}'" + max_content_reached: "MĹŻĹľete vybrat pouze {{count}} poloĹľek." emoji_picker: + filter_placeholder: Hledat emoji + people: LidĂ© nature: Příroda + food: JĂ­dlo + activity: ÄŚinnosti travel: CestovánĂ­ + objects: Objekty + celebration: SlavenĂ­ + custom: VlastnĂ­ emoji recent: Nedávno pouĹľitĂ© + default_tone: Bez barvy pleti + light_tone: SvÄ›tlá pleĹĄ + medium_light_tone: KrĂ©movÄ› svÄ›tlá pleĹĄ + medium_tone: MĂ­rnÄ› hnÄ›dá pleĹĄ + medium_dark_tone: HnÄ›dá pleĹĄ + dark_tone: ÄŚerná pleĹĄ composer: emoji: "SmajlĂ­ky :)" more_emoji: "vĂ­ce..." @@ -1036,10 +1156,15 @@ cs: saved_local_draft_tip: "uloĹľeno lokálnÄ›" similar_topics: "Podobná tĂ©mata" drafts_offline: "koncepty offline" + group_mentioned_limit: "VarovánĂ­! ZmĂ­nili jste {{group}}, ale protoĹľe má tato skupina vĂ­ce ÄŤlenĹŻ neĹľ je administrátorem nastavenĂ˝ limit maximálnĂ­ho poÄŤtu {{max}} uĹľivatelĹŻ, kteří mohou bĂ˝t informováni, nebude informován nikdo." group_mentioned: one: "ZmĂ­nÄ›nĂ­m {{group}}, upozorníš 1 ÄŤlovÄ›ka – jste si jistĂ˝/á?" few: "ZmĂ­nÄ›nĂ­m {{group}}, upozorníš {{count}} lidi – jste si jistĂ˝/á?" other: "ZmĂ­nÄ›nĂ­m {{group}}, upozorníš {{count}} lidĂ­ – jste si jistĂ˝/á?" + cannot_see_mention: + category: "ZmĂ­nili jste uĹľivatele {{username}}, ale oznámenĂ­ se mu nepošle, protoĹľe nemá přístup do tĂ©to kategorie. Budete jej muset pĹ™idat do skupiny, která má přístup do tĂ©to kategorie." + private: "ZmĂ­nili jste uĹľivatele {{username}}, ale oznámenĂ­ se mu nepošle, protoĹľe tuto osobnĂ­ zprávu nevidĂ­. MusĂ­te ho pozvat do tĂ©to osobnĂ­ konverzace." + duplicate_link: "Zdá se, Ĺľe odkaz na {{domain}} uĹľ uvedl v tomto tĂ©matu @ {{username}} v odpovÄ›di {{ago}} - opravdu chcete odkaz poslat znovu?" error: title_missing: "Název musĂ­ bĂ˝t vyplnÄ›n" title_too_short: "Název musĂ­ bĂ˝t dlouhĂ˝ alespoĹ {{min}} znakĹŻ" @@ -1063,6 +1188,7 @@ cs: edit_reason_placeholder: "proÄŤ byla nutná Ăşprava?" show_edit_reason: "(pĹ™idat dĹŻvod Ăşpravy)" reply_placeholder: "Pište sem. MĹŻĹľete použít Markdown, BBCode nebo HTML. Obrázky nahrajte pĹ™etáhnutĂ­m nebo vloĹľenĂ­m ze schránky." + reply_placeholder_no_images: "Pište sem. MĹŻĹľete použít Markdown, BBCode nebo HTML." view_new_post: "Zobrazit váš novĂ˝ příspÄ›vek." saving: "Ukládám" saved: "UloĹľeno!" @@ -1093,6 +1219,8 @@ cs: ulist_title: "OdrážkovĂ˝ seznam" list_item: "PoloĹľka seznam" help: "NápovÄ›da pro Markdown" + collapse: "minimalizovat panel editoru" + abandon: "zavřít editor a zahodit koncept" modal_ok: "OK" modal_cancel: "Zrušit" cant_send_pm: "BohuĹľel, nemĹŻĹľete poslat zprávu uĹľivateli %{username}." @@ -1100,12 +1228,64 @@ cs: title: "ZapomnÄ›l/a jste pĹ™idat pĹ™ijemce?" body: "NynĂ­ tuto zprávu posĂ­láte pouze sám/sama sobÄ›!" admin_options_title: "VolitelnĂ© redakÄŤnĂ­ nastavenĂ­ tĂ©matu" + composer_actions: + reply_to_post: + label: OdpovÄ›dÄ›t na příspÄ›vek %{postNumber} od %{postUsername} + desc: OdpovÄ›dÄ›t na konkrĂ©tnĂ­ příspÄ›vek + reply_as_new_topic: + label: OdpovÄ›dÄ›t v propojenĂ©m tĂ©matu + desc: VytvoĹ™it novĂ© tĂ©ma propojenĂ© s tĂ­mto tĂ©matem + reply_as_private_message: + label: Nová zpráva + desc: VytvoĹ™it novou osobnĂ­ zprávu + reply_to_topic: + label: OdpovÄ›dÄ›t na tĂ©ma + desc: OdpovÄ›dÄ›t na tĂ©ma, ale ne na konkrĂ©tnĂ­ příspÄ›vek + toggle_whisper: + label: PĹ™epnout šeptánĂ­ + desc: Ĺ eptánĂ­ vidĂ­ pouze redaktoĹ™i + create_topic: + label: "NovĂ© tĂ©ma" notifications: + tooltip: + regular: + one: "1 nepĹ™eÄŤtene upozornÄ›nĂ­" + few: "{{count}} nepĹ™eÄŤtená upozornÄ›nĂ­" + other: "{{count}} nepĹ™eÄŤtenĂ˝ch upozornÄ›nĂ­" + message: + one: "1 nepĹ™eÄŤtená zpráva" + few: "{{count}} nepĹ™eÄŤtenĂ© zprávy" + other: "{{count}} nepĹ™eÄŤtenĂ˝ch zpráv" title: "oznámenĂ­ o zmĂ­nkách pomocĂ­ @name, odpovÄ›di na vaše příspÄ›vky a tĂ©mata, zprávy, atd." none: "Notifikace nebylo moĹľnĂ© naÄŤĂ­st." empty: "ŽádnĂ© upozornÄ›nĂ­ nenalezeny." more: "zobrazit starší oznámenĂ­" total_flagged: "celkem nahlášeno příspÄ›vkĹŻ" + mentioned: "{{username}} {{description}}" + group_mentioned: "{{username}} {{description}}" + quoted: "{{username}} {{description}}" + replied: "{{username}} {{description}}" + posted: "{{username}} {{description}}" + edited: "{{username}} {{description}}" + liked: "{{username}} {{description}}" + liked_2: "{{username}}, {{username2}} {{description}}" + liked_many: + one: "{{username}}, {{username2}} a 1 další {{description}}" + few: "{{username}}, {{username2}} a {{count}} další {{description}}" + other: "{{username}}, {{username2}} a {{count}} dalších {{description}}" + private_message: "{{username}} {{description}}" + invited_to_private_message: "

{{username}} {{description}}" + invited_to_topic: "{{username}} {{description}}" + invitee_accepted: "{{username}} pĹ™ijal vaše pozvánĂ­" + moved_post: "{{username}} pĹ™esunul {{description}}" + linked: "{{username}} {{description}}" + granted_badge: "ZĂ­skáno '{{description}}'" + topic_reminder: "{{username}} {{description}}" + watching_first_post: "NovĂ© tĂ©ma {{description}}" + group_message_summary: + one: "{{count}} zpráva ve schránce skupiny {{group_name}}" + few: "{{count}} zprávy ve schránce skupiny {{group_name}}" + other: "{{count}} zpráv ve schránce skupiny {{group_name}}" alt: mentioned: "ZmĂ­nÄ›no" quoted: "Citováno" @@ -1113,6 +1293,8 @@ cs: posted: "PříspÄ›vek od" edited: "Editovat váš příspÄ›vek od" liked: "LĂ­bil se tvĹŻj příspÄ›vek" + private_message: "Soukromá zpráva od" + invited_to_private_message: "Pozván k soukromĂ© zprávÄ› od" invited_to_topic: "Pozván k tĂ©matu od" invitee_accepted: "Pozvánka pĹ™ijata od" moved_post: "TvĹŻj příspÄ›vek pĹ™esunul" @@ -1126,6 +1308,7 @@ cs: quoted: '{{username}} vás citoval v "{{topic}}" - {{site_title}}' replied: '{{username}} vám odpovÄ›dÄ›l v "{{topic}}" - {{site_title}}' posted: '{{username}} pĹ™ispÄ›l do "{{topic}}" - {{site_title}}' + private_message: '{{username}} vám poslal soukromou zprávu v "{{topic}}" - {{site_title}}' linked: '{{username}} odkázal na vás příspÄ›vek v "{{topic}}" - {{site_title}}' upload_selector: title: "VloĹľit obrázek" @@ -1152,14 +1335,23 @@ cs: select_all: "Vybrat vše" clear_all: "Vymazat vše" too_short: "HledanĂ˝ vĂ˝raz je příliš krátkĂ˝." + result_count: + one: "1 vĂ˝sledek pro \"{{term}}\"" + few: "{{count}}{{plus}} vĂ˝sledky pro \"{{term}}\"" + other: "{{count}}{{plus}} vĂ˝sledkĹŻ pro \"{{term}}\"" title: "vyhledávat tĂ©mata, příspÄ›vky, uĹľivatele nebo kategorie" no_results: "Nenalezeny žádnĂ© vĂ˝sledky." no_more_results: "Nenalezeny žádnĂ© další vĂ˝sledky." searching: "Hledám ..." post_format: "#{{post_number}} od {{username}}" + results_page: "VĂ˝sledky hledánĂ­ pro '{{term}}'" + more_results: "Existuje vĂ­ce vĂ˝sledkĹŻ. ProsĂ­m zpĹ™esnÄ›te kritĂ©ria vyhledávánĂ­." cant_find: "NemĹŻĹľete najĂ­t, co hledáte?" start_new_topic: "MoĹľná vytvoĹ™te novĂ© tĂ©ma?" + or_search_google: "Nebo zkuste vyhledávat pomocĂ­ Google:" + search_google: "Zkuste vyhledávat pomocĂ­ Google:" search_google_button: "Google" + search_google_title: "Hledat na tomto webu" context: user: "Vyhledat příspÄ›vky od @{{username}}" category: "Hledat v kategorii #{{category}}" @@ -1184,12 +1376,14 @@ cs: watching: Sleduji tracking: Sleduji. private: v mĂ˝ch zprávách + bookmarks: Mám v záloĹľkách first: jsou prvnĂ­ příspÄ›vek v tĂ©matu pinned: jsou pĹ™ipnuty unpinned: nejsou pĹ™ipnuty seen: která jsem ÄŤetl unseen: jsem neÄŤetl wiki: jsou wiki + images: vÄŤetnÄ› obrázkĹŻ all_tags: Všechny uvedenĂ© štĂ­tky statuses: label: Kde příspÄ›vky @@ -1216,6 +1410,7 @@ cs: select_all: "Vybrat vše" clear_all: "Zrušit vše" unlist_topics: "Odebrat tĂ©mata ze seznamu" + relist_topics: "ZnovunaÄŤĂ­st seznam tĂ©mat" reset_read: "reset pĹ™eÄŤtenĂ©ho" delete: "Smazat tĂ©mata" dismiss: "OdbĂ˝t" @@ -1238,6 +1433,7 @@ cs: change_tags: "ZamÄ›nit štĂ­tky" append_tags: "PĹ™idat štĂ­tky" choose_new_tags: "Zvolte novĂ© tagy pro tĂ©mata:" + choose_append_tags: "Zvolte novĂ© tagy, kterĂ© se majĂ­ pĹ™iĹ™adit k tÄ›mto tĂ©matĹŻm:" changed_tags: "Tagy tĂ©mat byly zmÄ›nÄ›ny." none: unread: "Nemáte žádná nepĹ™eÄŤtená tĂ©mata." @@ -1332,9 +1528,37 @@ cs: 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" + topic_status_update: + num_of_hours: "PoÄŤet hodin:" + publish_to: "Publikovat v:" + when: "KdyĹľ:" auto_update_input: + none: "Vyberte ÄŤasovĂ© obdobĂ­" later_today: "PozdÄ›ji bÄ›hem dnešnĂ­ho dne" + tomorrow: "ZĂ­tra" later_this_week: "PozdÄ›ji bÄ›hem tohoto tĂ˝dne" + this_weekend: "Tento tĂ˝den" + next_week: "Další tĂ˝den" + two_weeks: "Dva tĂ˝dny" + next_month: "Další mÄ›sĂ­c" + three_months: "TĹ™i mÄ›sĂ­ce" + six_months: "Ĺ est mÄ›sĂ­cĹŻ" + one_year: "Jeden rok" + forever: "NavĹľdy" + pick_date_and_time: "Vyberte datum a ÄŤas" + set_based_on_last_post: "Zavřít na základÄ› poslednĂ­ho příspÄ›vku" + publish_to_category: + title: "Naplánovat publikovánĂ­" + temp_open: + title: "Otevřít doÄŤasnÄ›" + auto_reopen: + title: "Automaticky otevřít tĂ©ma" + temp_close: + title: "Zavřít doÄŤasnÄ›" + auto_close: + title: "Automaticky zavřít tĂ©ma" + label: "Automaticky zavřít tĂ©ma za:" + error: "ProsĂ­m zadejte platnou hodnotu." auto_close_title: 'NastavenĂ­ automatickĂ©ho zavĹ™enĂ­' auto_close_immediate: one: "PoslednĂ­ příspÄ›vek v tĂ©metu je jiĹľ 1 hodinu starĂ˝, takĹľe toto tĂ©ma bude okamĹľitÄ› uzavĹ™eno." @@ -1549,9 +1773,9 @@ cs: select_all: vybrat vše deselect_all: zrušit vĂ˝bÄ›r description: - one: Máte oznaÄŤen 1 příspÄ›vek. - few: Máte oznaÄŤeny {{count}} příspÄ›vky. - other: Máte oznaÄŤeno {{count}} příspÄ›vkĹŻ. + one: "Máte oznaÄŤen 1 příspÄ›vek." + few: "Máte oznaÄŤeny {{count}} příspÄ›vky." + other: "Máte oznaÄŤeno {{count}} příspÄ›vkĹŻ." post: quote_reply: "Cituj" edit_reason: "DĹŻvod: " @@ -1805,7 +2029,6 @@ cs: email_in_allow_strangers: "PĹ™ijĂ­mat emaily i od neregistrovanĂ˝ch uĹľivatelĹŻ" email_in_disabled: "PĹ™idávánĂ­ novĂ˝ch tĂ©mat pĹ™ed email je zakázáno v NastavenĂ­ fĂłra. K povolenĂ­ novĂ˝ch tĂ©mat pĹ™es email," email_in_disabled_click: 'povolit nastavenĂ­ "email in"' - suppress_from_homepage: "PotlaÄŤ tuto kategorii na domovskĂ© stránce." allow_badges_label: "Povolit používánĂ­ odznakĹŻ v tĂ©to kategorii" edit_permissions: "Upravit oprávnÄ›nĂ­" add_permission: "PĹ™idat oprávnÄ›nĂ­" @@ -2299,7 +2522,6 @@ cs: edit: "Upravit skupiny" refresh: "Obnovit" new: "Nová" - selector_placeholder: "zadejte uĹľivatelskĂ© jmĂ©no" about: "Zde mĹŻĹľete upravit názvy skupin a ÄŤlenstvĂ­" group_members: "ÄŚlenovĂ© skupiny" delete: "Smazat" @@ -2473,8 +2695,18 @@ cs: mobile: "MobilnĂ­ verze" preview: "Náhled" color_scheme: "BarevnĂ© schĂ©ma" + add: "PĹ™idat" + scss: + text: "CSS" + header: + text: "HlaviÄŤka" + footer: + text: "PatiÄŤka" colors: + select_base: + title: "Vyberte základnĂ­ barvu" title: "Barvy" + edit: "Upravit barevná schĂ©mata" long_title: "Barevná schĂ©mata" new_name: "NovĂ© barevnĂ© schĂ©ma" copy_name_prefix: "Kopie" @@ -2573,6 +2805,14 @@ cs: type_placeholder: "souhrn, registrace..." reply_key_placeholder: "klĂ­ÄŤ pro odpověď" skipped_reason_placeholder: "dĹŻvod" + moderation_history: + no_results: "NenĂ­ k dispozici žádná historie akcĂ­ moderátorĹŻ." + actions: + delete_user: "smazánĂ­ uĹľivatele" + suspend_user: "zakázánĂ­ uĹľivatele" + silence_user: "ztišenĂ­ uĹľivatele" + delete_post: "smazánĂ­ příspÄ›ku" + delete_topic: "smazánĂ­ tĂ©matu" logs: title: "Logy a filtry" action: "Akce" @@ -2612,6 +2852,8 @@ cs: change_trust_level: "z. dĹŻvÄ›ryhodnosti" change_username: "ZmÄ›nit uĹľivatelskĂ© jmĂ©no" change_site_setting: "zmÄ›na nastavenĂ­" + change_theme: "zmÄ›na vzhledu" + delete_theme: "smazánĂ­ vzhledu" change_site_text: "zmÄ›nit text webu" suspend_user: "suspendovat uĹľivatele" unsuspend_user: "zrušit suspendovánĂ­" @@ -2625,6 +2867,7 @@ cs: change_category_settings: "zmÄ›nit nastavenĂ­ kategorie" delete_category: "smazat kategorii" create_category: "vytvoĹ™it kategorii" + silence_user: "ztišenĂ­ uĹľivatele" grant_admin: "udÄ›lit administrátorská práva" revoke_admin: "odebrat administrátorská práva" grant_moderation: "udÄ›lit moderátorská práva" @@ -2637,6 +2880,9 @@ cs: deactivate_user: "deaktivovat uĹľivatele" backup_download: "stáhnout zálohu" backup_destroy: "smazat zálohu" + post_locked: "uzamÄŤenĂ­ příspÄ›vku" + post_unlocked: "odemÄŤenĂ­ příspÄ›vku" + check_personal_message: "zkontrolovat soukromou zprávu" screened_emails: title: "FiltrovanĂ© emaily" description: "PĹ™i registraci novĂ©ho účtu budou konzultovány následujĂ­ci adresy. PĹ™i shodÄ› bude registrace zablokována, nebo bude provedena jiná akce." @@ -2661,8 +2907,23 @@ cs: ip_address: "IP adresa" add: "PĹ™idat" filter: "Vyhledat" + search_logs: + unique: "UnikátnĂ­" + types: + all_search_types: "Vyhledávat všechny typy" + header: "HlaviÄŤka" + full_page: "Celá stránka" logster: title: "ChybovĂ© záznamy" + watched_words: + title: "Sledovaná slova" + search: "hledat" + clear_filter: "Zrušit" + show_words: "zobrazit slova" + word_count: + one: "1 slovo" + few: "%{count} slova" + other: "%{count} slov" impersonate: title: "PĹ™ihlásit se jako" not_found: "Tento uĹľivatel nebyl nalezen." @@ -2886,10 +3147,7 @@ cs: recommended: "DoporuÄŤujeme uzpĹŻsobit následujĂ­cĂ­ text tak, aby vám vyhovoval:" show_overriden: 'Zobrazit pouze zmÄ›nÄ›ná nastavenĂ­' site_settings: - show_overriden: 'Zobrazit pouze zmÄ›nÄ›ná nastavenĂ­' title: 'NastavenĂ­' - reset: 'obnovit vĂ˝chozĂ­' - none: 'žádnĂ©' no_results: "Nenalezeny žádnĂ© vĂ˝sledky." clear_filter: "Zrušit" add_url: "pĹ™idat URL" @@ -3009,6 +3267,7 @@ cs: embed_truncate: "Useknout zabudovanĂ© příspÄ›vky" embed_classname_whitelist: "Povolená jmĂ©na CSS tříd" feed_polling_enabled: "Importovat příspÄ›vky pomocĂ­ RSS/ATOM" + feed_polling_url: "URL adresa RSS/ATOM kanálu pro procházenĂ­" save: "UloĹľit nastavenĂ­ zabudovánĂ­" permalink: title: "TrvalĂ© odkazy" @@ -3034,6 +3293,10 @@ cs: upload: "Nahrát" uploading: "Nahrává se..." quit: "MoĹľná pozdÄ›ji" + staff_count: + one: "Jste jedinĂ˝m ÄŤlenem v redakci vaší komunity." + few: "Vaše komunita má %{count} ÄŤleny v redakci." + other: "Vaše komunita má %{count} ÄŤlenĹŻ v redakci." invites: add_user: "pĹ™idat" none_added: "Nepozvali jste nikoho z redakce. UrÄŤitÄ› chcete pokraÄŤovat?" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index b652bd9ad8..b6c8347ae7 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -379,7 +379,6 @@ da: name: "Navn" user_count: "Antal medlemmer" bio: "Om Grupper" - selector_placeholder: "Tilføj medlemmer" owner: "ejer" index: title: "Grupper" @@ -1537,8 +1536,8 @@ da: select_all: marker alle deselect_all: marker ingen description: - one: Du har valgt 1 indlæg. - other: Du har valgt {{count}} indlæg. + one: "Du har valgt 1 indlæg." + other: "Du har valgt {{count}} indlæg." post: quote_reply: "CitĂ©r" edit_reason: "Reason: " @@ -1771,7 +1770,6 @@ da: email_in_allow_strangers: "Accepter emails fra ikke oprettede brugere" email_in_disabled: "Nye emner via email er deaktiveret i Site opsætning. For at aktivere oprettelse af nye emner via email," email_in_disabled_click: 'aktiver "email ind" indstilligen.' - suppress_from_homepage: "Undertryk denne kategori fra hjemmesiden" show_subcategory_list: "Vis oversigt med subkategorier ovenover emner i denne kategori." num_featured_topics: "Antal emner som skal vises pĂĄ siden med kategorier:" subcategory_num_featured_topics: "Antal af fremhævede emner pĂĄ siden for den overordnede kategori:" @@ -2265,7 +2263,6 @@ da: edit: "RedigĂ©r grupper" refresh: "Genindlæs" new: "Nye" - selector_placeholder: "indtast brugernavn" about: "RedigĂ©r gruppemedlemsskaber og gruppenavne her" group_members: "Gruppe medlemmer" delete: "Slet" @@ -2963,10 +2960,7 @@ da: recommended: "Vi anbefaler ændringer i følgende tekst:" show_overriden: 'Vis kun tilsidesatte' site_settings: - show_overriden: 'Vis kun tilsidesatte' title: 'Indstillinger' - reset: 'nulstil' - none: 'ingen' no_results: "Ingen resultater fundet." clear_filter: "Ryd" add_url: "tilføj URL" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 56e79ecfa8..5c091e73b1 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -166,7 +166,7 @@ de: eu_west_2: "EU (London)" sa_east_1: "SĂĽdamerika (SĂŁo Paulo)" us_east_1: "USA Ost (Nord-Virginia)" - us_east_2: "USA West (Ohio)" + us_east_2: "USA Ost (Ohio)" us_gov_west_1: "AWS GovCloud (USA)" us_west_1: "USA West (Nordkalifornien)" us_west_2: "USA West (Oregon)" @@ -174,6 +174,7 @@ de: not_implemented: "Entschuldige, diese Funktion wurde noch nicht implementiert!" no_value: "Nein" yes_value: "Ja" + submit: "Absenden" generic_error: "Entschuldige, es ist ein Fehler aufgetreten." generic_error_with_reason: "Ein Fehler ist aufgetreten: %{error}" sign_up: "Registrieren" @@ -346,7 +347,7 @@ de: remove_user_as_group_owner: "EigentĂĽmerrechte entziehen" groups: logs: - title: "Protokolle" + title: "Logs" when: "Wann" action: "Aktion" acting_user: "Benutzer der Aktion" @@ -388,7 +389,7 @@ de: name: "Name" user_count: "Anzahl der Mitglieder" bio: "Ăśber die Gruppe" - selector_placeholder: "Mitglieder hinzufĂĽgen" + selector_placeholder: "Benutzernamen eingeben" owner: "EigentĂĽmer" index: title: "Gruppen" @@ -539,6 +540,7 @@ de: disable_jump_reply: "Springe nicht zu meinem Beitrag, nachdem ich geantwortet habe" dynamic_favicon: "Zeige die Anzahl der neuen und geänderten Themen im Browser-Symbol an" theme_default_on_all_devices: "Ăśbernehme dieses Theme fĂĽr alle meine Geräte." + allow_private_messages: "Anderen Benutzern erlauben, mir persönliche Nachrichten zu schicken" external_links_in_new_tab: "Ă–ffne alle externen Links in einem neuen Tab" enable_quoting: "Aktiviere Zitatantwort mit dem hervorgehobenen Text" change: "ändern" @@ -634,6 +636,15 @@ de: set_password: "Passwort ändern" choose_new: "Wähle ein neues Passwort" choose: "Wähle ein Passwort" + second_factor: + title: "Zwei-Faktor Authentifizierung" + disable: "Zwei-Faktor-Authentifizierung deaktivieren" + enabled_status: "Status: aktiv" + disabled_status: "Status: inaktiv" + confirm_password_description: "Bestätige dein Passwort, um mit der Aktivierung der Zwei-Faktor Authentifizierung fortzufahren" + disable_description: "Gib den Authentifizierungs-SchlĂĽssel von deiner App ein" + show_key_description: "Oder gib den geheimen SchlĂĽssel manuell ein." + info_prompt: "Was ist Zwei-Faktor Authentifizierung?" change_about: title: "„Über mich“ ändern" error: "Beim Ă„ndern dieses Wertes ist ein Fehler aufgetreten." @@ -1162,6 +1173,9 @@ de: title: "Hast du vergessen Empfänger hinzuzufĂĽgen?" body: "Im Augenblick wird diese Nachricht nur an dich selbst gesendet!" admin_options_title: "Optionale Team-Einstellungen fĂĽr dieses Thema" + composer_actions: + reply_as_private_message: + label: Neue Nachricht notifications: tooltip: regular: @@ -1192,7 +1206,7 @@ de: invitee_accepted: "{{username}} hat deine Einladung angenommen" moved_post: "{{username}} hat {{description}} verschoben" linked: "{{username}} {{description}}" - granted_badge: "Hat '{{description}}' verliehen bekommen" + granted_badge: "Du hast '{{description}}' verliehen bekommen" topic_reminder: "{{username}} {{description}}" watching_first_post: "New Topic {{description}}" group_message_summary: @@ -1554,6 +1568,7 @@ de: visible: "Sichtbar machen" reset_read: "„Gelesen“ zurĂĽcksetzen" make_public: "Umwandeln in öffentliches Thema" + make_private: "in Nachricht umwandeln" feature: pin: "Thema anheften" unpin: "Thema loslösen" @@ -1691,8 +1706,8 @@ de: select_all: alle auswählen deselect_all: keine auswählen description: - one: Du hast 1 Beitrag ausgewählt. - other: Du hast {{count}} Beiträge ausgewählt. + one: "Du hast 1 Beitrag ausgewählt." + other: "Du hast {{count}} Beiträge ausgewählt." post: quote_reply: "Zitat" edit: " {{link}} {{replyAvatar}} {{username}}" @@ -1953,7 +1968,6 @@ de: email_in_disabled: "Das Erstellen von neuen Themen per E-Mail ist in den Website-Einstellungen deaktiviert. Um das Erstellen von neuen Themen per E-Mail zu erlauben," email_in_disabled_click: 'aktiviere die Einstellung „email in“.' mailinglist_mirror: "Kategorie spiegelt eine Mailingliste" - suppress_from_homepage: "Löse diese Kategorie von der Startseite." show_subcategory_list: "Zeige Liste von Unterkategorien oberhalb von Themen in dieser Kategorie" num_featured_topics: "Anzahl der Themen, die auf der Kategorien-Seite angezeigt werden" subcategory_num_featured_topics: "Anzahl beworbener Themen, die auf der Seite der ĂĽbergeordneten Kategorie angezeigt werden:" @@ -2481,7 +2495,6 @@ de: edit: "Gruppen bearbeiten" refresh: "Aktualisieren" new: "Neu" - selector_placeholder: "Benutzername eingeben" about: "Hier kannst du Gruppenzugehörigkeiten und Gruppennamen bearbeiten." group_members: "Gruppenmitglieder" delete: "Löschen" @@ -3269,10 +3282,7 @@ de: recommended: "Wir empfehlen, dass du den folgenden Text an deine BedĂĽrfnisse anpasst:" show_overriden: 'Nur geänderte Texte anzeigen' site_settings: - show_overriden: 'Nur geänderte Einstellungen anzeigen' title: 'Einstellungen' - reset: 'zurĂĽcksetzen' - none: 'keine' no_results: "Keine Ergebnisse gefunden." clear_filter: "Filter zurĂĽcksetzen" add_url: "URL hinzufĂĽgen" diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml index b20362728f..3190e6dfff 100644 --- a/config/locales/client.el.yml +++ b/config/locales/client.el.yml @@ -386,7 +386,6 @@ el: name: "Όνομα" user_count: "ΑĎιθμός Μελών" bio: "Σχετικά με την Ομάδα" - selector_placeholder: "ΠĎÎżĎθήκη μελών" owner: "ιδιοκτήτης" index: title: "Ομάδες" @@ -1650,8 +1649,8 @@ el: select_all: επιλογή όλων deselect_all: απεπιλογή όλων description: - one: Îχεις επιλέξει 1 ανάĎτηĎη. - other: Îχεις επιλέξει {{count}} αναĎτήĎεις. + one: "Îχεις επιλέξει 1 ανάĎτηĎη." + other: "Îχεις επιλέξει {{count}} αναĎτήĎεις." post: quote_reply: "ΠαĎάθεĎη" edit_reason: "Αιτία:" @@ -1897,7 +1896,6 @@ el: email_in_allow_strangers: "Αποδοχή emails από ανώνυμους χĎήĎτες χωĎÎŻĎ‚ λογαĎιαĎÎĽĎŚ" email_in_disabled: "Η δημιουĎγία νέων νημάτων μέĎω email είναι απενεĎγοποιημένη Ďτις ĎυθμίĎεις ÎąĎτοĎελίδας. Για να επιτĎαπεί η δημιουĎγία νέων νημάτων μέĎω email," email_in_disabled_click: 'ενεĎγοποίηĎε τη ĎύθμιĎη «ειĎεĎχόμενα email».' - suppress_from_homepage: "ΑποĎιώπηĎη αυτής της κατηγοĎίας από την αĎχική Ďελίδα." show_subcategory_list: "ΠĎοβολή λίĎτας υποκατηγοĎιών πάνω απο τα νήματα αυτής της κατηγοĎίας " num_featured_topics: "ΑĎιθμός νημάτων που εμφανίζονται Ďτην Ďελίδα κατηγοĎιών:" subcategory_num_featured_topics: "ΑĎιθμός Ď€Ďοτεινόμενων νημάτων Ďτην Ďελίδα της γονικής κατηγοĎίας:" @@ -2426,7 +2424,6 @@ el: edit: "ΕπεξεĎγαĎία Ομάδων" refresh: "ΑνανέωĎη " new: "Νέα" - selector_placeholder: "ειĎαγωγή ονόματος χĎήĎτη" about: "ΕπεξεĎγαĎία των μελών και των ονομάτων της ομάδας εδώ" group_members: "Μέλη ομάδας" delete: "ΔιαγĎαφή" @@ -3197,10 +3194,7 @@ el: recommended: "Σου Ď€Ďοτείνουμε να Ď€ĎÎżĎαĎÎĽĎŚĎεις το ακόλουθο κείμενο Ďτις ανάγκες Ďου:" show_overriden: 'Δείξε μόνο αυτά που άλλαξαν' site_settings: - show_overriden: 'Δείξε μόνο αυτά που άλλαξαν' title: 'ΡυθμίĎεις' - reset: 'επαναφοĎά' - none: 'κανένα' no_results: "Δεν βĎέθηκαν αποτελέĎματα." clear_filter: "ΚαθάĎÎąĎμα φίλτĎου" add_url: "Ď€ĎÎżĎθήκη URL" diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 383e300706..c2c3475806 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -207,6 +207,7 @@ en: not_implemented: "That feature hasn't been implemented yet, sorry!" no_value: "No" yes_value: "Yes" + submit: "Submit" generic_error: "Sorry, an error has occurred." generic_error_with_reason: "An error occurred: %{error}" sign_up: "Sign Up" @@ -446,7 +447,7 @@ en: name: "Name" user_count: "Number of Members" bio: "About Group" - selector_placeholder: "Add members" + selector_placeholder: "enter username" owner: "owner" index: title: "Groups" @@ -707,6 +708,28 @@ en: choose_new: "Choose a new password" choose: "Choose a password" + second_factor: + title: "Two Factor Authentication" + disable: "Disable Two Factor Authentication" + enabled_status: "Status: On" + disabled_status: "Status: Off" + confirm_password_description: "Confirm your password to continue enabling Two Factor Authentication" + enable_description: | + To complete Two Factor Authentication setup, scan the following QR code + in one of the supported apps + (Android and iOS, + Windows Phone) + and submit the generated Two Factor Authentication code. + disable_description: "Enter the authentication code from your app" + show_key_description: "Or enter the secret key manually." + info_prompt: "What is Two Factor Authentication?" + extended_description: | + Two Factor Authentication adds an extra security step to logging in by + requiring a one-time token in addition to your password. These tokens + can be generated on Android and iOS by Google Authenticator + and on Windows Phone by Authenticator. + oauth_enabled_warning: "Note that logins via social methods will be disabled once Two Factor Authentication has been enabled on your account." + change_about: title: "Change About Me" error: "There was an error changing this value." @@ -1085,7 +1108,8 @@ en: button_help: "Help" email_login: - label: "Login With Email" + link_label: "Email me a magic link" + button_label: "with email" complete_username: "If an account matches the username %{username}, you should receive an email with a magic login link shortly." complete_email: "If an account matches %{email}, you should receive an email with a magic login link shortly." complete_username_found: "We found an account that matches the username %{username}, you should receive an email with a magic login link shortly." @@ -1097,10 +1121,14 @@ en: title: "Log In" username: "User" password: "Password" + second_factor_title: "Two Factor Authentication Required" + second_factor_description: "Enter the authentication code from your app." + second_factor_label: "Code" email_placeholder: "email or username" caps_lock_warning: "Caps Lock is on" error: "Unknown error" rate_limit: "Please wait before trying to log in again." + blank_username: "Please enter your email or username." blank_username_or_password: "Please enter your email or username, and password." reset_password: 'Reset Password' logging_in: "Signing In..." @@ -1113,6 +1141,7 @@ en: not_allowed_from_ip_address: "You can't login from that IP address." admin_not_allowed_from_ip_address: "You can't log in as admin from that IP address." resend_activation_email: "Click here to send the activation email again." + omniauth_disallow_totp: "Your account has 2FA enabled. Please login with your password." resend_title: "Resend Activation Email" change_email: "Change Email Address" @@ -1173,6 +1202,7 @@ en: categories_only: "Categories Only" categories_with_featured_topics: "Categories with Featured Topics" categories_and_latest_topics: "Categories and Latest Topics" + categories_and_top_topics: "Categories and Top Topics" shortcut_modifier_key: shift: 'Shift' @@ -1308,16 +1338,16 @@ en: desc: Reply to a specific post reply_as_new_topic: label: Reply as linked topic - desc: Create a new topic + desc: Create a new topic linked to this topic reply_as_private_message: label: New message - desc: Create a private message + desc: Create a new personal message reply_to_topic: label: Reply to topic - desc: Reply to the original post without replying to a specific post + desc: Reply to the topic, not any specific post toggle_whisper: label: Toggle whipser - desc: Whispers will only be visible by staff members + desc: Whispers are only visible to staff members create_topic: label: "New Topic" @@ -1556,6 +1586,9 @@ en: move_to_inbox: title: 'Move to Inbox' help: 'Move message back to Inbox' + edit_message: + help: 'Edit first post of the message' + title: 'Edit Message' list: 'Topics' new: 'new topic' unread: 'unread' @@ -2194,7 +2227,7 @@ en: email_in_disabled: "Posting new topics via email is disabled in the Site Settings. To enable posting new topics via email, " email_in_disabled_click: 'enable the "email in" setting.' mailinglist_mirror: "Category mirrors a mailing list" - suppress_from_homepage: "Suppress this category from the homepage." + suppress_from_latest: "Suppress category from latest topics." show_subcategory_list: "Show subcategory list above topics in this category." num_featured_topics: "Number of topics shown on the categories page:" subcategory_num_featured_topics: "Number of featured topics on parent category's page:" @@ -2779,7 +2812,6 @@ en: edit: "Edit Groups" refresh: "Refresh" new: "New" - selector_placeholder: "enter username" about: "Edit your group membership and names here" group_members: "Group members" delete: "Delete" @@ -3013,6 +3045,7 @@ en: common: "Common" desktop: "Desktop" mobile: "Mobile" + settings: "Settings" preview: "Preview" is_default: "Theme is enabled by default" user_selectable: "Theme can be selected by users" @@ -3041,6 +3074,8 @@ en: updating: "Updating..." up_to_date: "Theme is up-to-date, last checked:" add: "Add" + theme_settings: "Theme Settings" + no_settings: "This theme has no settings." commits_behind: one: "Theme is 1 commit behind!" other: "Theme is {{count}} commits behind!" @@ -3065,6 +3100,9 @@ en: body_tag: text: "" title: "HTML that will be inserted before the tag" + yaml: + text: "YAML" + title: "Define theme settings in YAML format" colors: select_base: title: "Select base color scheme" @@ -3262,6 +3300,7 @@ en: post_locked: "post locked" post_unlocked: "post unlocked" check_personal_message: "check personal message" + disabled_second_factor: "disable Two Factor Authentication" screened_emails: title: "Screened Emails" description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed." @@ -3453,6 +3492,7 @@ en: private_topics_count: Private Topics posts_read_count: Posts Read post_count: Posts Created + second_factor_enabled: Two Factor Authentication Enabled topics_entered: Topics Viewed flags_given_count: Flags Given flags_received_count: Flags Received @@ -3599,11 +3639,12 @@ en: recommended: "We recommend customizing the following text to suit your needs:" show_overriden: 'Only show overridden' - site_settings: + settings: # used by theme and site settings show_overriden: 'Only show overridden' - title: 'Settings' reset: 'reset' none: 'none' + site_settings: + title: 'Settings' no_results: "No results found." clear_filter: "Clear" add_url: "add URL" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index c2cd21dee2..01bae49c7b 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -388,7 +388,6 @@ es: name: "Nombre" user_count: "NĂşmero de miembros" bio: "Acerca del grupo" - selector_placeholder: "Añadir miembros" owner: "propietario" index: title: "Grupos" @@ -1698,8 +1697,8 @@ es: select_all: seleccionar todo deselect_all: deshacer selecciĂłn description: - one: Has seleccionado 1 post. - other: Has seleccionado {{count}} posts. + one: "Has seleccionado 1 post." + other: "Has seleccionado {{count}} posts." post: quote_reply: "Citar" edit: "{{link}} {{replyAvatar}} {{username}} " @@ -1967,7 +1966,6 @@ es: email_in_disabled: "La posibilidad de publicar nuevos temas por email está deshabilitada en los ajustes del sitio. Para habilitar la publicaciĂłn de nuevos temas por email," email_in_disabled_click: 'activa la opciĂłn "email in".' mailinglist_mirror: "La categorĂ­a refleja una lista de correo" - suppress_from_homepage: "Ocultar categorĂ­a de la página de inicio." show_subcategory_list: "Mostrar la lista de subcategorĂ­as arriba de la lista de temas en esta categorĂ­a." num_featured_topics: "NĂşmero de temas a mostrar en la página de categorĂ­as:" subcategory_num_featured_topics: "NĂşmero de temas destacados a mostrar en la página superior de categorĂ­as:" @@ -2504,7 +2502,6 @@ es: edit: "Editar grupos" refresh: "Actualizar" new: "Nuevo" - selector_placeholder: "introduce nombre de usuario" about: "Edita los aquĂ­ los nombres de los grupos y sus miembros" group_members: "Miembros del grupo" delete: "Borrar" @@ -3298,10 +3295,7 @@ es: recommended: "Recomendamos personalizar los siguientes textos para que se ajusten a tus necesidades:" show_overriden: 'SĂłlo mostrar textos editados' site_settings: - show_overriden: 'SĂłlo mostrar lo personalizado' title: 'Ajustes del sitio' - reset: 'restablecer' - none: 'ninguno' no_results: "NingĂşn resultado encontrado" clear_filter: "Limpiar filtro" add_url: "añadir URL" diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml index 523ee1ccd1..ddad1d8003 100644 --- a/config/locales/client.et.yml +++ b/config/locales/client.et.yml @@ -377,7 +377,6 @@ et: name: "Nimi" user_count: "Liikmete arv" bio: "Grupist" - selector_placeholder: "Lisa liikmeid" owner: "omanik" index: title: "Grupid" @@ -1595,8 +1594,8 @@ et: select_all: vali kõik deselect_all: eemalda valik kõigilt description: - one: Oled valinud 1 postituse. - other: Oled valinud {{count}} postitust. + one: "Oled valinud 1 postituse." + other: "Oled valinud {{count}} postitust." post: quote_reply: "Tsitaat" edit_reason: "Põhjus:" @@ -1827,7 +1826,6 @@ et: email_in_allow_strangers: "Aktsepteeri meile kontota anonĂĽĂĽmsetelt kasutajatelt" email_in_disabled: "Uute teemade avamine meili teel on saidi sätetes välja lĂĽlitatud. Avamiseks" email_in_disabled_click: 'aktiveeri säte "sissetulev meil".' - suppress_from_homepage: "Eemalda see foorum avalehelt." show_subcategory_list: "Näita selles foorumis alamfoorumite nimekirja teemadest ĂĽleval pool." all_topics_wiki: "Tee uued teemad vaikimisi wikideks." allow_badges_label: "Luba selles foorumis autasustamist märgistega" @@ -2328,7 +2326,6 @@ et: edit: "Muuda gruppe" refresh: "Värskenda" new: "Uus" - selector_placeholder: "sisesta kasutajanimi" about: "Siin saad muuta oma grupi liikmelisust ja nimesid" group_members: "Grupi liikmed" delete: "Kustuta" @@ -3038,10 +3035,7 @@ et: recommended: "Soovitame järgnevat teksti oma vajadustele vastavalt kohandada:" show_overriden: 'Näita vaid käsitsi muudetuid' site_settings: - show_overriden: 'Näita vaid käsitsi muudetuid' title: 'Sätted' - reset: 'lähtesta' - none: 'mitte ĂĽkski' no_results: "Ei leidnud midagi." clear_filter: "Puhasta" add_url: "lisa URL" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index c0c07f992d..a04b6b0c74 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -356,7 +356,6 @@ fa_IR: name: "نام" user_count: "تعداد اعضا" bio: "درباره گرŮه" - selector_placeholder: "اŮزŮدن عضŮ" owner: "مالک" index: title: "گرŮه‌ها" @@ -1517,7 +1516,7 @@ fa_IR: select_all: انتخاب همه deselect_all: عدم انتخاب همه description: - other: شما {{count}} نŮشته انتخاب کرده اید + other: "شما {{count}} نŮشته انتخاب کرده اید" post: quote_reply: "نقل‌قŮŮ„" edit_reason: "دلیل:" @@ -1728,7 +1727,6 @@ fa_IR: email_in_allow_strangers: "تایید ایمیل ها از کاربران ناشناس بدŮن حساب کاربری" email_in_disabled: "ارسال Ů…ŮضŮعات جدید با ایمیل در تنظیمات سایت غیر Ůعال است. برای Ůعال سازی Ů…ŮضŮعات جدید را با ایمیل ارسال کنید،" email_in_disabled_click: 'Ůعال کردن تنظیمات "email in".' - suppress_from_homepage: "برداشتن این دسته‌بندی از صŮحه اصلی." show_subcategory_list: "تعداد دسته‌بندی‌های Ůرزند که بالای Ů…ŮضŮعات این دسته‌بندی نمایش داده می‌شŮند." num_featured_topics: "تعداد Ů…ŮضŮعاتی که در صŮحه دسته‌بندی نمایش داده می‌شŮند:" subcategory_num_featured_topics: "تعداد Ů…ŮضŮعات برجسته که در دسته‌بندی مادر نمایش داده می‌شŮند:" @@ -2205,7 +2203,6 @@ fa_IR: edit: "Ůیرایش گرŮه‌ها" refresh: "تازه کردن" new: "جدید" - selector_placeholder: "نام کاربری را Ůارد نمایید ." about: "عضŮŰŚŘŞ ٠نام‌های گرŮه را در اینجا Ůیرایش کنید" group_members: "اعضای گرŮه" delete: "حذŮ" @@ -2902,10 +2899,7 @@ fa_IR: recommended: "ما پیشنهاد میکنیم این متن را بر اساس نیاز های Ř®ŮŘŻ Ůیرایش کنید:" show_overriden: 'تنها بازنŮیسی‌شده‌ها را نمایش بده' site_settings: - show_overriden: 'تنها بازنŮیسی‌شده‌ها را نمایش بده' title: 'تنظیمات' - reset: 'بازنشانی' - none: 'هیچ کدام' no_results: "چیزی یاŮŘŞ نشد." clear_filter: "Ůاضح" add_url: "اضاŮه کردن URL" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 606a942356..3e527ccf2f 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -388,7 +388,6 @@ fi: name: "Nimi" user_count: "Jäsenmäärä" bio: "Tietoa ryhmästä" - selector_placeholder: "Lisää jäseniä" owner: "isäntä" index: title: "Ryhmät" @@ -1695,8 +1694,8 @@ fi: select_all: valitse kaikki deselect_all: poista kaikkien valinta description: - one: Olet valinnut yhden viestin. - other: Olet valinnut {{count}} viestiä. + one: "Olet valinnut yhden viestin." + other: "Olet valinnut {{count}} viestiä." post: quote_reply: "Lainaa" edit: " {{link}} {{replyAvatar}} {{username}}" @@ -1959,7 +1958,6 @@ fi: email_in_disabled: "Uusien ketjujen aloittaminen sähköpostitse on otettu pois käytöstä sivuston asetuksissa. Salliaksesi uusien ketjujen luomisen sähköpostilla, " email_in_disabled_click: 'ota käyttöön "email in" asetus.' mailinglist_mirror: "Alue jäljittelee postituslistaa" - suppress_from_homepage: "Vaimenna alue kotisivulta." show_subcategory_list: "Näytä lista tytäralueista ketjujen yläpuolella tällä alueella." num_featured_topics: "Kuinka monta ketjua näytetään Keskustelualueet-sivulla:" subcategory_num_featured_topics: "Kuinka monta ketjua näytetään emoalueen sivulla:" @@ -2497,7 +2495,6 @@ fi: edit: "Muokkaa ryhmiä" refresh: "Lataa uudelleen" new: "Uusi" - selector_placeholder: "syötä käyttäjätunnus" about: "Muokkaa ryhmien jäsenyyksiä ja nimiä täällä" group_members: "Ryhmään kuuluvat" delete: "Poista" @@ -3282,10 +3279,7 @@ fi: recommended: "On suositeltavaa muokata seuraavaa tekstiä tarpeidesi mukaan:" show_overriden: 'Näytä vain muokatut' site_settings: - show_overriden: 'Näytä vain muokatut' title: 'Asetukset' - reset: 'nollaa' - none: 'ei mitään' no_results: "Ei tuloksia." clear_filter: "Tyhjennä" add_url: "Lisää URL" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 7db09ec015..472c77cbcf 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -388,7 +388,6 @@ fr: name: "Nom" user_count: "Nombre de membres" bio: "Ă€ propos du groupe" - selector_placeholder: "Ajouter des membres" owner: "propriĂ©taire" index: title: "Groupes" @@ -1691,8 +1690,8 @@ fr: select_all: tout sĂ©lectionner deselect_all: tout dĂ©sĂ©lectionner description: - one: vous avez sĂ©lectionnĂ© 1 message. - other: Vous avez sĂ©lectionnĂ© {{count}} messages. + one: "vous avez sĂ©lectionnĂ© 1 message." + other: "Vous avez sĂ©lectionnĂ© {{count}} messages." post: quote_reply: "Citer" edit: " {{link}} {{replyAvatar}} {{username}}" @@ -1953,7 +1952,6 @@ fr: email_in_disabled: "La possibilitĂ© de crĂ©er des nouveaux sujets via courriel est dĂ©sactivĂ© dans les Paramètres. Pour l'activer," email_in_disabled_click: 'activer le paramètre « email in ».' mailinglist_mirror: "La catĂ©gorie reflète une liste de diffusion" - suppress_from_homepage: "Retirer cette catĂ©gorie de la page d'accueil" show_subcategory_list: "Afficher la liste des sous-catĂ©gories au dessus des sujets dans cette catĂ©gorie." num_featured_topics: "Nombre de sujets affichĂ©s sur la page des catĂ©gories :" subcategory_num_featured_topics: "Nombre de sujets sĂ©lectionnĂ©s sur la page des catĂ©gories parentes :" @@ -2484,7 +2482,6 @@ fr: edit: "Modifier les groupes" refresh: "Actualiser" new: "Nouveau" - selector_placeholder: "entrer le pseudo" about: "Modifier votre adhĂ©sion et les noms ici" group_members: "Membres du groupe" delete: "Supprimer" @@ -3272,10 +3269,7 @@ fr: recommended: "Nous vous recommandons de personnaliser le texte suivant selon vos besoins :" show_overriden: 'Ne montrer que ce qui a Ă©tĂ© personnalisĂ©' site_settings: - show_overriden: 'Ne montrer que ce qui a Ă©tĂ© changĂ©' title: 'Paramètres' - reset: 'rĂ©tablir' - none: 'rien' no_results: "Aucun rĂ©sultat trouvĂ©." clear_filter: "Effacer" add_url: "ajouter URL" diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml index a70d651147..0ee21ac084 100644 --- a/config/locales/client.gl.yml +++ b/config/locales/client.gl.yml @@ -101,6 +101,7 @@ gl: other: "%{count} anos despois" previous_month: 'Mes anterior' next_month: 'Mes seguinte' + placeholder: data share: topic: 'compartir unha ligazĂłn a este tema' post: 'publicaciĂłn %{postNumber}' @@ -149,6 +150,7 @@ gl: cn_north_1: "China (Beijing)" eu_central_1: "EU (Frankfurt)" eu_west_1: "EU (Irlanda)" + eu_west_2: "EU (Londres)" sa_east_1: "AmĂ©rica do Sur (SĂŁo Paulo)" us_east_1: "EE.UU. Leste (N. Virxinia)" us_gov_west_1: "AWS GovCloud (EE.UU)" @@ -158,6 +160,7 @@ gl: not_implemented: "SentĂ­molo pero esta funcionalidade non se implementou aĂ­nda." no_value: "Non" yes_value: "Si" + submit: "Enviar" generic_error: "SentĂ­molo pero produciuse un erro." generic_error_with_reason: "Produciuse un erro: %{error}" sign_up: "Crear unha conta" @@ -249,6 +252,7 @@ gl: uploading: "Actualizando..." uploading_filename: "Actualizando {{filename}}..." uploaded: "Actualizado!" + pasting: "A pegar..." enable: "Activar" disable: "Desactivar" undo: "Desfacer" @@ -318,7 +322,6 @@ gl: other: "%{count} usuarios" groups: add: "Engadir" - selector_placeholder: "Engadir membros" owner: "propietario" activity: "Actividade" members: "Membros" @@ -1185,8 +1188,8 @@ gl: select_all: seleccionar todo deselect_all: deseleccionar todo description: - one: Seleccionaches unha publicaciĂłn. - other: Seleccionaches {{count}} publicaciĂłns. + one: "Seleccionaches unha publicaciĂłn." + other: "Seleccionaches {{count}} publicaciĂłns." post: edit_reason: "RazĂłn:" post_number: "publicaciĂłn {{number}}" @@ -1389,7 +1392,6 @@ gl: email_in_allow_strangers: "Aceptar correos-e de usuarios anĂłnimos sen contas" email_in_disabled: "A publicaciĂłn de novos temas vĂ­a correo-e está desactivada nos axustes do sitio. Para activala," email_in_disabled_click: 'activar o axuste «email in».' - suppress_from_homepage: "Retirar esta categorĂ­a da páxina de inicio." allow_badges_label: "Permitir adxudicar insignias nesta categorĂ­a" edit_permissions: "Editar permisos" add_permission: "Engadir permisos" @@ -1661,7 +1663,6 @@ gl: edit: "Editar grupos" refresh: "Actualizar" new: "Novo" - selector_placeholder: "escribe o nome do usuario" about: "Edita aquĂ­ a tĂşa pertenza a un grupo e nomes" group_members: "Membros do grupo" delete: "Eliminar" @@ -2177,10 +2178,7 @@ gl: recommended: "Recomendamos personalizar o seguinte texto para axeitalo ás tĂşas necesidades:" show_overriden: 'Amosar sĂł os cambios' site_settings: - show_overriden: 'Amosar sĂł os cambios' title: 'Axustes' - reset: 'restabelecer' - none: 'ningunha' no_results: "Non se atoparon resultados." clear_filter: "Borrar" add_url: "engadir URL" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index c3a9655f08..5525ad8a72 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -374,7 +374,6 @@ he: name: "שם" user_count: "מספר חברים" bio: "×ודות הקבוצה" - selector_placeholder: "הוספת חברים וחברות" owner: "מנהל" index: title: "קבוצות" @@ -1558,8 +1557,8 @@ he: select_all: בחר הכל deselect_all: בחר כלום description: - one: בחרתם ×¤×•×ˇ× ×חד. - other: בחרתם {{count}} פוס×ים. + one: "בחרתם ×¤×•×ˇ× ×חד." + other: "בחרתם {{count}} פוס×ים." post: quote_reply: "צי×ו×" edit_reason: "סיבה: " @@ -1798,7 +1797,6 @@ he: email_in_allow_strangers: "קבלת דו×\"ל ממשתמשים ×נונימיים ×ś×ś× ×—×©×‘×•× ×•×Ş במערכת הפורומים" email_in_disabled: "×פשרות הפרסום של נוש×ים חדשים דרך דו×\"ל נו×רלה בהגדרות ×”×תר. כדי ל×פשר פרסום ב×מצעות משלוח דו×\"ל," email_in_disabled_click: '×פשרו ×ת ×ת ההגדרה "דו×"ל נכנס"' - suppress_from_homepage: "הרחיקו ×§×גוריה זו מהעמוד הר×שי." show_subcategory_list: "הצגת רשימת ×§×גוריות משנה מעל נוש×ים בק×גוריה זו." num_featured_topics: "מספר הנוש×ים המוצגים בדף ×”×§×גוריות:" subcategory_num_featured_topics: "מספר הנוש×ים המומלצים בדף ×§×גוריית ההורה:" @@ -2294,7 +2292,6 @@ he: edit: "ערוך קבוצות" refresh: "רענן" new: "חדש" - selector_placeholder: "הזינו שם משתמש/ת" about: "ערוך ×ת חברות הקבוצה שלך והשמות ×›×ן" group_members: "חברי הקבוצה" delete: "מחק" @@ -3029,10 +3026,7 @@ he: recommended: "×נחנו ממליצים להת×ים ×ת ×”××§×ˇ× ×”×‘× ×›×“×™ להת×ימו לצרכים שלכם:" show_overriden: 'הציגו רק דרוסים' site_settings: - show_overriden: 'הצג רק הגדרות ששונו' title: 'הגדרות' - reset: '×תחול' - none: 'לל×' no_results: "×ś× × ×ž×¦×ו תוצ×ות." clear_filter: "× ×§×”" add_url: "הוספת כתובת URL" diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index 6cd66cd73a..f1f17d17a5 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -320,7 +320,6 @@ id: name: "Nama" user_count: "Jumlah Anggota" bio: "Tentang Kelompok" - selector_placeholder: "Menambahkan anggota" owner: "pemilik" activity: "Aktifitas" members: "Anggota" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index e05a3d5f29..3b1d39cef2 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -386,7 +386,6 @@ it: name: "Nome" user_count: "Numero di Membri" bio: "Informazioni sul Gruppo" - selector_placeholder: "Aggiungi membri" owner: "proprietario" index: title: "Gruppi" @@ -1687,8 +1686,8 @@ it: select_all: seleziona tutto deselect_all: deseleziona tutto description: - one: Hai selezionato 1 messaggio. - other: Hai selezionato {{count}} messaggi. + one: "Hai selezionato 1 messaggio." + other: "Hai selezionato {{count}} messaggi." post: quote_reply: "Cita" edit_reason: "Motivo:" @@ -1934,7 +1933,6 @@ it: email_in_allow_strangers: "Accetta email da utenti anonimi senza alcun account" email_in_disabled: "Le Impostazioni Sito non permettono di creare nuovi argomenti via email. Per abilitare la creazione di argomenti via email," email_in_disabled_click: 'abilita l''impostazione "email entrante".' - suppress_from_homepage: "Elimina questa categoria dalla homepage." show_subcategory_list: "Mostra la lista delle sottocategorie sopra agli argomenti in questa categoria." num_featured_topics: "Numero degli argomenti mostrati nella pagina categorie:" subcategory_num_featured_topics: "Numero degli argomenti in evidenza nella pagina della categoria superiore" @@ -2459,7 +2457,6 @@ it: edit: "Modifica Gruppi" refresh: "Aggiorna" new: "Nuovo" - selector_placeholder: "inserisci nome utente" about: "Modifica qui la tua appartenenza ai gruppi e i loro nomi" group_members: "Membri del gruppo" delete: "Cancella" @@ -3231,10 +3228,7 @@ it: recommended: "Consigliamo di personalizzare il seguente testo per tue adattarlo alle necessitĂ :" show_overriden: 'Mostra solo le opzioni sovrascritte' site_settings: - show_overriden: 'Mostra solo le opzioni sovrascritte' title: 'Impostazioni' - reset: 'reimposta' - none: 'nessuno' no_results: "Nessun risultato trovato." clear_filter: "Pulisci" add_url: "aggiungi URL" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 3733350c17..79abac4382 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -359,7 +359,6 @@ ja: name: "ĺŤĺ‰Ť" user_count: "ăˇăłăăĽć•°" bio: "ă‚°ă«ăĽă—ă«ă¤ă„ă¦" - selector_placeholder: "ăˇăłăăĽă‚’追加" owner: "オăĽăŠăĽ" index: title: "ă‚°ă«ăĽă—" @@ -774,6 +773,7 @@ ja: title: "サăžăŞăĽ" stats: "çµ±č¨" time_read: "読んă ć™‚é–“" + recent_time_read: "現在ă®ăŞăĽă‰ă‚żă‚¤ă " topic_count: other: "ă¤ă®ăă”ăクを作ć" post_count: @@ -1564,7 +1564,7 @@ ja: select_all: ă™ăąă¦é¸ćŠžă™ă‚‹ deselect_all: ă™ăąă¦é¸ćŠžă‚’ĺ¤–ă™ description: - other: {{count}}個ă®ćŠ•ç¨żă‚’é¸ćŠžä¸­ă€‚ + other: "{{count}}個ă®ćŠ•ç¨żă‚’é¸ćŠžä¸­ă€‚" post: quote_reply: "引用" edit_reason: "ç†ç”±: " @@ -2172,7 +2172,6 @@ ja: edit: "ă‚°ă«ăĽă—ă®ç·¨é›†" refresh: "ć›´ć–°" new: "新規" - selector_placeholder: "ă¦ăĽă‚¶ĺŤă‚’入力" about: "ă‚°ă«ăĽă—ăˇăłăăĽă¨ă‚°ă«ăĽă—ĺŤă‚’編集" group_members: "ă‚°ă«ăĽă—ăˇăłăăĽ" delete: "削除" @@ -2760,10 +2759,7 @@ ja: recommended: "ăťă‚Śăžă‚Śă«ă‚わă›ă¦ă€ć–‡ç« ă‚’変ăă‚‹ă“ă¨ă‚’オススăˇă—ăľă™:" show_overriden: '上書ăŤé¨ĺ†ă®ăżčˇ¨ç¤ş' site_settings: - show_overriden: '上書ăŤé¨ĺ†ă®ăżčˇ¨ç¤ş' title: '設定' - reset: 'ă‡ă•ă‚©ă«ăă«ć»ă™' - none: 'ăŞă—' no_results: "何も見ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚" clear_filter: "クăŞă‚˘" add_url: "URL追加" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 557ba11bdc..9fd53aeddb 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -357,7 +357,6 @@ ko: name: "이름" user_count: "멤버 ě" bio: "ěť´ ę·¸ëŁąě— ëŚ€í•ě—¬" - selector_placeholder: "멤버 추가" owner: "소유ěž" index: title: "그룹" @@ -1576,7 +1575,7 @@ ko: select_all: 전체 ě„ íť deselect_all: 전체 ě„ íť í•´ě ś description: - other: {{count}}ę°śěť ę°śě‹śę¸€ěť„ ě„ íťí•셨어요. + other: "{{count}}ę°śěť ę°śě‹śę¸€ěť„ ě„ íťí•셨어요." post: quote_reply: "인용í•기" edit_reason: "Reason: " @@ -1793,7 +1792,6 @@ ko: email_in_allow_strangers: "계정이 없는 익명 유저들ě—게 이메일을 받습ë‹ë‹¤." email_in_disabled: "이메일로 ě 주제 작성í•기 기능이 비활성화ëě–´ ěžěеë‹ë‹¤. 사이트 설정ě—서 '이메일로 ě 주제 작성í•기'를 활성화 해주세요." email_in_disabled_click: '"email in" 활성화' - suppress_from_homepage: "í™íŽěť´ě§€ě—서 ěť´ 카테고리를 ę°ě¶Ąë‹ë‹¤." show_subcategory_list: "í•ěś„ 카테고리 목록을 í† í”˝ěś„ě— í‘śě‹śí•기." num_featured_topics: "ěť´ 카테고리 íŽěť´ě§€ě— 표시ë는 í† í”˝ěť ě:" subcategory_num_featured_topics: "부모 카테고리 íŽěť´ě§€ě— 표시ë는 주요 í† í”˝ěť ě:" @@ -2293,7 +2291,6 @@ ko: edit: "그룹 ěě •" refresh: "ě로고침" new: "ě로운" - selector_placeholder: "아이디를 ěž…ë Ąí•세요" about: "회ě›ęłĽ 이름을 변경" group_members: "그룹 멤버" delete: "ě‚­ě ś" @@ -3027,10 +3024,7 @@ ko: recommended: "ë‹¤ěťŚěť í…ŤěŠ¤íŠ¸ëĄĽ ěš”ęµ¬ě— ë§žę˛Ś 편집í•는 ę˛ěť„ 권장:" show_overriden: 'Override ëś ě„¤ě •ë§Ś 보여주기' site_settings: - show_overriden: 'ěě •ëś ę˛ë§Ś 표시' title: '사이트 설정' - reset: '기본값으로 재설정' - none: '없음' no_results: "No results found." clear_filter: "Clear" add_url: "URL 추가" diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml index df3d7e9591..3192480be9 100644 --- a/config/locales/client.lv.yml +++ b/config/locales/client.lv.yml @@ -400,7 +400,6 @@ lv: name: "VÄrds" user_count: "DalÄ«bnieku skaits" bio: "Par grupu" - selector_placeholder: "Pievienot dalÄ«bniekus" owner: "Ä«pašnieks" index: title: "Grupas" @@ -1619,9 +1618,9 @@ lv: select_all: izvÄ“lÄ“ties visu deselect_all: visiem noņemt izvÄ“li description: - zero: JĹ«s esat izvÄ“lÄ“jies 0 ierakstu. - one: JĹ«s esat izvÄ“lÄ“jies 1 ierakstu. - other: JĹ«s esat izvÄ“lÄ“jies {{count}} ierakstus. + zero: "JĹ«s esat izvÄ“lÄ“jies 0 ierakstu." + one: "JĹ«s esat izvÄ“lÄ“jies 1 ierakstu." + other: "JĹ«s esat izvÄ“lÄ“jies {{count}} ierakstus." post: quote_reply: "CitÄts" edit_reason: "Iemesls:" @@ -1873,7 +1872,6 @@ lv: images: "AttÄ“li" email_in_allow_strangers: "Pieņemt e-pastus no anonÄ«miem lietotÄjiem bez profiliem" email_in_disabled: "Jaunu tÄ“mu ievietošana, izmantojot e-pastu, ir atcelta foruma iestatÄ«jumos. Lai atÄĽautu ievietot jaunas tÄ“mas, izmantojot e-pastu," - suppress_from_homepage: "NerÄdÄ«t šo sadaÄĽu mÄjas lapÄ." show_subcategory_list: "RÄdÄ«t apakšsadaÄĽu sarakstu virs tÄ“mÄm šai sadaÄĽÄ." num_featured_topics: "SadaÄĽu lapÄ parÄdÄ«to tÄ“mu skaits:" all_topics_wiki: "PÄrveidot jaunÄs tÄ“mas par wiki pÄ“c noklusÄ“juma." @@ -2379,7 +2377,6 @@ lv: edit: "Labot grupas" refresh: "PÄrlÄdÄ“t" new: "Jauns" - selector_placeholder: "ievadi lietotÄjvÄrdu" about: "Labot jĹ«su piederÄ«gu grupÄm un to nosaukumus" group_members: "Grupas biedri" delete: "DzÄ“st" @@ -3033,10 +3030,7 @@ lv: recommended: "IesakÄm pielÄgot šo tekstu jĹ«su vajadzÄ«bÄm:" show_overriden: 'RÄdÄ«t tikai aizstÄtos' site_settings: - show_overriden: 'RÄdÄ«t tikai aizstÄtos' title: 'IestatÄ«jumi' - reset: 'atiestatÄ«t' - none: 'nekas' no_results: "Nekas nav atrasts." clear_filter: "Noņemt" add_url: "Pievienot URL" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index cba0adfda4..4c6367eb8e 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -174,6 +174,7 @@ nb_NO: not_implemented: "Beklager, den funksjonen er ikke blitt implementert enda." no_value: "Nei" yes_value: "Ja" + submit: "Send inn" generic_error: "Beklager, det har oppstĂĄtt en feil." generic_error_with_reason: "Det oppstod et problem: %{error}" sign_up: "Registrer deg" @@ -388,7 +389,7 @@ nb_NO: name: "Navn" user_count: "Antall medlemmer" bio: "Om gruppe" - selector_placeholder: "Legg til medlemmer" + selector_placeholder: "oppgi brukernavn" owner: "eier" index: title: "Grupper" @@ -636,6 +637,25 @@ nb_NO: set_password: "Sett passord" choose_new: "Velg et nytt passord" choose: "Velg et passord" + second_factor: + title: "Totrinnsverifisering" + enabled_status: "Status: PĂĄ" + disabled_status: "Status: Av" + confirm_password_description: "Bekreft ditt passord for ĂĄ aktivere totrinnsverifisering" + enable_description: | + For ĂĄ fullføre oppsettetet av totrinnsverifisering, skann den følgende QR-koden + i en av de støttede appene + (Android and iOS, + Windows Phone) + og send inn den genererte totrinnsverifiserings-koden. + disable_description: "Skriv inn verifiseringskoden fra din app" + show_key_description: "Eller skriv inn din hemmelige nøkkel manuelt. " + info_prompt: "Hva er Totrinnsverifisering?" + extended_description: | + Totrinnsverifisering git et ekstra sikkerhteslag ved innlogginv ved ĂĄ + kreve en engangskode i tillegg til et passord. Disse engangskodene + kan bligenerert i Android og iOS med Google Authenticator + og pĂĄ Windows Phone med Authenticator. change_about: title: "Rediger om meg" error: "Det oppstod en feil ved endring av denne verdien." @@ -977,14 +997,27 @@ nb_NO: help: "Fikk du ikke e-posten? Sjekk søppelposten din først.

Er du ikke sikker pĂĄ hvilken e-postadresse du brukte. Skriv inn adressen her og vi vil fortelle deg om den finnes her.

Hvis du ikke lenger har tilgang til e-postadressen pĂĄ kontoen din, kontakt vĂĄr hjelpfulle stab.

" button_ok: "OK" button_help: "Hjelp" + email_login: + link_label: "Sen meg en magisk lenke pĂĄ e-post" + button_label: "med e-post" + complete_username: "Hvis en konto med brukernavn %{username} finnes, vil du snart motta en e-post med en magisk innlogginslenke. " + complete_email: "Hvis en konto med %{email} finnes, vil du snart motta en e-post med en magisk innlogginslenke. " + complete_username_found: "Vi fant en konto med brukernavnet %{username}, Du vil snart motta en e-post med en magisk innlogginslenke." + complete_email_found: "Vi fant en konto med e-postadressen %{email}. Du mottar om litt en e-post med en magisk innloggingslenke. " + complete_username_not_found: "Ingen konto har med brukernavnet %{username} er registrert" + complete_email_not_found: "Ingen konto med e-postadressen %{email} er registrert" login: title: "Logg Inn" username: "Bruker" password: "Passord" + second_factor_title: "Totrinnsverifisering er pĂĄkrevd" + second_factor_description: "Skriv inn verifiseringskoden fra din app." + second_factor_label: "Kode" email_placeholder: "e-postadresse eller brukernavn" caps_lock_warning: "Caps Lock er pĂĄ" error: "Ukjent feil" rate_limit: "Vent litt før du logger inn igjen." + blank_username: "Vennligst oppgi din e-post eller brukernavn." blank_username_or_password: "Oppgi din e-postadresse eller brukernavn og ditt passord." reset_password: 'Nullstill passord' logging_in: "Logger inn…" @@ -1060,6 +1093,8 @@ nb_NO: default_header_text: Velg… no_content: Ingen treff funnet filter_placeholder: Søk… + create: "Opprett: {{content}}" + max_content_reached: "Du kan kun velge {{count}} gjenstander." emoji_picker: filter_placeholder: Søk etter emoji people: Folk @@ -1156,6 +1191,7 @@ nb_NO: olist_title: "Nummerert liste" ulist_title: "Kulepunktliste" list_item: "Listeelement" + toggle_direction: "Skift retning" help: "Hjelp for redigering i Markdown" collapse: "minimer redigeringspanelet" abandon: "lukk redigeringspanel og forkast utkast" @@ -1166,6 +1202,24 @@ nb_NO: title: "Glemte du ĂĄ legge til mottagere?" body: "NĂĄ sender du denne meldingen bare til deg selv!" admin_options_title: "Valgfrie trĂĄd-instillinger for stab" + composer_actions: + reply_to_post: + label: Svar pĂĄ innlegg %{postNumber} av %{postUsername} + desc: Svar pĂĄ et spesifikt innlegg + reply_as_new_topic: + label: Svar med lenket trĂĄd + desc: Opprette ny trĂĄd lenket til denne trĂĄden + reply_as_private_message: + label: Ny melding + desc: Opprett en ny personlig melding + reply_to_topic: + label: Svar pĂĄ trĂĄd + desc: 'Svar pĂĄ trĂĄd, ikke et spesifikt innlegg ' + toggle_whisper: + label: SlĂĄ pĂĄ/av hvisking + desc: Hvisking er kun synlig for stab + create_topic: + label: "Nytt trĂĄd" notifications: tooltip: regular: @@ -1699,8 +1753,8 @@ nb_NO: select_all: velg alle deselect_all: fjern alle description: - one: Du har valgt 1 innlegg. - other: Du har valgt {{count}} innlegg. + one: "Du har valgt 1 innlegg." + other: "Du har valgt {{count}} innlegg." post: quote_reply: "Sitat" edit: " {{link}} {{replyAvatar}} {{username}}" @@ -1968,7 +2022,7 @@ nb_NO: email_in_disabled: "Publisering av nye trĂĄder via e-post er deaktivert i innstillingene for nettstedet. For ĂĄ aktivere publisering av nye trĂĄder via e-post," email_in_disabled_click: 'aktiver innstillingen "e-post inn".' mailinglist_mirror: "Kategorien gjenspeiler en e-postliste" - suppress_from_homepage: "Utelat denne kategorien fra hjemmesiden." + suppress_from_latest: "Utelat denne kategorien fra siste trĂĄder." show_subcategory_list: "Vis listen over underkategorier over trĂĄder i denne kategorien." num_featured_topics: "Antall trĂĄder vist pĂĄ kategori-siden:" subcategory_num_featured_topics: "Antall fremhevede trĂĄder pĂĄ overkategoriens side:" @@ -2447,9 +2501,15 @@ nb_NO: moderation_history: "Moderatorhistorikk" agree: "Godta" agree_title: "Bekreft at denne rapporteringen er gyldig og korrekt" + agree_flag_hide_post: "Skjul innlegg" agree_flag_hide_post_title: "Gjem dette innlegget og send en melding til brukeren automatisk med en forespørsel om gjøre endringer." agree_flag_restore_post: "Samtykk og gjenopprett innlegg" agree_flag_restore_post_title: "Gjenopprett innlegget slik at alle brukere kan se det." + agree_flag_suspend: "Steng ute bruker" + agree_flag_suspend_title: "Si deg enig med rapportering og steng ute bruker. " + agree_flag_silence: "Demp bruker" + agree_flag_silence_title: "Si deg enig med rapportering og demp bruker. " + agree_flag: "Behold innlegg" agree_flag_title: "Samtykk med flagg og behold innlegget uendret." ignore_flag: "Ignorer" ignore_flag_title: "Fjern denne rapporteringen; den krever ingen handling pĂĄ dette tidspunktet." @@ -2507,7 +2567,6 @@ nb_NO: edit: "Rediger grupper" refresh: "Last inn pĂĄ nytt" new: "Ny" - selector_placeholder: "oppgi brukernavn" about: "Rediger gruppemedlemskap og navn her." group_members: "Gruppemedlemmer" delete: "Slett" @@ -2980,6 +3039,7 @@ nb_NO: post_locked: "innlegg lĂĄst" post_unlocked: "innlegg opplĂĄst" check_personal_message: "sjekk personlig melding" + disabled_second_factor: "deaktiver totrinnsverifisering" screened_emails: title: "Kontrollerte e-poster" description: "NĂĄr noen forsøker ĂĄ lage en ny konto, vil de følgende e-postadressene bli sjekket, og registreringen vil bli blokkert, eller en annen handling vil bli utført." @@ -3126,6 +3186,10 @@ nb_NO: suspended_until: "(til %{until})" cant_suspend: "Brukeren kan ikke utestenges." delete_all_posts: "Slett alle innlegg" + penalty_post_actions: "Hva ønsker du ĂĄ gjøre med det assosierte innlegg" + penalty_post_delete: "Slett innlegget" + penalty_post_edit: "Rediger innlegget" + penalty_post_none: "ikke gjør noe" delete_all_posts_confirm_MF: "Du er i ferd med ĂĄ slette {POSTS, plural, one {1 innlegg} other {# innlegg}} og {TOPICS, plural, one {1 trĂĄd} other {# trĂĄder}}. Er du sikker?" silence: "Demp" unsilence: "Opphev demping" @@ -3161,6 +3225,7 @@ nb_NO: private_topics_count: Private trĂĄder posts_read_count: Innlegg lest post_count: Innlegg skrevet + second_factor_enabled: totrinnsverifisering aktivert topics_entered: TrĂĄder sett flags_given_count: Rapporteringer tildelt flags_received_count: Rapporteringer mottatt @@ -3303,10 +3368,7 @@ nb_NO: recommended: "Skreddersøm av følgende tekst for ĂĄ kle dine behov anbefales:" show_overriden: 'Bare vis overstyrte' site_settings: - show_overriden: 'Bare vis overstyrte' title: 'Innstillinger' - reset: 'tilbakestill' - none: 'intet' no_results: "Ingen treff funnet." clear_filter: "Tøm" add_url: "legg til URL" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 05d96910ef..80386334f8 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -380,7 +380,6 @@ nl: name: "Naam" user_count: "Aantal leden" bio: "Over groep" - selector_placeholder: "Leden toevoegen" owner: "eigenaar" index: title: "Groepen" @@ -1620,8 +1619,8 @@ nl: select_all: alles selecteren deselect_all: alles deselecteren description: - one: U hebt 1 bericht geselecteerd. - other: U hebt {{count}} berichten geselecteerd. + one: "U hebt 1 bericht geselecteerd." + other: "U hebt {{count}} berichten geselecteerd." post: quote_reply: "Citeren" edit_reason: "Reden: " @@ -1861,7 +1860,6 @@ nl: email_in_allow_strangers: "E-mails van anonieme gebruikers zonder account accepteren" email_in_disabled: "Het plaatsen van nieuwe topics via e-mail is uitgeschakeld in de webite-instellingen. Om het plaatsen van nieuwe topics via e-mail mogelijk te maken, " email_in_disabled_click: 'schakelt u de instelling ''e-mail in'' in.' - suppress_from_homepage: "Deze categorie op de startpagina negeren" show_subcategory_list: "Subcategorielijsten boven topics tonen in deze categorie" num_featured_topics: "Aantal getoonde topics op de categoriepagina:" subcategory_num_featured_topics: "Aantal aanbevolen topics op pagina van bovenliggende categorie:" @@ -2374,7 +2372,6 @@ nl: edit: "Groepen bewerken" refresh: "Vernieuwen" new: "Nieuw" - selector_placeholder: "voer gebruikersnaam in" about: "Bewerk hier uw groepslidmaatschap en namen" group_members: "Groepsleden" delete: "Verwijderen" @@ -3119,10 +3116,7 @@ nl: recommended: "We raden aan de volgende tekst naar wens aan te passen:" show_overriden: 'Alleen overschreven tekst tonen' site_settings: - show_overriden: 'Alleen overschreven instellingen tonen' title: 'Instellingen' - reset: 'standaardinstellingen' - none: 'geen' no_results: "Geen resultaten gevonden." clear_filter: "Wis" add_url: "URL toevoegen" diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index c552e29164..56e15acec0 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -437,7 +437,6 @@ pl_PL: name: "Nazwa" user_count: "Ilość uĹĽytkownikĂłw" bio: "O grupie" - selector_placeholder: "Dodaj czĹ‚onkĂłw" owner: "wĹ‚aĹ›ciciel" index: title: "Grupy" @@ -594,6 +593,7 @@ pl_PL: disable_jump_reply: "Po odpowiedzi nie przechodĹş do nowego wpisu" dynamic_favicon: "Pokazuj licznik powiadomieĹ„ na karcie jako dynamiczny favicon" theme_default_on_all_devices: "Ustaw ten motyw jako domyĹ›lny na wszystkich moich 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" enable_quoting: "Włącz cytowanie zaznaczonego tekstu" change: "zmieĹ„" @@ -687,6 +687,23 @@ pl_PL: set_password: "Ustaw hasĹ‚o" choose_new: "Wyberz nowe hasĹ‚o" choose: "Wybierz hasĹ‚o" + second_factor: + title: "DwuskĹ‚adnikowe uwierzytelnianie" + enabled_status: "Status: Włączone" + disabled_status: "Status: Wyłączone" + confirm_password_description: "Aby kontynuować włączanie dwuskĹ‚adnikowego uwierzytelniania, potwierdĹş swoje hasĹ‚o" + enable_description: | + Aby zakoĹ„czyć konfiguracjÄ™ dwuskĹ‚adnikowego uwierzytelniania, zeskanuj nastÄ™pujÄ…cy kod QR za pomocÄ… jednej ze wspieranych aplikacji + (Android lub iOS, + Windows Phone) + a nastÄ™pnie wprowadĹş wygenerowany kod potwierdzajÄ…cy dla dwuskĹ‚adnikowego uwierzytelniania. + disable_description: "WprowadĹş kod potwierdzajÄ…cy ze swojej aplikacji" + show_key_description: "Lub wpisz tajny klucz rÄ™cznie." + info_prompt: "Czym jest uwierzytelnianie dwuskĹ‚adnikowe?" + extended_description: | + DwuskĹ‚adnikowe uwierzytelnianie zapewnia dodatkowy krok zabezpieczajÄ…cy logowanie hasĹ‚em, poprzez generowanie jednorazowego kodu potwierdzajÄ…cego. Kody potwierdzajÄ…ce + mogÄ… być generowane na Androidzie i iOSie za pomocÄ… Google Authenticator + a na Windows Phone za pomocÄ… Authenticator. change_about: title: "ZmieĹ„ O mnie" error: "WystÄ…piĹ‚ błąd podczas zmiany tej wartoĹ›ci." @@ -1778,10 +1795,10 @@ pl_PL: select_all: zaznacz wszystkie deselect_all: odznacz wszystkie description: - one: Wybrano 1 wpis. - few: Wybrano {{count}} wpisy. - many: Wybrano {{count}} wpisĂłw. - other: Wybrano {{count}} wpisĂłw. + one: "Wybrano 1 wpis." + few: "Wybrano {{count}} wpisy." + many: "Wybrano {{count}} wpisĂłw." + other: "Wybrano {{count}} wpisĂłw." post: quote_reply: "Cytuj" edit_reason: "PowĂłd" @@ -2074,7 +2091,6 @@ pl_PL: email_in_allow_strangers: "Akceptuj wiadomoĹ›ci email od anonimowych, nieposiadajÄ…cych kont uĹĽytkownikĂłw " email_in_disabled: "Tworzenie nowych tematĂłw emailem jest wyłączone w ustawieniach serwisu. " email_in_disabled_click: 'Kliknij tu, aby włączyć.' - suppress_from_homepage: "Nie wyĹ›wietlaj tej kategorii na stronie głównej." show_subcategory_list: "PokaĹĽ listÄ™ subkategorii powyĹĽej tematĂłw w tej kategorii." num_featured_topics: "Liczba wÄ…tkĂłw do wyĹ›wietlenia na stronie kategorii:" subcategory_num_featured_topics: "Liczba wÄ…tkĂłw do wyĹ›wietlenia na stronie kategorii:" @@ -2633,7 +2649,6 @@ pl_PL: edit: "Edytuj grupy" refresh: "OdĹ›wieĹĽ" new: "Nowa" - selector_placeholder: "nazwa uĹĽytkownika" about: "Tu moĹĽesz edytować przypisania do grup oraz ich nazwy" group_members: "CzĹ‚onkowie grupy" delete: "UsuĹ„" @@ -3438,10 +3453,7 @@ pl_PL: recommended: "Zalecamy zmianÄ™ poniĹĽszego tekstu, aby lepiej odpowiadaĹ‚ Twoim potrzebom:" show_overriden: 'PokaĹĽ tylko nadpisane' site_settings: - show_overriden: 'PokaĹĽ tylko nadpisane' title: 'Ustawienia' - reset: 'przywróć domyĹ›lne' - none: 'ĹĽadne' no_results: "Brak wynikĂłw wyszukiwania" clear_filter: "Wyczyść" add_url: "dodaj URL" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 641d809ba4..c8843da7e3 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -373,7 +373,6 @@ pt: name: "Nome" user_count: "NĂşmero de Membros" bio: "Sobre o Grupo" - selector_placeholder: "Adicionar membros" owner: "proprietário" index: title: "Grupos" @@ -1511,8 +1510,8 @@ pt: select_all: 'selecionar tudo ' deselect_all: desmarcar tudo description: - one: Selecionou 1 publicação. - other: Selecionou {{count}} publicações. + one: "Selecionou 1 publicação." + other: "Selecionou {{count}} publicações." post: quote_reply: "Citar" edit_reason: "Motivo:" @@ -1728,7 +1727,6 @@ pt: email_in_allow_strangers: "Aceitar emails de utilizadores anĂłnimos sem conta" email_in_disabled: "Publicar novos tĂłpicos atravĂ©s do email está desactivado nas Configurações do SĂ­tio. Para permitir a publicação de novos tĂłpicos atravĂ©s do email," email_in_disabled_click: 'ative a definição "email em".' - suppress_from_homepage: "Suprimir esta categoria da página principal." allow_badges_label: "Permitir a atribuição de distintivos nesta categoria" edit_permissions: "Editar Permissões" add_permission: "Adicionar Permissões" @@ -2203,7 +2201,6 @@ pt: edit: "Editar Grupos" refresh: "Atualizar" new: "Novo" - selector_placeholder: "insira o nome de utilizador" about: "Editar aqui a sua participação e nomes no grupo" group_members: "Membros do grupo" delete: "Eliminar" @@ -2824,10 +2821,7 @@ pt: recommended: "Recomendamos personalizar o seguinte texto para que se aplique Ă s suas necessidades." show_overriden: 'Apenas mostrar valores alterados' site_settings: - show_overriden: 'Apenas mostrar valores alterados' title: 'Configurações' - reset: 'repor' - none: 'nenhum' no_results: "NĂŁo foi encontrado nenhum resultado." clear_filter: "Remover" add_url: "adicionar URL" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index 95838a2e05..a3c161d449 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -378,7 +378,6 @@ pt_BR: name: "Nom" user_count: "NĂşmero de Membros" bio: "Sobre o Grupo" - selector_placeholder: "Adicionar membros" owner: "proprietário" index: title: "Grupos" @@ -1578,8 +1577,8 @@ pt_BR: select_all: selecionar tudo deselect_all: deselecionar tudo description: - one: 1 resposta selecionada. - other: {{count}} respostas selecionadas. + one: "1 resposta selecionada." + other: "{{count}} respostas selecionadas." post: quote_reply: "Citação" edit_reason: "Motivo:" @@ -1813,7 +1812,6 @@ pt_BR: email_in_allow_strangers: "Aceitar emails de usuários anĂ´nimos sem cont" email_in_disabled: "Postar novos tĂłpicos via email está desabilitado nas Configurações do Site. Para habilitar respostas em novos tĂłpicos via email," email_in_disabled_click: 'habilitar a configuração de "email em".' - suppress_from_homepage: "Suprimir esta categoria da página inicial." show_subcategory_list: "Exibir lista de subcategorias acima dos tĂłpicos nesta categoria." num_featured_topics: "NĂşmero de tĂłpicos exibidos na página de Categorias:" subcategory_num_featured_topics: "NĂşmero de tĂłpicos em destaque na página da categoria pai:" @@ -2316,7 +2314,6 @@ pt_BR: edit: "Editar Grupos" refresh: "Atualizar" new: "Novo" - selector_placeholder: "digite o nome de usuário" about: "Editar participação no grupo e nomes aqui" group_members: "Membros do grupo" delete: "Apagar" @@ -2979,10 +2976,7 @@ pt_BR: recommended: "Recomendamos a personalização do seguinte texto para se adequar as suas necessidades:" show_overriden: 'Apenas mostrar valores alterados' site_settings: - show_overriden: 'Exibir apenas valores alterados' title: 'Configurações' - reset: 'apagar' - none: 'nenhum' no_results: "Nenhum resultado encontrado." clear_filter: "Limpar" add_url: "adicionar URL" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index eead0fe643..bf192c5a6a 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -416,7 +416,6 @@ ro: name: "Nume" user_count: "NumÄr de membri" bio: "Despre grup" - selector_placeholder: "AdaugÄ membri" owner: "Proprietar" index: title: "Grupuri" @@ -1202,13 +1201,10 @@ ro: composer_actions: reply_as_new_topic: label: RÄspunde cu link cÄtre subiect - desc: CreeazÄ un nou subiect reply_as_private_message: label: Mesaj nou - desc: CreeazÄ mesaj privat reply_to_topic: label: RÄspunde la subiect - desc: RÄspunde la postarea originalÄ fÄrÄ a rÄspunde la vreo postare specificÄ toggle_whisper: label: ComutÄ modul discret notifications: @@ -1731,9 +1727,9 @@ ro: select_all: selecteazÄ tot deselect_all: deselecteazÄ tot description: - one: Ai selectat un mesaj. - few: Ai selectat {{count}} mesaje. - other: Ai selectat {{count}} de mesaje. + one: "Ai selectat un mesaj." + few: "Ai selectat {{count}} mesaje." + other: "Ai selectat {{count}} de mesaje." post: quote_reply: "CiteazÄ" edit_reason: "Motivul edit[rii: " @@ -2011,7 +2007,6 @@ ro: email_in_allow_strangers: "AcceptÄ emailuri de la utilizatori anonimi fÄrÄ cont" email_in_disabled: "Postarea subiectelor noi prin email este dezactivatÄ din setÄrile siteului. Pentru a activa postarea subiectelor noi prin email," email_in_disabled_click: 'activeazÄ opČ›iunea "primire email ".' - suppress_from_homepage: "EliminÄ aceastÄ categorie de pe pagina principalÄ." all_topics_wiki: "TransformÄ subiectele noi Ă®n wiki-uri, implicit." sort_order: "SorteazÄ lista de subiecte dupÄ:" default_view: "ListÄ implicitÄ de subiecte:" @@ -2545,7 +2540,6 @@ ro: edit: "EditeazÄ grupuri" refresh: "ReĂ®mprospÄteazÄ" new: "Noi" - selector_placeholder: "introdu nume de utilizator" about: "EditeazÄ aici numele Č™i apartenenČ›a la grupuri" group_members: "Membrii grupului" delete: "Čterge" @@ -3287,10 +3281,7 @@ ro: recommended: "ĂŽČ›i recomandÄm sÄ personalizezi urmÄtorul text pentru a se adapta nevoilor tale:" show_overriden: 'AratÄ doar ignorate' site_settings: - show_overriden: 'AratÄ doar pe cele ignorate' title: 'SetÄri' - reset: 'ReseteazÄ' - none: 'Nimic' no_results: "Nu s-au gÄsit rezultate." clear_filter: "Čterge" add_url: "adaugÄ URL" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 5309896b4b..bec9beba91 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -433,7 +433,6 @@ ru: name: "Название" user_count: "КоличеŃтво УчаŃтников" bio: "Đž ГрŃппе" - selector_placeholder: "Добавить ŃчаŃтников" owner: "владелец" index: title: "ГрŃппы" @@ -591,6 +590,7 @@ ru: admin: "{{user}} — админ" moderator_tooltip: "Этот пользователь модератор" admin_tooltip: "{{user}} — админ" + silenced_tooltip: "Этот пользователь отключнный" suspended_notice: "Пользователь заморожен Đ´Đľ {{date}}." suspended_permanently: "Этот пользователь заморожен." suspended_reason: "Причина:" @@ -641,6 +641,7 @@ ru: undo_revoke_access: "Отменить ЛиŃение прав Đ´ĐľŃŃ‚Ńпа" api_approved: "Подтверждено" theme: "Стиль" + staged: "Поэтапный" staff_counters: flags_given: "полезные жалобы" flagged_posts: "Ńообщения Ń Đ¶Đ°Đ»ĐľĐ±Đ°ĐĽĐ¸" @@ -852,6 +853,7 @@ ru: title: "Сводка" stats: "СтатиŃтика" time_read: "время чтения" + recent_time_read: "недавнее время чтения" topic_count: one: "Ń‚ĐµĐĽŃ Ńоздал" few: "темы Ńоздал" @@ -997,6 +999,7 @@ ru: private_message_info: title: "Сообщение" invite: "ПриглаŃить Đ´Ń€Ńгих..." + leave_message: "Đ’Ń‹ дейŃтвительно хотите ĐľŃтавить это Ńообщение?" remove_allowed_user: "Đ’Ń‹ дейŃтвительно хотите Ńдалить {{name}} из данного Ńообщения?" remove_allowed_group: "Đ’Ń‹ дейŃтвительно хотите Ńдалить {{name}} из данного Ńообщения?" email: 'Email' @@ -1024,6 +1027,11 @@ ru: help: "Электронное пиŃŃŚĐĽĐľ не доходит? Для начала проверьте ĐżĐ°ĐżĐşŃ Â«ĐˇĐżĐ°ĐĽÂ» ваŃего почтового ящика.

Не Ńверены в том, какой Đ°Đ´Ń€ĐµŃ Đ¸Ńпользовали? Введите его и ĐĽŃ‹ подŃкажем, еŃть ли он в наŃей базе.

Đ•Ńли вы более не имеете Đ´ĐľŃŃ‚Ńпа Đş ŃĐ˛ŃŹĐ·Đ°Đ˝Đ˝ĐľĐĽŃ Ń Đ˛Đ°Ńей Ńчётной запиŃŃŚŃŽ адреŃŃ ŃŤĐ»ĐµĐşŃ‚Ń€ĐľĐ˝Đ˝ĐľĐą почты, то, пожалŃĐąŃта, ŃвяжитеŃŃŚ Ń Đ°Đ´ĐĽĐ¸Đ˝Đ¸Ńтрацией.

" button_ok: "ОК" button_help: "Помощь" + email_login: + complete_username: "Đ•Ńли Ńчетная запиŃŃŚ ŃоответŃтвŃет имени пользователя %{username}, вы должны полŃчить электронное пиŃŃŚĐĽĐľ Ń ĐĽĐ°ĐłĐ¸Ń‡ĐµŃкой ŃŃылкой для входа в ŃиŃŃ‚ĐµĐĽŃ Đ˛ ближайŃее время." + complete_email: "Đ•Ńли аккаŃнт Ńовпадает %{email}, вы должны полŃчить электронное пиŃŃŚĐĽĐľ Ń ĐĽĐ°ĐłĐ¸Ń‡ĐµŃкой ŃŃылкой для входа в ŃиŃŃ‚ĐµĐĽŃ Đ˛ ближайŃее время." + complete_username_found: "Мы наŃли ŃчетнŃŃŽ запиŃŃŚ, которая Ńовпадает Ń Đ¸ĐĽĐµĐ˝ĐµĐĽ пользователя %{username}, вы должны полŃчить электронное пиŃŃŚĐĽĐľ Ń ĐĽĐ°ĐłĐ¸Ń‡ĐµŃкой ŃŃылкой для входа в ŃиŃŃ‚ĐµĐĽŃ Đ˛ ближайŃее время." + complete_email_found: "Мы наŃли аккаŃнт, который ŃоответŃтвŃет %{email}, вы должны полŃчить пиŃŃŚĐĽĐľ Ń ĐĽĐ°ĐłĐ¸Ń‡ĐµŃкой ŃŃылкой для входа в ŃиŃŃ‚ĐµĐĽŃ Đ˛ ближайŃее время." login: title: "Войти" username: "Пользователь" @@ -1107,6 +1115,7 @@ ru: default_header_text: Выбрать... no_content: Совпадений не найдено filter_placeholder: ПоиŃĐş... + create: "Создать: '{{content}}'" emoji_picker: filter_placeholder: ĐŃкать emoji people: People @@ -1205,17 +1214,28 @@ ru: body: "Đ’ ŃпиŃке полŃчателей ŃĐµĐąŃ‡Đ°Ń Ń‚ĐľĐ»ŃŚĐşĐľ вы Ńами!" admin_options_title: "Дополнительные наŃтройки темы для перŃонала" composer_actions: + reply_to_post: + desc: Ответ на конкретный поŃŃ‚ reply_as_new_topic: label: Ответить в новой Ńвязанной теме - desc: Создать новŃŃŽ Ń‚ĐµĐĽŃ reply_as_private_message: label: Новое Ńообщение - desc: НапиŃать личное Ńообщение reply_to_topic: label: Ответить на Ń‚ĐµĐĽŃ create_topic: label: "Новая тема" notifications: + tooltip: + regular: + one: "1 невидимое Ńведомление" + few: "{{count}} невидимые Ńведомления" + many: "{{count}} невидимых Ńведомлений" + other: "{{count}} невидимые Ńведомления" + message: + one: "1 непрочитанное Ńообщение" + few: "{{count}} непрочитанных Ńообщений" + many: "{{count}} непрочитанных Ńообщений" + other: "{{count}} непрочитанных Ńообщений" title: "Ńведомления об Ńпоминании @ĐżŃевдонима, ответах на ваŃи поŃты и темы, Ńообщения и Ń‚.Đ´." none: "Уведомления не могŃŃ‚ быть загрŃжены." empty: "Уведомления не найдены." @@ -1714,10 +1734,10 @@ ru: select_all: выбрать вŃе deselect_all: Ńнять веŃŃŚ выбор description: - one: Đ’Ń‹ выбрали {{count}} Ńообщение. - few: Đ’Ń‹ выбрали {{count}} Ńообщения. - many: Đ’Ń‹ выбрали {{count}} Ńообщений. - other: Đ’Ń‹ выбрали {{count}} Ńообщений. + one: "Đ’Ń‹ выбрали {{count}} Ńообщение." + few: "Đ’Ń‹ выбрали {{count}} Ńообщения." + many: "Đ’Ń‹ выбрали {{count}} Ńообщений." + other: "Đ’Ń‹ выбрали {{count}} Ńообщений." post: quote_reply: "Цитата" edit_reason: "Причина:" @@ -1995,7 +2015,6 @@ ru: email_in_allow_strangers: "Принимать пиŃьма от анонимных пользователей без Ńчётных запиŃей" email_in_disabled: "Создание новых тем через электроннŃŃŽ ĐżĐľŃ‡Ń‚Ń ĐľŃ‚ĐşĐ»ŃŽŃ‡ĐµĐ˝Đľ в наŃтройках Ńайта. Чтобы разреŃить Ńоздание новых тем через электроннŃŃŽ почтŃ," email_in_disabled_click: 'активирŃйте наŃŃ‚Ń€ĐľĐąĐşŃ "email in".' - suppress_from_homepage: "Не отображать этот раздел на главной Ńтранице." show_subcategory_list: "Показывать ŃпиŃок подразделов над ŃпиŃком тем в этом разделе." num_featured_topics: "КоличеŃтво тем на Ńтранице разделов" all_topics_wiki: "Создавать новые темы в вики-формате по Ńмолчанию." @@ -2575,7 +2594,6 @@ ru: edit: "Управление грŃппами" refresh: "Обновить" new: "Добавить новŃŃŽ" - selector_placeholder: "введите ĐżŃевдоним" about: "ЗдеŃŃŚ можно редактировать грŃппы и имена грŃпп" group_members: "УчаŃтники грŃппы" delete: "Удалить" @@ -3399,10 +3417,7 @@ ru: recommended: "Мы рекомендŃем изменить ŃледŃющий текŃŃ‚ под ваŃи Đ˝Ńжды:" show_overriden: 'Показывать только изменённые' site_settings: - show_overriden: 'Показывать только измененные' title: 'НаŃтройки' - reset: 'ВернŃть по Ńмолчанию' - none: '(нет)' no_results: "Ничего не найдено." clear_filter: "ОчиŃтить" add_url: "Добавить URL" diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml index 824557fc18..8bf066b922 100644 --- a/config/locales/client.sk.yml +++ b/config/locales/client.sk.yml @@ -142,6 +142,7 @@ sk: split_topic: "rozdeÄľ tĂ©mu %{when}" invited_user: "pozvanĂ˝ %{who} %{when}" invited_group: "pozvanĂ˝ %{who} %{when}" + user_left: "%{who}ktorĂ­ sa odobrali z tejto správy %{when}" removed_user: "odstránenĂ˝ %{who} %{when}" removed_group: "odstránenĂ˝ %{who} %{when}" autoclosed: @@ -383,12 +384,16 @@ sk: title: 'UpraviĹĄ skupinu' full_name: 'CelĂ© meno' add_members: "PridaĹĄ používateÄľov" + name_placeholder: "Meno skupiny, bez medzier, takĂ© istĂ© pravidlá ako pre používateÄľskĂ© meno" public_admission: "PovoliĹĄ používateÄľom slobodne sa staĹĄ ÄŤlen skupiny (kupina musĂ­ byĹĄ verejne viditeÄľná)" public_exit: "PovoliĹĄ používateÄľom slobodne opustiĹĄ skupinu." empty: posts: "Nie sĂş tu Ĺľiadne prĂ­spevky od ÄŤlenov tejto skupiny" members: "V tejto skupine niesĂş Ĺľiadny ÄŤlenovia" + mentions: "O tejto skupine nie sp Ĺľiadne zmienky." messages: "Pre tĂşto skupinu niesĂş Ĺľiadne správy" + topics: "ÄŚlenovia tejto skupiny nezaloĹľili Ĺľiadne tĂ©my." + logs: "Pre tĂşto skupinu neexistujĂş Ĺľiadne logy." add: "PridaĹĄ" join: "PridaĹĄ sa do skupiny" leave: "OpustiĹĄ skupinu" @@ -397,14 +402,15 @@ sk: automatic_group: Automatická skĂşpina closed_group: Uzavretá skupina is_group_user: "Si ÄŤlenom tejto skupiny" + allow_membership_requests: "Používatelia mĂ´Ĺľu posielaĹĄ Ĺľiadosti o ÄŤlenstvo vlastnĂ­kom skupiny" membership_request: submit: "Odošli poĹľiadavku" title: "PoĹľiadavka o ÄŤlenstvo v %{group_name}" + reason: "Dajte vedieĹĄ vlastnĂ­kom skupiny preÄŤo do tejto skupiny patrĂ­te" membership: "ÄŚlenstvo" name: "Meno" user_count: "PoÄŤet ÄŤlenov" bio: "O skupine" - selector_placeholder: "PridaĹĄ ÄŤlenov" owner: "vlastnĂ­k" index: title: "Skupiny" @@ -422,7 +428,10 @@ sk: visibility_levels: title: "Kto mĂ´Ĺľe vidieĹĄ tĂşto skupinu?" public: "KaĹľdĂ˝" + members: "VlastnĂ­ci skupiny, ÄŤlenovia a jej administrátori" + owners: "VlastnĂ­ci skupiny a jej administrátori" alias_levels: + messageable: "Kto mĂ´Ĺľe poslaĹĄ správu tejto skupine?" nobody: "Nikto" only_admins: "Iba administrátori" mods_and_admins: "Iba moderátori a administrátori" @@ -447,6 +456,8 @@ sk: muted: title: "IgnorovanĂ©" description: "Nikdy nebudete upozornenĂ­ na niÄŤ ohÄľadom novĂ˝ch tĂ©m v tejto skupine." + flair_preview_icon: "Ikona náhÄľadu" + flair_preview_image: "NáhÄľad obrázku" user_action_groups: '1': "RozdanĂ˝ch 'páči sa mi'" '2': "ObdrĹľanĂ˝ch 'páči sa mi'" @@ -538,8 +549,10 @@ sk: dismiss: 'ZahodiĹĄ' dismiss_notifications: "ZahodiĹĄ všetko" dismiss_notifications_tooltip: "OznaÄŤiĹĄ všetky nepreÄŤĂ­tanĂ© upozornenia ako preÄŤĂ­tanĂ©" + first_notification: "Vaša prvĂ© upozornenie! Vyberte ho ak chcete zaÄŤaĹĄ." disable_jump_reply: "NeskoÄŤiĹĄ na mĂ´j prĂ­spevok po odpovedi" dynamic_favicon: "ZobraziĹĄ poÄŤet novĂ˝ch/upravenĂ˝ch tĂ©m na ikone prehliadaÄŤa" + allow_private_messages: "UmoĹľniĹĄ inĂ˝m používateÄľom, aby mi posielali osobnĂ© správy" external_links_in_new_tab: "OtváraĹĄ všekty externĂ© odkazy na novej karte" enable_quoting: "UmoĹľniĹĄ odpoveÄŹ s citáciou z oznaÄŤenĂ©ho textu" change: "zmeniĹĄ" @@ -618,6 +631,7 @@ sk: emails: "Emaily" notifications: "Upozornenia" categories: "KategĂłrie" + interface: "Rozhranie" apps: "Aplikácie" change_password: success: "(email odoslanĂ˝)" @@ -625,6 +639,14 @@ sk: error: "(chyba)" action: "OdoslaĹĄ email na obnovenie hesla" set_password: "NastaviĹĄ heslo" + choose_new: "Vyberte si novĂ© heslo" + choose: "Vyberte si heslo" + second_factor: + title: "Dvojfaktorová autentifikácia" + enabled_status: "Stav: zapnutĂ˝" + disabled_status: "Stav: vypnutĂ˝" + disable_description: "VloĹľte autentifikaÄŤnĂ˝ kĂłd z vašej aplikácie" + info_prompt: "ÄŚo je dvojfaktorová atentifikácia?" change_about: title: "UpraviĹĄ O mne" error: "Nastala chyba pri zmene tejto hodnoty." @@ -748,6 +770,7 @@ sk: title: "Pozvánky" user: "PozvanĂ˝ používateÄľ" sent: "OdoslanĂ©" + none: "Nemáte Ĺľiadne pozvánky na zobrazenie." truncated: one: "Zobrazuje sa prvá pozvánka." few: "ZobrazujĂş sa prvĂ© {{count}} pozvánky." @@ -778,6 +801,7 @@ sk: bulk_invite: text: "Hromadná pozvánka zo sĂşboru" success: "SĂşbor bol Ăşspešne odoslanĂ˝. KeÄŹ sa nahrávanie dokonÄŤĂ­, budete na to upozornenĂ˝ cez správu." + error: "Prepáčte, sĂşbor musĂ­ byĹĄ v CSV formáte." password: title: "Heslo" too_short: "Vaše heslo je prĂ­liš krátke." @@ -933,15 +957,19 @@ sk: complete_email_found: "Našli sme účet priradenĂ˝ k %{email}, ÄŤoskoro dostanete e-mail s pokynmi, ako si obnoviĹĄ svoje heslo." complete_username_not_found: "Ĺ˝iadny účet nemá priradenĂ© používateÄľskĂ© meno %{username}" complete_email_not_found: "Ĺ˝iadny účet nie je s e-mailom %{email}" + button_ok: "OK" button_help: "Pomoc" login: title: "Prihlásenie" username: "PoužívateÄľ" password: "Heslo" + second_factor_title: "Je poĹľadovaná dvojfaktorová autentifikácia" + second_factor_label: "KĂłd" email_placeholder: "e-mail alebo používateÄľskĂ© meno" caps_lock_warning: "Caps Lock je zapnutĂ˝" error: "Neznáma chyba" rate_limit: "Pred opätovnĂ˝m prihlásenĂ­m chvĂ­Äľku poÄŤkajte." + blank_username: "ProsĂ­m vloĹľte Váš email alebo používateÄľskĂ© meno." blank_username_or_password: "Zadajte prosĂ­m svoj e-mail alebo používateÄľskĂ© meno a heslo." reset_password: 'ObnoviĹĄ heslo' logging_in: "Prebieha prihlásenie..." @@ -953,6 +981,7 @@ sk: not_allowed_from_ip_address: "Nie je moĹľnĂ© prihlásenie z tejto IP adresy." admin_not_allowed_from_ip_address: "Nie je moĹľnĂ© prihlásenie ako admin z tejto IP adresy." resend_activation_email: "Kliknite sem pre opätovnĂ© odoslanie aktivaÄŤnĂ©ho emailu." + resend_title: "Znova poslaĹĄ aktivaÄŤnĂ˝ email." change_email: "ZmeniĹĄ emailovĂş adresu" submit_new_email: "AktualizovaĹĄ emailovĂş adresu" sent_activation_email_again: "Odoslali sme vám ÄŹalší aktivaÄŤnĂ˝ email na {{currentEmail}}. MĂ´Ĺľe trvaĹĄ niekoÄľko minĂşt kĂ˝m prĂ­de; pre istotu si skontrolujte spamovĂ˝ prieÄŤinok." @@ -985,6 +1014,7 @@ 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Ă©)" emoji_set: apple_international: "Apple/MedzinárodnĂ©" @@ -1000,11 +1030,14 @@ sk: ctrl: 'Ctrl' alt: 'Alt' select_kit: + default_header_text: VybraĹĄ... filter_placeholder: HÄľadaĹĄ emoji_picker: people: Ä˝udia food: Jedlo + activity: Aktivita travel: Cestovanie + objects: Objekty composer: emoji: "Emoji :)" more_emoji: "viac ..." @@ -1080,6 +1113,10 @@ sk: composer_actions: reply_as_private_message: label: Nová správa + reply_to_topic: + label: OdpovedaĹĄ na tĂ©mu + create_topic: + label: "Nová tĂ©ma" notifications: title: "oznámenia o zmienkach pomocou @meno, odpovede na Vaše prĂ­spevky a tĂ©my, správy, atÄŹ." none: "Notifikácie sa nepodarilo naÄŤĂ­taĹĄ" @@ -1093,6 +1130,7 @@ sk: posted: "PrĂ­spevok od" edited: "Upravte Váš prĂ­spevok do" liked: "Váš prĂ­spevok sa páčil" + private_message: "Privátna správa od" invited_to_topic: "PozvanĂ˝ k tĂ©me od" invitee_accepted: "Pozvánka akceptovaná " moved_post: "Váš prĂ­spevok bol presunutĂ˝ " @@ -1120,10 +1158,12 @@ sk: uploading: "Nahrávanie" select_file: "ZvoÄľte sĂşbor" image_link: "adresa na ktorĂş bude odkazovaĹĄ váš obrázok" + default_image_alt_text: Obrázok search: sort_by: "ZoradiĹĄ podÄľa" relevance: "Relevancia" latest_post: "Najnovší prĂ­spevok" + latest_topic: "Posledná tĂ©ma" most_viewed: "Najviac prezeranĂ©" most_liked: "Najviac sa páčia" select_all: "OznaÄŤ všetky" @@ -1134,6 +1174,7 @@ sk: no_more_results: "Nenašlo sa viac vĂ˝sledkov" searching: "VyhÄľadávam....." post_format: "#{{post_number}} podÄľa {{username}}" + cant_find: "NemĂ´Ĺľete nájsĹĄ to ÄŤo hÄľadáte?" context: user: "VyhÄľadávanie podÄľa @{{username}}" category: "VyhÄľadaĹĄ kategĂłriu #{{category}}" @@ -1143,20 +1184,31 @@ sk: title: RozširenĂ© vyhÄľadávanie posted_by: label: Autor prĂ­spevku + in_category: + label: KategorizovanĂ˝ filters: likes: sa mi páčia posted: obsahujĂş mĂ´j prĂ­spevok watching: Sledujem tracking: Pozorujem + private: V mojich správach + pinned: sĂş pripnutĂ© + unpinned: nie sĂş pripnutĂ© unseen: som neÄŤĂ­tal statuses: label: Kde tĂ©my + open: sĂş otvorenĂ© closed: uzavretĂ© + archived: sĂş archivovanĂ© + 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 + after: po hamburger_menu: "prejsĹĄ na inĂ© tĂ©my, alebo kategĂłrie" new_item: "novĂ˝" go_back: 'späť' @@ -1165,6 +1217,8 @@ sk: topics: new_messages_marker: "posledná návševa" bulk: + select_all: "Vyber všetko" + clear_all: "Zruš všetko" unlist_topics: "DĂ´vernĂ© tĂ©my" reset_read: "ObnoviĹĄ preÄŤĂ­tanĂ©" delete: "ZmazaĹĄ tĂ©my" @@ -1178,6 +1232,7 @@ sk: actions: "HromadnĂ© akcie" close_topics: "UzavrieĹĄ tĂ©mu" archive_topics: "Archivuj tĂ©my" + notification_level: "Upozornenia" choose_new_category: "Vyberte pre tĂ©mu novĂş kategĂłriu:" selected: one: "OznaÄŤĂ­li ste 1 tĂ©mu." @@ -1278,6 +1333,9 @@ sk: jump_reply_up: prejsĹĄ na predchádzajĂşcu odpoveÄŹ jump_reply_down: prejsĹĄ na nasledujĂşcu odpoveÄŹ deleted: "TĂ©ma bola vymazaná" + topic_status_update: + num_of_hours: "PoÄŤet hodĂ­n:" + when: "Kedy:" auto_update_input: tomorrow: "Zajtra" this_weekend: "Tento vĂ­kend" @@ -1287,6 +1345,12 @@ sk: three_months: "Tri mesiace" six_months: "Ĺ esĹĄ mesiacov" one_year: "Jeden rok" + forever: "VĹľdy" + pick_date_and_time: "Vyberte dátum a ÄŤas" + temp_close: + title: "DoÄŤasne zatvoriĹĄ" + reminder: + title: "Upozorni ma" auto_close_title: 'Nastavenia automatickĂ©ho zatvárania' auto_close_immediate: one: "PoslednĂ˝ prĂ­spevok k tĂ©me je starĂ˝ uĹľ 1 hodinu, takĹľe tĂ©ma bude okamĹľite uzavretá. " @@ -1371,6 +1435,8 @@ sk: share: title: 'ZdieÄľaj' help: 'zdieÄľaj odkaz na tĂşto tĂ©mu' + print: + title: 'TlaÄŤiĹĄ' flag_topic: title: 'OznaÄŤ' help: 'sĂşkromne oznaÄŤiĹĄ tĂşto tĂ©mu do pozornosti, alebo na Ĺu poslaĹĄ sĂşkromne upozornenie' @@ -1475,14 +1541,18 @@ sk: multi_select: select: 'oznaÄŤ' selected: 'oznaÄŤenĂ˝ch ({{count}})' + select_post: + label: 'vybraĹĄ' + selected_post: + label: 'vybratĂ©' delete: zmaĹľ oznaÄŤenĂ© cancel: zrušiĹĄ vĂ˝ber select_all: oznaÄŤ všetko deselect_all: odznaÄŤ všetko description: - one: OznaÄŤili ste 1 prĂ­spevok - few: OznaÄŤili ste {{count}} prĂ­spevky - other: OznaÄŤili ste {{count}} prĂ­spevkov + one: "OznaÄŤili ste 1 prĂ­spevok" + few: "OznaÄŤili ste {{count}} prĂ­spevky" + other: "OznaÄŤili ste {{count}} prĂ­spevkov" post: quote_reply: "Citácia" edit_reason: "DĂ´vod:" @@ -1660,6 +1730,10 @@ sk: title: "ZobraziĹĄ rozdiely v generovanom vĂ˝stupe vedÄľa seba" side_by_side_markdown: title: "ZobraziĹĄ rozdiely v pĂ´vodnom zdroji vedÄľa seba" + raw_email: + displays: + text_part: + button: 'Text' category: can: 'mĂ´Ĺľe … ' none: '(Bez kategĂłrie)' @@ -1705,7 +1779,6 @@ sk: email_in_allow_strangers: "PrijĂ­maĹĄ emaily od anonymnĂ˝ch používateÄľov bez účtu" email_in_disabled: "Vkladanie novĂ˝ch tĂ©m cez email je zablokovanĂ© v Nastaveniach stránky. Ak chcete povoliĹĄ vkladanie novĂ˝ch tĂ©me cez email," email_in_disabled_click: 'povoÄľte nastavenie "email in"' - suppress_from_homepage: "PozastaviĹĄ kategĂłriu z domovskej stránky." allow_badges_label: "PovoliĹĄ zĂ­skavanie odznakov v tejto kategĂłrii" edit_permissions: "UpraviĹĄ práva" add_permission: "PridaĹĄ práva" @@ -1730,6 +1803,8 @@ sk: muted: title: "StíšenĂ©" description: "Nikdy nebudete informovanĂ­ o udalostiach v novĂ˝ch tĂ©mach tĂ˝chto kategĂłriĂ­. Tieto tĂ©my sa zároveĹ nebudĂş zobrazovaĹĄ v zozname poslednĂ˝ch udalostĂ­." + sort_options: + activity: "Aktivity" flagging: title: 'ÄŽakujeme, Ĺľe pomáhate udrĹľiavaĹĄ slušnosĹĄ v našej komunite!' action: 'OznaÄŤ prĂ­spevok' @@ -2162,7 +2237,6 @@ sk: edit: "UpraviĹĄ skupiny" refresh: "ObnoviĹĄ" new: "NovĂ˝" - selector_placeholder: "zadaĹĄ používateÄľskĂ© meno" about: "Tu upravĂ­te Vaše ÄŤlenstvo v skupinách a mená" group_members: "ÄŚlenovia skupiny" delete: "OdstrániĹĄ" @@ -2700,10 +2774,7 @@ sk: recommended: "Odporúčame prispĂ´sobenie nasledujĂşceho textu podÄľa vašich potrieb:" show_overriden: 'UkázaĹĄ iba zmenenĂ©' site_settings: - show_overriden: 'UkázaĹĄ iba zmenenĂ©' title: 'Nastavenia' - reset: 'zrušiĹĄ' - none: 'Ĺľiadne' no_results: "Ĺ˝iadne vĂ˝sledky" clear_filter: "VyÄŤistiĹĄ" add_url: "pridaj URL" diff --git a/config/locales/client.sl.yml b/config/locales/client.sl.yml new file mode 100644 index 0000000000..9bc84fc5e0 --- /dev/null +++ b/config/locales/client.sl.yml @@ -0,0 +1,1914 @@ +# encoding: utf-8 +# +# Never edit this file. It will be overwritten when translations are pulled from Transifex. +# +# To work with us on translations, join this project: +# https://www.transifex.com/projects/p/discourse-org/ + +sl: + js: + number: + format: + separator: "." + delimiter: "," + human: + storage_units: + format: '%n %u' + units: + gb: GB + kb: KB + mb: MB + tb: TB + short: + thousands: "{{number}}k" + millions: "{{number}}M" + dates: + time: "h:mm a" + timeline_date: "MMM YYYY" + long_no_year: "MMM D h:mm a" + long_no_year_no_time: "MMM D" + full_no_year_no_time: "MMMM Do" + long_with_year: "MMM D, YYYY h:mm a" + long_with_year_no_time: "MMM D, YYYY" + full_with_year_no_time: "MMMM Do, YYYY" + long_date_with_year: "MMM D, 'YY LT" + long_date_without_year: "MMM D, LT" + long_date_with_year_without_time: "MMM D, 'YY" + long_date_without_year_with_linebreak: "MMM D
LT" + long_date_with_year_with_linebreak: "MMM D, 'YY
LT" + wrap_ago: "pred %{date}" + tiny: + half_a_minute: "< 1 min" + less_than_x_seconds: + one: "< %{count} s" + two: "< %{count} s" + few: "< %{count} s" + other: "< %{count} s" + x_seconds: + one: "%{count} s" + two: "%{count} s" + few: "%{count} s" + other: "%{count} s" + x_minutes: + one: "%{count} min" + two: "%{count} min" + few: "%{count} min" + other: "%{count} min" + about_x_hours: + one: "%{count} h" + two: "%{count} h" + few: "%{count} h" + other: "%{count} h" + x_days: + one: "%{count} d" + two: "%{count} d" + few: "%{count} d" + other: "%{count} d" + about_x_years: + one: "%{count} l" + two: "%{count} l" + few: "%{count} l" + other: "%{count} l" + over_x_years: + one: "> %{count} l" + two: "> %{count} l" + few: "> %{count} l" + other: "> %{count} l" + almost_x_years: + one: "%{count} l" + two: "%{count} l" + few: "%{count} l" + other: "%{count} l" + date_month: "MMM D" + date_year: "MMM 'YY" + medium: + x_minutes: + one: "%{count} minuto" + two: "%{count} minuti" + few: "%{count} minute" + other: "%{count} minut" + x_hours: + one: "%{count} uro" + two: "%{count} uri" + few: "%{count} ure" + other: "%{count} ur" + x_days: + one: "%{count} dan" + two: "%{count} dneva" + few: "%{count} dnevi" + other: "%{count} dni" + date_year: "MMM D, 'YY" + medium_with_ago: + x_minutes: + one: "pred %{count} minuto" + two: "pred %{count} minutama" + few: "pred %{count} minutami" + other: "pred %{count} minutami" + x_hours: + one: "pred %{count} uro" + two: "pred %{count} urama" + few: "pred %{count} urami" + other: "pred %{count} urami" + x_days: + one: "pred %{count} dnevom" + two: "pred %{count} dnevoma" + few: "pred %{count} dnevi" + other: "pred %{count} dnevi" + later: + x_days: + one: "čez %{count} dan" + two: "čez %{count} dneva" + few: "čez %{count} dneve" + other: "čez %{count} dni" + x_months: + one: "čez %{count} mesec" + two: "čez %{count} meseca" + few: "čez %{count} mesece" + other: "čez %{count} mesecev" + x_years: + one: "čez %{count} leto" + two: "čez %{count} leti" + few: "čez %{count} leta" + other: "čez %{count} let" + previous_month: 'Prejšnji mesec' + next_month: 'Naslednji mesec' + placeholder: datum + share: + topic: 'deli povezavo do te teme' + post: 'objava #%{postNumber}' + close: 'zapri' + twitter: 'deli povezavo na Twitterju' + facebook: 'deli povezao na acebooku' + google+: 'deli povezavo na Google+' + email: 'pošlji povezavo preko e-pošte' + action_codes: + public_topic: "je naredil temo javno %{when}" + private_topic: "je naredil temo privatno %{when}" + split_topic: "je razdelil temo %{when}" + invited_user: "je povabil %{who} %{when}" + invited_group: "je povabil %{who} %{when}" + user_left: "%{who}se je odstranil s tega sporočila %{when}" + removed_user: "je odstranil %{who} %{when}" + removed_group: "je odstranil %{who} %{when}" + autoclosed: + enabled: 'zaprto %{when}' + disabled: 'odprto %{when}' + closed: + enabled: 'zaprto %{when}' + disabled: 'odprto %{when}' + archived: + enabled: 'arhivirano %{when}' + disabled: 'odarhivirano %{when}' + pinned: + enabled: 'pripeto %{when}' + disabled: 'odpeto %{when}' + pinned_globally: + enabled: 'globalno pripeto %{when}' + disabled: 'odpeto %{when}' + visible: + enabled: 'objavljeno %{when}' + disabled: 'odstranjeno %{when}' + banner: + enabled: 'je ustvaril ta oglas %{when}. Pojavil se bo na vrhu vsake strani, dokler ga uporabnik ne odstrani.' + disabled: 'je odstranil/a ta oglas %{when}. Ne bo se več pojavljal na vrhu vsake strani.' + topic_admin_menu: "ukrepi administratorja teme" + wizard_required: "Dobrodošli v vašem novem Discoursu! Začnimo z čarovnikom za nastavitve ✨" + emails_are_disabled: "Vsa odhodna e-pošta je bila globalno onemogočena iz strani administratorja. E-poštne obvestila ne bodo poslana." + bootstrap_mode_enabled: "Zaradi olajšanja zagona vaše nove strani ste v \"bootstrap\" načinu. Vsi novi uporabniki bodo prejeli nivo zaupanja 1 ter imeli omogočene dnevne povzetke preko e-pošte. To bo samodejno izključeno ko skupno število uporabnikov preseže %{min_users}." + bootstrap_mode_disabled: "\"Bootstrap\" način bo onemogočen v naslednjih 24 urah." + themes: + default_description: "Privzeto" + s3: + regions: + ap_northeast_1: "Asia Pacific (Tokyo)" + ap_northeast_2: "Asia Pacific (Seoul)" + ap_south_1: "Asia Pacific (Mumbai)" + ap_southeast_1: "Asia Pacific (Singapore)" + ap_southeast_2: "Asia Pacific (Sydney)" + cn_north_1: "China (Beijing)" + eu_central_1: "EU (Frankfurt)" + eu_west_1: "EU (Ireland)" + eu_west_2: "EU (London)" + sa_east_1: "South America (Sao Paulo)" + us_east_1: "US East (N. Virginia)" + us_east_2: "US East (Ohio)" + us_gov_west_1: "AWS GovCloud (US)" + us_west_1: "US West (N. California)" + us_west_2: "US West (Oregon)" + edit: 'uredi naslov in kategorijo te teme' + not_implemented: "Oprosti, ta funkcija še ni bila implementirana!" + no_value: "Ne" + yes_value: "Ja" + generic_error: "Ups, prišlo je do napake." + generic_error_with_reason: "Napaka: %{error}" + sign_up: "Prijava" + log_in: "Vpis" + age: "Starost" + joined: "Pridružen" + admin_title: "Administrator" + flags_title: "Zastave" + show_more: "Več" + show_help: "možnosti" + links: "Povezave" + links_lowercase: + one: "povezava" + two: "povezavi" + few: "povezave" + other: "povezave" + faq: "FAQ" + guidelines: "Navodila" + privacy_policy: "Politika zasebnost" + privacy: "Zasebnost" + terms_of_service: "Pogoji storitve" + mobile_view: "Mobilni pogled" + desktop_view: "Namizni pogled" + you: "Ti" + or: "ali" + now: "ravnokar" + read_more: 'preberi več' + more: "Več" + less: "Manj" + never: "nikoli" + every_30_minutes: "vsakih 30 minut" + every_hour: "vsako uro" + daily: "dnevno" + weekly: "tedensko" + every_two_weeks: "vsaka dva tedna" + every_three_days: "vsake tri dni" + max_of_count: "največ od {{count}}" + alternation: "ali" + character_count: + one: "{{count}} znak" + two: "{{count}} znaka" + few: "{{count}} znakov" + other: "{{count}} znakov" + suggested_topics: + title: "Predlagane Teme" + pm_title: "Predlagana Sporočil" + about: + simple_title: "O" + title: "O %{title}" + stats: "Statistike Strani" + our_admins: "Naši Administratorji" + our_moderators: "Naši Moderatorji" + stat: + all_time: "Vse" + last_7_days: "Zadnjih 7 dni" + last_30_days: "Zadnjih 30 dni" + like_count: "Všečki" + topic_count: "Teme" + post_count: "Objave" + user_count: "Uporabniki" + active_user_count: "Aktivni uporabniki" + contact: "Kontaktirajte nas" + contact_info: "V primeru kritične težave glede strani nas prosim kontaktirajte na %{contact_info}." + bookmarked: + title: "Zaznamek" + clear_bookmarks: "Počisti zaznamke" + help: + bookmark: "Klikni, da ustvariš zaznamek prve objave v tej temi" + unbookmark: "Klikni za odstranitev vseh zaznamkov v tej temi" + bookmarks: + not_logged_in: "oprosti, moraš biti prijavljen za ustvarjanje zaznamkov" + created: "ustvarili ste zaznamek te objave" + not_bookmarked: "prebrali ste to objavo; klinite, da ustvarite zaznamek" + last_read: "to je zadnja objava katero ste prebrali; kliknite, da ustvarite zaznamek" + remove: "Odstrani Zaznamek" + confirm_clear: "Ste prepričani, da želite počistiti vse zaznamke s te teme?" + topic_count_latest: + one: "{{count}} nova ali posodobljena tema." + two: "{{count}} novi ali posodobljeni temi." + few: "{{count}} nove ali posodobljene teme." + other: "{{count}} novih ali posodobljenih tem." + topic_count_unread: + one: "{{count}} neprebrana tema." + two: "{{count}} neprebrani temi." + few: "{{count}} neprebrane teme." + other: "{{count}} neprebranih tem." + topic_count_new: + one: "{{count}} nova tema." + two: "{{count}} novi temi." + few: "{{count}} nove teme." + other: "{{count}} novih tem." + click_to_show: "Klikni za prikaz." + preview: "predogled" + cancel: "prekliči" + save: "Shrani Spremembe" + saving: "Shranjujem..." + saved: "Shranjeno!" + upload: "Naloži" + uploading: "Nalagam..." + uploading_filename: "Nalagam {{filename}}..." + uploaded: "Naloženo!" + pasting: "Prilepljam..." + enable: "Omogoči" + disable: "Onemogoči" + undo: "Razveljavi" + revert: "Povrni" + failed: "Spodletelo" + switch_to_anon: "Vklopi anonimni način" + switch_from_anon: "Izklopi anonimni način" + banner: + close: "Zavrni ta banner." + edit: "Uredi ta banner >>" + choose_topic: + none_found: "Nobena tema najdena." + title: + search: "Išči Temo po imenu, url ali id:" + placeholder: "vpiši naslov teme tukaj" + queue: + topic: "Tema:" + approve: 'Odobri' + reject: 'Zavrni' + delete_user: 'Izbriši uporabnika' + title: "Potrebuje Odobritev" + none: "Ni objav za pregled." + edit: "Uredi" + cancel: "Prekliči" + view_pending: "ogled čakajočih objav" + has_pending_posts: + one: "Ta tema ima 1 objavo, ki čaka odobritev" + two: "Ta tema ima 2 objavi, ki čakata odobritev" + few: "Ta tema ima {{count}} objav, ki čakajo odobritev" + other: "Ta tema ima {{count}} objavo, ki čakajo odobritev" + confirm: "Shrani Spremembe" + delete_prompt: "Ali ste prepričani, da želite izbrisati %{username}? To bo odstranilo vse njihove objave ter blokiralo njihov e-poštni naslov in IP naslov." + approval: + title: "Objava Potrebuje Odobritev." + description: "Prejeli smo vašo novo objavo vendar potrebuje odobritev iz strani moderatorja preden bo objavljena. Prosimo za potrpežljivost." + pending_posts: + one: "Imate 1 čakajočo objavo." + two: "Imate {{count}} čakajoče objave." + few: "Imate {{count}} čakajočih objav." + other: "Imate {{count}} čakajočih objav." + ok: "V redu" + user_action: + user_posted_topic: "{{user}} je objavil temo" + you_posted_topic: "Ti si objavil temo" + user_replied_to_post: "{{user}} je odgovoril na {{post_number}}" + you_replied_to_post: "Ti si odgovoril na {{post_number}}" + user_replied_to_topic: "{{user}} je odgovoril na temo" + you_replied_to_topic: "Ti si odgovoril na temo" + user_mentioned_user: "{{user}} je omenil {{another_user}}" + user_mentioned_you: "{{user}} je omenil tebe" + you_mentioned_user: "Ti si omenil {{another_user}}" + posted_by_user: "Objavleno od {{user}}" + posted_by_you: "Objavljeno od tebe" + sent_by_user: "Poslano od {{user}}" + sent_by_you: "Poslano od tebe" + directory: + filter_name: "filtriraj po uporabniškem imenu" + title: "Uporabniki" + likes_given: "Dano" + likes_received: "Prejeto" + topics_entered: "Ogledano" + topics_entered_long: "Ogledane Teme" + time_read: "Prebrano" + topic_count: "Teme" + topic_count_long: "Ustvarjene Teme" + post_count: "Odgovori" + post_count_long: "Objavljeni Odgovori" + no_results: "Noben rezultat ni bil najden." + days_visited: "Obiski" + days_visited_long: "Dni obiskov" + posts_read: "Prebrano" + posts_read_long: "Objave Prebrane" + total_rows: + one: "1 uporabnik" + two: "%{count} uporabnika" + few: "%{count} uporabniki" + other: "%{count} uporabnikov" + group_histories: + actions: + change_group_setting: "Spremeni nastavitve za skupino" + add_user_to_group: "Dodaj uporabnika" + remove_user_from_group: "Odstrani uporabnika" + make_user_group_owner: "Spremeni v lastnika" + remove_user_as_group_owner: "Odstrani lastništvo" + groups: + logs: + title: "Dnevnik" + when: "Kdaj" + action: "Aktivnost" + acting_user: "Nastopajoči uporabnik" + target_user: "Ciljni uporabnik" + subject: "Zadeva" + details: "Podrobnosti" + from: "Od" + to: "Za" + edit: + title: 'Uredi skupino' + full_name: 'Ime skupine' + add_members: "Dodaj člane" + delete_member_confirm: "Odstrani '%{username}' iz skupine '%{group}'?" + name_placeholder: "Ime skupine, brez presledkov, enako kot pravilo pri uporabniškem imenu" + public_admission: "Dovoli uporabnikom, da se sami pridružijo skupini (skupina mora biti javna)" + public_exit: "Dovoli uporabnikom da sami zapustijo skupino" + empty: + posts: "Ni objav članov skupine." + members: "Ta skupina nima članov." + mentions: "Ta skupina ni bila nikjer omenjena." + messages: "Ni sporočil za to skupino." + topics: "Člani te skupine nimajo nobene teme." + logs: "Dnevnik za to skupino je prazen." + add: "Dodaj" + join: "Pridruži se skupini" + leave: "Zapusti skupino" + request: "Zaprosi za članstvo v skupini" + message: "Sporočilo" + automatic_group: Avtomatska skupina + closed_group: Zaprta skupina + is_group_user: "Si član te skupine" + allow_membership_requests: "Dovoli uporabnikom, da zaprosijo za članstvo pri lastnikih skupine" + membership_request_template: "Predloga za prikaz uporabnikom, ko zaprosijo za članstvo" + membership_request: + submit: "Zaprosi za članstvo" + title: "Prošnja za pridružitev @%{group_name}" + reason: "Sporoči lastnikom skupine zakaj spadaš v to skupino" + membership: "Članstvo" + name: "Ime" + user_count: "Število članov" + bio: "O skupini" + selector_placeholder: "vnesi uporabniško ime" + owner: "lastnik" + index: + title: "Skupine" + empty: "Ni javnih skupin." + title: + one: "Skupina" + two: "Skupini" + few: "Skupin" + other: "Skupin" + activity: "Aktivnost" + members: "Člani" + topics: "Teme" + posts: "Objave" + mentions: "Omembe" + messages: "Sporočila" + notification_level: "Privzeti nivo obvestil za sporočila skupini" + visibility_levels: + title: "Kdo lahko vidi skupino?" + public: "Vsi" + members: "Lastniki skupne, člani in admini" + staff: "Lastniki skupine in skrbniki" + owners: "Lastniki skupine in admini" + alias_levels: + mentionable: "Kdo lahko @omeni skupino?" + messageable: "Kdo lahko pošlje sporočilo skupini?" + nobody: "Nihče" + only_admins: "Le administratorji" + mods_and_admins: "Le moderatorji in administratorji" + members_mods_and_admins: "Le člani skupine, moderatorji in admnistratorji" + everyone: "Vsi" + trust_levels: + title: "Nivo zaupanja samodejno podeljeno članom ko so dodani:" + none: "Brez" + notifications: + watching: + title: "Spremlja" + description: "Obveščeni boste o vsaki novi objavi v vsakem sporočilu in seštevek novih odgovorov bo prikazan." + watching_first_post: + title: "Opazuješ prvo objavo" + description: "Obveščeni boste samo ob prvi objavi v vsaki novi temi v tej skupini." + tracking: + title: "Sledi" + description: "Obveščeni boste, če nekdo omeni vaše @ime ali odgovori vam in seštevek novih odgovorov bo prikazan." + regular: + title: "Normalno" + description: "Obveščeni boste, če nekdo omeni vaše @ime ali vam odgovori." + muted: + title: "Molčeče" + description: "O novih temah v tej skupini ne boste obveščeni." + flair_bg_color_placeholder: "(Poljubno) Hex barvna vrednost" + flair_preview_icon: "Predogled ikone" + flair_preview_image: "Predogled slike" + user_action_groups: + '1': "Dani všečki" + '2': "Prejeti všečki" + '3': "Zaznamki" + '4': "Teme" + '5': "Odgovori" + '6': "Odzivi" + '7': "Omembe" + '9': "Citati" + '11': "Urejanja" + '12': "Poslano" + '13': "Prejeto" + '14': "Čaka odobritev" + categories: + all: "vse kategorije" + all_subcategories: "vse v %{categoryName}" + no_subcategory: "nič" + category: "Kategorija" + category_list: "Prikaži seznam kategorij" + reorder: + title: "Razvrsti Kategorije" + title_long: "Reorganiziraj seznam kategorij" + fix_order: "Fiksiraj pozicije" + fix_order_tooltip: "Nimajo vse kategorije unikatne številke pozicije kar lahko pripelje do nepričakovanih rezultatov." + save: "Shrani vrstni red" + apply_all: "Uporabi" + position: "Pozicija" + posts: "Objave" + topics: "Teme" + latest: "Najnovejše" + latest_by: "najnovejše po" + toggle_ordering: "preklop nadzora razvrščanja" + subcategories: "Pod Kategorija" + topic_sentence: + one: "1 tema" + two: "1 tema" + few: "%{count} tem" + other: "%{count} tem" + topic_stat_sentence: + one: "%{count} nova tema v preteklih %{unit}." + two: "%{count} novi temi v preteklih %{unit}." + few: "%{count} nove teme v preteklih %{unit}." + other: "%{count} novih tem v preteklih %{unit}." + ip_lookup: + title: Poizvedba IP naslova + hostname: Ime gostitelja + location: Lokacija + location_not_found: (neznano) + organisation: Organizacija + phone: Telefon + other_accounts: "Ostali računi s tem IP naslovom:" + delete_other_accounts: "Izbriši %{count}" + username: "uporabniško ime" + trust_level: "TL" + read_time: "čas prebiranja" + topics_entered: "vstop v teme" + post_count: "# objave" + confirm_delete_other_accounts: "Si prepričan, da želiš izbrisati te račune?" + powered_by: "podprto s strani ipinfo.io" + user_fields: + none: "(izberi možnost)" + user: + said: "{{username}}:" + profile: "Profil" + mute: "Molče" + edit: "Uredi Nastavitve" + download_archive: + button_text: "Prenesi vse" + confirm: "Si prepričan/a, da želiš prenesti svoje objave?" + success: "Prenos se je začel, obveščen/a boš s sporočilom, ki bo prenos končan." + rate_limit_error: "Objave so lahko prevešene enkrat dnevno, poskusi ponovno jutri." + new_private_message: "Novo Sporočilo" + private_message: "Sporočilo" + private_messages: "Sporočila" + activity_stream: "Aktivnost" + preferences: "Nastavitve" + expand_profile: "Razširi" + bookmarks: "Zaznamki" + bio: "O meni" + invited_by: "Povabljen od" + trust_level: "Nivo zaupanja" + notifications: "Obvestila" + statistics: "Statistika" + desktop_notifications: + label: "Namizna obvestila" + not_supported: "Oprosti. Obvestila niso podprta s tem brekalnikom." + perm_default: "Vklopi Obvestila" + perm_denied_btn: "Dovoljenje Zavrnjeno" + perm_denied_expl: "Zavrnili ste dovoljenje za obvestila. Omogočite obvestila v nastavitvah vašega brskalnika." + disable: "Onemogoči Obvestila" + enable: "Omogoči Obvestila" + each_browser_note: "Opomba: To nastavitev morate spremeniti v vseh brskalnikih, ki jih uporabljate." + dismiss_notifications_tooltip: "Označi vsa neprebrana sporočila kot Prebrana" + disable_jump_reply: "Ne skoči na mojo objavo po tem ko odgovorim" + dynamic_favicon: "Pokaži števec novih /posodobljenih tem na ikoni brskalnika" + allow_private_messages: "Dovoli drugim uporabnikom da mi pošiljajo zasebna sporočila." + external_links_in_new_tab: "Odpri vse zunanje povezave v novem zavihku" + enable_quoting: "Omogoči odgovarjanje s citiranjem za poudarjen tekst" + change: "spremeni" + moderator: "{{user}} je moderator" + admin: "{{user}} je admin" + moderator_tooltip: "Uporabnik je moderator" + admin_tooltip: "Uporabnik je admin" + silenced_tooltip: "Ta uporabnik je utišan" + suspended_notice: "Ta uporabnik je suspendiran do {{date}}." + suspended_permanently: "Ta uporabnik je suspendiran" + suspended_reason: "Razlog:" + github_profile: "Github" + email_activity_summary: "Povzetek aktivnosti" + mailing_list_mode: + instructions: | + Ta nastavitev spremeni povzetek aktivnosti.
+ Izključene teme in kategorije niso vključene v ta obvestila. + individual: "Pošlji email za vsako novo objavo" + individual_no_echo: "Pošlji email za vsako novo objavo, razen mojih" + watched_categories: "Spremljano" + tracked_categories: "Sledena" + watched_first_post_tags: "Opazuješ prvo objavo" + muted_categories: "Zamolčano" + muted_categories_instructions: "O temah v teh kategorijah ne boste obveščeni in ne bodo se pojavile med najnovejšimi." + no_category_access: "Kot moderator imaš omejen dostop do kategorije, shranjevanje je onemogočeno" + delete_account: "Izbriši Moj Račun" + delete_account_confirm: "Si prepričan, da želiš trajno izbrisati svoj račun? Tega postopka ni mogoče razveljaviti!" + deleted_yourself: "Vaš račun je bil uspešno izbrisan." + delete_yourself_not_allowed: "Trenutno ne morete izbrisati vašega računa. Kontaktiratje administratorja, da izbriše vaš račun." + unread_message_count: "Sporočila" + admin_delete: "Izbriši" + users: "Uporabniki" + muted_users: "Zamolčano" + muted_users_instructions: "Onemogoči vsa obvestila povezana s temi uporabniki." + muted_topics_link: "Pokaži zamolčane teme" + watched_topics_link: "Pokaži opazovane teme" + tracked_topics_link: "Pokaži sledene teme" + automatically_unpin_topics: "Samodejno odpni temo ko dosežem dno teme." + staff_counters: + flags_given: "oznake v pomoč" + flagged_posts: "označene objave" + deleted_posts: "izbrisane objave" + warnings_received: "opozorila" + messages: + all: "Vse" + inbox: "Prejeto" + sent: "Poslano" + archive: "Arhiv" + groups: "Moje Skupine" + bulk_select: "Izberi Sporočila" + move_to_inbox: "Premakni v Prejeto" + move_to_archive: "Arhiv" + preferences_nav: + profile: "Profil" + emails: "Emaili" + notifications: "Obvestila" + categories: "Kategorije" + tags: "Oznake" + interface: "Uporabniški vmesnik" + apps: "Aplikacije" + change_password: + success: "(email poslan)" + in_progress: "(pošiljanje emaila)" + error: "(napaka)" + action: "Pošlji sporočilo za ponastavitev gesla" + set_password: "Nastavi geslo" + choose_new: "Izberite novo geslo" + choose: "Izberite geslo" + second_factor: + enabled_status: "Status: vklopljeno" + disabled_status: "Status: izključeno" + change_about: + title: "Spremeni O meni" + change_username: + title: "Spremeni uporabniško ime" + confirm: "Če spremenite uporabniško ime, vaši obstoječi citati in omembe @imena ne bodo več delovali. Ali ste prepričani da to želite?" + taken: "Oprosti, to uporabniško ime je zasedeno." + invalid: "Uporabniško ime ni pravilno. Vsebuje lahko samo črke in številke. Preslednica in posebni znaki niso dovoljeni." + change_email: + title: "Spremeni email" + taken: "Ta email ni na voljo" + error: "Prišlo je do napake pri menjavi emaila. Je ta email že v uporabi?" + change_avatar: + title: "Menjaj profilno sliko" + gravatar_title: "Spremeni svoj avatar na Gravatar strani" + refresh_gravatar_title: "Osveži svoj Gravatar" + letter_based: "Sistemsko privzeta profilna slika" + uploaded_avatar: "Slika po meri" + uploaded_avatar_empty: "Dodaj sliko po meri" + upload_title: "Prenesi svojo sliko" + upload_picture: "Prenesi sliko" + image_is_not_a_square: "Opozorilo: obrezali smo vašo sliko; širina in višina nista bili enaki." + cache_notice: "Uspešno ste menjali profilno sliko, vendar utegne zaradi brskalnikovega predpomnilnika trajati nekaj časa, da se pojavi na strani." + change_profile_background: + title: "Ozadje profila" + instructions: "Ozadje profila bo centrirano in imelo širino 850px." + change_card_background: + title: "Ozadje kartice uporabnika" + email: + title: "Email" + instructions: "nikoli ne pokaži javno" + invalid: "Prosim vnesite veljaven email naslov" + name: + title: "Ime" + instructions: "vaše polno ime (neobvezno)" + instructions_required: "Vaše polno ime" + too_short: "Ime je prekratko" + username: + title: "Uporabniško ime" + instructions: "unikatno, brez presledkov, kratko" + short_instructions: "Ljudje vas lahko omenijo kot @{{username}}" + available: "Vaše uporabniško ime je prosto" + not_available: "Ni na voljo. Poskusi {{suggestion}}?" + not_available_no_suggestion: "Ni na voljo" + too_short: "Vaše uporabniško ime je prekratko" + too_long: "Vaše uporabniško ime je predolgo" + checking: "Preverjam če je uporabniško ime prosto..." + prefilled: "Email ustreza uporabniškemu imenu" + locale: + title: "Jezik vmesnika" + instructions: "Jezik uporabniškega vmesnika. Zamenjal se bo ko boste osvežili stran." + default: "(privzeto)" + any: "katerokoli" + password_confirmation: + title: "Geslo - ponovno" + last_posted: "Zadnja objava" + last_seen: "Viden/a" + created: "Pridružen/a" + log_out: "Izpis" + location: "Lokacija" + card_badge: + title: "Značka kartice uporabnika" + website: "Spletna stran" + email_settings: "Email" + like_notification_frequency: + title: "Obvesti o všečkih" + always: "Zmeraj" + first_time_and_daily: "Prvič ko je objava všečkana in dnevno" + first_time: "Prvič ko je objava všečkana" + never: "Nikoli" + email_previous_replies: + title: "Vključi prejšnje odgovore na koncu emailov" + unless_emailed: "če ni že poslano" + always: "zmeraj" + never: "nikoli" + email_digests: + title: "Kadar ne obiščem redno, mi pošljite pregled popularnih tem in objav." + every_30_minutes: "vsakih 30 minut" + every_hour: "vsako uro" + daily: "dnevno" + every_three_days: "vsake tri dni" + weekly: "tedensko" + every_two_weeks: "vsaka dva tedna" + include_tl0_in_digests: "V preglednih emailih vključi vsebino, ki so jo dodali novi uporabniki " + email_direct: "Pošlji mi email, kadar me kdo citira, odgovori na mojo objavo, omeni moje @uporabniško ime ali pa me povabi k temi" + email_private_messages: "Pošlji mi email, kadar prejmem sporočilo" + email_always: "Pošlji mi email obvestila tudi kadar sem aktivna/en na strani" + other_settings: "Ostalo" + categories_settings: "Kategorije" + new_topic_duration: + after_1_day: "ustvarjeno v zadnjem dnevu" + after_2_days: "ustvarjeno v zadnjih 2 dnevih" + after_1_week: "ustvarjeno v zadnjem tednu" + after_2_weeks: "ustvarjeno v zadnjih 2 tednih" + auto_track_options: + never: "nikoli" + immediately: "takoj" + after_30_seconds: "po 30 sekundah" + after_1_minute: "po 1 minuti" + after_2_minutes: "po 2 minutah" + after_3_minutes: "po 3 minutah" + after_4_minutes: "po 4 minutah" + after_5_minutes: "po 5 minutah" + after_10_minutes: "po 10 minutah" + invited: + title: "Povabila" + user: "Povabljen uporabnik" + sent: "Poslano" + posts_read_count: "Prebranih objav" + rescind: "Odstrani" + rescinded: "Vabilo odstranjeno" + rescind_all: "Odstrani vsa vabila" + rescinded_all: "Vsa vabila so odstranjena!" + rescind_all_confirm: "Ste prepričani, da želite odstraniti vsa vabila?" + reinvite: "Ponovno pošlji vabilo" + reinvite_all: "Ponovno pošlji vsa vabila" + reinvite_all_confirm: "Ste prepričani, da želite ponovno poslati vsa vabila?" + reinvited: "Vabilo ponovno poslano" + reinvited_all: "Vsa vabila ponovno poslana!" + time_read: "Čas branja" + account_age_days: "Starost računa v dnevih" + create: "Pošlji vabilo" + password: + title: "Geslo" + too_short: "Vaše geslo je prekratko." + common: "To geslo je preveč običajno." + same_as_username: "Vađe geslo je enako kot uporabniško ime." + same_as_email: "Vaše geslo je enako kot uporabniško ime." + ok: "Vaše geslo je videti v redu." + summary: + title: "Vsebina" + stats: "Statistika" + time_read: "čas branja" + recent_time_read: "nedaven čas branja" + post_count: + one: "ustvarjena objava" + two: "ustvarjeni objavi" + few: "ustvarjenih objav" + other: "ustvarjenih objav" + topics_entered: + one: "ogledana tema" + two: "ogledani temi" + few: "ogledanih tem" + other: "ogledanih tem" + posts_read: + one: "prebrana objava" + two: "prebrani objavi" + few: "prebranih objav" + other: "prebranih objav" + top_replies: "Najboljši odgovori" + top_topics: "Najboljše teme" + no_topics: "Ni tem." + more_topics: "Več tem" + top_badges: "Najboljše značke" + top_links: "Najboljše povezave" + stream: + posted_by: "Objavil" + the_topic: "tema" + errors: + reasons: + forbidden: "Dostop Zavrnjen" + unknown: "Napaka" + desc: + server: "Oznaka napake: {{status}}" + unknown: "Nekaj je šlo narobe." + buttons: + back: "Nazaj" + close: "Zapri" + logout: "Bili ste odjavljeni." + too_few_topics_and_posts_notice: "Začnimo z diskusijo! Trenutno imamo %{currentTopics} / %{requiredTopics} tem in %{currentPosts} / %{requiredPosts} objav. Novi obiskovalci potrebujejo vsebine, ki jih lahko preberejo in se nanje odzovejo." + too_few_topics_notice: "Začnimo z diskusijo! Trenutno imamo %{currentTopics} / %{requiredTopics} tem. Novi obiskovalci potrebujejo vsebine, ki jih lahko preberejo in se nanje odzovejo." + too_few_posts_notice: "Začnimo z diskusijo! Trenutno imamo %{currentPosts} / %{requiredPosts} objav. Novi obiskovalci potrebujejo vsebine, ki jih lahko preberejo in se nanje odzovejo." + all_time_desc: 'skupaj ustvarjenih tem' + year: 'leto' + year_desc: 'teme, ustvarjene v preteklih 365 dneh' + month_desc: 'teme, ustvarjene v preteklih 30 dneh' + week: 'teden' + week_desc: 'teme, ustvarjene v preteklih 7 dneh' + first_post: Prva objava + last_post: Objavljeno + replies_lowercase: + one: odgovor + two: odgovora + few: odgovorov + other: odgovorov + summary: + enabled_description: "Ogledujete si povzetek teme: najbolj zanimive objave, kot jih je določila skupnost." + enable: 'Pripravi povzetek teme' + disable: 'Prikaži vse objave' + deleted_filter: + enabled_description: "Ta tema vsebuje izbrisane objave, ki so skrite." + disabled_description: "Izbrisane objave so prikazane." + enable: "Skrij izbrisane objave" + disable: "Prikaži izbrisane objave" + username: 'Uporabniško ime' + search_hint: 'uporabniško ime, email ali IP naslov' + create_account: + failed: "Nekaj je šlo narobe, morda je ta email naslov že registriran, poskusite povezavo za pozabljeno geslo" + forgot_password: + title: "Ponastavitev gesla" + action: "Pozabil sem geslo" + invite: "Vpišite uporabniško ime ali email in poslali vam bomo email za ponastavitev gesla." + reset: "Ponastavi geslo" + help: "Ali elektronska pošta ne prihaja? Najprej preverite mapo z neželeno pošto.

Ne veste kateri elektronski naslov ste uporabili? Vpišite elektronski naslov in povedali vam bomo če obstaja pri nas.

Če nimate več dostopa do elektronskega naslova vašega računa kontaktirajte o našo ekipo.

" + login: + title: "Prijava" + username: "Uporabnik" + password: "Geslo" + email_placeholder: "email ali uporabniško ime" + blank_username_or_password: "Prosimo vpišite email ali uporabniško ime in geslo." + reset_password: 'Ponastavi geslo' + awaiting_activation: "Vaš uporabniški račun čaka aktivacijo, uporabite povezavo za pozabljeno geslo če želite prejeti še en email za aktivacijo." + provide_new_email: "Vpišite nov naslov in ponovno vam bomo poslali potrditveno sporočilo." + to_continue: "Prosimo če se prijavite" + preferences: "Za spremembo nastavitev morate biti prijavljeni." + google: + message: "Overjanje z Googlom (pazi, da pojavno okno ne bo preprečeno)" + google_oauth2: + message: "Overjanje z Googlom (pazi, da pojavno okno ne bo preprečeno)" + twitter: + message: "Overjanje s Twittrom (pazi, da pojavno okno ne bo preprečeno)" + facebook: + message: "Overjanje s Facebookom (pazi, da pojavno okno ne bo preprečeno)" + yahoo: + message: "Overjanje z Yahoojem (pazi, da pojavno okno ne bo preprečeno)" + github: + message: "Overjanje z GitHubom (pazi, da pojavno okno ne bo preprečeno)" + invites: + password_label: "Nastavi geslo" + category_page_style: + categories_with_featured_topics: "Kategorije z izpostavljenimi temami" + categories_and_latest_topics: "Kategorije in najnovejše teme" + emoji_picker: + activity: Aktivnost + composer: + cannot_see_mention: + category: "Omenil si uporabnika {{username}} , a ta ne bo obveščen saj nima dostopa do te kategorije. Dodati ga je potrebno v skupino ki lahko dostopa do kategorije." + private: "Omenili ste uporabnika {{username}} , a ta ne bo obveščen, ker ne more videti tega osebnega sporočila. Moraii ga boste povabiti v to osebno sporočilo." + error: + post_missing: "Objava ne sme biti prazna" + post_length: "Objava mora vsebovati vsaj {{min}} znakov" + category_missing: "Izbrati morate kategorijo" + reply_here: "Odgovori tu" + reply: "Odgovori" + create_topic: "Ustvari temo" + users_placeholder: "Dodaj uporabnika" + title_or_link_placeholder: "Na tem mestu vpiši naslov ali prilepi povezavo " + reply_placeholder: "Tu lahko pišeš. Možna je uporaba Markdown, BBcode ali HTML za oblikovanje. Sem lahko povlečeš ali prilepiš sliko." + view_new_post: "Oglej si tvojo novo objavo" + saved_draft: "Osnutek objave v pripravi. Izberi za nadaljevanje." + quote_post_title: "Citiraj celo objavo" + paste_code_text: "vpiši ali prilepi kodo" + composer_actions: + toggle_whisper: + desc: Šepeti so vidni samo skrbnikom + notifications: + mentioned: "{{username}} {{description}}" + group_mentioned: "{{username}} {{description}}" + quoted: "{{username}} {{description}}" + replied: "{{username}} {{description}}" + posted: "{{username}} {{description}}" + edited: "{{username}} {{description}}" + liked: "{{username}} {{description}}" + liked_2: "{{username}}, {{username2}} {{description}}" + liked_many: + one: "{{username}}, {{username2}} in še 1 {{description}}" + two: "{{username}}, {{username2}} in 2 druga {{description}}" + few: "{{username}}, {{username2}} in {{count}} drugih {{description}}" + other: "{{username}}, {{username2}} in {{count}} drugih {{description}}" + private_message: "{{username}} {{description}}" + invited_to_private_message: "

{{username}} {{description}}" + invited_to_topic: "{{username}} {{description}}" + invitee_accepted: "{{username}} je sprejel tvoje vabilo" + linked: "{{username}} {{description}}" + topic_reminder: "{{username}} {{description}}" + alt: + posted: "Objavil" + edited: "Urejeno s strani " + liked: "Všečkal tvojo objavo" + moved_post: "Tvojo objavo je premaknil " + linked: "Povezava do tvoje objave" + search: + sort_by: "Uredi po" + relevance: "Relevanci" + latest_post: "Zadnja objava" + latest_topic: "Zadnja tema" + most_viewed: "Številu ogledov" + most_liked: "Številu všečkov" + select_all: "Izberi vse" + clear_all: "Počisti vse" + too_short: "Niz za iskanje je prekratek" + result_count: + one: "1 zadetek za {{term}}" + two: "{{count}}{{plus}} zadetka za {{term}}" + few: "{{count}}{{plus}} zadetkov za {{term}}" + other: "{{count}}{{plus}} zadetkov za {{term}}" + title: "išči po temah, objavah, uporabnikih ali kategorijah" + no_results: "Iskanje nima zadetkov." + no_more_results: "Ni več zadetkov iskanja." + searching: "Iščem ..." + post_format: "#{{post_number}} od {{username}}" + results_page: "Zadetki iskanja za '{{term}}'" + more_results: "Obstaja več zadetkov. Prosimo zožite kriterije iskanja." + cant_find: "Ne najdete tega kar iščete?" + start_new_topic: "Bi mogoče ustvarili novo temo?" + or_search_google: "Ali poskusite iskati z Googlom:" + search_google: "Poskusite iskati z Googlom:" + search_google_button: "Google" + search_google_title: "Išči po tem spletnem mestu" + context: + user: "Išči objave od @{{username}}" + category: "Išči po #{{category}} kategoriji" + topic: "Išči po tej temi" + private_messages: "Išči po sporočilih" + advanced: + title: Napredno iskanje + posted_by: + label: Objavil + in_category: + label: Kategorizirano + in_group: + label: V skupini + with_badge: + label: Z značko + with_tags: + label: Označeno + filters: + label: Vrni samo teme/objave... + likes: Ki sem jih všečkal + posted: Objavil sem v + watching: Ki jih opazujem + tracking: Ki jim sledim + private: V mojih sporočilih + bookmarks: Kjer sem ustvaril zaznamek + first: ob prvi objavi + pinned: so pripete + unpinned: niso pripete + seen: Ki sem jih prebral + unseen: Ki jih nisem prebral + wiki: so wiki + images: Vsebujejo sliko(e) + all_tags: Vse zgornje oznake + statuses: + label: Kjer tema + open: so odprte + closed: so zaprte + archived: so arhivirane + noreplies: nimajo nobenega odgovora + single_user: vključujejo samo enega uporabnika + post: + count: + label: Minimalno število objav + time: + label: Objavljeno + before: pred + after: po + hamburger_menu: "pojdi na drug seznam tem ali kategorijo" + not_logged_in_user: 'stran uporabnika s povzetkom trenutnih aktivnosti in nastavitev' + current_user: 'pojdi na svojo uporabniško stran' + topics: + new_messages_marker: "zadnji obisk" + bulk: + select_all: "Izberi vse" + clear_all: "Počisti vse" + unlist_topics: "Odstrani temo s seznama" + relist_topics: "Postavi temo nazaj na seznam" + reset_read: "Ponastavi prebrano" + delete: "Izbriši teme" + dismiss: "Zavrni" + dismiss_read: "Zavrni vse neprebrane" + dismiss_button: "Zavrni" + dismiss_new: "Zavrni Nove" + change_category: "Določi kategorijo" + choose_new_category: "Izberi novo kategorijo za temo:" + none: + unread: "Nimate neprebranih tem." + new: "Nimate novih tem." + latest: "Ni najnovejših tem. " + bookmarks: "Zaenkrat še nimate tem z zaznamki." + category: "Ni tem za kategorijo {{category}} ." + educate: + new: '

Tu se prikažejo vaše nove teme.

Privzeto se teme prikaĹľejo kot nove in bodo imele oznako novo ÄŤe so bile ustvarjene v zadnjih 2 dneh.

Obiščite nastavitve če želite spremembo.

' + unread: '

Tu se prikažejo vaše neprebrane teme.

Privzeto se teme prikažejo kot neprebrane z prikazom števila 1 če ste:

  • Ustvarili temo
  • Odgovorili na temo
  • Brali temo veÄŤ kot 4 minute

Izrecno nastavili temo kot "Opazovano" ali "Sledeno" preko nastavitev obveščenja na dnu teme.

Pojdite na nastavitve ÄŤe Ĺľelite spremembo.

' + bottom: + latest: "Ni veÄŤ najnovejših tem" + hot: "Ni veÄŤ vroÄŤih tem." + posted: "Ni veÄŤ objavljenih tem." + read: "Ni veÄŤ prebranih tem." + new: "Ni veÄŤ novih tem-" + unread: "Ni veÄŤ neprebranih tem." + category: "Ni veÄŤ tem v kategoriji {{category}}." + top: "Ni veÄŤ najboljših tem." + bookmarks: "Ni veÄŤ tem z zaznamki." + topic: + create: 'Nova tema' + create_long: 'Ustvari novo temo' + new: 'nova tema' + new_topics: + one: '1 nova tema' + two: '2 novi temi' + few: '{{count}} novih tem' + other: '{{count}} novih tem' + title: 'Tema' + invalid_access: + title: "Tema je zasebna" + description: "Oprostite, do te teme nimate dostopa!" + browse_all_categories: Brskajte po vseh kategorijah + view_latest_topics: poglej najnovejše teme + topic_status_update: + when: "Kdaj:" + private_timer_types: ÄŚasovne nastavitve uporabniških tem + auto_update_input: + none: "Izberi ÄŤasovni okvir" + later_today: "Kasneje v dnevu" + tomorrow: "Jutri" + later_this_week: "Kasneje v tednu" + this_weekend: "Ta vikend" + next_week: "Naslednji teden" + two_weeks: "Dva tedna" + next_month: "Naslednji mesec" + three_months: "Tri mesece" + six_months: "Ĺ est mesecev" + one_year: "Eno leto" + pick_date_and_time: "Izberi datum in ÄŤas" + set_based_on_last_post: "Kloniraj na podlagi zadnje objave" + reminder: + title: "Opomni me" + status_update_notice: + auto_publish_to_category: "Ta tema bo objavljena v #%{categoryName} %{timeLeft}." + timeline: + back: "Nazaj" + progress: + go_top: "na vrh" + go_bottom: "dno" + go: "pojdi" + jump_bottom: "skoÄŤi na zadnjo objavo" + jump_prompt: "skoÄŤi na..." + jump_prompt_of: "od %{count} objav" + jump_bottom_with_number: "skoÄŤi do objave %{post_number}" + total: število objav + notifications: + title: spremenite kako pogosto Ĺľelite biti obveščeni o tej temi + reasons: + mailing_list_mode: "OmogoÄŤen imate naÄŤin seznama elektronske pošte, zato boste o tej temi obveščeni preko elektronske pošte." + '3_6': 'Prejemali boste obvestila ker opazujete to kategorijo.' + '2_8': 'Videli boste število novih odgovorov, ker sledite tej kategoriji.' + '2_4': 'Videli boste število novih odgovorov, ker ste objavili odgovor na to temo.' + '2_2': 'Videli boste število novih odgovorov, ker sledite tej temi.' + '2': 'Videli boste število novih odgovorov, ker ste . prebrali to temo.' + '0_7': 'Ignorirate vsa obvestila za to kategorijo.' + watching_pm: + title: "Opazujem" + watching: + title: "Opazujem" + tracking_pm: + title: "Sledim" + tracking: + title: "Sledim" + regular: + title: "Normalno" + regular_pm: + title: "Normalno" + muted_pm: + title: "Utišano" + muted: + title: "Utišano" + description: "Nikoli ne boste obveščeni o tej temi. Ta tema se ne bo pojavila med najnovejšimi." + actions: + delete: "Izbriši temo" + open: "Odpri temo" + close: "Zapri temo" + pin: "Pripni temo" + unpin: "Odpni temo" + archive: "Arhiviraj temo" + make_public: "Spremeni v javno temo" + make_private: "Spremeni v osebno sporoÄŤilo" + feature: + pin: "Pripni temo" + unpin: "Odpni temo" + pin_globally: "Pripni temo globalno" + reply: + title: 'Odgovori' + help: 'zaÄŤni sestavljati odgovor na to temo' + clear_pin: + title: "Odpni temo" + share: + title: 'Deli' + help: 'deli povezavo do te teme' + print: + title: 'Natisni' + help: 'Odpri tiskalniku prilagojeno verzijo te teme' + flag_topic: + title: 'Zastavica' + success_message: 'Uspešno ste oznaÄŤili to temo.' + feature_topic: + pin: "Naj se ta tema prikaĹľe na vrhu kategorije {{categoryLink}} do" + confirm_pin: "Trenutno imate {{count}} pripetih tem. PreveÄŤ pripetih tem je lahko breme za nove in anonimne uporabnike. Ali ste prepriÄŤani da Ĺľelite pripeti še eno temo v to kategorijo?" + pin_validation: "Datum je zahtevan, ÄŤe Ĺľelite pripeti to temo." + not_pinned: "Ni pripetih tem v {{categoryLink}}." + already_pinned: + one: "Trenutno pripeta tema v {{categoryLink}}: {{count}}" + two: "Trenutno pripeti temi v {{categoryLink}}: {{count}}" + few: "Trenutno pripete teme v {{categoryLink}}: {{count}}" + other: "Trenutno pripete teme v {{categoryLink}}: {{count}}" + pin_globally: "Ta tema naj se pojavi na vrhu vseh seznamov tem do" + confirm_pin_globally: "Trenutno imate {{count}} globalno pripetih tem. PreveÄŤ pripetih tem je lahko breme za nove in anonimne uporabnike. Ali ste prepriÄŤani da Ĺľelite pripeti še eno globalno temo?" + unpin_globally: "Odstrani to temo z vrha vseh seznamov tem." + global_pin_note: "Uporabniki lahko sami zase odpnejo temo." + not_pinned_globally: "Ni globalno pripetih tem." + already_pinned_globally: + one: "Trenutno globalno pripeta tema: 1" + two: "Trenutno globalno pripeti temi: 2" + few: "Trenutno globalno pripete teme: {{count}}" + other: "Trenutno globalno pripete teme: {{count}}" + invite_private: + title: 'Povabi k sporoÄŤilu' + email_or_username: "Email ali uporabniško ime vabljenega" + email_or_username_placeholder: "email ali uporabniško ime " + action: "Povabi" + success: "Povabili smo uporabnico/ka, da sodeluje v tem pogovoru." + success_group: "Povabili smo skupino, da sodeluje v tem pogovoru." + error: "Prišlo je do napake ob vabilu uporabnika." + group_name: "ime skupine" + invite_reply: + title: 'Povabi' + username_placeholder: "uporabniško ime" + action: 'Pošlji vabilo' + login_reply: 'Za odgovor je potrebna prijava' + filters: + cancel: "Odstrani filter" + split_topic: + title: "Prestavi v novo temo" + action: "prestavi v novo temo" + topic_name: "Ime nove teme" + post: + quote_reply: "Citiraj" + abandon: + no_value: "Ne, ohrani" + yes_value: "Ja, zavrĹľi" + controls: + edit: "uredi objavo" + edit_action: "Uredi" + delete: "izbriši objavo" + more: "VeÄŤ" + actions: + flag: 'Zastavica' + revisions: + controls: + edit_wiki: "Uredi wiki" + edit_post: "Uredi objavo" + displays: + inline: + button: 'HTML' + side_by_side: + button: 'HTML' + raw_email: + displays: + text_part: + button: 'Besedilo' + html_part: + button: 'HTML' + category: + none: '(brez kategorije)' + all: 'Vse kategorije' + choose: 'Izberi kategorijo…' + edit: 'uredi' + edit_long: "Uredi" + view: 'Poglej teme v kategoriji' + tags_allowed_tags: "V tej kategoriji dovoli samo te oznake:" + tags_allowed_tag_groups: "V tej kategoriji bodo dovoljene samo oznake teh skupin:" + topic_featured_link_allowed: "Dovoli najboljše povezave v tej kategoriji" + delete: 'Izbriši kategorijo' + create: 'Nova kategorija' + create_long: 'Ustvari novo kategorijo' + save: 'Shrani kategorijo' + slug: 'Url kategorije' + creation_error: Prišlo je do napake pri kreiranju kategorije. + save_error: Prišlo je do napake pri shranjevanju kategorije. + name: "Ime kategorije" + description: "Opis" + topic: "kategorija teme" + logo: "Logotip slika kategorije" + background_image: "Slika ozadja kategorije" + badge_colors: "Barve znaÄŤk" + background_color: "Barva ozadja" + foreground_color: "Barva ospredja" + delete_confirm: "Ste prepriÄŤani da Ĺľelite izbrisati kategorijo?" + delete_error: "Prišlo je do napake pri brisanju kategorije." + no_description: "Prosimo dodajte opis kategorije." + change_in_category_topic: "Uredi opis" + already_used: 'Ta barva je Ĺľe uporabljena na drugi kategoriji.' + security: "Varnost" + special_warning: "Ta kategorija je prednastavljena, zato varnostnih nastavitev ni mogoÄŤe urejati. ÄŚe ne Ĺľelite uporabljati te kategorije jo raje izbrišite kot uporabljajte v drug namen." + images: "Slike" + mailinglist_mirror: "Kategorija zrcali poštni seznam" + show_subcategory_list: "PrikaĹľi seznam podkategorij nad temami za to kategorijo." + subcategory_num_featured_topics: "Ĺ tevilo osrednjih tem na strani nadrejene kategorije:" + subcategory_list_style: "Stil seznama podkategorij:" + allow_badges_label: "OmogoÄŤi nagrajevanje z znaÄŤkami v tej kategoriji" + this_year: "to leto" + position_disabled: "Kategorije bodo razvrščene glede na aktivnost. Za kontrolo razvrščanja kategorij na seznamih, " + position_disabled_click: 'omogoÄŤi nastavitev "fiksnih poloĹľajev kategorij"' + parent: "Nadrejena kategorija" + notifications: + regular: + title: "Normalno" + muted: + title: "Utišan" + description: "O novih temah v teh kategorijah ne boste obveščeni in ne bodo se pojavile med najnovejšimi." + sort_options: + default: "privzeto" + likes: "VšeÄŤki" + op_likes: "VšeÄŤki originalne objave" + views: "Ogledi" + posts: "Objave" + activity: "Aktivnost" + posters: "Avtorji" + category: "Kategorija" + created: "Ustvarjeno" + sort_ascending: 'NaraščajoÄŤe' + sort_descending: 'PadajoÄŤe' + flagging: + action: 'Postavi zastavico na objavo' + notify_action: 'SporoÄŤilo' + ip_address_missing: "(N/A)" + hidden_email_address: "(skrito)" + flagging_topic: + action: "Postavi zastavico na objavo" + notify_action: "SporoÄŤilo" + topic_map: + title: "Povzetek teme" + links_title: "Popularne povezave" + topic_statuses: + pinned_globally: + title: "Pripeto globalno" + help: "Ta tema je pripeta globalno; prikazala se bo na vrhu zadnjih in njene kategorije" + pinned: + title: "Pripeto" + help: "Ta tema je pripeta za vas; prikazala se bo na vrhu njene kategorije" + posts: "Objave" + original_post: "Izvirna objava" + views: "Ogledi" + views_lowercase: + one: "ogleda" + two: "ogleda" + few: "ogledov" + other: "ogledov" + replies: "Odgovori" + activity: "Aktivnost" + likes: "VšeÄŤki" + likes_lowercase: + one: "všeÄŤek" + two: "všeÄŤka" + few: "všeÄŤki" + other: "všeÄŤki" + users: "Uporabniki" + users_lowercase: + one: "uporabnik" + two: "uporabnika" + few: "uporabnikov" + other: "uporabnikov" + category_title: "Kategorija" + filters: + with_topics: "%{filter} tem" + with_category: "%{filter} %{category} tem" + latest: + title: "Najnovejše" + title_with_count: + one: "Zadnji (1)" + two: "Zadnja ({{count}})" + few: "Zadnjih ({{count}})" + other: "Zadnjih ({{count}})" + help: "teme z nedavnimi objavami" + hot: + title: "VroÄŤe" + help: "izbira najbolj vroÄŤih tem" + read: + title: "Prebrano" + help: "teme, ki jih prebrali, urejene po ÄŤasu zadnjega branja" + search: + title: "Iskanje" + help: "išči po vseh temah" + categories: + title: "Kategorije" + title_in: "Kategorija - {{categoryName}}" + help: "vse teme grupirane po kategorijah" + unread: + title: "Neprebrano" + title_with_count: + one: "Neprebrani (1)" + two: "Neprebrani (2)" + few: "Neprebrani ({{count}})" + other: "Neprebranih ({{count}})" + help: "teme, ki jih spremljate ali jim sledite z neprebranimi sporoÄŤili" + lower_title_with_count: + one: "1 neprebran" + two: "{{count}} neprebrana" + few: "{{count}} neprebranih" + other: "{{count}} neprebranih" + new: + lower_title_with_count: + one: "1 nov" + two: "2 novi" + few: "{{count}} novih" + other: "{{count}} novih" + lower_title: "novo" + title: "Novo" + title_with_count: + one: "Novi (1)" + two: "Novi (1)" + few: "Novi (1)" + other: "Novi ({{count}})" + help: "teme ustvarjene v zadnjih dneh" + posted: + help: "teme v katerih ste objavljali" + bookmarks: + title: "Zaznamki" + help: "teme v zaznamkih" + category: + title: "{{categoryName}}" + title_with_count: + one: "{{categoryName}} (1)" + two: "{{categoryName}} ({{count}})" + few: "{{categoryName}} ({{count}})" + other: "{{categoryName}} ({{count}})" + help: "zadnje teme v kategoriji {{categoryName}}" + top: + title: "Najboljše" + help: "najbolj aktivne teme v zadnjem letu, mesecu, tednu ali dnevu" + all: + title: "Ves ÄŤas" + yearly: + title: "V letu" + quarterly: + title: "V ÄŤetrtletju" + monthly: + title: "MeseÄŤno" + weekly: + title: "Tedensko" + daily: + title: "Dnevno" + all_time: "Ves ÄŤas" + this_year: "Leto" + this_quarter: "ÄŚetrtletje" + this_month: "Mesec" + this_week: "Teden" + today: "Danes" + other_periods: "poglej najboljše" + keyboard_shortcuts_help: + jump_to: + top: 'Na vrh' + actions: + flag: '! Zastavica na objavo' + badges: + title: ZnaÄŤke + tagging: + filters: + with_category: "%{filter} %{tag} tem v %{category}" + untagged_with_category: "%{filter} neoznaÄŤenih tem v %{category}" + groups: + new: "Nova skupina" + parent_tag_placeholder: "Poljubno" + save: "Shrani" + delete: "Izbriši" + topics: + none: + unread: "Nimate neprebranih tem." + new: "Nimate novih tem." + bookmarks: "Zaenkrat še nimate tem z zaznamki." + bottom: + latest: "Ni veÄŤ zadnjih tem." + hot: "Ni veÄŤ vroÄŤih tem." + posted: "Ni veÄŤ objavljenih tem." + read: "Ni veÄŤ prebranih tem." + new: "Ni veÄŤ novih tem-" + unread: "Ni veÄŤ neprebranih tem." + top: "Ni veÄŤ najboljših tem." + bookmarks: "Ni veÄŤ tem z zaznamki." + invite: + custom_message_link: "sporoÄŤilo po meri" + custom_message_placeholder: "Vnesi sporoÄŤilo po meri" + admin_js: + admin: + title: 'Discourse Admin' + moderator: 'Moderator' + dashboard: + version: "Verzija" + installed_version: "Nameščeno" + latest_version: "Najnovejše" + moderators: 'Moderatorji:' + admins: 'Admini:' + backups: "varnostne kopije" + page_views: "Ogledov strani" + page_views_short: "Ogledov strani" + reports: + today: "Danes" + yesterday: "VÄŤeraj" + last_7_days: "Zadnjih 7 dni" + last_30_days: "Zadnjih 30 dni" + 7_days_ago: "pred 7 dnevi" + 30_days_ago: "pred 30 dnevi" + view_table: "tabela" + view_graph: "graf" + start_date: "ZaÄŤetni datum" + end_date: "KonÄŤni datum" + flags: + agree_flag_hide_post: "Skrij objavo" + agree_flag_suspend: "Suspendiraj uporabnika" + agree_flag_silence: "Utišaj uporabnika" + agree_flag: "Ohrani objavo" + ignore_flag: "Ignoriraj" + delete: "Izbriši" + delete_flag_modal_title: "Izbriši in..." + disagree_flag_unhide_post_title: "Odstrani zastave s te objave in naredi objavo spet vidno" + more: "(veÄŤ odgovorov...)" + suspend_user: "Suspendiraj uporabnika" + system: "Sistem" + error: "Prišlo je do napake" + reply_message: "Odgovori" + show_full: "pokaĹľi celo objavo" + show_details: "PokaĹľi podrobnosti zastave" + details: "podrobnosti" + flagged_topics: + topic: "Tema" + users: "Uporabniki" + short_names: + off_topic: "ne ustreza temi (off-topic)" + inappropriate: "neprimerno" + spam: "neĹľeleno" + notify_user: "po meri" + notify_moderators: "po meri" + groups: + primary: "Primarna skupina" + no_primary: "(ni primarne skupine)" + title: "Skupine" + edit: "Uredi skupino" + refresh: "OsveĹľi" + group_members: "ÄŚlani skupine" + delete: "Izbriši" + delete_confirm: "Izbriši skupino?" + add: "Dodaj" + add_members: "Dodaj ÄŤlane" + custom: "Po meri" + bulk_complete: "Uporabnice/ki, ki so bili dodani v skupino" + bulk_select: "(izberi skupino)" + automatic: "Avtomatsko" + default_title: "Privzet naziv za vse uporabnice/ke v tej skupini" + primary_group: "Avtomatsko nastavi kot primarno skupino" + group_owners: Lastniki + add_owners: Dodaj lastnike + incoming_email_placeholder: "vnesite email naslov" + api: + user: "Uporabnik" + title: "API" + key: "API kljuÄŤ" + all_users: "Vsi uporabniki" + web_hooks: + create: "Ustvari" + save: "Shrani" + destroy: "Izbriši" + description: "Opis" + secret: "Skriven" + wildcard_event: "Pošlji mi vse." + user_event: + name: "Dogodek uporabnika" + delivery_status: + inactive: "Nedejaven" + successful: "Uspešno" + events: + timestamp: "Ustvarjeno" + plugins: + title: "VtiÄŤniki" + installed: "Nameščeni vtiÄŤniki" + name: "Ime" + version: "Verzija" + enabled: "VkljuÄŤeno?" + is_enabled: "Y" + not_enabled: "N" + change_settings: "Spremeni nastavitve" + change_settings_short: "Nastavitve" + howto: "Kako namestim vtiÄŤnike?" + backups: + title: "Varnostne kopije" + menu: + backups: "Varnostne kopije" + columns: + filename: "Ime datoteke" + size: "Velikost" + upload: + label: "NaloĹľi" + uploading: "Nalagam..." + operations: + cancel: + label: "PrekliÄŤi" + backup: + label: "Varnostna kopija" + title: "Ustvari varnostno kopijo" + confirm: "Ĺ˝elite ustvariti varnostno kopijo?" + without_uploads: "Ja (ne vkljuÄŤi datotek)" + download: + label: "NaloĹľi" + export_csv: + button_text: "Izvozi" + export_json: + button_text: "Izvozi" + invite: + button_text: "Pošlji vabilo" + button_title: "Pošlji vabilo" + customize: + title: "Prilagodi" + preview: "predogled" + save: "Shrani" + import: "Uvozi" + delete: "Izbriši" + delete_confirm: "Izbriši temo?" + color: "Barva" + opacity: "Transparentnost" + copy: "Kopiraj" + copy_to_clipboard: "Kopiraj v odloĹľišče" + copied_to_clipboard: "Kopirano v odloĹľišče" + copy_to_clipboard_error: "Prišlo je do napake pri kopiranju v odloĹľišče" + email_templates: + title: "Email predloge" + subject: "Zadeva" + multiple_subjects: "Ta email predloga ima veÄŤ zadev." + revert: "PrekliÄŤi spremembe" + revert_confirm: "Ste prepriÄŤani, da Ĺľelite preklicati spremembe?" + theme: + import_theme: "Uvozi temo" + customize_desc: "Prilagodi:" + title: "Teme" + edit: "Uredi" + common: "Skupno" + desktop: "Namizno" + mobile: "Mobilno" + preview: "Predogled" + color_scheme: "Barvna shema" + uploads: "Prenosi" + upload: "Prenesi" + child_themes_check: "Tema vkljuÄŤuje ostale pod-teme" + css_html: "CSS/HTML po meri" + edit_css_html: "Uredi CSS/HTML" + edit_css_html_help: "Niste uredili še nobenega CSS ali HTML." + about_theme: "O temi" + license: "Licenca" + updating: "Posodabljam..." + add: "Dodaj" + scss: + text: "CSS" + head_tag: + text: "" + title: "HTML, ki bo vstavljen pred oznako" + body_tag: + text: "" + title: "HTML, ki bo vstavljen pred oznako" + colors: + select_base: + title: "Izberi osnovno barvno shemo" + description: "Osnovna shema:" + title: "Barve" + edit: "Uredi barvne sheme" + long_title: "Barvne sheme" + new_name: "Nova barvna shema" + copy_name_prefix: "Kopija od" + delete_confirm: "Izbriši barvno shemo?" + undo: "razveljavi" + highlight: + name: 'izpostavljeno' + danger: + name: 'nevarnost' + success: + name: 'uspeh' + email: + title: "Emaili" + settings: "Nastavitve" + templates: "Predloge" + sending_test: "Pošiljam testni email..." + sent: "Poslano" + received: "Prejeto" + rejected: "Zavrnjeno" + sent_at: "Poslano" + time: "ÄŚas" + user: "Uporabnik" + logs: + filters: + user_placeholder: "uporabniško ime" + moderation_history: + actions: + delete_user: "Uporabnik izbrisan" + suspend_user: "Uporabnik suspendiran" + silence_user: "Uporabnik utišan" + delete_topic: "Tema izbrisana" + logs: + topic_id: "ID teme" + category_id: "ID kategorije" + delete: 'Izbriši' + edit: 'Uredi' + save: 'Shrani' + screened_actions: + block: "blokiraj" + do_nothing: "ne naredi niÄŤ" + staff_actions: + all: "vse" + filter: "Filter:" + title: "Aktivnosti skrbnikov" + clear_filters: "PokaĹľi vse" + when: "Kdaj" + context: "Kontekst" + details: "Podrobnosti" + show: "PokaĹľi" + modal_title: "Podrobnosti" + actions: + delete_user: "izbriši uporabnika" + change_username: "spremeni uporabniško ime" + change_site_setting: "spremeni nastavitve strani" + change_theme: "menjaj temo" + delete_theme: "izbriši temo" + suspend_user: "suspendiraj uporabnika" + unsuspend_user: "prekini suspenzijo uporabnika" + grant_badge: "podeli znaÄŤko" + revoke_badge: "odvzemi znaÄŤko" + check_email: "preveri email" + delete_topic: "izbriši temo" + delete_post: "izbriši objavo" + anonymize_user: "anonimiziraj uporabnika" + change_category_settings: "spremeni nastavitve kategorije" + delete_category: "izbriši kategorijo" + create_category: "ustvari kategorijo" + silence_user: "utišaj uporabnika" + unsilence_user: "ukini utišanje uporabnika" + backup_create: "ustvari varnostno kopijo" + deleted_tag: "izbriši oznako" + renamed_tag: "preimenuj oznako" + lock_trust_level: "zakleni nivo zaupanja" + unlock_trust_level: "odkleni nivo zaupanja" + activate_user: "aktiviraj uporabnika" + deactivate_user: "deaktiviraj uporabnika" + backup_download: "NaloĹľi varnostno kopijo" + backup_destroy: "UniÄŤi varnostno kopijo" + post_locked: "objava zaklenjena" + post_unlocked: "objava odklenjena" + check_personal_message: "preveri osebno sporoÄŤilo" + screened_emails: + email: "Email naslov" + actions: + allow: "Dovoli" + screened_urls: + url: "URL" + domain: "Domena" + screened_ips: + actions: + block: "Blokiraj" + do_nothing: "Dovoli" + allow_admin: "Dovoli adminu" + form: + add: "Dodaj" + search_logs: + types: + full_page: "Cela stran" + watched_words: + search: "išči" + clear_filter: "PoÄŤisti" + show_words: "pokaĹľi besede" + word_count: + one: "1 beseda" + two: "2 besedi" + few: "%{count} besed" + other: "%{count} besed" + actions: + block: 'Blokiraj' + censor: 'Cenzuriraj' + require_approval: 'Zahtevaj odobritev' + flag: 'Zastava' + form: + label: 'Nova beseda:' + placeholder_regexp: "Regularni izraz" + add: 'Dodaj' + success: 'Uspeh' + upload: "Prenesi" + users: + title: 'Uporabniki' + show_emails: "PokaĹľi emaile" + nav: + new: "Novi" + active: "Aktivni" + suspended: 'Suspendirani' + silenced: 'Utišani' + approved: "Potrjen?" + check_email: + text: "PokaĹľi" + user: + suspend_failed: "Nekaj je šlo narobe pri suspendiranju uporabnika {{error}}" + unsuspend_failed: "Nekaj je šlo narobe pri preklicu suspenza {{error}}" + suspend_duration: "Za kako dolgo bo uporabnik suspendiran?" + suspend_reason_hidden_label: "Zakaj je uporabnik suspendiran? To besedilo bo prikazano uporabniku, ko se bo poskušal vpisati. Naj bo kratko." + suspend_reason: "Razlog" + suspend_reason_placeholder: "Razlog za suspenz" + suspend_message: "Email sporoÄŤilo" + silence_reason: "Razlog" + silence_modal_title: "Utišaj uporabnika/co" + silence_duration: "Za kako dolgo bo uporabnik/ca utišan/a?" + silence_reason_label: "Zakaj bi rad utišal/a uporabnika/co?" + silence_reason_placeholder: "Razlog za utišanje" + silence_message: "Email sporoÄŤilo" + cant_suspend: "Ta uporabnik ne more biti suspendiran." + delete_all_posts: "Izbriši vse objave" + penalty_post_actions: "Kaj bi rad naredil/a z asociirano objavo?" + penalty_post_delete: "Izbriši objavo" + penalty_post_edit: "Uredi objavo" + penalty_post_none: "Ne naredi niÄŤ" + silenced: "Utišan?" + moderator: "Moderator?" + admin: "Admin?" + show_admin_profile: "Admin" + show_public_profile: "PokaĹľi javni profil" + log_out: "Izpiši" + grant_admin_confirm: "Poslali smo ti email za potrditev novega administratorja. Odpri ga in sledi navodilom." + unsuspend: 'PrekliÄŤi suspenz' + suspend: 'Suspendiraj' + show_flags_received: "PokaĹľi prejete zastave" + flags_received_by: "Zastave prejete od %{username}" + flags_received_none: "Ta uporabnik ni prejel nobene zastave." + permissions: Dovoljenja + activity: Aktivnost + like_count: Podeljeni / prejeti všeÄŤki + last_100_days: 'v zadnjih 100 dnevih' + private_topics_count: Privatne teme + posts_read_count: Prebrane objave + post_count: Ustvarjene objave + topics_entered: Ogledane teme + flags_given_count: Podeljene zastave + flags_received_count: Prejete zastave + warnings_received_count: Prejeta opozorila + flags_given_received_count: 'Podeljene/prejete zastave' + approve: 'Potrdi' + approved_by: "potrjen od" + approve_success: "Uporabnik/ca je potrjen/a, email z navodili za aktivacijo raÄŤuna je bil poslan." + approve_bulk_success: "Uspeh! Vsi izbrani uporabniki/ce, so bili potrjeni in obveščeni." + time_read: "ÄŚas branja" + anonymize: "Anonimiziraj uporabnika" + delete: "Izbriši uporabnika" + delete_forbidden_because_staff: "Adminov in moderatorjev se ne da izbrisati." + delete_confirm: "Si prepriÄŤan/a, da Ĺľeliš izbrisati tega uporabnika? Sprememba bo trajna!" + delete_and_block: "Izbriši in blokiraj ta email in IP naslov" + deleted: "Ta uporabnik je bil izbrisan" + activate: "Aktiviraj raÄŤun" + activate_failed: "Prišlo je do napake pri aktivaciji uporabnika." + deactivate_account: "Deaktiviraj raÄŤun" + deactivate_failed: "Prišlo je do napake pri deaktivaciji uporabnika." + unsilence_failed: 'Prišlo je do napake pri prekinitvi utišanja uporabnika.' + silence_failed: 'Prišlo je do napake pri utišanju uporabnika.' + silence_accept: 'Ja, utišaj uporabnika/co' + reset_bounce_score: + label: "Resetiraj" + tl3_requirements: + days: "dni" + posts_read: "Prebrane objave" + sso: + external_username: "Uporabniško ime" + external_name: "Ime" + external_email: "Email" + user_fields: + title: "Podatki uporabnika" + create: "Dodaj podatek uporabnika" + name: "Ime polja" + type: "Vrsta polja" + description: "Opis polja" + save: "Shrani" + edit: "Uredi" + delete: "Izbriši" + cancel: "PrekliÄŤi" + delete_confirm: "Si prepriÄŤan/a, da Ĺľeliš izbrisati to polje?" + options: "MoĹľnosti" + required: + title: "Zahtevano ob prijavi?" + enabled: "zahtevano" + disabled: "ni zahtevano" + editable: + title: "Uredljivo po prijavi?" + enabled: "uredljivo" + disabled: "ni uredljivo" + show_on_profile: + title: "PrikaĹľi na javnem profilu?" + enabled: "prikazano na profilu" + disabled: "ni prikazano na profilu" + show_on_user_card: + title: "PrikaĹľi na uporabnikovi izkaznici?" + enabled: "prikazano na uporabnikovi izkaznici" + disabled: "ni prikazano na uporabnikovi izkaznici" + field_types: + text: 'Besedilno polje' + confirm: 'Potrditev' + dropdown: "Spustni meni" + site_text: + search: "Išči besedilo, ki bi ga rad/a uredil/a" + title: 'Vsebina besedila' + edit: 'uredi' + site_settings: + title: 'Nastavitve' + categories: + users: 'Uporabniki' + api: 'API' + uncategorized: 'Ostalo' + login: "Prijava" + user_preferences: "Nastavitve uporabnika" + search: "Išči" + groups: "Skupine" + badges: + title: ZnaÄŤke + new_badge: Nova znaÄŤka + name: Ime + badge: ZnaÄŤka + description: Opis + badge_grouping: Skupina + save: Shrani + delete: Izbriši + delete_confirm: Si prepriÄŤan/a, da Ĺľeliš odstraniti to znaÄŤko? + revoke: Odstrani + reason: Razlog + icon: Ikona + trigger: SproĹľi + trigger_type: + none: "Posodobi dnevno" + post_revision: "Ko uporabnik ustvari ali uredi objavo" + preview: + bad_count_warning: + header: "POZOR!" + sample: "Primer:" + emoji: + title: "Emoji" + add: "Dodaj nov emoji" + name: "Ime" + embedding: + edit: "uredi" + category: "Objavi v kategorijo" + embed_by_username: "Uporabniško ime za kreiranje teme" + permalink: + url: "URL" + topic_id: "ID teme" + topic_title: "Tema" + post_id: "ID objave" + post_title: "Objava" + category_id: "ID kategorije" + category_title: "Kategorija" + external_url: "Zunanji URL" + delete_confirm: Si prepriÄŤan/a, da Ĺľeliš odstraniti permalink? + form: + label: "Novo:" + add: "Dodaj" + filter: "Išči (URL ali zunanji URL)" + wizard_js: + wizard: + done: "KonÄŤano" + back: "Nazaj" + next: "Naprej" + upload: "NaloĹľi" + uploading: "Nalagam..." + quit: "Morda kasneje" + invites: + add_user: "dodaj" + roles: + admin: "Admin" + moderator: "Moderator" + regular: "ObiÄŤajni uporabnik" diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index ceca48bba6..02869e2096 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -115,6 +115,7 @@ sq: split_topic: "ndaje kĂ«tĂ« teme %{when}" invited_user: "ka ftuar %{who} %{when}" invited_group: "ka ftuar %{who} %{when}" + user_left: "u larguan nga ky mesazh" removed_user: "hequr %{who} %{when}" removed_group: "hequr %{who} %{when}" autoclosed: @@ -139,6 +140,7 @@ sq: wizard_required: "MirseerdhĂ«t tek faqja juaj e re Discourse! Le t'ia nisim me \"setup wizard\" ✨" emails_are_disabled: "Emailat janĂ« çaktivizuar globalisht nga administratori i faqes. AsnjĂ« njoftim me email nuk do tĂ« dĂ«rgohet. " bootstrap_mode_enabled: "PĂ«r tĂ« thjeshtuar nisjen e komunitetit, jeni nĂ« formatin \"bootstrap\". TĂ« gjithĂ« anĂ«tarĂ«t e rinj do tĂ« kenĂ« nivelin e besimit 1 dhe emailat e pĂ«rditshĂ«m tĂ« aktivizuar. KĂ«to opsione do tĂ« çaktivizohen automatikisht kur numri total i anĂ«tarĂ«ve tĂ« kalojĂ« numrin %{min_users}." + bootstrap_mode_disabled: "MĂ«nyra bootstrap do tĂ« çaktivizohet nĂ« 24 orĂ«t e ardhshme." themes: default_description: "Paracaktuar" s3: @@ -347,6 +349,11 @@ sq: add_members: "Shto anĂ«tarĂ«" delete_member_confirm: "Do tĂ« heqĂ«sh '%{username}' nga grupi '%{group}'?" name_placeholder: "Emri i grupit, pa hapĂ«sira, si username-t" + public_admission: "Lejo qĂ« pĂ«rdoruesit tĂ« bashkohen lirisht ne grupin (KĂ«rkon grupin e dukshĂ«m publik)" + public_exit: "Lejo pĂ«rdoruesit tĂ« largohen nga grupi lirshĂ«m" + empty: + posts: "Nuk ka postime nga anĂ«tarĂ«t e kĂ«tij grupi." + members: "Nuk ka anĂ«tarĂ« nĂ« kĂ«tĂ« grup." add: "Shto" join: "Bashkoju grupit" leave: "Ik nga grupi" @@ -357,7 +364,6 @@ sq: name: "Emri" user_count: "Numri i anĂ«tarĂ«ve" bio: "Mbi grupin" - selector_placeholder: "Shto anĂ«tarĂ«" owner: "autori" index: title: "Grupet" @@ -629,6 +635,7 @@ sq: title: "Gjuha e faqes" instructions: "Gjuha e faqes pĂ«r pĂ«rdoruesin. Do tĂ« ndryshojĂ« pasi tĂ« rifreskoni faqen. " default: "(paracaktuar)" + any: "ndonjĂ«" password_confirmation: title: "Rishkruani fjalĂ«kalimin" last_posted: "Postimi i fundit" @@ -1388,8 +1395,8 @@ sq: select_all: pĂ«rzgjidhi tĂ« gjitha deselect_all: pastro zgjedhjen description: - one: Keni pĂ«rzgjedhur 1 postim. - other: Keni pĂ«rzgjedhur {{count}} postime. + one: "Keni pĂ«rzgjedhur 1 postim." + other: "Keni pĂ«rzgjedhur {{count}} postime." post: edit_reason: "Arsyeja:" post_number: "postimi {{number}}" @@ -1609,7 +1616,6 @@ sq: email_in_allow_strangers: "Prano emaila nga anĂ«tarĂ« anonimĂ« pa llogari nĂ« faqe" email_in_disabled: "Postimi i temave tĂ« reja me email Ă«shtĂ« çaktivizuar nĂ« Rregullimet e faqes. PĂ«r tĂ« aktivizuar postimet e temave tĂ« reja me email," email_in_disabled_click: 'aktivizo rregullimin "email in".' - suppress_from_homepage: "Hiqe kĂ«tĂ« kategori nga faqja e parĂ«." allow_badges_label: "Lejo tĂ« jepen stemat nĂ« kĂ«tĂ« kategori" edit_permissions: "Ndryshoni autorizimet" add_permission: "Shtoni autorizim" @@ -2056,7 +2062,6 @@ sq: edit: "Redakto Grup" refresh: "Rifresko" new: "I Ri" - selector_placeholder: "vendos emrin e pĂ«rdoruesit" about: "Modifiko anĂ«tarĂ«t e grupit dhe emrin kĂ«tu" group_members: "PĂ«rdorues grupi" delete: "Fshij" @@ -2523,10 +2528,7 @@ sq: recommended: "Ju rekomandojmĂ« tĂ« ndryshoni tekstin mĂ« poshtĂ« sipas nevojave tuaja: " show_overriden: 'Shfaq vetĂ«m tĂ« ndryshuarat' site_settings: - show_overriden: 'Shfaq vetĂ«m tĂ« ndryshuarat' title: 'Rregullimet' - reset: 'rivendos' - none: 'asnjĂ«' no_results: "Nuk u gjet asnjĂ« rezultat." clear_filter: "Pastro" add_url: "shto URL" diff --git a/config/locales/client.sr.yml b/config/locales/client.sr.yml index ebf9090332..dc1a80a4a1 100644 --- a/config/locales/client.sr.yml +++ b/config/locales/client.sr.yml @@ -1116,9 +1116,9 @@ sr: select_all: odaberi sve deselect_all: odustani od odabira description: - one: Odabrali ste {{count}} poruka. - few: Odabrali ste {{count}} poruka. - other: Odabrali ste {{count}} poruka. + one: "Odabrali ste {{count}} poruka." + few: "Odabrali ste {{count}} poruka." + other: "Odabrali ste {{count}} poruka." post: edit_reason: "Razlog:" post_number: "poruka {{number}}" @@ -1534,7 +1534,6 @@ sr: edit: "Izmeni Grupe" refresh: "OsveĹľi" new: "Novo" - selector_placeholder: "unesite korisniÄŤko ime" about: "Ovde izmenite svoje ÄŤlanstvo u grupama i imenima grupa" group_members: "ÄŚlanovi grupe" delete: "Obriši" @@ -1950,10 +1949,7 @@ sr: site_text: title: 'SadĹľaj Teksta' site_settings: - show_overriden: 'Pokazuj samo promenjeno' title: 'Podešavanja' - reset: 'resetuj' - none: 'ništa' no_results: "Nema pronaÄ‘enih rezultata." clear_filter: "ÄŚisto" categories: diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index bbb3959bf5..b5388cf61b 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -219,7 +219,7 @@ sv: stat: all_time: "Alla dagar" last_7_days: "Senaste 7 dagarna" - last_30_days: "Senaste 30 Dagarna" + last_30_days: "Senaste 30 dagarna" like_count: "Gillningar" topic_count: "Ă„mnen" post_count: "Inlägg" @@ -374,7 +374,6 @@ sv: name: "Namn" user_count: "Antal medlemmar" bio: "Om grupp" - selector_placeholder: "Lägg till medlemmar" owner: "ägare" index: title: "Grupper" @@ -1482,8 +1481,8 @@ sv: select_all: markera alla deselect_all: avmarkera alla description: - one: Du har markerat 1 inlägg. - other: Du har markerat {{count}} inlägg. + one: "Du har markerat 1 inlägg." + other: "Du har markerat {{count}} inlägg." post: quote_reply: "Citat" edit_reason: "Anledning:" @@ -1713,7 +1712,6 @@ sv: email_in_allow_strangers: "Acceptera e-post frĂĄn anonyma användare utan konton" email_in_disabled: "Att skapa nya ämnen via e-post är avaktiverat i webbplatsinställningarna. För att aktivera ämnen skapade via e-post," email_in_disabled_click: 'aktivera "inkommande e-post" inställningen.' - suppress_from_homepage: "Undanta den här kategorin frĂĄn hemsidan." show_subcategory_list: "Visa listan med underkategorier ovanför ämnen i denna kategori." num_featured_topics: "Antal ämnen som visas pĂĄ sidan kategorier:" all_topics_wiki: "Gör nya inlägg till wikis som standard" @@ -2203,7 +2201,6 @@ sv: edit: "Redigera Grupper" refresh: "Uppdatera" new: "Ny" - selector_placeholder: "ange användarnamn" about: "Redigera dina gruppmedlemskap och -namn här." group_members: "Gruppmedlemmar" delete: "Radera" @@ -2834,10 +2831,7 @@ sv: recommended: "Vi rekommenderar att du anpassar följande texter:" show_overriden: 'Visa bara överskrivna' site_settings: - show_overriden: 'Visa bara överskrivna' title: 'Webbplatsinställningar' - reset: 'ĂĄterställ' - none: 'inget' no_results: "Inga resultat hittades." clear_filter: "Rensa" add_url: "lägg till URL" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index b949e17287..f0b202062c 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -779,8 +779,8 @@ te: select_all: అన్నీ ŕ°Žŕ°‚ŕ°šŕ±ŕ°•ో deselect_all: అన్నీ వియెంచŕ±ŕ°•ో description: - one: మీరౠ1 ŕ°źŕ°Şŕ°ľ ŕ°Žŕ°‚ŕ°šŕ±ŕ°•ŕ±ŕ°¨ŕ±Ťŕ°¨ŕ°ľŕ°°ŕ± - other: మీరౠ{{count}} టపాలౠఎంచŕ±ŕ°•ŕ±ŕ°¨ŕ±Ťŕ°¨ŕ°ľŕ°°ŕ± + one: "మీరౠ1 ŕ°źŕ°Şŕ°ľ ŕ°Žŕ°‚ŕ°šŕ±ŕ°•ŕ±ŕ°¨ŕ±Ťŕ°¨ŕ°ľŕ°°ŕ±" + other: "మీరౠ{{count}} టపాలౠఎంచŕ±ŕ°•ŕ±ŕ°¨ŕ±Ťŕ°¨ŕ°ľŕ°°ŕ±" post: edit_reason: "ŕ°•ŕ°ľŕ°°ŕ°Łŕ°‚:" post_number: "ŕ°źŕ°Şŕ°ľ {{number}}" @@ -1140,7 +1140,6 @@ te: edit: "ŕ°—ŕ±ŕ°‚ŕ°Şŕ±ŕ°˛ŕ± సవరించŕ±" refresh: "తాజా ŕ°Şŕ°°ŕ±ŕ°šŕ±" new: "కొత్త" - selector_placeholder: "సభ్యనామం రాయండి" about: "మీ ŕ°—ŕ±ŕ°‚పౠమెంబర్షిప్పౠమరియౠపేర్లౠఇక్కడ సవరించండి" group_members: "ŕ°—ŕ±ŕ°‚పౠసభ్యŕ±ŕ°˛ŕ±" delete: "తొలగించŕ±" @@ -1550,10 +1549,7 @@ te: site_text: title: 'పాఠ్య కాంటెంటŕ±' site_settings: - show_overriden: 'ప్రాబల్యం ఉన్న వాటిని మాత్రమే చూపించŕ±' title: 'అమరికలŕ±' - reset: 'రీసెట్' - none: 'ఏదీకాదŕ±' no_results: "ŕ°Ź ఫలితాలూ కనిపించలేదŕ±." clear_filter: "ŕ°¶ŕ±ŕ°­ŕ±Ťŕ°°ŕ°Şŕ°°ŕ±ŕ°šŕ±" add_url: "URL ŕ°•ŕ°˛ŕ±ŕ°Şŕ±" diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml index d78a454e76..95e541e1a6 100644 --- a/config/locales/client.th.yml +++ b/config/locales/client.th.yml @@ -293,6 +293,7 @@ th: other: "%{count} ผู้ŕąŕ¸Šŕą‰" group_histories: actions: + change_group_setting: "ŕąŕ¸ŕą‰ŕą„ขŕ¸ŕ¸˛ŕ¸Łŕ¸•ั้งคŕąŕ¸˛ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇ" add_user_to_group: "เพิŕąŕ¸ˇŕ¸śŕ¸ąŕą‰ŕąŕ¸Šŕą‰" remove_user_from_group: "ลบผู้ŕąŕ¸Šŕą‰" groups: @@ -308,10 +309,12 @@ th: full_name: 'ชืŕąŕ¸­-นามสŕ¸ŕ¸¸ŕ¸Ą' add_members: "เพิŕąŕ¸ˇŕ¸Şŕ¸ˇŕ¸˛ŕ¸Šŕ¸´ŕ¸" delete_member_confirm: "ลบ '%{username}' ออŕ¸ŕ¸ŕ¸˛ŕ¸ '%{group}' ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇ?" + public_exit: "อนุญาตŕąŕ¸«ŕą‰ŕ¸śŕ¸ąŕą‰ŕąŕ¸Šŕą‰ŕ¸­ŕ¸­ŕ¸ŕ¸ŕ¸˛ŕ¸ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇŕ¸­ŕ¸˘ŕąŕ¸˛ŕ¸‡ŕ¸­ŕ¸´ŕ¸Şŕ¸Łŕ¸°" add: "เพิŕąŕ¸ˇ" join: "เข้ารŕąŕ¸§ŕ¸ˇŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇ" leave: "ออŕ¸ŕ¸ŕ¸˛ŕ¸ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇ" request: "สŕąŕ¸‡ŕ¸„ำขอเข้ารŕąŕ¸§ŕ¸ˇŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇ" + message: "ข้อความ

" automatic_group: ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇŕ¸­ŕ¸±ŕ¸•โนมัติ closed_group: ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇŕ¸›ŕ¸´ŕ¸” is_group_user: "คุณเป็นสมาชิŕ¸ŕ¸‚องŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇŕ¸™ŕ¸µŕą‰" @@ -319,7 +322,6 @@ th: name: "ชืŕąŕ¸­" user_count: "ŕ¸ŕ¸łŕ¸™ŕ¸§ŕ¸™ŕ¸Şŕ¸ˇŕ¸˛ŕ¸Šŕ¸´ŕ¸" bio: "เŕ¸ŕ¸µŕąŕ¸˘ŕ¸§ŕ¸ŕ¸±ŕ¸šŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇ" - selector_placeholder: "เพิŕąŕ¸ˇŕ¸Şŕ¸ˇŕ¸˛ŕ¸Šŕ¸´ŕ¸" owner: "เŕ¸ŕą‰ŕ¸˛ŕ¸‚อง" index: title: "ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇ" @@ -330,7 +332,10 @@ th: posts: "โพสต์" mentions: "พูดถึง" messages: "ข้อความ" + visibility_levels: + public: "ทุŕ¸ŕ¸„น" alias_levels: + messageable: "ŕąŕ¸„รสามารถสŕąŕ¸‡ŕ¸‚้อความŕąŕ¸™ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇŕ¸™ŕ¸µŕą‰?" nobody: "ไมŕąŕ¸ˇŕ¸µ" only_admins: "เฉพาะผู้ดูŕąŕ¸Ąŕ¸Łŕ¸°ŕ¸šŕ¸š" mods_and_admins: "ผู้ดูŕąŕ¸Ąŕ¸Łŕ¸°ŕ¸šŕ¸šŕąŕ¸Ąŕ¸°ŕ¸śŕ¸ąŕą‰ŕ¸•รวŕ¸ŕ¸Şŕ¸­ŕ¸š" @@ -491,12 +496,18 @@ th: move_to_archive: "เŕ¸ŕą‡ŕ¸š" failed_to_move: "เŕ¸ŕ¸´ŕ¸”ความผิดพลาดŕąŕ¸™ŕ¸ŕ¸˛ŕ¸Łŕ¸˘ŕą‰ŕ¸˛ŕ¸˘ŕ¸«ŕ¸±ŕ¸§ŕ¸‚้อทีŕąŕą€ŕ¸Ąŕ¸·ŕ¸­ŕ¸ (อาŕ¸ŕą€ŕ¸ŕ¸´ŕ¸”ŕ¸ŕ¸˛ŕ¸”เครือขŕąŕ¸˛ŕ¸˘ŕ¸‚องคุณลŕąŕ¸ˇ)" select_all: "เลือŕ¸ŕ¸—ั้งหมด" + preferences_nav: + profile: "ข้อมูลผู้ŕąŕ¸Šŕą‰

" + emails: "อีเมล" + tags: "ป้าย" change_password: success: "(อีเมลทีŕąŕ¸Şŕąŕ¸‡ŕąŕ¸Ąŕą‰ŕ¸§)" in_progress: "(ŕ¸ŕ¸łŕ¸Ąŕ¸±ŕ¸‡ŕ¸Şŕąŕ¸‡ŕ¸­ŕ¸µŕą€ŕ¸ˇŕ¸Ą)" error: "(ผิดพลาด)" action: "สŕąŕ¸‡ŕ¸­ŕ¸µŕą€ŕ¸ˇŕ¸Ąŕ¸Łŕ¸µŕą€ŕ¸‹ŕą‡ŕ¸—รหัสผŕąŕ¸˛ŕ¸™" set_password: "ตั้งรหัสผŕąŕ¸˛ŕ¸™" + choose_new: "เลือŕ¸ŕ¸Łŕ¸«ŕ¸±ŕ¸Şŕ¸śŕ¸ąŕą‰ŕąŕ¸Šŕą‰ŕąŕ¸«ŕ¸ˇŕą" + choose: "เลือŕ¸ŕ¸Łŕ¸«ŕ¸±ŕ¸Şŕ¸śŕ¸ąŕą‰ŕąŕ¸Šŕą‰" change_about: title: "เปลีŕąŕ¸˘ŕ¸™ŕ¸‚้อมูลเŕ¸ŕ¸µŕąŕ¸˘ŕ¸§ŕ¸ŕ¸±ŕ¸šŕ¸‰ŕ¸±ŕ¸™" error: "เŕ¸ŕ¸´ŕ¸”ความผิดพลาดŕąŕ¸™ŕ¸ŕ¸˛ŕ¸Łŕąŕ¸ŕą‰ŕą„ขคŕąŕ¸˛ŕ¸™ŕ¸µŕą‰" @@ -537,11 +548,13 @@ th: other: "เราŕ¸ŕ¸°ŕ¸­ŕ¸µŕą€ŕ¸ˇŕ¸Ąŕ¸«ŕ¸˛ŕ¸„ุณเฉพาะสิŕąŕ¸‡ŕ¸—ีŕąŕ¸„ุณไมŕąŕą„ด้เห็นŕąŕ¸™ŕ¸Šŕąŕ¸§ŕ¸‡ {{count}} นาที" name: title: "ชืŕąŕ¸­" + instructions: "ชืŕąŕ¸­ŕą€ŕ¸•็มของคุณ (ไมŕąŕ¸šŕ¸±ŕ¸‡ŕ¸„ับ)" instructions_required: "ชืŕąŕ¸­ŕą€ŕ¸•็มของคุณ" too_short: "ชืŕąŕ¸­ŕ¸‚องคุณสั้นไป" ok: "ชืŕąŕ¸­ŕ¸‚องคุณมีลัŕ¸ŕ¸©ŕ¸“ะทีŕąŕ¸”ี" username: title: "ชืŕąŕ¸­ŕ¸śŕ¸ąŕą‰ŕąŕ¸Šŕą‰" + instructions: "ไมŕąŕą€ŕ¸«ŕ¸ˇŕ¸·ŕ¸­ŕ¸™ŕąŕ¸„ร, ไมŕąŕ¸ˇŕ¸µŕ¸Šŕąŕ¸­ŕ¸‡ŕ¸§ŕąŕ¸˛ŕ¸‡, สั้น" short_instructions: "ผู้คนสามารถพูดถึงคุณ @{{username}}" available: "ชืŕąŕ¸­ŕ¸śŕ¸ąŕą‰ŕąŕ¸Šŕą‰ŕ¸‚องคุณสามารถŕąŕ¸Šŕą‰ŕą„ด้" not_available: "ŕąŕ¸Šŕą‰ŕ¸ŕ¸˛ŕ¸Łŕą„มŕąŕą„ด้ ลอง{{suggestion}}?" @@ -1127,7 +1140,7 @@ th: select_all: เลือŕ¸ŕ¸—ั้งหมด deselect_all: ไมŕąŕą€ŕ¸Ąŕ¸·ŕ¸­ŕ¸ŕ¸—ั้งหมด description: - other: คุณได้เลือภ{{count}} โพสต์. + other: "คุณได้เลือภ{{count}} โพสต์." post: edit_reason: "เหตุผล:" post_number: "โพสต์ {{number}}" @@ -1378,7 +1391,6 @@ th: edit: "ŕąŕ¸ŕą‰ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇ" refresh: "รีโหลด" new: "ŕąŕ¸«ŕ¸ˇŕą" - selector_placeholder: "ชืŕąŕ¸­ŕ¸śŕ¸ąŕą‰ŕąŕ¸Šŕą‰ŕąŕ¸«ŕ¸ˇŕą" about: "ŕąŕ¸ŕą‰ŕą„ขสมาชิŕ¸ŕąŕ¸Ąŕ¸°ŕ¸Šŕ¸·ŕąŕ¸­ŕ¸ŕ¸Ąŕ¸¸ŕąŕ¸ˇŕ¸‚องคุณได้ทีŕąŕ¸™ŕ¸µŕą" group_members: "ชืŕąŕ¸­ŕ¸Şŕ¸ˇŕ¸˛ŕ¸Šŕ¸´ŕ¸" delete: "ลบ" @@ -1582,8 +1594,6 @@ th: recommended: "เราŕąŕ¸™ŕ¸°ŕ¸™ŕ¸łŕąŕ¸«ŕą‰ŕ¸„ุณŕąŕ¸ŕą‰ŕą„ขข้อความŕąŕ¸«ŕą‰ŕ¸•รงŕ¸ŕ¸±ŕ¸šŕ¸„วามต้องŕ¸ŕ¸˛ŕ¸Łŕ¸‚องคุณ:" site_settings: title: 'ŕ¸ŕ¸˛ŕ¸Łŕ¸•ั้งคŕąŕ¸˛' - reset: 'ล้าง' - none: 'ไมŕąŕ¸ˇŕ¸µ' no_results: "ไมŕąŕ¸ˇŕ¸µŕ¸śŕ¸Ąŕ¸ŕ¸˛ŕ¸Łŕ¸„้นหา" clear_filter: "ล้าง" add_url: "เพิŕąŕ¸ˇ URL" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index ab264a3e03..b3caff0229 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -16,6 +16,7 @@ tr_TR: format: '%n %u' units: byte: + one: Bayt other: Bayt gb: GB kb: KB @@ -42,48 +43,67 @@ tr_TR: tiny: half_a_minute: "< 1d" less_than_x_seconds: + one: "< %{count}s" other: "< %{count}s" x_seconds: + one: "%{count}s" other: "%{count}s" less_than_x_minutes: + one: "< %{count}dk" other: "< %{count}dk" x_minutes: + one: "%{count}d" other: "%{count}d" about_x_hours: + one: "%{count}s" other: "%{count}s" x_days: + one: "%{count}g" other: "%{count}g" x_months: + one: "%{count}ay" other: "%{count}ay" about_x_years: + one: "%{count}y" other: "%{count}y" over_x_years: + one: "> %{count}y" other: "> %{count}y" almost_x_years: + one: "%{count}y" other: "%{count}y" date_month: "D MMM" date_year: "MMM 'YY" medium: x_minutes: + one: "%{count} dakika" other: "%{count} dakika" x_hours: + one: "%{count} saat" other: "%{count} saat" x_days: + one: "%{count} gĂĽn" other: "%{count} gĂĽn" date_year: "D MMM, 'YY" medium_with_ago: x_minutes: + one: "%{count} dakika önce" other: "%{count} dakika önce" x_hours: + one: "%{count} saat önce" other: "%{count} saat önce" x_days: + one: "%{count} gĂĽn önce" other: "%{count} gĂĽn önce" later: x_days: + one: "%{count} gĂĽn sonra" other: "%{count} gĂĽn sonra" x_months: + one: "%{count} ay sonra" other: "%{count} ay sonra" x_years: + one: "%{count} yıl sonra" other: "%{count} yıl sonra" previous_month: 'Ă–nceki Ay' next_month: 'Sonraki Ay' @@ -165,6 +185,7 @@ tr_TR: show_help: "seçenekler" links: "BaÄźlantılar" links_lowercase: + one: "baÄźlantılar" other: "baÄźlantılar" faq: "SSS" guidelines: "Yönergeler" @@ -189,6 +210,7 @@ tr_TR: max_of_count: "azami {{count}}" alternation: "ya da" character_count: + one: "{{count}} karakter" other: "{{count}} karakter" suggested_topics: title: "Ă–nerilen Konular" @@ -224,10 +246,13 @@ tr_TR: remove: "İmi Kaldır" confirm_clear: "Bu konuya ait tĂĽm imleri kaldırmak istediÄźinize emin misiniz?" topic_count_latest: + one: "{{count}} yeni ya da gĂĽncellenmiĹź konu." other: "{{count}} yeni ya da gĂĽncellenmiĹź konu." topic_count_unread: + one: "{{count}} okunmamış konu." other: "{{count}} okunmamış konu." topic_count_new: + one: "{{count}} yeni konu." other: "{{count}} yeni konu." click_to_show: "GörĂĽntĂĽlemek için tıklayın." preview: "önizleme" @@ -266,6 +291,7 @@ tr_TR: cancel: "İptal" view_pending: "bekleyen gönderileri görĂĽntĂĽleyin" has_pending_posts: + one: "Bu konuda {{count}} sayıda onay bekleyen gönderi var" other: "Bu konuda {{count}} sayıda onay bekleyen gönderi var" confirm: "DĂĽzenlemeleri Kaydet" delete_prompt: "%{username} kullanıcısını silmek istediÄźinize emin misiniz? Bunu yaparsanız tĂĽm gönderileri silinecek, e-posta adresi ve IP adresi engellenecek." @@ -273,6 +299,7 @@ tr_TR: title: "Gönderi Onay Gerektirir" description: "Gönderinizi aldık fakat gösterilmeden önce bir moderatör tarafından onaylanması gerekiyor. LĂĽtfen sabırlı olun." pending_posts: + one: "Bekleyen {{count}} gönderiniz bulunmaktadır." other: "Bekleyen {{count}} gönderiniz bulunmaktadır." ok: "Tamam" user_action: @@ -307,6 +334,7 @@ tr_TR: posts_read: "OkunmuĹźlar" posts_read_long: "OkunmuĹź Gönderiler" total_rows: + one: "%{count} kullanıcı" other: "%{count} kullanıcı" group_histories: actions: @@ -358,12 +386,12 @@ tr_TR: name: "İsim" user_count: "Ăśyeler" bio: "Grup Hakkında" - selector_placeholder: "Ăśye ekle" owner: "sahip" index: title: "Gruplar" empty: "GörĂĽnen hiç bir grup bulunmamaktadır." title: + one: "Gruplar" other: "Gruplar" activity: "Etkinlik" members: "Ăśyeler" @@ -447,8 +475,10 @@ tr_TR: toggle_ordering: "sıralama kontrolĂĽnĂĽ aç/kapa" subcategories: "Alt kategoriler" topic_sentence: + one: "%{count} konu" other: "%{count} konu" topic_stat_sentence: + one: "%{unit} beri %{count} yeni konu." other: "%{unit} beri %{count} yeni konu." ip_lookup: title: IP Adresi Ara @@ -640,6 +670,7 @@ tr_TR: authenticated: "E-posta adresiniz {{provider}} tarafından doÄźrulanmıştır" frequency_immediately: "EÄźer yollamak ĂĽzere olduÄźumuz Ĺźeyi okumadıysanız size direk e-posta yollayacağız." frequency: + one: "Sadece son {{count}} dakika içinde sizi görmediysek e-posta yollayacağız." other: "Sadece son {{count}} dakika içinde sizi görmediysek e-posta yollayacağız." name: title: "İsim" @@ -728,6 +759,7 @@ tr_TR: sent: "Gönderildi" none: "Gösterilecek davet yok." truncated: + one: "İlk {{count}} davet gösteriliyor." other: "İlk {{count}} davet gösteriliyor." redeemed: "Kabul Edilen Davetler" redeemed_tab: "Kabul Edildi" @@ -774,20 +806,28 @@ tr_TR: stats: "Sayıtımlar" time_read: "okuma sĂĽresi" topic_count: + one: "oluĹźturulan konular" other: "oluĹźturulan konular" post_count: + one: "oluĹźturulan gönderiler" other: "oluĹźturulan gönderiler" likes_given: + one: "verilen" other: "verilen" likes_received: + one: "alınan" other: "alınan" days_visited: + one: "ziyaret edilen gĂĽn" other: "ziyaret edilen gĂĽn" topics_entered: + one: "görĂĽntĂĽlenmiĹź baĹźlıklar" other: "görĂĽntĂĽlenmiĹź baĹźlıklar" posts_read: + one: "okunmuĹź gönderi" other: "okunmuĹź gönderi" bookmark_count: + one: "imler" other: "imler" top_replies: "BaĹźlıca Cevapları" no_replies: "HenĂĽz cevap bulunmuyor." @@ -856,6 +896,7 @@ tr_TR: reached: "%{relativeAge} – %{rate} , %{siteSettingRate} 'in site ayarları limitine ulaĹźtı." exceeded: "%{relativeAge} – %{rate} , %{siteSettingRate} 'in site ayarları limitini aĹźtı." rate: + one: "%{count} hata/%{duration}" other: "%{count} hata/%{duration}" learn_more: "daha fazlasını öğren..." all_time: 'toplam' @@ -874,6 +915,7 @@ tr_TR: time_read: Okuma last_reply_lowercase: son cevap replies_lowercase: + one: cevap other: cevap signup_cta: sign_up: "Kayıt Ol" @@ -923,6 +965,8 @@ tr_TR: complete_email_not_found: "Hiçbir hesap %{email} adresi ile eĹźleĹźmiyor" button_ok: "Tamam" button_help: "Yardım" + email_login: + complete_email_not_found: "%{email}Hiç bir hesap bulunamadı" login: title: "GiriĹź Yap" username: "Kullanıcı" @@ -1006,6 +1050,7 @@ tr_TR: default_header_text: Seç... no_content: Hiçbir sonuç bulunamadı filter_placeholder: Ara... + max_content_reached: "{{count}} konu seçtiniz." emoji_picker: filter_placeholder: Emoji ara people: İnsanlar @@ -1040,6 +1085,7 @@ tr_TR: similar_topics: "Konunuz Ĺźunlara çok benziyor..." drafts_offline: "çevrimdışı taslaklar" group_mentioned: + one: "{{group}} hakkında konuĹźarak {{count}} kiĹźiyi bilgilendirmek ĂĽzeresin, emin misin?" other: "{{group}} hakkında konuĹźarak {{count}} kiĹźiyi bilgilendirmek ĂĽzeresin, emin misin?" cannot_see_mention: category: "{{username}} adlı kullanıcıdan bahsettiniz fakat Ona bildirim gönderilmeyecek çünkĂĽ bu kategoriye ulaĹźma izni yok. Bunun gerçekleĹźmesi için kiĹźi bu gruba eklemeniz gerekmektedir." @@ -1109,11 +1155,18 @@ tr_TR: title: "Alıcıları eklemeyi unuttun mu?" body: "Bu ileti Ĺźu an sadece sana gönderiliyor!" admin_options_title: "Bu konu için isteÄźe baÄźlı görevli ayarları" + composer_actions: + reply_as_private_message: + label: Ă–zel Mesaj + reply_to_topic: + label: Cevapla notifications: tooltip: regular: + one: "{{count}} görĂĽlmemiĹź bildirim" other: "{{count}} görĂĽlmemiĹź bildirim" message: + one: "{{count}} okunmamış ileti" other: "{{count}} okunmamış ileti" title: "@isim bahsediliĹźleri, gönderileriniz ve konularınıza verilen cevaplar, iletilerle vb. ilgili bildiriler" none: "Ĺžu an için bildirimler yĂĽklenemiyor." @@ -1129,6 +1182,7 @@ tr_TR: liked: "{{username}} {{description}}" liked_2: "{{username}}, {{username2}} {{description}}" liked_many: + one: "{{username}}, {{username2}} ve {{count}} diÄźer {{description}}" other: "{{username}}, {{username2}} ve {{count}} diÄźer {{description}}" private_message: "{{username}} {{description}}" invited_to_private_message: "

{{username}} {{description}}" @@ -1186,6 +1240,7 @@ tr_TR: clear_all: "Tümünü Temizle" too_short: "Aradığın terim çok kısa." result_count: + one: "{{term}} için {{count}}{{plus}} sonuç" other: "{{term}} için {{count}}{{plus}} sonuç" title: "konu, gönderi, kullanıcı veya kategori ara" no_results: "Hiç bir sonuç bulunamadı." @@ -1274,6 +1329,7 @@ tr_TR: notification_level: "Bildirimler" choose_new_category: "Konular için yeni bir kategori seçin:" selected: + one: "{{count}} konu seçtiniz." other: "{{count}} konu seçtiniz." change_tags: "Etiketleri Değiştir" append_tags: "Etiketleri Ekle" @@ -1309,6 +1365,7 @@ tr_TR: stop_notifications: "Artık {{title}} için daha az bildirim alacaksınız." change_notification_state: "Geçerli bildirim durumunuz" filter_to: + one: "konuda {{count}} tane gönderi var" other: "konuda {{count}} tane gönderi var" create: 'Yeni Konu' create_long: 'Yeni bir konu oluştur' @@ -1323,8 +1380,10 @@ tr_TR: new: 'yeni konu' unread: 'okunmamış' new_topics: + one: '{{count}} yeni konu' other: '{{count}} yeni konu' unread_topics: + one: '{{count}} okunmamış konu' other: '{{count}} okunmamış konu' title: 'Konu' invalid_access: @@ -1338,12 +1397,16 @@ tr_TR: title: "Konu bulunamadı." description: "Üzgünüz, bu konuyu bulamadık. Belki de moderatör tarafından kaldırıldı?" total_unread_posts: + one: "bu konuda {{count}} okunmamış gönderi var" other: "bu konuda {{count}} okunmamış gönderi var" unread_posts: + one: "bu konuda {{count}} tane okunmamış eski gönderi var" other: "bu konuda {{count}} tane okunmamış eski gönderi var" new_posts: + one: "bu konuda, son okumanızdan bu yana {{count}} yeni gönderi var" other: "bu konuda, son okumanızdan bu yana {{count}} yeni gönderi var" likes: + one: "bu konuda {{count}} beğeni var" other: "bu konuda {{count}} beğeni var" back_to_list: "Konu listesine geri dön" options: "Konu Seçenekleri" @@ -1405,6 +1468,7 @@ tr_TR: auto_publish_to_category: "Bu konu %{timeLeft} #%{categoryName} kategorisinde yayınlanacak." auto_close_title: 'Otomatik Kapatma Ayarları' auto_close_immediate: + one: "Konudaki son gönderi zaten %{count} saat eski, bu yüzden konu hemen kapatılacak." other: "Konudaki son gönderi zaten %{count} saat eski, bu yüzden konu hemen kapatılacak." timeline: back: "Geri" @@ -1467,6 +1531,7 @@ tr_TR: open: "Konuyu Aç" close: "Konuyu Kapat" multi_select: "Gönderileri Seç..." + timed_update: "Konu Zamanlayıcısı" pin: "Başa Tuttur..." unpin: "Baştan Kaldır..." unarchive: "Konuyu Arşivden Kaldır" @@ -1507,6 +1572,7 @@ tr_TR: pin_validation: "Bu konuyu başa tutturmak için bir tarih gerekli." not_pinned: " {{categoryLink}} kategorisinde başa tutturulan herhangi bir konu yok." already_pinned: + one: "Şu an {{categoryLink}} kategorisinde başa tutturulan konular: {{count}}." other: "Şu an {{categoryLink}} kategorisinde başa tutturulan konular: {{count}}." pin_globally: "Şu zamana kadar bu konunun bütün konu listelerinin başında yer almasını sağla" confirm_pin_globally: "Zaten her yerde başa tutturulan {{count}} konunuz var. Çok fazla konuyu başa tutturmak yeni ve anonim kullanıcılara sıkıntı çektirebilir. Bir konuyu daha her yerde başa tutturmak istediğinizden emin misiniz?" @@ -1515,6 +1581,7 @@ tr_TR: global_pin_note: "Kullanıcılar kendileri için konunun başa tutturulmasını kaldırabilir." not_pinned_globally: "Her yerde başa tutturulan herhangi bir konu yok." already_pinned_globally: + one: "Şu an her yerde başa tutturulan konular: {{count}}." other: "Şu an her yerde başa tutturulan konular: {{count}}." make_banner: "Bu konuyu tüm sayfaların en üstünde görünecek şekilde manşetleştir." remove_banner: "Tüm sayfaların en üstünde görünen manşeti kaldır." @@ -1551,6 +1618,7 @@ tr_TR: login_reply: 'Cevaplamak için oturum açın' filters: n_posts: + one: "{{count}} gönderi" other: "{{count}} gönderi" cancel: "Filteri kaldır" split_topic: @@ -1559,12 +1627,14 @@ tr_TR: topic_name: "Yeni Konu Adı" error: "Gönderiler yeni konuya taşınırken bir hata oluştu." instructions: + one: "Yeni bir konu oluşturmak ve bu konuyu seçtiğiniz {{count}} gönderi ile doldurmak üzeresiniz." other: "Yeni bir konu oluşturmak ve bu konuyu seçtiğiniz {{count}} gönderi ile doldurmak üzeresiniz." merge_topic: title: "Var Olan Bir Konuya Taşı" action: "var olan bir konuya taşı" error: "Gönderiler konuya aşınırken bir hata oluştu." instructions: + one: "Lütfen bu {{count}} gönderiyi taşımak istediğiniz konuyu seçin. " other: "Lütfen bu {{count}} gönderiyi taşımak istediğiniz konuyu seçin. " merge_posts: title: "Seçili Gönderileri Birleştir" @@ -1577,6 +1647,7 @@ tr_TR: label: "Gönderilerin Yeni Sahibi" placeholder: "yeni sahibin kullanıcı adı" instructions: + one: "Lütfen {{old_user}} kullanıcısına ait {{count}} gönderinin yeni sahibini seçin." other: "Lütfen {{old_user}} kullanıcısına ait {{count}} gönderinin yeni sahibini seçin." instructions_warn: "Bu gönderi ile ilgili geriye dönük biriken bildirimler yeni kullanıcıya aktarılmayacak.
Uyarı: Ĺžu an, yeni kullanıcıya hiç bir gönderi-tabanlı ek bilgi aktarılmıyor. Dikkatli olun." change_timestamp: @@ -1587,12 +1658,15 @@ tr_TR: multi_select: select: 'seç' selected: '({{count}}) seçildi' + select_post: + label: 'seç' delete: seçilenleri sil cancel: seçimi iptal et select_all: hepsini seç deselect_all: tĂĽm seçimi kaldır description: - other: {{count}} gönderi seçtiniz. + one: "{{count}} gönderi seçtiniz." + other: "{{count}} gönderi seçtiniz." post: quote_reply: "Alıntı" edit_reason: "Neden: " @@ -1606,19 +1680,25 @@ tr_TR: show_full: "Gönderinin Tamamını Göster" show_hidden: 'GizlenmiĹź içeriÄźi görĂĽntĂĽle.' deleted_by_author: + one: "(yazarı tarafından geri alınan gönderi, bildirilmediÄźi takdirde %{count} saat içinde otomatik olarak silinecek.)" other: "(yazarı tarafından geri alınan gönderi, bildirilmediÄźi takdirde %{count} saat içinde otomatik olarak silinecek.)" expand_collapse: "aç/kapat" gap: + one: "gizlenen {{count}} yorumu gör" other: "gizlenen {{count}} yorumu gör" unread: "Gönderi okunmamış" has_replies: + one: "{{count}} Cevap" other: "{{count}} Cevap" has_likes: + one: "{{count}} BeÄźeni" other: "{{count}} BeÄźeni" has_likes_title: + one: "{{count}} kiĹźi bu gönderiyi beÄźendi" other: "{{count}} kiĹźi bu gönderiyi beÄźendi" has_likes_title_only_you: "bu gönderiyi beÄźendiniz" has_likes_title_you: + one: "siz ve {{count}} diÄźer kiĹźi bu gönderiyi beÄźendi" other: "siz ve {{count}} diÄźer kiĹźi bu gönderiyi beÄźendi" errors: create: "ĂśzgĂĽnĂĽz, gönderiniz oluĹźturulurken bir hata oluĹźtu. LĂĽtfen tekrar deneyin." @@ -1693,37 +1773,53 @@ tr_TR: vote: "Bu gönderiyi oyladınız" by_you_and_others: off_topic: + one: "Siz ve {{count}} diÄźer kiĹźi bunu konu dışı olarak bildirdi" other: "Siz ve {{count}} diÄźer kiĹźi bunu konu dışı olarak bildirdi" spam: + one: "Siz ve {{count}} diÄźer kiĹźi bunu istenmeyen olarak bildirdi" other: "Siz ve {{count}} diÄźer kiĹźi bunu istenmeyen olarak bildirdi" inappropriate: + one: "Siz ve {{count}} diÄźer kiĹźi bunu uygunsuz olarak bildirdi" other: "Siz ve {{count}} diÄźer kiĹźi bunu uygunsuz olarak bildirdi" notify_moderators: + one: "Siz ve {{count}} diÄźer kiĹźi bunu denetlenmesi için bildirdi" other: "Siz ve {{count}} diÄźer kiĹźi bunu denetlenmesi için bildirdi" notify_user: + one: "Siz ve {{count}} diÄźer kiĹźi bu kullanıcıya ileti yolladı" other: "Siz ve {{count}} diÄźer kiĹźi bu kullanıcıya ileti yolladı" bookmark: + one: "Siz ve {{count}} diÄźer kiĹźi bu gönderiyi imledi" other: "Siz ve {{count}} diÄźer kiĹźi bu gönderiyi imledi" like: + one: "Siz ve {{count}} baĹźka kiĹźi bunu beÄźendi" other: "Siz ve {{count}} baĹźka kiĹźi bunu beÄźendi" vote: + one: "Siz ve {{count}} kiĹźi bu gönderiyi oyladı" other: "Siz ve {{count}} kiĹźi bu gönderiyi oyladı" by_others: off_topic: + one: "{{count}} kiĹźi bunu konu dışı olarak bildirdi" other: "{{count}} kiĹźi bunu konu dışı olarak bildirdi" spam: + one: "{{count}} kiĹźi bunu istenmeyen olarak bildirdi" other: "{{count}} kiĹźi bunu istenmeyen olarak bildirdi" inappropriate: + one: "{{count}} kiĹźi bunu uygunsuz olarak bildirdi" other: "{{count}} kiĹźi bunu uygunsuz olarak bildirdi" notify_moderators: + one: "{{count}} kiĹźi bunu moderasyon için bildirdi" other: "{{count}} kiĹźi bunu moderasyon için bildirdi" notify_user: + one: "{{count}} bu kullanıcıya ileti yolladı" other: "{{count}} bu kullanıcıya ileti yolladı" bookmark: + one: "{{count}} kiĹźi bu gönderiyi imledi" other: "{{count}} kiĹźi bu gönderiyi imledi" like: + one: "{{count}} kiĹźi bunu beÄźendi" other: "{{count}} kiĹźi bunu beÄźendi" vote: + one: "{{count}} kiĹźi bu gönderiyi oyladı" other: "{{count}} kiĹźi bu gönderiyi oyladı" revisions: controls: @@ -1797,7 +1893,6 @@ tr_TR: email_in_allow_strangers: "Hesabı olmayan, anonim kullanıcılardan e-posta kabul et" email_in_disabled: "E-posta ĂĽzerinden yeni konu oluĹźturma özelliÄźi Site Ayarları'nda devre dışı bırakılmış. E-posta ĂĽzerinden yeni konu oluĹźturma özelliÄźini etkinleĹźtirmek için," email_in_disabled_click: '"e-postala" ayarını etkinleĹźtir' - suppress_from_homepage: "Bu kategoriyi ana sayfadan gizle" all_topics_wiki: "Yeni konuyu varsayılan olarak wiki yap" allow_badges_label: "Bu kategoride rozet verilmesine izin ver" edit_permissions: "İzinleri DĂĽzenle" @@ -1859,10 +1954,13 @@ tr_TR: custom_placeholder_notify_moderators: "Sizi neyin endiĹźelendirdiÄźini açıklayıcı bir dille bize bildirin ve mĂĽmkĂĽn olan yerlerde konu ile alakalı baÄźlantıları paylaşın." custom_message: at_least: + one: "en azından {{count}} karakter girin" other: "en azından {{count}} karakter girin" more: + one: "{{count}} daha..." other: "{{count}} daha..." left: + one: "{{count}} kaldı" other: "{{count}} kaldı" flagging_topic: title: "TopluluÄźumuzun dĂĽzenli kalmasına desteÄźiniz için teĹźekkĂĽrler!" @@ -1874,10 +1972,12 @@ tr_TR: links_title: "Gözde BaÄźlantılar" links_shown: "daha fazla baÄźlantı göster..." clicks: + one: "%{count} tıklama" other: "%{count} tıklama" post_links: about: "bu gönderi için daha fazla baÄźlantı koy" title: + one: "%{count} daha" other: "%{count} daha" topic_statuses: warning: @@ -1912,17 +2012,21 @@ tr_TR: original_post: "Ă–zgĂĽn Gönderi" views: "Gösterim" views_lowercase: + one: "gösterim" other: "gösterim" replies: "Cevap" views_long: + one: "bu konu {{number}} defa görĂĽntĂĽlendi" other: "bu konu {{number}} defa görĂĽntĂĽlendi" activity: "Etkinlik" likes: "BeÄźeni" likes_lowercase: + one: "beÄźeni" other: "beÄźeni" likes_long: "bu konuda {{number}} beÄźeni var" users: "Kullanıcı" users_lowercase: + one: "kullanıcı" other: "kullanıcı" category_title: "Kategori" history: "GeçmiĹź" @@ -1936,6 +2040,7 @@ tr_TR: latest: title: "En son" title_with_count: + one: "En Son ({{count}})" other: "En Son ({{count}})" help: "yakın zamanda gönderi alan konular" hot: @@ -1954,16 +2059,20 @@ tr_TR: unread: title: "Okunmamış" title_with_count: + one: "Okunmamış ({{count}})" other: "Okunmamış ({{count}})" help: "okunmamış gönderiler bulunan gözlediÄźiniz ya da takip ettiÄźiniz konular" lower_title_with_count: + one: "{{count}} okunmamış" other: "{{count}} okunmamış" new: lower_title_with_count: + one: "{{count}} yeni" other: "{{count}} yeni" lower_title: "yeni" title: "Yeni" title_with_count: + one: "Yeni ({{count}}) " other: "Yeni ({{count}}) " help: "son birkaç gĂĽnde oluĹźturulmuĹź konular" posted: @@ -1975,6 +2084,7 @@ tr_TR: category: title: "{{categoryName}}" title_with_count: + one: "{{categoryName}} ({{count}})" other: "{{categoryName}} ({{count}})" help: "{{categoryName}} kategorisindeki en son konular" top: @@ -2059,15 +2169,19 @@ tr_TR: print: 'ctrl+p Konuyu yazdır' badges: earned_n_times: + one: "Bu rozet %{count} defa kazanılmış" other: "Bu rozet %{count} defa kazanılmış" granted_on: "%{date} tarihinde verildi" others_count: "Bu rozete sahip diÄźer kiĹźiler (%{count})" title: Rozetler badge_count: + one: "%{count} Rozet" other: "%{count} Rozet" more_badges: + one: "+%{count} Daha" other: "+%{count} Daha" granted: + one: "%{count} izin verildi" other: "%{count} izin verildi" select_badge_for_title: BaĹźlık yazısı olarak kullanılacak bir rozet seçin badge_grouping: @@ -2271,7 +2385,6 @@ tr_TR: edit: "Grupları DĂĽzenle" refresh: "Yenile" new: "Yeni" - selector_placeholder: "kullanıcı adı girin" about: "Grup ĂĽyeliÄźinizi ve isimleri burada dĂĽzenleyin" group_members: "Grup ĂĽyeleri" delete: "Sil" @@ -2356,8 +2469,10 @@ tr_TR: none: "İlgili bir olay yok." redeliver: "Yeniden ilet" incoming: + one: "Ĺžu anda {{count}} yeni olay var." other: "Ĺžu anda {{count}} yeni olay var." completed_in: + one: "{{count}} saniyede tamamlandı." other: "{{count}} saniyede tamamlandı." request: "İstek" response: "Yanıt" @@ -2733,8 +2848,10 @@ tr_TR: suspect: 'KuĹźkulanılan' approved: "Onaylanmış mı?" approved_selected: + one: "({{count}}) kullanıcıyı onayla " other: "({{count}}) kullanıcıyı onayla " reject_selected: + one: "({{count}}) kullanıcıyı reddet" other: "({{count}}) kullanıcıyı reddet" titles: active: 'Etkin Kullanıcılar' @@ -2751,8 +2868,10 @@ tr_TR: suspended: 'UzaklaĹźtırılmış Kullanıcılar' suspect: 'KuĹźkulanılan Kullanıcılar' reject_successful: + one: "BaĹźarıyla reddedilmiĹź %{count} kullanıcı." other: "BaĹźarıyla reddedilmiĹź %{count} kullanıcı." reject_failures: + one: "Reddedilemeyen %{count} kullanıcı." other: "Reddedilemeyen %{count} kullanıcı." not_verified: "Onaylanmayan" check_email: @@ -2815,10 +2934,13 @@ tr_TR: delete_forbidden_because_staff: "Yöneticiler ve moderatörler silinemez." delete_posts_forbidden_because_staff: "Yöneticiler ve moderatörlerin tĂĽm gönderileri silinemez." delete_forbidden: + one: "Gönderisi olan kullanıcılar silinemez. Kullanıcıyı silmeden önce tĂĽm gönderilerini silin. (%{count} gĂĽnden eski gönderiler silinemez.)" other: "Gönderisi olan kullanıcılar silinemez. Kullanıcıyı silmeden önce tĂĽm gönderilerini silin. (%{count} gĂĽnden eski gönderiler silinemez.)" cant_delete_all_posts: + one: "TĂĽm gönderileri silemezsiniz. Bazı gönderiler %{count} gĂĽnden daha eski. (delete_user_max_post_age ayarı.)" other: "TĂĽm gönderileri silemezsiniz. Bazı gönderiler %{count} gĂĽnden daha eski. (delete_user_max_post_age ayarı.)" cant_delete_all_too_many_posts: + one: "TĂĽm gönderileri silemezsiniz çünkĂĽ kullanıcının %{count} 'ten daha fazla gönderisi var. (delete_all_posts_max)" other: "TĂĽm gönderileri silemezsiniz çünkĂĽ kullanıcının %{count} 'ten daha fazla gönderisi var. (delete_all_posts_max)" delete_confirm: "Bu kullanıcıyı silmek istediÄźinize EMİN misiniz? Bu iĹźlem geri alınamaz!" delete_and_block: "Sil ve bu e-posta ve IP adresini engelle" @@ -2855,6 +2977,7 @@ tr_TR: tl3_requirements: title: "GĂĽven Seviyesi 3 için Gerekenler" table_title: + one: "Son %{count} gĂĽnde:" other: "Son %{count} gĂĽnde:" value_heading: "DeÄźer" requirement_heading: "Gereksinim" @@ -2930,10 +3053,7 @@ tr_TR: recommended: "İzleyen metni ihtiyaçlarınıza uygun Ĺźekilde özelleĹźtirmenizi öneririz:" show_overriden: 'Sadece deÄźiĹźtirdiklerimi göster' site_settings: - show_overriden: 'Sadece deÄźiĹźtirdiklerimi göster' title: 'Ayarlar' - reset: 'sıfırla' - none: 'Hiçbiri' no_results: "Hiç sonuç bulunamadı." clear_filter: "Temizle" add_url: "URL ekle" @@ -3024,6 +3144,7 @@ tr_TR: text: "Bazı veriliĹź örnekleri bulunamıyor. Bu durum, rozet sorgusundan varolmayan kullanıcı IDsi veya gönderi IDsi dönĂĽnce gerçekleĹźir. İleride beklenmedik sonuçlara sebep olabilir - lĂĽtfen sorgunuzu tekrar kontrol edin." no_grant_count: "Verilecek rozet bulunmuyor." grant_count: + one: "%{count} rozet verilecek." other: "%{count} rozet verilecek." sample: "Ă–rnek:" grant: @@ -3090,6 +3211,7 @@ tr_TR: uploading: "YĂĽkleniyor..." quit: "Belki Sonra" staff_count: + one: "TopluluÄźunuzda %{count} görevli ĂĽye var." other: "TopluluÄźunuzda %{count} görevli ĂĽye var." invites: add_user: "ekle" diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index eec08baed0..75f8626197 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -18,7 +18,7 @@ uk: byte: one: Байт few: Байтів - other: Байтів + other: Байт gb: ГБ kb: КБ mb: МБ @@ -250,7 +250,6 @@ uk: delete_member_confirm: "Видалити '%{username}' Đ· грŃпи '%{group}'?" add: "Додати" closed_group: Закрита ГрŃпа - selector_placeholder: "Додати ŃчаŃників" members: "УчаŃники" topics: "Теми" posts: "ДопиŃи" @@ -856,9 +855,9 @@ uk: select_all: обрати ŃŃе deselect_all: ŃкаŃŃвати вибір вŃього description: - one: Ви обрали 1 допиŃ. - few: 'Ви обрали допиŃів: {{count}}.' - other: 'Ви обрали допиŃів: {{count}}.' + one: "Ви обрали 1 допиŃ." + few: "Ви обрали допиŃів: {{count}}." + other: "Ви обрали допиŃів: {{count}}." post: edit_reason: "Причина: " post_number: "Đ´ĐľĐżĐ¸Ń {{number}}" @@ -1166,7 +1165,6 @@ uk: edit: "РедагŃвати грŃпи" refresh: "Оновити" new: "Новий" - selector_placeholder: "введіть Ń–ĐĽ'ŃŹ кориŃŃ‚Ńвача" about: "Edit your group membership and names here" group_members: "УчаŃники грŃпи" delete: "Видалити" @@ -1595,10 +1593,7 @@ uk: go_back: "ПовернŃтиŃŃŚ Đ´Đľ поŃŃĐşŃ" show_overriden: 'ПоказŃвати тільки перевизначені' site_settings: - show_overriden: 'ПоказŃвати тільки перевизначені' title: 'НалаŃŃ‚Ńвання' - reset: 'ŃкинŃти' - none: 'none' no_results: "Не знайдено резŃльтатів." clear_filter: "ОчиŃтити" add_url: "додати URL" diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml index d4543a45ac..69767facb2 100644 --- a/config/locales/client.vi.yml +++ b/config/locales/client.vi.yml @@ -360,7 +360,6 @@ vi: name: "TĂŞn" user_count: "Số lượng thĂ nh viĂŞn" bio: "ThĂ´ng tin vá» nhĂłm" - selector_placeholder: "ThĂŞm thĂ nh viĂŞn" owner: "chá»§" index: title: "NhĂłm" @@ -1436,7 +1435,7 @@ vi: select_all: chọn tất cả deselect_all: bỏ chọn tất cả description: - other: Bạn đã chọn {{count}} bĂ i viáşżt. + other: "Bạn đã chọn {{count}} bĂ i viáşżt." post: quote_reply: "TrĂ­ch dáş«n" edit_reason: "LĂ˝ do: " @@ -1636,7 +1635,6 @@ vi: email_in_allow_strangers: "Nháş­n thư Ä‘iện tá»­ từ người gá»­i vĂ´ danh khĂ´ng tĂ i khoản" email_in_disabled: "Tạo chá»§ đỠmá»›i thĂ´ng qua email đã được tắt trong thiáşżt láş­p. Äá» báş­t tĂ­nh nÄng nĂ y, " email_in_disabled_click: 'kĂ­ch hoạt thiáşżt láş­p thư Ä‘iện tá»­' - suppress_from_homepage: "NgÄn cháş·n chuyĂŞn mục nĂ y hiá»n thị trĂŞn trang chá»§." allow_badges_label: "Cho phĂ©p thưởng huy hiệu trong chuyĂŞn mục nĂ y" edit_permissions: "Sá»­a quyá»n" add_permission: "ThĂŞm quyá»n" @@ -1981,7 +1979,6 @@ vi: edit: "Sá»­a nhĂłm" refresh: "LĂ m má»›i" new: "Má»›i" - selector_placeholder: "nháş­p tĂŞn tĂ i khoản" about: "Chỉnh sá»­a nhĂłm thĂ nh viĂŞn vĂ  tĂŞn cá»§a bạn ở đây" group_members: "NhĂłm thĂ nh viĂŞn" delete: "XĂła" @@ -2503,10 +2500,7 @@ vi: recommended: "Bạn nĂŞn tĂąy biáşżn các ná»™i dung sau đây cho phĂą hợp vá»›i nhu cáş§u:" show_overriden: 'Chỉ hiá»n thị chá»— ghi đè' site_settings: - show_overriden: 'Chỉ hiện thị đã ghi đè' title: 'Xác láş­p' - reset: 'trạng thái đầu' - none: 'khĂ´ng cĂł gì' no_results: "KhĂ´ng tìm thấy káşżt quả." clear_filter: "XĂła" add_url: "thĂŞm URL" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 7b38316673..afb09c58a5 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -360,7 +360,6 @@ zh_CN: name: "群组ID" user_count: "ćĺ‘ć•°ç›®" bio: "关于群组" - selector_placeholder: "添加ćĺ‘" owner: "所有者" index: title: "群组" @@ -508,6 +507,7 @@ zh_CN: disable_jump_reply: "回复ĺŽä¸Ťč·łč˝¬č‡łć–°ĺ¸–ĺ­" dynamic_favicon: "在浏č§ĺ™¨ĺ›ľć ‡ä¸­ćľç¤şä¸»é˘ć›´ć–°ć•°é‡Ź" theme_default_on_all_devices: "将其设为ć‘所有设备上的é»č®¤ä¸»é˘" + allow_private_messages: "ĺ…许其他用ć·ĺŹ‘é€ç§äżˇç»™ć‘" external_links_in_new_tab: "在新标签页打开外é¨é“ľćŽĄ" enable_quoting: "在选择文字时ćľç¤şĺĽ•用回复按钮" change: "修改" @@ -603,6 +603,9 @@ zh_CN: set_password: "设置密ç " choose_new: "输入新密ç " choose: "输入密ç " + second_factor: + title: "双重验čŻ" + info_prompt: "什äąćŻĺŹŚé‡ŤéŞŚčŻďĽź" change_about: title: "更改个人信ćŻ" error: "ćŹäş¤äż®ć”ąć—¶ĺ‡şé”™äş†" @@ -1620,7 +1623,7 @@ zh_CN: select_all: 全选 deselect_all: 全不选 description: - other: 已选择 {{count}} 个帖ĺ­ă€‚ + other: "已选择 {{count}} 个帖ĺ­ă€‚" post: quote_reply: "引用" edit: " {{link}} {{replyAvatar}} {{username}}" @@ -1843,7 +1846,6 @@ zh_CN: email_in_disabled: "站点设置中已经ç¦ç”¨é€ščż‡é‚®ä»¶ĺʑ莍㖰䏻é˘ă€‚欲ĺŻç”¨é€ščż‡é‚®ä»¶ĺʑ莍㖰䏻é˘ďĽŚ" email_in_disabled_click: 'ĺŻç”¨â€śé‚®ä»¶ĺŹ‘čˇ¨â€ťč®ľç˝®ă€‚' mailinglist_mirror: "ĺ†ç±»é•śĺŹäş†ä¸€ä¸Şé‚®ä»¶ĺ—表" - suppress_from_homepage: "不在主页中ćľç¤şčż™ä¸Şĺ†ç±»ă€‚" show_subcategory_list: "在这个ĺ†ç±»ä¸­ćŠŠĺ­ĺ†ç±»ĺ—表ćľç¤şĺś¨ä¸»é˘çš„上面" num_featured_topics: "ĺ†ç±»éˇµéť˘ä¸Šćľç¤şçš„主é˘ć•°é‡ŹďĽš" subcategory_num_featured_topics: "ç¶ĺ†ç±»éˇµéť˘ä¸Šçš„推čŤä¸»é˘ć•°é‡ŹďĽš" @@ -2358,7 +2360,6 @@ zh_CN: edit: "编辑群组" refresh: "ĺ·ć–°" new: "新群组" - selector_placeholder: "输入用ć·ĺŤ" about: "在这里编辑群组的ĺŤĺ­—ĺ’Śćĺ‘" group_members: "群组ćĺ‘" delete: "ĺ é™¤" @@ -2994,6 +2995,7 @@ zh_CN: private_topics_count: ç§ćś‰ä¸»é˘ć•°é‡Ź posts_read_count: ĺ·˛é…帖ĺ­ć•°é‡Ź post_count: 发表的帖ĺ­ć•°é‡Ź + second_factor_enabled: 双重验čŻĺ·˛ĺŻç”¨ topics_entered: 已查看的主é˘ć•°é‡Ź flags_given_count: ćŹäş¤ć ‡č®°ć•°é‡Ź flags_received_count: 被他人标记数量 @@ -3132,10 +3134,7 @@ zh_CN: recommended: "ć‘们建议自定义以下文本以符ĺ你的需求:" show_overriden: '只ćľç¤şäż®ć”ąčż‡çš„' site_settings: - show_overriden: '只ćľç¤şäż®ć”ąčż‡çš„' title: '设置' - reset: '重置为é»č®¤' - none: 'ć— ' no_results: "找不ĺ°ç»“果。" clear_filter: "清除" add_url: "增加链接" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 0a26c393e2..e94cce83f1 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -337,7 +337,6 @@ zh_TW: name: "ĺŤĺ­—" user_count: "ć員數量" bio: "關於群組" - selector_placeholder: "新增ć員" owner: "ć“有者" index: title: "群組" @@ -1402,7 +1401,7 @@ zh_TW: select_all: é¸ć“‡ĺ…¨é¨ deselect_all: 取ć¶é¸ĺŹ– description: - other: ä˝ ĺ·˛é¸ć“‡äş† {{count}} 篇文章。 + other: "ä˝ ĺ·˛é¸ć“‡äş† {{count}} 篇文章。" post: quote_reply: "引用" edit_reason: "原因: " @@ -1602,7 +1601,6 @@ zh_TW: email_in_allow_strangers: "接受非用ć¶çš„é›»éµ" email_in_disabled: "\"用電ĺ­éµä»¶ĺĽµč˛Ľć–°çš„討論話題\"功č˝ĺ·˛č˘«é—śé–‰ă€‚若č¦ä˝żç”¨ć­¤ĺŠźč˝ďĽŚ" email_in_disabled_click: '請啟用"email in"功č˝' - suppress_from_homepage: "不在首é ä¸ŠéˇŻç¤şć­¤ĺ†éˇžă€‚" show_subcategory_list: "在此ĺ†éˇžä¸­ďĽŚĺ°‡ĺ­ĺ†éˇžéˇŻç¤şĺś¨ä¸»éˇŚä¸Šć–ąă€‚" all_topics_wiki: "新的主題é č¨­ç‚şĺ…±ç­†ă€‚" allow_badges_label: "ĺ…許ćŽäşćś¬ĺ†éˇžçš„徽章" @@ -2065,7 +2063,6 @@ zh_TW: edit: "編輯群組" refresh: "重新整ç†" new: "建立" - selector_placeholder: "輸入用ć¶ĺŤç¨±" about: "請在此編輯你的群組ć員č‡ĺŤç¨±" group_members: "群組ć員" delete: "ĺŞé™¤" @@ -2682,10 +2679,7 @@ zh_TW: recommended: "ć‘們建議自定義以下文本以符ĺ你的需求:" show_overriden: '只顯示修改éŽçš„é …ç›®' site_settings: - show_overriden: '只顯示修改éŽçš„é …ç›®' title: '設定' - reset: '重設' - none: '無' no_results: "未找ĺ°ä»»ä˝•çµćžśă€‚" clear_filter: "清除" add_url: "加入網址" diff --git a/config/locales/names.yml b/config/locales/names.yml index 60e9925014..764cdff0a0 100644 --- a/config/locales/names.yml +++ b/config/locales/names.yml @@ -436,7 +436,7 @@ sk: nativeName: slovenÄŤina sl: name: Slovene - nativeName: slovenski jezik + nativeName: slovenščina sm: name: Samoan nativeName: gagana fa'a Samoa diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index c2549c23b3..60293d4cc6 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -110,11 +110,11 @@ ar: template: body: 'حدثت مشاŮŮ„ بالحقŮŮ„ الآتية:' header: - zero: لا أخطاء تمنع هذا %{model} من الحŮظ - one: خطأ Ůاحد منع هذا %{model} من الحŮظ - two: خطأن منعا هذا %{model} من الحŮظ - few: أخطاء قليل تمنع هذا %{model} من الحŮظ - many: أخطاء Ůثيرة تمنع هذا %{model} من الحŮظ + zero: 'لا أخطاء تمنع هذا %{model} من الحŮظ' + one: 'خطأ Ůاحد منع هذا %{model} من الحŮظ' + two: 'خطأن منعا هذا %{model} من الحŮظ' + few: 'أخطاء قليل تمنع هذا %{model} من الحŮظ' + many: 'أخطاء Ůثيرة تمنع هذا %{model} من الحŮظ' other: '%{count} أخطاء منعت هذا %{model} من الحŮظ' embed: load_from_remote: "Ř­ŘŻŘ« عطل أثناء ŘŞŘ­Ů…ŮŠŮ„ هذا المنشŮر." @@ -213,7 +213,6 @@ ar: few: "آسŮŮن، ŮŠŮ…Ůن للمستخدمين الجدد Ůضع %{count} رŮابط Ůقط ŮŮŠ ŮŮ„ منشŮر." many: "آسŮŮن، ŮŠŮ…Ůن للمستخدمين الجدد Ůضع %{count} رابطا Ůقط ŮŮŠ ŮŮ„ منشŮر." other: "عذرا، ŮŠŮ…Ůن للأعضاء الجدد Ůضع %{count} رابط Ůقط ŮŮŠ ŮŮ„ منشŮر." - contains_blocked_words: "المنشŮر الخاص ب٠يحتŮŮŠ علي Ůلمات غير مسمŮŘ­ بها." spamming_host: "عذرا، لا ŮŠŮ…Ůن٠نشر رابط لهذا العنŮان." user_is_suspended: "غير مسمŮŘ­ للأعضاء المعلّقة عضŮيتهم النشر." topic_not_found: "Ř­ŘŻŘ« خطب ما. قد ŮŠŮŮن المŮضŮŘą أُغلق أ٠حُذ٠Ůأنت تشاهده؟" @@ -376,21 +375,21 @@ ar: title: "مرحبا ب٠ŮŮŠ الاستراحة" body: |2 - تهانينا! :confetti_ball: + تهانينا! :confetti_ball: - إذا ŘŞŮ…Ůنت من رؤية هذا المŮضŮŘą, Ůقد ŘŞŮ… ترقيت٠مؤخرا لـمستŮŮŠ**منتظم** (مستŮى الثقة 3). + إذا ŘŞŮ…Ůنت من رؤية هذا المŮضŮŘą, Ůقد ŘŞŮ… ترقيت٠مؤخرا لـمستŮŮŠ**منتظم** (مستŮى الثقة 3). - انت الأن تستطيع &hellip + انت الأن تستطيع &hellip - * تعديل العنŮان لأي Ů…ŮضŮŘą. - * تغيير القسم لأي Ů…ŮضŮŘą. - * جميع الرŮابط التي تضعها تُأرش٠من قبل محرŮات البحث ( خيار [automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) غير Ů…Ůعل). - * الŮصŮŮ„ لقسم خاص للأعضاء بمستŮى الثقة 3 أ٠أعلى. - * ŘĄŘ®Ůاء رسائل السبام ببلاغ Ůاحد. + * تعديل العنŮان لأي Ů…ŮضŮŘą. + * تغيير القسم لأي Ů…ŮضŮŘą. + * جميع الرŮابط التي تضعها تُأرش٠من قبل محرŮات البحث ( خيار [automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) غير Ů…Ůعل). + * الŮصŮŮ„ لقسم خاص للأعضاء بمستŮى الثقة 3 أ٠أعلى. + * ŘĄŘ®Ůاء رسائل السبام ببلاغ Ůاحد. - من هنا [القائمة الحالية للأعضاء بمستŮŮŠ متابع منتظم](/badges/3/regular). + من هنا [القائمة الحالية للأعضاء بمستŮŮŠ متابع منتظم](/badges/3/regular). - Ř´Ůرا Ů„ŮŮن٠جزءً هاما من هذا المجتمع! + Ř´Ůرا Ů„ŮŮن٠جزءً هاما من هذا المجتمع! category: topic_prefix: "عن القسم %{category}." replace_paragraph: "(قم بإستبدال هذة الŮقرة بŮص٠مختصر للقسم الجديد. هذا الŮص٠سŮ٠يظهر ŮŮŠ منطقة إختيار القسم, لذا حاŮŮ„ ان ŮŠŮŮن اقل من 200 حرŮ. ** Ř­ŘŞŮŠ ŘŞŮ‚ŮŮ… بتعديل هذا الŮص٠أ٠تقŮŮ… بإضاŮŘ© Ů…ŮضŮعات, هذا القسم لن يظهر ŮŮŠ صŮŘ­Ř© الأقسام**)" @@ -672,8 +671,6 @@ ar: long_form: 'ترŮŘą علم هذا عن صŮرة غير ملائمة' notify_user: title: 'أرسل رسالة إلى @{{username}}' - description: 'ŘŁŮŘŻ الحديث Ů…Řą هذا الشخص مباشرة Ůعلى انŮراد عن هذا المنشŮر.' - short_description: 'ŘŁŮŘŻ الحديث Ů…Řą هذا الشخص مباشرة Ůعلى انŮراد عن هذا المنشŮر.' long_form: 'العض٠أرسل' email_title: 'منشŮر٠ŮŮŠ "%{title}"' email_body: "%{link}\n\n%{message}" @@ -918,7 +915,6 @@ ar: subfolder_ends_in_slash: "إعدادات المجلدات الداخلية خاطئ;ال DISCOURSE_RELATIVE_URL_ROOT يجب ان تنتهي ب سلاش." site_settings: censored_words: "الŮلمات التي ستُستبدل آليًّا ب‍ ■■■■" - censored_pattern: "نمط التّعبير النّمطيّ الذي سيُستبدل آليًّا ب‍ ■■■■" delete_old_hidden_posts: "سيتم حذ٠المنشŮرات المخŮŮŠŘ© تلقائيًا إذا زادت Ů…ŘŻŘ© الإخŮاء ŘŁŮثر من 30 ŮŠŮمًا" allow_user_locale: "اسمح للمستخدمين باختيار لغة الŮاجهة التي تناسبهم" set_locale_from_accept_language_header: "اختيار لغة الŮاجة للمستخدمين المتخŮŮن طبقا للغة المختارة بمتصŮŘ­ الشبŮŘ©. ( إعداد تجريبى، لا يعمل Ů…Řą ذاŮرة المتصŮŘ­ )" @@ -1052,8 +1048,6 @@ ar: google_oauth2_client_id: "رقم التعري٠لتطبيق جŮجل الخاص بŮ" google_oauth2_client_secret: "الرقم السري لتطبيق جŮجل الخاص بŮ." enable_twitter_logins: "ŘŞŮعيل تسجيل الدخŮŮ„ بحساب ŘŞŮيتر." - twitter_consumer_key: "Ů…Ůتاح المستهل٠لنظام تسجيل الدخŮŮ„ بحساب ŘŞŮيتر، ŮŠŮ…Ůن الحصŮŮ„ علي Ůاحد من http://dev.twitter.com" - twitter_consumer_secret: "رقم المستهل٠السري لنظام تسجيل الدخŮŮ„ بحساب ŘŞŮيتر، ŮŠŮ…Ůن الحصŮŮ„ علي Ůاحد من http://dev.twitter.com" enable_instagram_logins: "ŘŞŮعيل تسجيل الدخŮŮ„ عن طريق حساب انستغرام." instagram_consumer_key: "Ů…Ůتاح المستهل٠لنظام تسجيل الدخŮŮ„ بحساب انستجرام." instagram_consumer_secret: "رقم المستهل٠السري لنظام تسجيل الدخŮŮ„ بحساب انستجرام." @@ -1068,7 +1062,6 @@ ar: allow_restore: "تسمح استعادة، Ůالتي ŮŠŮ…Ůن أن ŘŞŘ­Ů„ Ů…Ř­Ů„ ALL بيانات المŮقع! تر٠Ůاذبة إلا إذا Ůنت تخطط لاستعادة نسخة احتياطية" maximum_backups: "ŘŁŮبر قدر Ů…Ů…Ůن من النسخ الاحتياطي للحŮاظ على القرص. ŮŠŘŞŮ… حذ٠النسخ الاحتياطية القديمة تلقائيا" automatic_backups_enabled: "Ůعل النسخ الإحتياطي التلقائي بشŮŮ„ Ů…ŘŞŮرر Ůما ه٠محدد." - backup_frequency: "ŮŮ…ŮŠŘ© النسخ الإحتياطية المتŮررة التي أنشأناها، ŮŮŠ اليŮŮ…." enable_s3_backups: "ŘŞŘ­Ů…ŮŠŮ„ النسخ الاحتياطي Ů„S3 عند اŮتماله. هام: يتطلب اعتماد S3 صالحة دخلت ŮŮŠ إعدادات الملŮات." s3_backup_bucket: "الرŮŘą عن بعد لإجراء نسخ إحتياطية. تحذير : ŘŞŘŁŮŘŻ من أنه رŮŘą خاص." s3_disable_cleanup: "عطل النسخ الاحتياطيه المحذŮŮه من S3 عندما ŮŠŘŞŮ… حذŮها محلياً" diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml index f99df09f81..8de706ea84 100644 --- a/config/locales/server.bs_BA.yml +++ b/config/locales/server.bs_BA.yml @@ -139,25 +139,25 @@ bs_BA: title: "Welcome to the Lounge" body: |2 - Congratulations! :confetti_ball: + Congratulations! :confetti_ball: - If you can see this topic, you were recently promoted to **regular** (trust level 3). + If you can see this topic, you were recently promoted to **regular** (trust level 3). - You can now … + You can now … - * Edit the title of any topic - * Change the category of any topic - * Have all your links followed ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) is removed) - * Access a private Lounge category only visible to users at trust level 3 and higher - * Hide spam with a single flag + * Edit the title of any topic + * Change the category of any topic + * Have all your links followed ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) is removed) + * Access a private Lounge category only visible to users at trust level 3 and higher + * Hide spam with a single flag - Here's the [current list of fellow regulars](/badges/3/regular). Be sure to say hi. + Here's the [current list of fellow regulars](/badges/3/regular). Be sure to say hi. - Thanks for being an important part of this community! + Thanks for being an important part of this community! - (For more information on trust levels, [see this topic][trust]. Please note that only members who continue to meet the requirements over time will remain regulars.) + (For more information on trust levels, [see this topic][trust]. Please note that only members who continue to meet the requirements over time will remain regulars.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "About the %{category} category" errors: @@ -434,8 +434,6 @@ bs_BA: google_oauth2_client_id: "Client ID of your Google application." google_oauth2_client_secret: "Client secret of your Google application." enable_twitter_logins: "Enable Twitter authentication, requires twitter_consumer_key and twitter_consumer_secret" - twitter_consumer_key: "Consumer key for Twitter authentication, registered at http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret for Twitter authentication, registered at http://dev.twitter.com" enable_facebook_logins: "Enable Facebook authentication, requires facebook_app_id and facebook_app_secret" facebook_app_id: "App id for Facebook authentication, registered at https://developers.facebook.com/apps" facebook_app_secret: "App secret for Facebook authentication, registered at https://developers.facebook.com/apps" diff --git a/config/locales/server.ca.yml b/config/locales/server.ca.yml index e65ec18c10..fcef452e5b 100644 --- a/config/locales/server.ca.yml +++ b/config/locales/server.ca.yml @@ -72,7 +72,6 @@ ca: invalid: Ă©s invĂ lid is_invalid: "no sembla clar, Ă©s una frase sencera?" contains_censored_words: "hi contĂ© els segĂĽents mots censurats: %{censored_words}" - matches_censored_pattern: "hi contĂ© els segĂĽents mots que coincideixen amb les expressions regulars censurades del lloc: %{censored_words}" less_than: ha de ser menys de %{count} less_than_or_equal_to: ha de ser igual o menor a %{count} not_a_number: no Ă©s una xifra @@ -324,41 +323,41 @@ ca: title: "Benvinguda o benvingut a Discourse" body: |2 - El primer parĂ graf d'aquest tema fixat será visible com a missatge de benvinguda per a totes les noves visites a la teva pĂ gina principal. És important! + El primer parĂ graf d'aquest tema fixat será visible com a missatge de benvinguda per a totes les noves visites a la teva pĂ gina principal. És important! - **Edita això** per a una breu descripciĂł de la teva comunitat: + **Edita això** per a una breu descripciĂł de la teva comunitat: - - Per a qui Ă©s? - - Què hi poden trobar? - - Per què haurien de venir aquĂ­? - - On poden llegir-ne mĂ©s (enllaços, recursos, etcètera)? + - Per a qui Ă©s? + - Què hi poden trobar? + - Per què haurien de venir aquĂ­? + - On poden llegir-ne mĂ©s (enllaços, recursos, etcètera)? - + - Potser desitges tancar aquest tema amb l'eina d'adminstraciĂł :wrench: (dalt a la dreta i abaix), perquè les respostes no s'amunteguin sobre un anunci. + Potser desitges tancar aquest tema amb l'eina d'adminstraciĂł :wrench: (dalt a la dreta i abaix), perquè les respostes no s'amunteguin sobre un anunci. lounge_welcome: title: "Et donem la benvinguda al salĂł" body: |2 - Enhorabona! :confetti_ball: + Enhorabona! :confetti_ball: - Si pots veure aquest tema, se t'ha promogut recentment a **regular** (nivell 3 de confiança). + Si pots veure aquest tema, se t'ha promogut recentment a **regular** (nivell 3 de confiança). - Ara pots … + Ara pots … - * Editar el tĂ­tol de qualsevol tema - * Canviar la categoria de qualsevol tema - * Tenir tots els teus enllaços seguits ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) Ă©s esborrat) - * Accedir a la categoria privada de SalĂł nomĂ©s visible per a persones usuĂ ries a nivell 3 de confiança i superior - * Amagar correu brossa amb una Ăşnica bandera + * Editar el tĂ­tol de qualsevol tema + * Canviar la categoria de qualsevol tema + * Tenir tots els teus enllaços seguits ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) Ă©s esborrat) + * Accedir a la categoria privada de SalĂł nomĂ©s visible per a persones usuĂ ries a nivell 3 de confiança i superior + * Amagar correu brossa amb una Ăşnica bandera - Vet aquĂ­ la llista [current list of fellow regulars](/badges/3/regular). Assegura't d'enviar-hi una salutaciĂł. + Vet aquĂ­ la llista [current list of fellow regulars](/badges/3/regular). Assegura't d'enviar-hi una salutaciĂł. - GrĂ cies per formar part d'una part important d'aquesta comunitat! + GrĂ cies per formar part d'una part important d'aquesta comunitat! - (Per a mĂ©s informaciĂł sobre nivell de confiança [see this topic][trust]. Si us plau, fixa't que nomĂ©s membres que continuen reunint els requisits seguiran essent ordinaris). + (Per a mĂ©s informaciĂł sobre nivell de confiança [see this topic][trust]. Si us plau, fixa't que nomĂ©s membres que continuen reunint els requisits seguiran essent ordinaris). - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Sobre la categoria %{category} " replace_paragraph: "(Reemplaça aquest primer parĂ graf amb una breu descripciĂł de la teva nova categoria. Aquesta guia apareixerĂ  a l'Ă rea de selecciĂł de categoria, perquè s'hi pugui mantenir per sota de 200 carĂ cters. **Fins que no editis aquesta descripciĂł o en creĂŻs temes, aquesta categoria no apareixerĂ  a la pĂ gina de categories.**)" @@ -528,7 +527,6 @@ ca: long_form: 'marcar amb bandera d''inapropiat' notify_user: title: 'Envia un missatge a @{{username}}' - description: 'Vull parlar directament i privadament amb aquesta persona sobre la seva publicaciĂł.' long_form: 'persona contactada' email_title: 'La teva publicaciĂł a "%{title}"' email_body: "%{link}\n\n%{message}" @@ -775,7 +773,6 @@ ca: poll_pop3_auth_error: "La connexiĂł al servidor POP3 estĂ  fallant amb un error d'autenticaciĂł. Si us plau, revisa la teva configuraciĂł de POP3." site_settings: censored_words: "Paraules que es reemplaçaran automĂ ticament amb ■■■■" - censored_pattern: "PatrĂł d'expressiĂł regular que serĂ  reemplaçat automĂ ticament amb ■■■■" delete_old_hidden_posts: "Esborra automĂ ticament qualsevol publicaciĂł que resten oculta durant mĂ©s de 30 dies." allow_user_locale: "Permet que les persones usuĂ ries triĂŻn la seva preferència de llengua d'interfĂ­cie" set_locale_from_accept_language_header: "configura la llengua d'interfĂ­cie per a persones anònimes des dels encapçalaments dels seus navegadors. (EXPERIMENTAL, no funciona amb memòria cau anònima)" @@ -912,8 +909,6 @@ ca: google_oauth2_client_id: "ID de client a la teva aplicaciĂł Google." google_oauth2_client_secret: "Client secret a la teva aplicaciĂł Google." enable_twitter_logins: "Activar l'autenticaciĂł de Twitter, necessita twitter_consumer_key i twitter_consumer_secret" - twitter_consumer_key: "Clau de consum per a l'autenticaciĂł amb Twitter, registrada a http://dev.twitter.com" - twitter_consumer_secret: "Secret de consum per a l'autenticaciĂł amb Twitter, registrat a http://dev.twitter.com" enable_instagram_logins: "Activa l'autenticaciĂł amb Instagram, necessita instagram_consumer_key i instagram_consumer_secret" instagram_consumer_key: "Clau de consum per a l'autenticaciĂł amb Instagram" instagram_consumer_secret: "Secret de consum per a l'autenticaciĂł amb Instagram" @@ -928,7 +923,6 @@ ca: allow_restore: "Permet restablir, que pot reemplaçar TOTES les dades del lloc! Deixa-ho com a \"false\" si no pretens restablir una còpia de seguretat" maximum_backups: "Quantitat mĂ xima de còpies de seguretat per mantenir al disc. Les còpies mĂ©s antigues seran esborrades automĂ ticament" automatic_backups_enabled: "Executa còpies de seguretat automĂ tiques tal com estan definides a la freqüències de la còpia de seguretat" - backup_frequency: "Amb quina freqüència podem crear una còpia de seguretat del lloc, en dies." enable_s3_backups: "Carrega còpies de seguretat a S3 quan es completi. IMPORTANT: necessita credencials vĂ lides de S3 introduĂŻdes a la configuraciĂł de fitxer." s3_backup_bucket: "Sistema remot de cĂ rrega de còpies de seguretat. ATENCIĂ“: Assegura't que Ă©s una cĂ rrega privada." s3_disable_cleanup: "Inhabilita l'eliminaciĂł de còpies de seguretat de S3 quan s'eliminin localment." @@ -1374,47 +1368,6 @@ ca: subject_template: "Estableix contrasenya per al teu compte de %{site_name}" test_mailer: title: "Prova remitent" - text_body_template: | - Aquest Ă©s un correu de - - [**%{base_url}**][0] - - La subministrabilitat del correu Ă©s complicada. AquĂ­ hi ha algunes coses importants que hauries de revisar primer: - - - "Assegura't" de d'establir correctament el de: de `notification email` a la teva configuraciĂł del lloc. **El domini especificat a l'adreça "de" dels correus que enviĂŻs Ă©s el domini que tornarĂ  a validar-se**. - - - SĂ pigues com veure el codi font dels missatges al teu client de correu, per tal que puguis examinar els encapçalaments per a pistes importants. A Gmail, es mostra a l'opciĂł "mostra l'original" al menĂş desplegable de la part superior dreta de cada missatge. - - - **IMPORTANT:** La teva ISP tĂ© un registre de DNS inversa per associar els noms de dominis i les adreces IP que envies des del correu? [Prova el teu registre Revers PTR][2] aquĂ­. Si la teva ISP no introdueix el registre adequat de DNS inversa, no Ă©s gaire provable que rebis cap correu. - - - És correcte el [registre SPF][8] de domini? [Prova el teu registre SPF][1] aquĂ­. Fixa't que TXT Ă©s el registre correcte oficial per a SPF. - - - És correcte el teu domini [DKIM record][3]? Això millorarĂ  força la lliurabilitat del correu. [Test your DKIM record][7] here. - - - Si executes el teu propi servidor de correu, revisa per assegurar-te que les IP del teu servidor de correu no sĂłn [a cap de les meves llistes negres][4]. Verifica tambĂ© que segur que envia un nom d'amfitriĂł totalment qualifiicat que resol la DNS al seu missatge HELO. En cas contrari, això farĂ  que el teu correu sigui rebutjat per molts serveis de correu. - - - Et recomanem **envia un correu de prova a [mail-tester.com][mt]** per tal de verificar que tot l'esmentat funcioni correctament. - - (La manera "fĂ cil* Ă©s crear un compte lliure a [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] o [Mailjet][mj], que sĂłn generosos amb plans de correu gratuĂŻts i funcionarĂ  per a la majoria de comunitats petites. Encara et caldrĂ  configurar els registres SPF i DKIM a la teva DNS!) - - Esperem que hagis rebut correctament aquesta prova de subministrabilitat de correu! - - Bona sort, - - La teva gent a [Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "Nova versiĂł remitent" new_version_mailer_with_notes: @@ -1751,7 +1704,7 @@ ca: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "Un breu resum de %{site_link} des de la teva darrera visita a %{last_seen_at}" since_last_visit: "Des de la teva darrera visita" diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml index 3b2076df2f..2cb269da54 100644 --- a/config/locales/server.cs.yml +++ b/config/locales/server.cs.yml @@ -41,11 +41,19 @@ cs: default_subject: "Toto tĂ©ma potĹ™ebuje nadpis" show_trimmed_content: "Ukázat upravenĂ˝ obsah" maximum_staged_user_per_email_reached: "Dosáhli jste maximálnĂ­ho poÄŤtu uĹľivatelĹŻ na jeden e-mail." + no_subject: "(bez pĹ™edmÄ›tu)" + no_body: "(bez textu)" errors: empty_email_error: "Toto se stává, kdyĹľ e-mail, kterĂ˝ obdržíme, je prázdnĂ˝." no_message_id_error: "Toto se stává, kdyĹľ e-mail nemá žádnou hlaviÄŤku s \"ID zprávy\"." + inactive_user_error: "Toto se stává, kdyĹľ odesĂ­latel nenĂ­ aktivnĂ­." + silenced_user_error: "Toto se stává, kdyĹľ odesĂ­latel byl umlÄŤen." + topic_not_found_error: "Toto se stává, kdyĹľ pĹ™ijde odpověď k smazanĂ©mu tĂ©matu." + topic_closed_error: "Toto se stává, kdyĹľ pĹ™ijde odpověď k uzavĹ™enĂ©mu tĂ©matu." + unrecognized_error: "Neznámá chyba" errors: &errors format: '%{attribute} %{message}' + format_with_full_message: '%{attribute}: %{message}' messages: too_long_validation: "je limitováno na %{max} znakĹŻ; zadali jste %{length}." invalid_boolean: "NevalidnĂ­ boolean." @@ -63,6 +71,8 @@ cs: has_already_been_used: "jiĹľ byl pouĹľit" inclusion: nenĂ­ zahrnutĂ˝ v seznamu invalid: je neplatnĂ˝ + is_invalid: "zdá se to bĂ˝t nejasnĂ©, je to kompletnĂ­ vÄ›ta?" + contains_censored_words: "obsahuje tato cenzurovaná slova: %{censored_words}" less_than: musĂ­ bĂ˝t menší neĹľ %{count} less_than_or_equal_to: musĂ­ bĂ˝t menší nebo roven %{count} not_a_number: nenĂ­ ÄŤĂ­slo @@ -71,7 +81,7 @@ cs: record_invalid: 'OvěřenĂ­ selhalo: %{errors}' restrict_dependent_destroy: one: "Nelze odstranit záznam, protoĹľe závisejĂ­cĂ­ %{record} existuje" - many: "NemĹŻĹľu smazat záznam protoĹľe na nÄ›m závisĂ­ %{record}." + many: "Nelze smazat záznam protoĹľe na nÄ›m závisĂ­ %{record}." too_long: one: je příliš dlouhĂ˝ ( maximálnÄ› %{count} znak) few: je to příliš dlouhĂ© (maximálnÄ› %{count} znakĹŻ) @@ -88,28 +98,36 @@ cs: template: body: 'Nastaly problĂ©my s následujĂ­cĂ­mi poli: ' header: - one: 1 chyba zamezila uloĹľenĂ­ modelu %{model} + one: '1 chyba zamezila uloĹľenĂ­ modelu %{model}' few: '%{count} chyb zamezilo uloĹľenĂ­ modelu %{model}' other: '%{count} chyb zamezilo uloĹľenĂ­ modelu %{model}' embed: load_from_remote: "PĹ™i naÄŤĂ­tánĂ­ příspÄ›vku nastala chyba." site_settings: - min_username_length_exists: "NemĹŻĹľeš nastavit minimálnĂ­ dĂ©lku uĹľivatelskĂ©ho jmĂ©na delší neĹľ je nejkratší uĹľivatelskĂ© jmĂ©no." - min_username_length_range: "NemĹŻĹľeš nastavit minimum nad maximum." - max_username_length_exists: "NemĹŻĹľeš nastavit dĂ©lku maximálnĂ­ uĹľivatelskĂ©ho jmĂ©na kratší neĹľ je nejdelší uĹľivatelskĂ© jmĂ©no. " - max_username_length_range: "NemĹŻĹľeš nastavit maximum pod minimum." - default_categories_already_selected: "NemĹŻĹľeš vybrat kategorii používanou v jinĂ©m seznamu." + min_username_length_exists: "NemĹŻĹľete nastavit minimálnĂ­ dĂ©lku uĹľivatelskĂ©ho jmĂ©na delší neĹľ je nejkratší uĹľivatelskĂ© jmĂ©no." + min_username_length_range: "NemĹŻĹľete nastavit minimum nad maximum." + max_username_length_exists: "NemĹŻĹľete nastavit dĂ©lku maximálnĂ­ uĹľivatelskĂ©ho jmĂ©na kratší neĹľ je nejdelší uĹľivatelskĂ© jmĂ©no. " + max_username_length_range: "NemĹŻĹľete nastavit maximum pod minimum." + default_categories_already_selected: "NemĹŻĹľete vybrat kategorii používanou v jinĂ©m seznamu." + s3_upload_bucket_is_required: "NemĹŻĹľete nahrávat na S3 dokud nezadáte 's3_upload_bucket'." invite: + not_found: "Odkaz pozvánky je neplatnĂ˝. Kontaktujte prosĂ­m administrátora stránky." user_exists: "NetĹ™eba posĂ­lat pozvánku na %{email}. Tento email je veden u tohoto účtu!" bulk_invite: file_should_be_csv: "NahranĂ˝ soubor by mÄ›l bĂ˝t ve formátu csv." error: "Nastala chyba pĹ™i nahrávánĂ­ souboru. ProsĂ­m opakujte akci pozdÄ›ji." + topic_invite: + user_exists: "Omlouváme se, uĹľivatel byl jiĹľ pozván. K tĂ©matu mĹŻĹľete pozvat uĹľivatele jen jednou." backup: operation_already_running: "PrávÄ› probĂ­há operace %{operation}. V tuto chvĂ­li nelze zahájit novou operaci %{operation}." backup_file_should_be_tar_gz: "Záloha by mÄ›la bĂ˝t archiv s příponou .tar.gz." not_enough_space_on_disk: "Na disku nenĂ­ dostatek mĂ­sta pro nahránĂ­ tĂ©to zálohy." + invalid_filename: "Název zálohy obsahuje neplatnĂ© znaky. PlatnĂ˝mi znaky jsou a-z 0-9 . - _." + invalid_params: "Do poĹľadavku jste zadali neplatnĂ© parametry: %{message}" not_logged_in: "Na tuto akci musĂ­te bĂ˝t pĹ™ihlášenĂ­." not_found: "Žádaná URL adresa nebo zdroj nelze najĂ­t." + invalid_access: "Nemáte oprávnÄ›nĂ­ na zobrazenĂ­ poĹľadovanĂ©ho zdroje." + invalid_api_credentials: "Nemáte oprávnÄ›nĂ­ na zobrazenĂ­ poĹľadovanĂ©ho zdroje. UĹľivatelskĂ© jmĂ©no nebo klĂ­ÄŤ k API je nevalidnĂ­." read_only_mode_enabled: "Tato stránka je v reĹľimu jen pro ÄŤtenĂ­. Interakce jsou zakázány." reading_time: "ÄŚas ÄŤtenĂ­" likes: "LĂ­bĂ­ se" @@ -138,15 +156,34 @@ cs: few: "BohuĹľel, mĹŻĹľete zmĂ­nit jen %{count} uĹľivatele v jednom příspÄ›vku." other: "BohuĹľel, mĹŻĹľete zmĂ­nit jen %{count} uĹľivatelĹŻ v jednom příspÄ›vku." no_mentions_allowed_newuser: "BohuĹľel, novĂ­ uĹľivatelĂ© nemohou zmiĹovat ostatnĂ­ uĹľivatele." + too_many_mentions_newuser: + one: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou zmĂ­nit jen jednoho uĹľivatele v příspÄ›vku." + few: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou zmĂ­nit jen %{count} uĹľivatele v příspÄ›vku." + other: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou zmĂ­nit jen %{count} uĹľivatelĹŻ v příspÄ›vku." no_images_allowed: "BohuĹľel, novĂ­ uĹľivatelĂ© nemohou vkládat obrázky do příspÄ›vkĹŻ." + too_many_images: + one: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou do příspÄ›vku vloĹľit jen jeden obrázek." + few: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou do příspÄ›vku vloĹľit jen %{count} obrázky." + other: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou do příspÄ›vku vloĹľit jen %{count} obrázkĹŻ." no_attachments_allowed: "BohuĹľel, novĂ­ uĹľivatelĂ© nemohou do příspÄ›vkĹŻ vkládat přílohy." + too_many_attachments: + one: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou do příspÄ›vku vloĹľit jen jednu přílohu." + few: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou do příspÄ›vku vloĹľit jen %{count} přílohy." + other: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou do příspÄ›vku vloĹľit jen %{count} příloh." no_links_allowed: "BohuĹľel, novĂ­ uĹľivatelĂ© nemohou vkládat odkazy do příspÄ›vkĹŻ." - contains_blocked_words: "TvĹŻj příspÄ›vek obsahuje slova, která nejsou povolena." + links_require_trust: "BohuĹľel, nemĹŻĹľete vkládat odkazy do příspÄ›vkĹŻ." + too_many_links: + one: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou do příspÄ›vku vloĹľit maximálnÄ› jeden odkaz." + few: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou do příspÄ›vku vloĹľit maximálnÄ› %{count} odkazy." + other: "BohuĹľel, novĂ­ uĹľivatelĂ© mohou do příspÄ›vku vloĹľit maximálnÄ› %{count} odkazĹŻ." spamming_host: "BohuĹľel, na tento server nemĹŻĹľete odkazovat." user_is_suspended: "VylouÄŤenĂ­ uĹľivatelĂ© nemohou pĹ™idávat příspÄ›vky." + topic_not_found: "NÄ›co se pokazilo. TĂ©ma mohlo bĂ˝t uzavĹ™eno nebo smazáno bÄ›hem vašeho prohlĂ­ĹľenĂ­." not_accepting_pms: "BohuĹľel, %{username} v souÄŤasnosti nepĹ™ijĂ­má zprávy." + max_pm_recepients: "BohuĹľel, mĹŻĹľete poslat zprávu maximálnÄ› %{recipients_limit} příjemcĹŻm." just_posted_that: "je příliš podobnĂ˝ vašemu příspÄ›vku z poslednĂ­ doby" invalid_characters: "obsahuje neplatnĂ© znaky" + is_invalid: "zdá se to bĂ˝t nejasnĂ©, je to kompletnĂ­ vÄ›ta?" next_page: "další strana →" prev_page: "↠pĹ™edchozĂ­ strana" page_num: "Strana %{num}" @@ -163,42 +200,66 @@ cs: latest: "PoslednĂ­ tĂ©mata" hot: "PopulárnĂ­ tĂ©mata" top: "Nejlepší tĂ©mata" - posts: "PoslednĂ­ příspÄ›veky" + top_all: "Nejlepší tĂ©mata za celou dobu" + top_yearly: "RoÄŤnĂ­ nejlepší tĂ©mata" + top_quarterly: "Nejlepší tĂ©mata za kvartál" + top_monthly: "MÄ›sĂ­ÄŤnĂ­ nejlepší tĂ©mata" + top_weekly: "TĂ˝dennĂ­ nejlepší tĂ©mata" + top_daily: "DennĂ­ nejlepší tĂ©mata" + posts: "PoslednĂ­ příspÄ›vky" + private_posts: "PoslednĂ­ osobnĂ­ zprávy" group_posts: "PoslednĂ­ příspÄ›vky z @{group_name}" + group_mentions: "PoslednĂ­ zmĂ­nky z @{group_name}" user_posts: "PoslednĂ­ příspÄ›vky od @{username}" + user_topics: "PoslednĂ­ tĂ©mata od @{username}" + tag: "OštĂ­tkovaná tĂ©mata" + badge: "%{display_name} odznak na %{site_title}" too_late_to_edit: "Tento příspÄ›vek byl vytvoĹ™en velmi dávno. UĹľ ho nenĂ­ moĹľnĂ© smazat ani upravovat." + revert_version_same: "AktuálnĂ­ verze je stejná jako verze, na kterou se pokoušíte vrátit." excerpt_image: "obrázek" + queue: + delete_reason: "Smazáno moderátorem" + not_found: "PříspÄ›vek nebyl nalezen nebo jiĹľ byl aktualizován." groups: + success: + bulk_add: "UĹľivatelĂ© %{users_added} byli pĹ™idáni do skupiny." errors: can_not_modify_automatic: "Nelze mÄ›nit automatickĂ© skupiny" member_already_exist: "'%{username}' je uĹľ ÄŤlenem tĂ©to skupiny." + invalid_domain: "'%{domain}' nenĂ­ platná domĂ©na." + invalid_incoming_email: "'%{email}' nenĂ­ platná emailová adresa." + email_already_used_in_group: "'%{email}' uĹľ je pouĹľit pro skupinu '%{group_name}'." + email_already_used_in_category: "'%{email}' uĹľ je pouĹľit pro kategorii '%{category_name}'." + cant_allow_membership_requests: "NemĹŻĹľete povolit žádosti o ÄŤlenstvĂ­ pro skupinu bez žádnĂ˝ch vlastnĂ­kĹŻ." default_names: everyone: "kdokoliv" - admins: "admins" - moderators: "moderators" - staff: "staff" + admins: "administrátoĹ™i" + moderators: "moderátoĹ™i" + staff: "redaktoĹ™i" trust_level_0: "trust_level_0" trust_level_1: "trust_level_1" trust_level_2: "trust_level_2" trust_level_3: "trust_level_3" trust_level_4: "trust_level_4" + request_membership_pm: + title: "Žádost o ÄŤlenstvĂ­ pro @%{group_name}" education: until_posts: one: "příspÄ›vek" few: "%{count} příspÄ›vky" other: "%{count} příspÄ›vkĹŻ" new-topic: | - VĂ­tej na %{site_name} — **dĂ­ky za novĂ˝ příspÄ›vek!** + VĂ­tejte na %{site_name} — **dĂ­ky za novĂ© tĂ©ma!** - - ZnĂ­ název zajĂ­mavÄ›, kdyĹľ si jej pĹ™eÄŤteš nahlas? Je to dobrĂ© shrnutĂ­ danĂ©ho tĂ©matu? + - ZnĂ­ název zajĂ­mavÄ›, kdyĹľ si jej pĹ™eÄŤtete nahlas? Je to dobrĂ© shrnutĂ­ danĂ©ho tĂ©matu? - - Koho by to mohlo zajĂ­mat? ProÄŤ na tom záleží? JakĂ© typy odpovÄ›dĂ­ oÄŤekáváš? + - Koho by to mohlo zajĂ­mat? ProÄŤ na tom záleží? JakĂ© typy odpovÄ›dĂ­ oÄŤekáváte? - - Používej ve svĂ©m tĂ©matu klĂ­ÄŤová slova, aby jej ostatnĂ­ mohli *najĂ­t*. Pro zaĹ™aĹľenĂ­ svĂ©ho tĂ©matu pro nÄ›j vyber kategorii. + - Používejte ve svĂ©m tĂ©matu klĂ­ÄŤová slova, aby jej ostatnĂ­ mohli *najĂ­t*. ZaĹ™aÄŹte svĂ© tĂ©ma do vhodnĂ© kategorie. - VĂ­ce se dozvíš v [pravidlech komunity](/guidelines). Tenhle panel se zobrazĂ­ pouze pro vaše prvnĂ­ %{education_posts_text}. + VĂ­ce se dozvĂ­te v [pravidlech komunity](/guidelines). Tenhle panel se zobrazĂ­ pouze pro vaše prvnĂ­ %{education_posts_text}. new-reply: | - VĂ­tejte na %{site_name} — **dĂ­ky za pĹ™ispĂ­vánĂ­!** + VĂ­tejte na %{site_name} — **dĂ­ky za pĹ™ispÄ›vek!** - PĹ™ispĂ­vá nÄ›jak vaše odpověď k diskuzi? @@ -207,6 +268,15 @@ cs: - KonstruktivnĂ­ kritika je vĂ­taná, ale kritizujte *myšlenky*, ne lidi. Další doporuÄŤenĂ­ máme v [pravidlech komunity](/guidelines). Tenhle panel se zobrazĂ­ pouze pro vaše prvnĂ­ %{education_posts_text}. + avatar: |+ + ### Nechcete si nastavit svĹŻj profilovĂ˝ obrázek? + + PĹ™idali jste nÄ›kolik tĂ©mat a příspÄ›vkĹŻ, ale Váš profilovĂ˝ obrázek nenĂ­ tak jedineÄŤnĂ˝ jako Vy - je to len pĂ­smeno. + + ZvaĹľovali jste **[navštĂ­vit svĹŻj uĹľivatelskĂ˝ profil]()** a nahrát v nÄ›m obrázek, kterĂ˝ vás vystihuje? + + Je jednodušší sledovat diskusi a nalĂ©zt v konverzaci zajĂ­mavĂ© lidi, kdyĹľ má kaĹľdĂ˝ jedineÄŤnĂ˝ profilovĂ˝ obrázek! + activerecord: attributes: category: @@ -222,8 +292,15 @@ cs: topic: attributes: base: + warning_requires_pm: "VarovánĂ­ mĹŻĹľete pĹ™idat pouze k osobnĂ­m zprávám." too_many_users: "VarovánĂ­ mĹŻĹľete najednou poslat pouze jednomu uĹľivateli." + cant_send_pm: "BohuĹľel, tomuto uĹľivateli nemĹŻĹľete poslat osobnĂ­ zprávu." no_user_selected: "MusĂ­te vybrat platnĂ©ho uĹľivatele." + reply_by_email_disabled: "Odpověď pĹ™es email byla vypnuta." + target_user_not_found: "Jeden z uĹľivatelĹŻ, kterĂ˝m posĂ­láte tuto zprávu, nebyl nalezen." + featured_link: + invalid: "je neplatná. URL adresa by mÄ›la obsahovat http:// nebo https://." + invalid_category: "nelze upravit v tĂ©to kategorii." user: attributes: password: @@ -234,6 +311,10 @@ cs: unique_characters: "má příliš mnoho opakujĂ­cĂ­ch se znakĹŻ. PouĹľijte prosĂ­m bezpeÄŤnÄ›jší heslo." ip_address: signup_not_allowed: "Registrace z tĂ©to adresy nenĂ­ povolena." + user_email: + attributes: + user_id: + reassigning_primary_email: "OpÄ›tovnĂ© pĹ™iĹ™azenĂ­ primárnĂ­ho emailu jinĂ©mu uĹľivateli nenĂ­ povoleno." color_scheme_color: attributes: hex: @@ -241,26 +322,85 @@ cs: post_reply: base: different_topic: "PříspÄ›vek a odpověď musĂ­ patĹ™it ke stejnĂ©mu tĂ©matu." + web_hook: + attributes: + payload_url: + invalid: "URL adresa je neplatná. MÄ›la by obsahovat http:// nebo https:// a nesmĂ­ v nĂ­ bĂ˝t mezery." + custom_emoji: + attributes: + name: + taken: jiĹľ je pouĹľito u jinĂ©ho emoji + topic_timer: + attributes: + execute_at: + in_the_past: "musĂ­ bĂ˝t v budoucnosti." + watched_word: + attributes: + word: + too_many: "Příliš mnoho slov pro tuto akci" <<: *errors user_profile: - no_info_other: "

%{name} o sobě zatím žádné informace nevyplnil
" + no_info_me: "
Pole 'o mně' na vašem profilu je v tuto chvíli prázdné, nechcete si ho vyplnit?
" + no_info_other: "
%{name} o sobě zatím nevyplnil žádné informace
" vip_category_name: "VIP" vip_category_description: "Kategorie je přístupná vĂ˝hradnÄ› ÄŤlenĹŻm s dĹŻvÄ›ryhodnostĂ­ 3 a vyšší." meta_category_name: "Poznámky k webu" meta_category_description: "Diskutuje tento web, jeho organizaci, jak funguje a jak ho vylepšit." staff_category_name: "Redakce" - staff_category_description: "PrivátnĂ­ kategorie pro diskuze štábu. TĂ©mata jsou viditelná pouze pro moderátory a správce." + staff_category_description: "PrivátnĂ­ kategorie pro diskuze redakce. TĂ©mata jsou viditelná pouze pro moderátory a správce." + assets_topic_title: "Soubory pro vzhled webu" + assets_topic_body: "Toto tĂ©ma je viditelnĂ© pouze pro redaktory a slouží pro uchovávánĂ­ obrázkĹŻ a souborĹŻ pouĹľitĂ˝ch v designu tohoto webu. NemaĹľte ho!\n\n\nJak postupovat:\n\n1. OdpovÄ›zte na toto tĂ©ma.\n2. Nahrajte sem všechny obrázky kterĂ© chcete používat pro loga, favicony a podobnÄ›. (PouĹľijte ikonku „nahrát“ v panelu nad editorem, nebo obrázky rovnou pĹ™etáhnÄ›te a vloĹľte)\n3. Pošlete tuto odpověď.\n4. KliknÄ›te pravĂ˝m tlaÄŤĂ­tkem na obrázky ve vašem novĂ©m příspÄ›vku a zkopĂ­rujte si adresy, nebo kliknÄ›te na Ăşpravu příspÄ›vku a adresy vykopĂ­rujte odtud.\n5. VloĹľte tyto adresy do [základnĂ­ho nastavenĂ­](/admin/site_settings/category/required).\n\nPokud potĹ™ebujete nahrát jinĂ© soubory, zmÄ›Ĺte nastavenĂ­ `authorized_extensions` v [nastavenĂ­ souborĹŻ](/admin/site_settings/category/files)." discourse_welcome_topic: title: "VĂ­tejte v Discourse" + body: |2 + + PrvnĂ­ odstavec tohoto pĹ™ipnutĂ©ho tĂ©matu se zobrazĂ­ na domovskĂ© stránce jako uvĂ­tacĂ­ zpráva pro všechny novĂ© návštÄ›vnĂ­ky. Je to dĹŻleĹľitĂ©! + + **Upravte tento text** do struÄŤnĂ©ho popisu Vaší komunity: + + - Pro koho tu je? + - Co zde lze nalĂ©zt? + - ProÄŤ by sem mÄ›li lidĂ© chodit? + - Kde se lze dozvÄ›dÄ›t vĂ­c (odkazy, zdroje, atd.)? + + + + MoĹľná budete chtĂ­t toto tĂ©ma v administraci (vpravo nahoĹ™e a dole) zavřít, aby se nehromadily odpovÄ›di na oznámenĂ­ch. lounge_welcome: - title: "VĂ­tejte v Redakci" + title: "VĂ­tejte ve VIP" + body: |2 + + Gratulujeme! :confetti_ball: + + Pokud vidĂ­te toto tĂ©ma, byli jste nedávno povýšeni na **pravidelnĂ©ho** uĹľivatele (dĹŻvÄ›ryhodnost 3). + + TeÄŹ mĹŻĹľete … + + * Upravovat nadpis jakĂ©hokoliv tĂ©matu + * MÄ›nit kategorii jakĂ©hokoliv tĂ©matu + * Budou se sledovat všecky Vaše odkazy ([automatickĂ© nofollow](http://en.wikipedia.org/wiki/Nofollow) je odstranÄ›no) + * Máte přístup do privátnĂ­ kategorie VIP přístupnĂ© jen ÄŤlenĹŻm s dĹŻvÄ›ryhodnostĂ­ 3 a vyšší + * SkrĂ˝t spam pomocĂ­ vlajky + + Zde je [aktuálnĂ­ seznam pravidelnĂ˝ch uĹľivatelĹŻ](/badges/3/regular). UrÄŤitÄ› je pozdravte. + + DÄ›kujeme, Ĺľe jste dĹŻleĹľitou součástĂ­ tĂ©to komunity! + + (VĂ­ce informacĂ­ naleznete na stránce o [dĹŻvÄ›ryhodnosti][trust]. ProsĂ­m nezapomeĹte, Ĺľe jen ÄŤlenovĂ©, kteří stále splĹujĂ­ poĹľadavky zĹŻstanou pravidelnĂ˝mi uĹľivateli.) + + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: - topic_prefix: "Definice kategorie pro %{category}" - post_template: "%{replace_paragraph}\n\nPouĹľijte následujĂ­cĂ­ odstavce pro delší popis, nebo pro vytvoĹ™enĂ­ pokynĹŻ ke kategorii nebo pravidel:\n\n- ProÄŤ by lidĂ© mÄ›li používat tuto kategorii? K ÄŤemu slouží?\n\nJak pĹ™esnÄ› je tato kategorie odlišná od ostatnĂ­ch, kterĂ© jiĹľ máme?\n\n- Co by mÄ›ly tĂ©mata v tĂ©to kategorii obvykle obsahovat?\n\nPotĹ™ebujeme tuto kategorii? MĹŻĹľeme ji slouÄŤit s jinou kategoriĂ­ nebo subkategoriĂ­?\n" + topic_prefix: "O kategorii %{category}" + replace_paragraph: "(NahraÄŹte tento prvnĂ­ odstavec struÄŤnĂ˝m popisem novĂ© kategorie. Tento návod se objevĂ­ v seznamu kategoriĂ­, takĹľe se zkuste vejĂ­t do 200 znakĹŻ. **Kategorie se neobjevĂ­ v seznamu kategoriĂ­ pokud neupravĂ­te tento popis nebo v nĂ­ nevytvoříte tĂ©mata.**)" + post_template: "%{replace_paragraph}\n\nPouĹľijte následujĂ­cĂ­ odstavce pro delší popis, nebo pro vytvoĹ™enĂ­ pokynĹŻ ke kategorii nebo pravidel:\n\n- ProÄŤ by lidĂ© mÄ›li používat tuto kategorii? K ÄŤemu slouží?\n\n- Jak pĹ™esnÄ› je tato kategorie odlišná od ostatnĂ­ch, kterĂ© jiĹľ máme?\n\n- Co by mÄ›ly tĂ©mata v tĂ©to kategorii obvykle obsahovat?\n\n- PotĹ™ebujeme tuto kategorii? MĹŻĹľeme ji slouÄŤit s jinou kategoriĂ­ nebo subkategoriĂ­?\n" errors: uncategorized_parent: "„Bez kategorie“ nemĹŻĹľe mĂ­t nadĹ™azenou kategorii" self_parent: "NadĹ™azená kategorie nemĹŻĹľe zároveĹ bĂ˝t podkategorie" - depth: "Pokategorie se nedajĂ­ vnoĹ™ovat" + depth: "Podkategorie se nedajĂ­ vnoĹ™ovat" + invalid_email_in: "'%{email}' nenĂ­ platná emailová adresa." + email_already_used_in_group: "'%{email}' uĹľ je pouĹľit pro skupinu '%{group_name}'." + email_already_used_in_category: "'%{email}' uĹľ je pouĹľit pro kategorii '%{category_name}'." + description_incomplete: "PříspÄ›vek s popisem kategorie musĂ­ obsahovat alespoĹ jeden odstavec." cannot_delete: uncategorized: "„Bez kategorie“ nejde smazat" has_subcategories: "Tato kategorie nejde smazat protoĹľe obsahuje podkategorie." @@ -277,11 +417,17 @@ cs: title: "základnĂ­ uĹľivatel" member: title: "ÄŤlen" + regular: + title: "pravidelnĂ˝ uĹľivatel" + leader: + title: "vĹŻdce" + change_failed_explanation: "Pokusili jste se snĂ­Ĺľit dĹŻvÄ›ryhodnost uĹľivatele %{user_name} na '%{new_trust_level}'. NicmĂ©nÄ› jeho aktuálnĂ­ dĹŻvÄ›ryhodnost uĹľ je '%{current_trust_level}'. UĹľivatel %{user_name} zĹŻstane na Ăşrovni '%{current_trust_level}'. Pokud chcete snĂ­Ĺľit dĹŻvÄ›ryhodnost uĹľivatele, zamknÄ›te nejdříve dĹŻvÄ›ryhodnost." post: image_placeholder: broken: "Tento obrázek je rozbitĂ˝" rate_limiter: slow_down: "Tuto akci provádĂ­te příliš ÄŤasto, zkuste to prosĂ­m pozdÄ›ji." + too_many_requests: "DÄ›láte tuto akci příliš ÄŤasto. ProsĂ­m poÄŤkejte %{time_left} a zkuste to znovu." by_type: pms_per_day: "Odeslal jsi maximum povolenĂ˝ch zpráv za den. Další mĹŻĹľeš odeslat za {time_left}." hours: @@ -390,7 +536,7 @@ cs: few: "tĂ©měř pĹ™ed %{count} roky" other: "tĂ©měř pĹ™ed %{count} roky" password_reset: - choose_new: "Vyber si novĂ© heslo" + choose_new: "Vyberte si novĂ© heslo" choose: "Vybrat heslo" update: 'zmÄ›nit heslo' save: 'Nastavit heslo' @@ -649,8 +795,6 @@ cs: google_oauth2_client_id: "Client ID vaší Google aplikace." google_oauth2_client_secret: "Client secret vaší Google aplikace." enable_twitter_logins: "Povolit pĹ™ihlašovánĂ­ pĹ™es Twitter, vyĹľaduje správnÄ› nastavenĂ© 'twitter_consumer_key' a 'twitter_consumer_secret'" - twitter_consumer_key: "'Consumer key' pro pĹ™ihlašovánĂ­ pĹ™es Twitter, registrace na http://dev.twitter.com" - twitter_consumer_secret: "'Consumer secret' pro pĹ™ihlašovánĂ­ pĹ™es Twitter, registrace na http://dev.twitter.com" enable_facebook_logins: "Povolit pĹ™ihlašovánĂ­ pĹ™es Facebook, vyĹľaduje správnÄ› nastavenĂ© 'facebook_app_id' a 'facebook_app_secret'" facebook_app_id: "'App id' pro pĹ™ihlašovánĂ­ pĹ™es Facebook, registrace na https://developers.facebook.com/apps" facebook_app_secret: "'App secret' pro pĹ™ihlašovánĂ­ pĹ™es Facebook, registrace na https://developers.facebook.com/apps" @@ -752,11 +896,21 @@ cs: There are new user signups waiting to be approved (or rejected) before they can access this forum. [Please review them in the admin section](%{base_url}/admin/users/list/pending). + unsubscribe_link: | + Pro zrušenĂ­ zasĂ­lánĂ­ tÄ›chto emailĹŻ [kliknÄ›te zde](%{unsubscribe_url}). + unsubscribe_link_and_mail: | + Pro zrušenĂ­ zasĂ­lánĂ­ tÄ›chto emailĹŻ [kliknÄ›te zde](%{unsubscribe_url}). + unsubscribe_mailing_list: | + ObdrĹľeli jste tento email, protoĹľe máte zapnutĂ˝ reĹľim elektronická pošta. + + Pro zrušenĂ­ zasĂ­lánĂ­ tÄ›chto emailĹŻ [kliknÄ›te zde](%{unsubscribe_url}). user_notifications: previous_discussion: "PĹ™edchozĂ­ diskuze" unsubscribe: title: "Odhlásit z odbÄ›ru emailĹŻ" description: "Nechcete jiĹľ od nás dostávat emaily? ŽádnĂ˝ problĂ©m! KliknÄ›te na odkaz nĂ­Ĺľe pro okamĹľitĂ© odhlášenĂ­ z odbÄ›ru emailĹŻ:" + reply_by_email: "K odpovÄ›di [navštivte tĂ©ma](%{base_url}%{url}) nebo odepište na tento email." + visit_link_to_respond: "K odpovÄ›di [navštivte tĂ©ma](%{base_url}%{url})." posted_by: "Zaslal uĹľivatel %{username} dne %{post_date}" digest: why: "KrátkĂ˝ souhrn z webu %{site_link} od poslednĂ­ návštÄ›vy v %{last_seen_at}" diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml index 218bd22837..b6eabed9ee 100644 --- a/config/locales/server.da.yml +++ b/config/locales/server.da.yml @@ -76,7 +76,6 @@ da: invalid: er ikke gyldig is_invalid: "virker upræcis, er det en hel sætning?" contains_censored_words: "indeholder følgende censurerede ord: %{censurerede ord}" - matches_censored_pattern: "indeholder følgende ord som matcher sitets censurerede regexp'er: %{censored_words}" less_than: skal være mindre end %{count} less_than_or_equal_to: skal være mindre end eller lig med %{count} not_a_number: er ikke et nummer @@ -99,7 +98,7 @@ da: template: body: 'Der var problemer med følgende felter:' header: - one: Een fejl forhindrede denne %{model} for at gemme. + one: 'Een fejl forhindrede denne %{model} for at gemme.' other: '%{count} fejl forhindrede denne %{model} i at gemme.' embed: load_from_remote: "Der opstod en fejl ved indlæsningen af dette indlæg." @@ -354,40 +353,40 @@ da: title: "Velkommen til Discourse" body: |2 - Det første afsnit af dette fastgjorte emne vil kunne ses som en velkomstbesked for alle nye besøgende pĂĄ din hjemmeside. Dette er vigtigt! + Det første afsnit af dette fastgjorte emne vil kunne ses som en velkomstbesked for alle nye besøgende pĂĄ din hjemmeside. Dette er vigtigt! - **RedigĂ©r dette** til en kortfattet beskrivelse af dit fællesskab: + **RedigĂ©r dette** til en kortfattet beskrivelse af dit fællesskab: - - Hvem er det rettet mod? - - Hvad kan de finde her? - - Hvorfor bør de komme her? - - Hvor finde de mere information (links, ressourcer, osv.)? - + - Hvem er det rettet mod? + - Hvad kan de finde her? + - Hvorfor bør de komme her? + - Hvor finde de mere information (links, ressourcer, osv.)? + - Du bør eventuelt lukke dette emne via administratorfunktionen :wrench: (øverst til højre og i bunden), sĂĄledes at der ikke hober sig svar op til denne meddelelse. + Du bør eventuelt lukke dette emne via administratorfunktionen :wrench: (øverst til højre og i bunden), sĂĄledes at der ikke hober sig svar op til denne meddelelse. lounge_welcome: title: "Velkommen i loungen" body: |2 - Tillykke! :confetti_ball: + Tillykke! :confetti_ball: - Hvis du kan se dette er du nu vlevet opgraderet som **regular** (trust level 3) bruger. + Hvis du kan se dette er du nu vlevet opgraderet som **regular** (trust level 3) bruger. - Nu kan du … + Nu kan du … - * Rediger enhver emne titel - * Ændre alle kategorier og emner - * Følge alle links ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) ier fjernet) - * FĂĄ adgang til en privat lounge kategori kun synlig for brugere med Trust LEvel 3, eller højere - * Gemme spam - med et enkelt flag + * Rediger enhver emne titel + * Ændre alle kategorier og emner + * Følge alle links ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) ier fjernet) + * FĂĄ adgang til en privat lounge kategori kun synlig for brugere med Trust LEvel 3, eller højere + * Gemme spam - med et enkelt flag - Her er [current list of fellow regulars](/badges/3/regular). Sving forbi og hils pĂĄ! + Her er [current list of fellow regulars](/badges/3/regular). Sving forbi og hils pĂĄ! - Tak fordi du er en vigtig del af dette fællesskab! + Tak fordi du er en vigtig del af dette fællesskab! - (Formere information om Trust Levels [see this topic][trust]. Bemærk venligst at kun brugere der vedbliver at leve op til Trust Level betingelserne, vil have fortsat adgang) + (Formere information om Trust Levels [see this topic][trust]. Bemærk venligst at kun brugere der vedbliver at leve op til Trust Level betingelserne, vil have fortsat adgang) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Kategoridefinition for %{category}" replace_paragraph: "(Beskriv din nye kategori her. Beskriv ramme og formĂĄl med kategori sĂĄledes at det er tydeligt for andre. Forsøg at holde beskrivelsen pĂĄ max 200 tegn. **Kategorien vil ikke fremgĂĄ pĂĄ kategori listen, før du har redigeret beskrivelsen, eller oprettet emner)." @@ -565,7 +564,6 @@ da: long_form: 'markerede dette som stødende' notify_user: title: 'Send @{{username}} en besked' - description: 'Jeg ønsker at tale med denne person direkte om vedkommendes indlæg' long_form: 'sendt besked til brugeren' email_title: 'Dit indlæg i "%{title}"' email_body: "%{link}\n\n%{message}" @@ -811,7 +809,6 @@ da: poll_pop3_auth_error: "Forbindelsen til POP3 serveren melder fejl. Venligst tjek POP3 settings." site_settings: censored_words: "Ord der automatisk vil blive erstattet med ■■■■" - censored_pattern: "Regex-mønster som automatisk vil blive erstattet med ■■■■" delete_old_hidden_posts: "Slet automatisk skjulte indlæg der forbliver skjulte i mere end 30 dage." allow_user_locale: "Tillad brugere at vælge egne sprog præferencer pĂĄ site" set_locale_from_accept_language_header: "set interface language for anonymous users from their web browser's language headers. (EXPERIMENTAL, does not work with anonymous cache)" @@ -913,8 +910,6 @@ da: google_oauth2_client_id: "Client ID of your Google application." google_oauth2_client_secret: "Client secret of your Google application." enable_twitter_logins: "AktivĂ©r Twitter-login, kræver twitter_consumer_key og twitter_consumer_secret" - twitter_consumer_key: "Consumer key til Twitter-login, oprettes pĂĄ http://dev.twitter.com." - twitter_consumer_secret: "Consumer secret til Twitter-login, oprettes pĂĄ http://dev.twitter.com." enable_instagram_logins: "AktivĂ©r Instagram-login, kræver instagram_consumer_key og instagram_consumer_secret" enable_facebook_logins: "AktivĂ©r Facebook-login, kræver facebook_app_id og facebook_app_secret" facebook_app_id: "App id til Facebook-login, oprettes pĂĄ https://developers.facebook.com/apps" @@ -1383,7 +1378,7 @@ da: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "Et kort resumĂ© af %{site_link} siden dit sidste besøg %{last_seen_at}" since_last_visit: "Siden dit sidste besøg" diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 7388ad3a1c..e6bb5d190a 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -82,7 +82,6 @@ de: invalid: ist ungĂĽltig is_invalid: "scheint unklar, ist das ein ganzer Satz?" contains_censored_words: "enthält die folgenden nicht erlaubten Wörter: %{censored_words}" - matches_censored_pattern: "enthält folgende Wörter, die dem regulären Ausdruck fĂĽr nicht erlaubte Wörter der Seite entsprechen: %{censored_words}" less_than: muss weniger als %{count} sein less_than_or_equal_to: muss weniger oder gleich %{count} sein not_a_number: ist keine Zahl @@ -105,7 +104,7 @@ de: template: body: 'Es gab Probleme mit diesen Feldern:' header: - one: 1 Fehler verhindert, dass %{model} gespeichert wird + one: '1 Fehler verhindert, dass %{model} gespeichert wird' other: '%{count} Fehler verhindern, dass %{model} gespeichert wird' embed: load_from_remote: "Beim Laden des Beitrags ist ein Fehler aufgetreten." @@ -179,7 +178,6 @@ de: too_many_links: one: "Entschuldige, neue Benutzer können Beiträgen höchstens einen Link hinzufĂĽgen." other: "Entschuldige, neue Benutzer können Beiträgen höchstens %{count} Links hinzufĂĽgen." - contains_blocked_words: "Dein Beitrag enthält Wörter, die nicht erlaubt sind." spamming_host: "Entschuldigung, du kannst keine Links zu diesem Webserver posten." user_is_suspended: "Gesperrte Benutzer dĂĽrfen keine Beiträge schreiben." topic_not_found: "Etwas ist schief gelaufen. Wurde das Thema eventuell geschlossen oder gelöscht, während du es angeschaut hast?" @@ -385,41 +383,41 @@ de: title: "Willkommen bei Discourse" body: |2 - Der erste Absatz dieses angehefteten Themas wird allen neuen Besuchern deiner Webseite als Willkommensnachricht angezeigt. Er ist wichtig! + Der erste Absatz dieses angehefteten Themas wird allen neuen Besuchern deiner Webseite als Willkommensnachricht angezeigt. Er ist wichtig! - **Bearbeite dies** und erstelle eine kurze Beschreibung deiner Community: + **Bearbeite dies** und erstelle eine kurze Beschreibung deiner Community: - - FĂĽr wen ist sie? - - Was findet man hier? - - Warum sollte man hier vorbeischauen? - - Wo kann man mehr erfahren (Links, Dokumente, usw.)? + - FĂĽr wen ist sie? + - Was findet man hier? + - Warum sollte man hier vorbeischauen? + - Wo kann man mehr erfahren (Links, Dokumente, usw.)? - + - Du solltest dieses Thema eventuell schlieĂźen ĂĽber die Administration :wrench: (oben rechts oder unten), damit sich an einer AnkĂĽndigung wie dieser nicht die Antworten aufstapeln. + Du solltest dieses Thema eventuell schlieĂźen ĂĽber die Administration :wrench: (oben rechts oder unten), damit sich an einer AnkĂĽndigung wie dieser nicht die Antworten aufstapeln. lounge_welcome: title: "Willkommen in der Lounge" body: |2 - Gratuliere! :confetti_ball: + Gratuliere! :confetti_ball: - Wenn du dieses Thema sehen kannst, wurdest du vor Kurzem zum **Stammgast** (Vertrauensstufe 3) befördert. + Wenn du dieses Thema sehen kannst, wurdest du vor Kurzem zum **Stammgast** (Vertrauensstufe 3) befördert. - Du kannst nun … + Du kannst nun … - * den Titel eines jeden Themas ändern - * Themen in andere Kategorien verschieben - * Links veröffentlichen, die von Suchmaschinen weiterverfolgt werden (das automatische [nofollow](http://de.wikipedia.org/wiki/Nofollow) wird entfernt) - * auf die private Lounge-Kategorie zugreifen, die fĂĽr Benutzer mit Vertrauensstufe 3 oder höher sichtbar ist - * Spam durch eine einzige Meldung ausblenden + * den Titel eines jeden Themas ändern + * Themen in andere Kategorien verschieben + * Links veröffentlichen, die von Suchmaschinen weiterverfolgt werden (das automatische [nofollow](http://de.wikipedia.org/wiki/Nofollow) wird entfernt) + * auf die private Lounge-Kategorie zugreifen, die fĂĽr Benutzer mit Vertrauensstufe 3 oder höher sichtbar ist + * Spam durch eine einzige Meldung ausblenden - Hier ist die [aktuelle Liste aller Stammgäste](/badges/3/regular). Vergiss nicht, hallo zu sagen! + Hier ist die [aktuelle Liste aller Stammgäste](/badges/3/regular). Vergiss nicht, hallo zu sagen! - Vielen Dank dafĂĽr, dass du ein wichtiger Teil dieser Community bist! + Vielen Dank dafĂĽr, dass du ein wichtiger Teil dieser Community bist! - (Wenn du mehr ĂĽber Vertrauensstufen wissen möchtest, kannst du [dieses Thema lesen][trust]. Beachte bitte, dass nur jene Mitglieder Stammgäste bleiben, die auch im Laufe der Zeit die Anforderungen erfĂĽllen.) + (Wenn du mehr ĂĽber Vertrauensstufen wissen möchtest, kannst du [dieses Thema lesen][trust]. Beachte bitte, dass nur jene Mitglieder Stammgäste bleiben, die auch im Laufe der Zeit die Anforderungen erfĂĽllen.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Ăśber die Kategorie %{category}" replace_paragraph: "(Ersetze diesen ersten Absatz mit einer kurzen Beschreibung deiner neuen Kategorie. Diese Richtlinie wird in der Kategorienauswahl angezeigt, versuche also weniger als 200 Zeichen zu benutzen. **Bis du diese Beschreibung geändert oder Themen angelegt hast, wird diese Kategorie nicht auf der Kategorie-Seite angezeigt.**)" @@ -605,8 +603,6 @@ de: long_form: 'dies als unangemessen gemeldet' notify_user: title: 'Schreibe @{{username}} eine Nachricht' - description: 'Ich möchte mit dieser Person direkt und persönlich ĂĽber ihren Beitrag reden.' - short_description: 'Ich möchte mit dieser Person direkt und persönlich ĂĽber ihren Beitrag reden.' long_form: 'angeschriebener Benutzer' email_title: 'Dein Beitrag in „%{title}“' email_body: "%{link}\n\n%{message}" @@ -865,7 +861,6 @@ de: poll_pop3_auth_error: "Die Verbindung zum POP3-Server schlägt mit einem Authentisierungsfehler fehl. ĂśberprĂĽfe deine POP3-Einstellungen." site_settings: censored_words: "Wörter, die automatisch durch ■■■■ ersetzt werden" - censored_pattern: "Regex-Muster das automatisch ersetzt wird mit ■■■■" delete_old_hidden_posts: "Automatisch alle Beiträge löschen, die länger als 30 Tage versteckt bleiben." allow_user_locale: "Erlaube Benutzern, ihre eigene Oberflächensprache zu wählen" set_locale_from_accept_language_header: "Sprache der Benutzeroberfläche fĂĽr anonyme Benutzer an Hand der Spracheinstellung ihres Browsers wählen (EXPERIMENTELL, funktioniert nicht mit Caches fĂĽr anonyme Benutzer)" @@ -1025,8 +1020,6 @@ de: google_oauth2_client_id: "Client-ID deiner Google-Anwendung." google_oauth2_client_secret: "Client-Secret deiner Google-Anwendung." enable_twitter_logins: "Aktiviere Twitter-Authentifizierung (benötigt twitter_consumer_key und twitter_consumer_secret)." - twitter_consumer_key: "Consumer Key fĂĽr Twitter-Authentifizierung, registriert auf http://dev.twitter.com" - twitter_consumer_secret: "Consumer Secret fĂĽr Twitter-Authentifizierung, registriert auf http://dev.twitter.com" enable_instagram_logins: "Aktiviere Instagram-Authentifizierung (benötigt instagram_consumer_key und instagram_consumer_secret)." instagram_consumer_key: "Consumer Key fĂĽr Instagram-Authentifizierung" instagram_consumer_secret: "Consumer Secret fĂĽr Instagram-Authentifizierung" @@ -1042,7 +1035,6 @@ de: allow_restore: "Wiederherstellung von Sicherungen zulassen, die ALLE vorhandenen Daten ĂĽberschreiben! Auf 'false' lassen, sofern Sie nicht planen, eine Sicherung wiederherzustellen." maximum_backups: "Die maximale Anzahl an Sicherungen, die auf dem Server gespeichert werden. Ă„ltere Sicherungen werden automatisch gelöscht." automatic_backups_enabled: "Automatische Backups aktivieren. Die Backups werden im eingestellten Zeitintervall erstellt." - backup_frequency: "Wie häufig sollen Backups erstellt werden (Angabe in Tagen)?" enable_s3_backups: "Lade fertige Backups zu S3 hoch. WICHTIG: In den Dateien-Einstellungen mĂĽssen gĂĽltige S3-Kontodaten eingegeben sein." s3_backup_bucket: "Der entfernte Speicherort fĂĽr Ihre Sicherungen. WARNUNG: Stellen Sie sicher, dass es sich um einen privaten Speicherort handelt." s3_disable_cleanup: "Deaktiviere das Löschen von Backups aus S3 wenn sie lokal entfernt werden" @@ -1651,47 +1643,6 @@ de: test_mailer: title: "Test-E-Mail" subject_template: "[%{email_prefix}] Test der E-Mail-Zustellbarkeit" - text_body_template: | - Dies ist eine Test-E-Mail von - - [**%{base_url}**][0] - - E-Mail-Zustellbarkeit ist eine komplizierte Sache. Hier sind ein paar wichtige Dinge aufgefĂĽhrt, die du am Anfang prĂĽfen solltest: - - - Stelle *sicher*, dass du die `notification_email` (Absender von Benachrichtigungen) in den Einstellungen richtig gesetzt hast. **Die Domain in der "From"-Adresse der E-Mails, die du schickst, ist die Domain, gegen die deine E-Mail validiert wird**. - - - Lerne, wie du den Quelltext der E-Mail in deinem E-Mail-Programm anzeigen lassen kannst, damit du die E-Mail-Kopfzeilen auf wichtige Anhaltspunkte hin ĂĽberprĂĽfen kannst. In Google Mail ist dies die "Zeige Original"-Option in der Auswahlliste oben rechts von jeder E-Mail. - - - **WICHTIG:** Hat dein Internetdienstanbieter (ISP) einen Reverse-DNS-Eintrag eingegeben, der die Domainnamen und die IP-Adressen verknĂĽpft, von denen du E-Mails verschickst? [ĂśberprĂĽfe deinen Reverse PTR-Eintrag][2] hier. Wenn dein ISP den Reverse-DNS-Eintrag nicht richtig eingibt, ist es sehr unwahrscheinlich, dass eine deiner E-Mails zugestellt wird. - - - Ist der [SPF-Eintrag][8] deiner Domain richtig? [ĂśberprĂĽfe deinen SPF-Eintrag][1] hier. Beachte, dass TXT der richtige Eintragstyp fĂĽr SPF ist. - - - Ist der [DKIM-Eintrag][3] deiner Domain richtig? Dies wird die E-Mai-lZustellbarkeit signifikant verbessern. [ĂśberprĂĽfe deinen DKIM-Eintrag][7] hier. - - - Wenn du deinen eigenen E-Mail-Server betreibst, stelle sicher, dass die IPs deiner E-Mail-Server [nicht auf irgendwelchen E-Mail-Blacklist][4] stehen. ĂśberprĂĽfe auch, dass es definitiv einen vollqualifizierten Hostnamen in der HELO-Nachricht schickt, der in DNS auflöst. Wenn nicht, wird dies dazu fĂĽhren, dass deine E-Mail von vielen E-Mail-Diensten abgelehnt wird. - - - Wir empfehlen dir sehr, **eine Test-E-Mail an [mail-tester.com][mt]** zu schicken, um zu ĂĽberprĂĽfen, dass alles erwähnte korrekt funktioniert. - - (Der *einfache* Weg besteht darin, ein kostenloses Konto bei [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] oder [Mailjet][mj] anzulegen, die groĂźzĂĽgige kostenlose E-Mail-Tarife haben und fĂĽr kleine Communitys ausreichen werden. Du wirst allerdings dennoch die SPF- und DKIM-Einträge in deinen DNS-Einstellungen eintragen mĂĽssen!) - - Wir hoffen, dass du diesen Test der E-Mail-Zustellbarkeit erfolgreich erhalten hast! - - Viel Erfolg, - - Deine Freunde von [Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "Neue Version" subject_template: "[%{email_prefix}] Neue Discourse-Version, Update verfĂĽgbar" @@ -2326,7 +2277,7 @@ de: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} account_suspended: title: "Konto gesperrt" subject_template: "[%{email_prefix}] Dein Konto wurde gesperrt" diff --git a/config/locales/server.el.yml b/config/locales/server.el.yml index 1b01dce4bb..ed02140b24 100644 --- a/config/locales/server.el.yml +++ b/config/locales/server.el.yml @@ -82,7 +82,6 @@ el: invalid: δεν είναι έγκυĎÎż is_invalid: "φαίνεται αĎαφές, είναι μια ολοκληĎωμένη Ď€ĎόταĎη;" contains_censored_words: "πεĎιέχει τις ακόλουθες λογοκĎιμένες λέξεις: %{censored_words}" - matches_censored_pattern: "πεĎιέχει τις ακόλουθες λέξεις που αντιĎτιχοίζονται με τα λογοκĎιθέντα regexp: %{censored_words}" less_than: Ď€Ďέπει να είναι λιγότεĎÎż από %{count} less_than_or_equal_to: Ď€Ďέπει να είναι λιγότεĎÎż ή ÎŻĎÎż με %{count} not_a_number: δεν είναι αĎιθμός @@ -177,7 +176,6 @@ el: too_many_links: one: "ΛυπούμαĎτε, οι νέοι χĎήĎτες μποĎούν να βάλουν μόνο έναν ĎύνδεĎÎĽÎż Ďε κάθε ανάĎτηĎη." other: "ΛυπούμαĎτε, οι νέοι χĎήĎτες μποĎούν να βάλουν μόνο %{count} ĎυνδέĎμους Ďε κάθε ανάĎτηĎη." - contains_blocked_words: "Η ανάĎτηĎή Ďας πεĎιλαμβάνει λέξεις που δεν επιτĎέπονται." spamming_host: "Συγγνώμη αλλά δεν μποĎείτε να αναĎτήĎετε ĎύνδεĎÎĽÎż Ďε αυτόν τον διακομιĎτή." user_is_suspended: "Δεν επιτĎέπονται οι αναĎτήĎεις από αποβλημένους χĎήĎτες." topic_not_found: "Κάτι πήγε ĎĎ„Ďαβά. ΊĎως το νήμα έκλειĎε ή διαγĎάφηκε ενώ το κοιτούĎες;" @@ -375,47 +373,47 @@ el: body: |2+ - Η Ď€Ďώτη παĎάγĎαφος αυτού του καĎφιτĎωμένου θέματος θα είναι ÎżĎατή ως μήνυμα καλωĎÎżĎÎŻĎματος Ď€Ďος τους νέους επιĎκέπτες της Ďελίδας. Είναι Ďημαντικό! + Η Ď€Ďώτη παĎάγĎαφος αυτού του καĎφιτĎωμένου θέματος θα είναι ÎżĎατή ως μήνυμα καλωĎÎżĎÎŻĎματος Ď€Ďος τους νέους επιĎκέπτες της Ďελίδας. Είναι Ďημαντικό! - **Αλλάξτε αυτό** με μία Ďύντομη πεĎιγĎαφή της κοινότητάς Ďας: + **Αλλάξτε αυτό** με μία Ďύντομη πεĎιγĎαφή της κοινότητάς Ďας: - - Σε ποιον απευθύνεται η Ďελίδα; - - Τι θα βĎει εδώ πέĎα; - - Γιατί αξίζει να επιĎκευθεί τη Ďελίδα; - - Που αλλού θα μποĎούĎαν να βĎουν υλικό Ďχετικά με το θέμα; (url κτλπ) + - Σε ποιον απευθύνεται η Ďελίδα; + - Τι θα βĎει εδώ πέĎα; + - Γιατί αξίζει να επιĎκευθεί τη Ďελίδα; + - Που αλλού θα μποĎούĎαν να βĎουν υλικό Ďχετικά με το θέμα; (url κτλπ) - + - Îα ήταν καλό να κλείĎετε αυτό το θέμα μέĎω του διαχειĎÎąĎτή :wrench: (Ďτην πάνω δεξιά και κάτω θέĎη), έτĎÎą ĎŽĎτε οι απαντήĎεις να μην μαζευτούν Ďτην ανακοίνωĎη. + Îα ήταν καλό να κλείĎετε αυτό το θέμα μέĎω του διαχειĎÎąĎτή :wrench: (Ďτην πάνω δεξιά και κάτω θέĎη), έτĎÎą ĎŽĎτε οι απαντήĎεις να μην μαζευτούν Ďτην ανακοίνωĎη. lounge_welcome: title: "Καλώς ήĎθατε Ďτο Σαλόνι" body: |2+ - ΣυγχαĎητήĎια! :confetti_ball: + ΣυγχαĎητήĎια! :confetti_ball: - Εάν μποĎείς να δεις αυτό το θέμα, έχεις Ď€ĎĎŚĎφατα Ď€Ďοαχθεί Ďε **τακτικό μέλος** (επίπεδο εμπιĎτοĎύνης 3) + Εάν μποĎείς να δεις αυτό το θέμα, έχεις Ď€ĎĎŚĎφατα Ď€Ďοαχθεί Ďε **τακτικό μέλος** (επίπεδο εμπιĎτοĎύνης 3) - ΤώĎα μποĎείς να &hellip, + ΤώĎα μποĎείς να &hellip, - * ΕπεξεĎγαĎτείς τον τίτλο οποιουδήποτε θεματος + * ΕπεξεĎγαĎτείς τον τίτλο οποιουδήποτε θεματος - * Αλλάξεις την κατηγοĎία οποιουδήποτε θέματος + * Αλλάξεις την κατηγοĎία οποιουδήποτε θέματος - * Ακολουθούνται όλοι Ďου οι ĎύνδεĎμοι ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) έχει αφαιĎεθεί) + * Ακολουθούνται όλοι Ďου οι ĎύνδεĎμοι ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) έχει αφαιĎεθεί) - * Îχεις Ď€ĎĎŚĎβαĎη Ďτην ιδιωτική κατηγοĎία, που εμφανίζεται μόνο Ďε χĎήĎτες του επιπέδου 3 και πάνω. + * Îχεις Ď€ĎĎŚĎβαĎη Ďτην ιδιωτική κατηγοĎία, που εμφανίζεται μόνο Ďε χĎήĎτες του επιπέδου 3 και πάνω. - * ΚĎĎŤĎεις ανεπιθύμητη αλληλογĎαφία με την Ďημαία + * ΚĎĎŤĎεις ανεπιθύμητη αλληλογĎαφία με την Ďημαία - Εδώ είναι η [λίĎτα με όλα τα κανονικά μέλη](/badges/3/regular). Μπες να πεις ένα γεια. + Εδώ είναι η [λίĎτα με όλα τα κανονικά μέλη](/badges/3/regular). Μπες να πεις ένα γεια. - ΕυχαĎÎąĎτούμε που Ďυνεχίζεις να είĎαι ένα Ďημαντικό μέĎος αυτής της κοινότητας! + ΕυχαĎÎąĎτούμε που Ďυνεχίζεις να είĎαι ένα Ďημαντικό μέĎος αυτής της κοινότητας! - (ΠαĎακαλώ ĎημείωĎε πως μόνο τα μέλη που Ďυνεχίζουν να τηĎούν τις απαĎαίτητες Ď€ĎοϋποθέĎεις θα παĎαμένουν τακτικά μέλη.) + (ΠαĎακαλώ ĎημείωĎε πως μόνο τα μέλη που Ďυνεχίζουν να τηĎούν τις απαĎαίτητες Ď€ĎοϋποθέĎεις θα παĎαμένουν τακτικά μέλη.) category: topic_prefix: "Σχετικά με την κατηγοĎία %{category} " @@ -602,8 +600,6 @@ el: long_form: 'το επιĎήμαναν ως ανάĎÎĽÎżĎτο' notify_user: title: 'ΑποĎτολή μηνύματος Ďτον @{{username}} ' - description: 'Îέλω να μιλήĎω με αυτό το άτομο άμεĎα και ιδιωτικά Ďχετικά με αυτή την ανάĎτηĎη.' - short_description: 'Îέλω να μιλήĎω με αυτό το άτομο άμεĎα και ιδιωτικά Ďχετικά με αυτή την ανάĎτηĎη.' long_form: 'ειδοποίηĎε το μέλος' email_title: 'Η δημοĎίευĎη Ďας Ďτο "%{title}"' email_body: "%{link}\n\n%{message}" @@ -864,7 +860,6 @@ el: poll_pop3_auth_error: "ΥπήĎξε Ďφάλμα ελεχγου ταυτότητας κατά την ĎύνδεĎη με τον διακομιĎτή POP3. ΠαĎακαλώ όπως ελέξετε τις POP3 ĎυθμίĎεις Ďας. " site_settings: censored_words: "Αυτόματη αντικατάĎταĎη λέξεων θα γίνεται με ■■■■" - censored_pattern: "Το Regex Ď€Ďότυπο που θα αντικαταĎταθεί αυτόματα με ■■■&#;" delete_old_hidden_posts: "ΔιάγĎαĎε αυτόματα όποιες ÎşĎυμμένες αναĎτήĎεις μένουν ÎşĎυφές για πάνω από 30 ημέĎες. " allow_user_locale: "ΕπίτĎεĎε Ďτους χĎήĎτες να επιλέγουν την γλώĎĎα που επιθυμούν. " set_locale_from_accept_language_header: "θέĎτε την γλώĎĎα του interface για ανώνυμους χĎήĎτες από τη γλώĎĎα που χĎηĎιμοποιεί το Ď€ĎĎŚÎłĎαμμα πεĎιήγηĎης τους. (ΠΕΙΡΑΜΑΤΙΚΟ ΧΑΡΑΚΤΗΡΙΣΤΙΚΟ, δεν λειτουĎγεί με ανώνυμη cache)" @@ -1023,8 +1018,6 @@ el: google_oauth2_client_id: "Client ID της Google εφαĎμογής Ďας." google_oauth2_client_secret: "ΜυĎτικό πελάτη της Google εφαĎμογής Ďας. " enable_twitter_logins: "ΕνεĎγοποίηĎε την επαλήθευĎη μέĎω Twitter. Απαιτεί twitter_consumer_key και twitter_consumer_secret" - twitter_consumer_key: "Consumer key για επαλήθευĎη μέĎω Twitter, εγγεγĎαμμένο Ďτο http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret για επαλήθευĎη μέĎω Twitter, εγγεγĎαμμένο Ďτο http://dev.twitter.com" enable_instagram_logins: "ΕνεĎγοποίηĎε την επαλήθευĎη μέĎω Instagram, απαιτεί instagram_consumer_key και instagram_consumer_secret" instagram_consumer_key: "Consumer key για επαλήθευĎη μέĎω Instagram" instagram_consumer_secret: "Consumer secret για επαλήθευĎη μέĎω Instagram" @@ -1039,7 +1032,6 @@ el: allow_restore: "ΕπίτĎεĎε επαναφοĎά, η οποία μποĎεί να αντικαταĎτήĎει ΟΛΑ τα δεδομένα του ÎąĎτότοπου! ΑφήĎτε απενεĎγοποιημένο, εκτός αν Ďχεδιάζετε να επαναφέĎετε αντίγĎαφο αĎφαλείας" maximum_backups: "Το μέγιĎτο ποĎĎŚ αντιγĎάφων αĎφαλείας το οποία θα διατηĎηθούν Ďτο δίĎκο. ΠαλαιότεĎα αντίγĎαφα αĎφαλείας διαγĎάφονται αυτόματα" automatic_backups_enabled: "ΠάĎε αυτόματα αντίγĎαφα αĎφαλείας όπως ÎżĎίζεται Ďτη Ďυχνότητα αντιγĎάφων αĎφαλείας" - backup_frequency: "ΠόĎÎż Ďυχνά δημιουĎγούμε αντίγĎαφο αĎφαλείας ÎąĎτοτόπου, Ďε ημέĎες." enable_s3_backups: "ΑνέβαĎε τα αντίγĎαφα αφαλείας Ďτο S3 όταν ολοκληĎωθούν. ΣΗΜΑΝΤΙΚΟ: απαιτεί έγκυĎα διαπιĎτευτήĎια S3 καταχωĎημένα Ďτις ĎυθμίĎεις αĎχείων." s3_backup_bucket: "ΑπομακĎĎ…Ďμένος αποθηκευτικός χώĎος για τα αντίγĎαφα αĎφαλείας. ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Βεβαιωθείτε οτι Îż χώĎος είναι Ď€ĎÎżĎωπικός." s3_disable_cleanup: "ΑπενεĎγοποιήĎτε την αφαίĎεĎη των αντιγĎάφων αĎφαλείας από το S3 όταν αφαιĎεθούν τοπικά" @@ -1580,47 +1572,6 @@ el: test_mailer: title: "ΔοκιμαĎτική ΑποĎτολή" subject_template: "[%{email_prefix}] Email Deliverability Test" - text_body_template: | - Αυτό είναι το Ď€Ďώτο δοκιμαĎτικό μήνυμα ηλεκτĎονικού ταχυδĎομείου από - - [**%{base_url}**][0] - - Η δοκιμή ικανότητας παĎάδοĎης με μήνυμα ηλεκτĎονικού ταχυδĎομείου είναι πεĎίπλοκη. Εδώ θα βĎεις μεĎικά Ďημαντικά Ď€Ďάγματα που Ď€Ďέπει Ď€Ďώτα να ελέγξεις: - - -*ΣιγουĎέĎου* ότι έθεĎες τις `ειδοποιήĎεις ηλεκτĎονικού ταχυδĎομείου ` από: διεύθυνĎη ĎωĎτά Ďτις ĎυθμίĎεις ÎąĎτοτόπου Ďου. **Îź τομέας που πεĎιγĎάφεται Ďτην "από" διεύθυνĎη ηλεκτĎονικού ταχυδĎομείου από την οποία Ďτέλνεις είναι Îż τομέας απέναντι Ďτον οποίο θα εξακĎιβωθεί η διεύθυνĎη ηλεκτĎονικού ταχυδĎομείου Ďου **. - - - Μάθε πως μποĎείς να δεις την αĎχική πηγή των μηνυμάτων ηλεκτĎονικού ταχυδĎομείου Ďτην αλληλογĎαφία του πελάτη Ďου, ĎŽĎτε να μποĎείς να εξετάζεις τίτλους μηνυμάτων ηλεκτĎονικού ταχυδĎομείου για Ďημαντικά Ďτοιχεία. Στο Gmail, είναι η "δείξε το αĎχικό" επιλογή Ďτο ανοιγμένο μενού Ďτην πάνω δεξιά μεĎιά κάθε μηνύματος ηλεκτĎονικού ταχυδĎομείου. - - - **ΣΗΜΑΝΤΙΚΟ:** Îχει Îż ISP Ďου αντίĎĎ„Ďοφο αĎχείο DNS που έχει ειĎαχθεί για να ĎĎ…Ďχετίζει τα ονόματα τομέων και τις διευθύνĎεις IP από τις οποίες Ďτέλνεις αλληλογĎαφία; [Îλεγξε το αντίĎĎ„Ďοφο αĎχείο PTR ][2] εδώ. Αν Îż ISP Ďου δεν ειĎάγει το ĎωĎτό κέĎĎÎżĎα του αντίĎĎ„Ďοφου αĎχείου DNS, είναι απίθανο οποιοδήποτε μήνυμα ηλεκτĎονικού ταχυδĎομείου Ďου να παĎαδοθεί. - - - Είναι το [SPF record][8] του τομέα Ďου ĎωĎτό; [Îλεγξε το αĎχείο SPF][1] εδώ. ΣημείωĎε ότι TXT είναι Îż ĎωĎτός επίĎημος τύπος αĎχείου SPF. - - - Είναι το [DKIM record][3] του τομέα Ďου ĎωĎτό? Αυτό θα βελτιώĎει την ικανότητα παĎάδοĎης με μήνυμα ηλεκτĎονικού ταχυδĎομείου Ďημαντικά. [Îλεγξε το αĎχείο DKIM Ďου][7] εδώ. - - - Αν Ď„Ďέχεις το δικό Ďου διακομιĎτή αλληλογĎαφίας, έλεγξε και επιβεβαίωĎε ότι τα IPs του διακομιĎτή αλληλογĎαφίας Ďου [δεν είναι Ďε μαύĎη λίĎτα μηνυμάτων ηλεκτĎονικής αλληλογĎαφίας][4]. ΕπίĎης, επιβεβαίωĎε ότι ĎίγουĎα Ďτέλνει ένα εξειδικευμένο όνομα δέκτη που παίĎνει τη ÎĽÎżĎφή DNS Ďτο HELO μήνυμα. Αν όχι, αυτό θα Ď€ĎοκαλέĎει απόĎĎÎąĎη του ηλεκτĎονικού ταχυδĎομείου Ďου από πολλές υπηĎεĎίες αλληλογĎαφίας. - - - Σου Ď€Ďοτείνουμε να **Ďτείλεις ένα δοκιμαĎτικό μήνυμα ηλεκτĎονικού ταχυδĎομείου [mail-tester.com][mt]** για να εξακĎιβώĎεις αν όλα τα παĎαπάνω λειτουĎγούν κανονικά. - - (Îź *εύκολος* Ď„Ďόπος είναι να δημιουĎγήĎεις ένα δωĎεάν λογαĎιαĎÎĽĎŚ Ďτο [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] ή [Mailjet][mj], τα οποία έχουν γεναιόδωĎα ελεύθεĎα πλάνα αλληλογĎαφίας και θα είναι εντάξει για μικĎές κοινότητες. Ακόμη χĎειάζεται να ενεĎγοποιήĎεις τα αĎχεία SPF και DKIM Ďτο DNS Ďου, όμως!) - - Ελπίζουμε να έλαβες αυτή τη δοκιμή ικανότητας παĎάδοĎης με μήνυμα ηλεκτĎονικού ταχυδĎομείου ΕΝΤΑΞΕΙ! - - Καλή επιτυχία, - - Οι φίλοι Ďου Ďτο [Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "ΑποĎτολή ΚαινούĎγιας ÎκδοĎης" subject_template: "[%{email_prefix}] Νέα έκδοĎη Discourse, διαθέĎιμη ενημέĎωĎη" @@ -2204,7 +2155,7 @@ el: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} account_suspended: title: "ΧĎήĎτης Ďε Αποβολη" subject_template: "[%{email_prefix}] Îź λογαĎιαĎÎĽĎŚĎ‚ Ďου είναι Ďε κατάĎταĎη αποβολής" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index c72ba85ded..13b8fe4f6b 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -49,6 +49,7 @@ en: loading: "Loading" powered_by_html: 'Powered by Discourse, best viewed with JavaScript enabled' log_in: "Log In" + submit: "Submit" purge_reason: "Automatically deleted as abandoned, deactivated account" disable_remote_images_download_reason: "Remote images download was disabled because there wasn't enough disk space available." @@ -58,6 +59,22 @@ en: themes: bad_color_scheme: "Can not update theme, invalid color scheme" other_error: "Something went wrong updating theme" + settings_errors: + invalid_yaml: "Provided YAML is invalid." + data_type_not_a_number: "Setting `%{name}` type is unsupported. Supported types are `integer`, `bool`, `list` and `enum`" + name_too_long: "There is a setting with a too long name. Maximum length is 255" + default_value_missing: "Setting `%{name}` has no default value" + default_not_match_type: "Setting `%{name}` default value's type doesn't match with the setting type." + default_out_range: "Setting `%{name}` default value isn't in the specified range." + enum_value_not_valid: "Selected value isn't one of the enum choices." + number_value_not_valid: "New value isn't within the allowed range." + number_value_not_valid_min_max: "It must be between %{min} and %{max}." + number_value_not_valid_min: "It must be larger than or equal to %{min}." + number_value_not_valid_max: "It must be smaller than or equal to %{max}." + string_value_not_valid: "New value length isn't within the allowed range." + string_value_not_valid_min_max: "It must be between %{min} and %{max} character long." + string_value_not_valid_min: "It must be at least %{min} characters long." + string_value_not_valid_max: "It must be at most %{max} characters long." emails: incoming: default_subject: "This topic needs a title" @@ -107,13 +124,13 @@ en: invalid: is invalid is_invalid: "seems unclear, is it a complete sentence?" contains_censored_words: "contains the following censored words: %{censored_words}" - matches_censored_pattern: "contains the following words that matches the site's censored regexp: %{censored_words}" less_than: must be less than %{count} less_than_or_equal_to: must be less than or equal to %{count} not_a_number: is not a number not_an_integer: must be an integer odd: must be odd record_invalid: ! 'Validation failed: %{errors}' + max_emojis: "can't have more than %{max_emojis_count} emoji" restrict_dependent_destroy: one: "Cannot delete record because a dependent %{record} exists" many: "Cannot delete record because dependent %{record} exist" @@ -206,6 +223,7 @@ en: too_many_mentions_newuser: one: "Sorry, new users can only mention one other user in a post." other: "Sorry, new users can only mention %{count} users in a post." + no_images_allowed_trust: "Sorry, you can't put images in a post" no_images_allowed: "Sorry, new users can't put images in posts." too_many_images: one: "Sorry, new users can only put one image in a post." @@ -219,7 +237,7 @@ en: too_many_links: one: "Sorry, new users can only put one link in a post." other: "Sorry, new users can only put %{count} links in a post." - contains_blocked_words: "Your post contains words that aren't allowed." + contains_blocked_words: "Your post contains a word that's not allowed: %{word}" spamming_host: "Sorry you cannot post a link to that host." user_is_suspended: "Suspended users are not allowed to post." @@ -698,8 +716,8 @@ en: long_form: 'flagged this as inappropriate' notify_user: title: 'Send @{{username}} a message' - description: 'I want to talk to this person directly and privately about their post.' - short_description: 'I want to talk to this person directly and privately about their post.' + description: 'I want to talk to this person directly and personally about their post.' + short_description: 'I want to talk to this person directly and personally about their post.' long_form: 'messaged user' email_title: 'Your post in "%{title}"' email_body: "%{link}\n\n%{message}" @@ -962,7 +980,7 @@ en: email_polling_errored_recently: one: "Email polling has generated an error in the past 24 hours. Look at the logs for more details." other: "Email polling has generated %{count} errors in the past 24 hours. Look at the logs for more details." - missing_mailgun_api_key: "The server is configured to send emails via mailgun but you haven't provided an API key used to verify the webhook messages." + missing_mailgun_api_key: "The server is configured to send emails via Mailgun but you haven't provided an API key used to verify the webhook messages." bad_favicon_url: "The favicon is failing to load. Check your favicon_url setting in Site Settings." poll_pop3_timeout: "Connection to the POP3 server is timing out. Incoming email could not be retrieved. Please check your POP3 settings and service provider." poll_pop3_auth_error: "Connection to the POP3 server is failing with an authentication error. Please check your POP3 settings." @@ -970,7 +988,6 @@ en: site_settings: censored_words: "Words that will be automatically replaced with ■■■■" - censored_pattern: "Regex pattern that will be automatically replaced with ■■■■" delete_old_hidden_posts: "Auto-delete any hidden posts that stay hidden for more than 30 days." default_locale: "The default language of this Discourse instance" allow_user_locale: "Allow users to choose their own language interface preference" @@ -985,6 +1002,7 @@ en: min_topic_title_length: "Minimum allowed topic title length in characters" max_topic_title_length: "Maximum allowed topic title length in characters" min_personal_message_title_length: "Minimum allowed title length for a message in characters" + max_emojis_in_title: "Maximum allowed emojis in topic title" min_search_term_length: "Minimum valid search term length in characters" search_tokenize_chinese_japanese_korean: "Force search to tokenize Chinese/Japanese/Korean even on non CJK sites" search_prefer_recent_posts: "If searching your large forum is slow, this option tries an index of more recent posts first" @@ -1005,6 +1023,7 @@ en: download_remote_images_max_days_old: "Don't download remote images for posts that are more than n days old." disabled_image_download_domains: "Remote images will never be downloaded from these domains. Pipe-delimited list." editing_grace_period: "For (n) seconds after posting, editing will not create a new version in the post history." + editing_grace_period_max_diff: "Maximum number of character changes allowed in editing grace period, if more changed store another post revision" staff_edit_locks_post: "Posts will be locked from editing if they are edited by staff members" post_edit_time_limit: "The author can edit or delete their post for (n) minutes after posting. Set to 0 for forever." edit_history_visible_to_public: "Allow everyone to see previous versions of an edited post. When disabled, only staff members can view." @@ -1046,7 +1065,7 @@ en: enable_personal_messages: "Allow trust level 1 (configurable via min trust level to send messages) users to create messages and reply to messages. Note that staff can always send messages no matter what." enable_system_message_replies: "Allows users to reply to system messages, even if personal messages are disabled" - enable_personal_email_messages: "Allow trust level 4 (configurable via min trust level to send messages) users to send private email messages. Note that staff can always send messages no matter what." + enable_personal_email_messages: "Allow trust level 4 (configurable via min trust level to send messages) users to send personal email messages. Note that staff can always send messages no matter what." enable_long_polling: "Message bus used for notification can use long polling" long_polling_base_url: "Base URL used for long polling (when a CDN is serving dynamic content, be sure to set this to origin pull) eg: http://origin.site.com" @@ -1162,10 +1181,12 @@ en: enable_google_oauth2_logins: "Enable Google Oauth2 authentication. This is the method of authentication that Google currently supports. Requires key and secret." google_oauth2_client_id: "Client ID of your Google application." google_oauth2_client_secret: "Client secret of your Google application." + google_oauth2_prompt: "A space-delimited list of string values that specifies whether the authorization server prompts the user for reauthentication and consent. See https://developers.google.com/identity/protocols/OpenIDConnect#prompt for the possible values." + google_oauth2_hd: "Google Apps Hosted domain that the sign-in will be limited to. See https://developers.google.com/identity/protocols/OpenIDConnect#hd-param for more details." enable_twitter_logins: "Enable Twitter authentication, requires twitter_consumer_key and twitter_consumer_secret" - twitter_consumer_key: "Consumer key for Twitter authentication, registered at http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret for Twitter authentication, registered at http://dev.twitter.com" + twitter_consumer_key: "Consumer key for Twitter authentication, registered at https://apps.twitter.com/" + twitter_consumer_secret: "Consumer secret for Twitter authentication, registered at https://apps.twitter.com/" enable_instagram_logins: "Enable Instagram authentication, requires instagram_consumer_key and instagram_consumer_secret" instagram_consumer_key: "Consumer key for Instagram authentication" @@ -1185,7 +1206,7 @@ en: allow_restore: "Allow restore, which can replace ALL site data! Leave false unless you plan to restore a backup" maximum_backups: "The maximum amount of backups to keep on disk. Older backups are automatically deleted" automatic_backups_enabled: "Run automatic backups as defined in backup frequency" - backup_frequency: "How frequently we create a site backup, in days." + backup_frequency: "The number of days between backups." enable_s3_backups: "Upload backups to S3 when complete. IMPORTANT: requires valid S3 credentials entered in Files settings." s3_backup_bucket: "The remote bucket to hold backups. WARNING: Make sure it is a private bucket." s3_disable_cleanup: "Disable the removal of backups from S3 when removed locally." @@ -1286,6 +1307,7 @@ en: tl3_requires_likes_given: "The minimum number of likes that must be given in the last (tl3 time period) days to qualify for promotion to trust level 3." tl3_requires_likes_received: "The minimum number of likes that must be received in the last (tl3 time period) days to qualify for promotion to trust level 3." tl3_links_no_follow: "Do not remove rel=nofollow from links posted by trust level 3 users." + trusted_users_can_edit_others: "Allow users with high trust levels to edit content from other users" min_trust_to_create_topic: "The minimum trust level required to create a new topic." allow_flagging_staff: "If enabled, users can flag posts from staff accounts." @@ -1302,6 +1324,7 @@ en: min_trust_to_flag_posts: "The minimum trust level required to flag posts" min_trust_to_post_links: "The minimum trust level required to include links in posts" + min_trust_to_post_images: "The minimum trust level required to include images in a post" newuser_max_links: "How many links a new user can add to a post." newuser_max_images: "How many images a new user can add to a post." @@ -1315,7 +1338,7 @@ en: create_thumbnails: "Create thumbnails and lightbox images that are too large to fit in a post." email_time_window_mins: "Wait (n) minutes before sending any notification emails, to give users a chance to edit and finalize their posts." - personal_email_time_window_seconds: "Wait (n) seconds before sending any private notification emails, to give users a chance to edit and finalize their messages." + personal_email_time_window_seconds: "Wait (n) seconds before sending any personal message notification emails, to give users a chance to edit and finalize their messages." email_posts_context: "How many prior replies to include as context in notification emails." flush_timings_secs: "How frequently we flush timing data to the server, in seconds." title_max_word_length: "The maximum allowed word length, in characters, in a topic title." @@ -1335,6 +1358,7 @@ en: max_image_size_kb: "The maximum image upload size in kB. This must be configured in nginx (client_max_body_size) / apache or proxy as well." max_attachment_size_kb: "The maximum attachment files upload size in kB. This must be configured in nginx (client_max_body_size) / apache or proxy as well." authorized_extensions: "A list of file extensions allowed for upload (use '*' to enable all file types)" + authorized_extensions_for_staff: "A list of file extensions allowed for upload for staff users in addition to the list defined in the `authorized_extensions` site setting. (use '*' to enable all file types)" theme_authorized_extensions: "A list of file extensions allowed for theme uploads (use '*' to enable all file types)" max_similar_results: "How many similar topics to show above the editor when composing a new topic. Comparison is based on title and body." @@ -1361,6 +1385,7 @@ en: 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." + log_anonymizer_details: "Whether to keep a user's details in the log after being anonymized. When complying to GDPR you'll need to turn this off." newuser_spam_host_threshold: "How many times a new user can post a link to the same host within their `newuser_spam_host_threshold` posts before being considered spam." @@ -1471,6 +1496,7 @@ en: staff_user_custom_fields: "A whitelist of custom fields for a user that can be shown to staff." enable_user_directory: "Provide a directory of users for browsing" enable_group_directory: "Provide a directory of groups for browsing" + group_in_subject: "Set %{optional_pm} in email subject to name of first group in PM, see: https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" allow_anonymous_posting: "Allow users to switch to anonymous mode" anonymous_posting_min_trust_level: "Minimum trust level required to enable anonymous posting" 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." @@ -1610,6 +1636,7 @@ en: tags_listed_by_group: "List tags by tag group on the Tags page (/tags)." tag_style: "Visual style for tag badges." staff_tags: "A list of tags that can only be applied by staff members" + allow_staff_to_tag_pms: "Allow staff members to tag any personal message" min_trust_level_to_tag_topics: "Minimum trust level required to tag topics" suppress_overlapping_tags_in_list: "If tags match exact words in topic titles, don't show the tag" remove_muted_tags_from_latest: "Don't show topics tagged with muted tags in the latest topic list." @@ -1756,6 +1783,7 @@ en: login: not_approved: "Your account hasn't been approved yet. You will be notified by email when you are ready to log in." incorrect_username_email_or_password: "Incorrect username, email or password" + incorrect_password: "Incorrect password" wait_approval: "Thanks for signing up. We will notify you when your account has been approved." active: "Your account is activated and ready to use." activate_email: "

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

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

" @@ -1778,6 +1806,9 @@ en: auth_complete: "Authentication is complete." click_to_continue: "Click here to continue." already_logged_in: "Oops, looks like you are attempting to accept an invitation for another user. If you are not %{current_user}, please log out and try again." + second_factor_title: "Two Factor Authentication Required" + second_factor_description: "Enter the authentication code from your app." + invalid_second_factor_code: "Invalid Two Factor Authentication Code" user: no_accounts_associated: "No accounts associated" @@ -1946,7 +1977,7 @@ en: - Be *sure* to set the `notification email` from: address correctly in your site settings. **The domain specified in the "from" address of the emails you send is the domain your email will be validated against**. - - Know how to view the raw source of the email in your mail client, so you can examine email headers for important clues. in Gmail, it is the "show original" option in the drop-down menu at the top right of each mail. + - Know how to view the raw source of the email in your mail client, so you can examine email headers for important clues. In Gmail, it is the "show original" option in the drop-down menu at the top right of each mail. - **IMPORTANT:** Does your ISP have a reverse DNS record entered to associate the domain names and IP addresses you send mail from? [Test your Reverse PTR record][2] here. If your ISP does not enter the proper reverse DNS pointer record, it's very unlikely any of your email will be delivered. @@ -2725,6 +2756,15 @@ en: + account_second_factor_disabled: + title: "Two Factor Authentication disabled" + subject_template: "[%{email_prefix}] Two Factor Authentication disabled" + text_body_template: | + Your account’s Two Factor Authentication at %{site_name} has been disabled. The account no longer needs a Two Factor Authentication code to sign in. + + If you have any questions, [contact our friendly staff](%{base_url}/about). + + digest: why: "A brief summary of %{site_link} since your last visit on %{last_seen_at}" since_last_visit: "Since your last visit" diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index a8e2900321..54aa6e3997 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -82,7 +82,6 @@ es: invalid: no es válido is_invalid: "parece poco claro, Âżes una oraciĂłn completa?" contains_censored_words: "contiene las siguientes palabras censuradas: %{censored_words}" - matches_censored_pattern: "contiene las siguientes palabras que coinciden con el regexp de censuras: %{censored_words}" less_than: debe ser menor que %{count} less_than_or_equal_to: debe ser menor o igual que %{count} not_a_number: no es un nĂşmero @@ -105,7 +104,7 @@ es: template: body: 'Hubo problemas con los siguientes campos:' header: - one: 1 error impidiĂł guardar este %{model} + one: '1 error impidiĂł guardar este %{model}' other: '%{count} errores impidieron guardar este %{model}' embed: load_from_remote: "Hubo un error al cargar ese post." @@ -178,7 +177,6 @@ es: too_many_links: one: "Lo sentimos, los nuevos usuarios solo pueden poner un link en un post." other: "Lo sentimos, los nuevos usuarios solo pueden poner %{count} enlaces en un post." - contains_blocked_words: "Tu post contiene palabras que no están permitidas." spamming_host: "Lo sentimos, no puedes publicar un enlace a esa web." user_is_suspended: "A los usuarios suspendidos no se les permite publicar." topic_not_found: "Algo ha salido mal. ÂżTal vez este tema ha sido cerrado o eliminado mientras estabas lo estabas mirando?" @@ -393,41 +391,41 @@ es: title: "Te damos la bienvenida a Discourse" body: |2 - El primer párrafo de este tema destacado (tema fijo) será visible para todos los usuarios como mensaje de bienvenida en tu página principal. Es importante! + El primer párrafo de este tema destacado (tema fijo) será visible para todos los usuarios como mensaje de bienvenida en tu página principal. Es importante! - **Estia esto** con un resumen de lo que es tu comunidad: + **Estia esto** con un resumen de lo que es tu comunidad: - - ÂżPara quiĂ©nes es? - - ÂżQuĂ© pueden encontrar aquĂ­? - - ÂżPor quĂ© deben venir aquĂ­? - - ÂżDĂłnde pueden leer más sobre la comunidad (enlaces, recursos, etc)? + - ÂżPara quiĂ©nes es? + - ÂżQuĂ© pueden encontrar aquĂ­? + - ÂżPor quĂ© deben venir aquĂ­? + - ÂżDĂłnde pueden leer más sobre la comunidad (enlaces, recursos, etc)? - + - Quizás quieras "cerrar" este tema :wrench: (en la parte superior derecha o en la parte de abajo), para que las nuevas respuestas no se sumen en el anuncio. + Quizás quieras "cerrar" este tema :wrench: (en la parte superior derecha o en la parte de abajo), para que las nuevas respuestas no se sumen en el anuncio. lounge_welcome: title: "Bienvenido a la Sala VIP" body: |2 - ¡Enhorabuena! :confetti_ball: + ¡Enhorabuena! :confetti_ball: - Si puedes ver este tema, es que has sido promocionado a usuario **habitual** (nivel de confianza 3). + Si puedes ver este tema, es que has sido promocionado a usuario **habitual** (nivel de confianza 3). - Ahora puedes … + Ahora puedes … - * Editar el tĂ­tulo de cualquier tema - * Cambiar la categorĂ­a de cualquier tema - * Tus enlaces no llevarán automáticamente la etiqueta [nofollow](http://es.wikipedia.org/wiki/Nofollow) - * Acceder a la categorĂ­a privada Sala VIP, solo visible para usuarios de nivel 3 o más - * Ocultar spam con una sola vez que reportes + * Editar el tĂ­tulo de cualquier tema + * Cambiar la categorĂ­a de cualquier tema + * Tus enlaces no llevarán automáticamente la etiqueta [nofollow](http://es.wikipedia.org/wiki/Nofollow) + * Acceder a la categorĂ­a privada Sala VIP, solo visible para usuarios de nivel 3 o más + * Ocultar spam con una sola vez que reportes - Consulta aquĂ­ [la lista actualizada de usuarios habituales](/badges/3/regular). ¡Manda un saludo! + Consulta aquĂ­ [la lista actualizada de usuarios habituales](/badges/3/regular). ¡Manda un saludo! - ¡Gracias por formar la parte más importante de esta comunidad! + ¡Gracias por formar la parte más importante de esta comunidad! - (Para saber más sobre niveles de confianza, [mira este tema][trust]. Por favor, ten en cuenta que solo los usuarios que continĂşen cumpliendo los requisitos a lo largo del tiempo seguirán en el nivel de usuarios habituales.) + (Para saber más sobre niveles de confianza, [mira este tema][trust]. Por favor, ten en cuenta que solo los usuarios que continĂşen cumpliendo los requisitos a lo largo del tiempo seguirán en el nivel de usuarios habituales.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "DefiniciĂłn de la categorĂ­a %{category}" replace_paragraph: "(Sustituye este primer párrafo con una descripciĂłn breve de tu nueva categorĂ­a. Esta descripciĂłn aparecerá en el área de selecciĂłn de categorĂ­a, por ello, intenta que sea inferior a 200 caracteres. **Hasta que edites esta descripciĂłn o se creen temas, esta categorĂ­a no aparecerá en la página de categorĂ­as.**)" @@ -613,8 +611,6 @@ es: long_form: 'reportado como inapropiado' notify_user: title: 'Enviar un mensaje a @{{username}}' - description: 'Quiero hablar con esta persona de forma directa y privada sobre su mensaje.' - short_description: 'Quiero hablar con esta persona de forma directa y privada sobre su mensaje.' long_form: 'usuario notificado vĂ­a mensaje' email_title: 'Tu publicaciĂłn en "%{title}"' email_body: "%{link}\n\n%{message}" @@ -873,7 +869,6 @@ es: poll_pop3_auth_error: "La conexiĂłn al servidor POP3 está fallando debido a un error de autenticaciĂłn. Por favor revisa los ajustes POP3." site_settings: censored_words: "Las palabras serán reemplazadas con ■■■■" - censored_pattern: "Patrones de Regex serán automáticamente reemplazados con ■■■■" delete_old_hidden_posts: "Auto-borrar cualquier post que se quede oculto por mas de 30 dĂ­as." allow_user_locale: "Permitir que los usuarios escojan su propio idioma para la interfaz" set_locale_from_accept_language_header: "Establece el lenguaje de la interfaz para usuarios anĂłnimos desde el lenguaje declarado por su navegador web. (EXPERIMENTAL, no funciona con cachĂ© anĂłnimo)" @@ -1034,8 +1029,6 @@ es: google_oauth2_client_id: "Client ID de tu aplicaciĂłn Google." google_oauth2_client_secret: "Client secret de tu aplicaciĂłn Google." enable_twitter_logins: "Activar autenticaciĂłn por Twitter, requiere una twitter_consumer_key y un twitter_consumer_secret" - twitter_consumer_key: "Comsumer key para autenticaciĂłn por Twitter, registrado en http://dev.twitter.com" - twitter_consumer_secret: "Comsumer secret para autenticaciĂłn por Twitter, registrado en http://dev.twitter.com" enable_instagram_logins: "Activar la autenticaciĂłn por Instagram, requiere instagram_consumer_key y instagram_consumer_secret" instagram_consumer_key: "Consumer key para autenticaciĂłn por Instagram" instagram_consumer_secret: "Consumer secret para autenticaciĂłn por Instagram" @@ -1051,7 +1044,6 @@ es: allow_restore: "Permitir restauraciĂłn, la cual puede sobreescribir TODOS los datos del sitio! Dejela en falso a menos que tenga planeado recuperar sus datos desde una copia de respaldo. " maximum_backups: "La cantidad máxima de copias de seguridad a tener en el disco. Las copias de seguridad más antiguas se eliminan automáticamente" automatic_backups_enabled: "Ejecutar backups automáticos definidos por la opciĂłn de frecuencia de backups" - backup_frequency: "Con quĂ© frecuencia, en dĂ­as, crearemos un backup del sitio." enable_s3_backups: "Sube copias de seguridad a S3 cuando complete. IMPORTANTE: requiere credenciales validas de S3 puestas Archivos configuraciĂłn." s3_backup_bucket: "El bucket remoto para mantener copias de seguridad. AVISO: AsegĂşrate de que es un bucket privado." s3_disable_cleanup: "Desactivar el eliminado de backups de S3 cuando se eliminen de forma local." @@ -1660,47 +1652,6 @@ es: test_mailer: title: "Mail de prueba" subject_template: "[%{email_prefix}] Prueba de envĂ­o de email" - text_body_template: | - Este es un correo electrĂłnico de prueba de - - [**%{base_url}**][0] - - La entrega de correo electrĂłnico es complicada. AquĂ­ hay unas cuantas cosas importantes que deberĂ­as comprobar primero: - - - *AsegĂşrate* de ajustar la direcciĂłn `notification email` desde: correctamente en los ajustes de tu sitio. **El dominio especificado en la direcciĂłn "from" de los emails que envĂ­es es el dominio contra el que se validará tu email**. - - - EntĂ©rate de como ver el cĂłdigo fuente del email desde tu cliente de correo, para que asĂ­ puedas examinar las cabeceras para ver pistas importantes. En Gmail está la opciĂłn "mostrar original" en el menĂş de lista desplegable en la parte superior derecha de cada email. - - - **IMPORTANTE:** ÂżTu ISP tiene registros de DNS inversa para asociar los nombres de dominio y las direcciones IP desde donde envĂ­as los correos? [Comprueba tu registro PTR Inverso][2] aquĂ­. Si tu ISP no introduce el puntero de registro DNS inverso adecuado, es muy improbable que cualquiera de tus correos pueda ser entregado. - - - ÂżEs el [registro SPF][8] de tu dominio correcto? [Comprueba tu registro SPF][1] aquĂ­. Ten en cuenta que TXT es el tipo de registro oficial correcto para SPF. - - - ÂżEs el [registro DKIM][3] de tu dominio correcto? Esto puede mejorar significativamente la entregabilidad de correos. [Comprueba tu registro DKIM][7] aquĂ­. - - - Si estás corriendo tu propio servidor de correo, asegĂşrate de que las IPs de tu servidor de correo [no están en ninguna lista negra de correos][4]. TambiĂ©n verifica que definitivamente se está enviando un nombre de huĂ©sped "hostname" completamente cualificado que resuelva en DNS en su mensaje HELO. Si no es asĂ­, esto causará que tu correo sea denegado por muchos servicios de correo. - - -Recomendamos **enviar un email de prueba a [mail-tester.com][mt]** para verificar que todo está funcionando correctamente. - - (La forma *fácil* es crear una cuenta gratuita en [Sendgrip][sg], [SparkPost][sp], [Mailgun][mg] o [Mailjet][mj], que proveen generosamente planes de mailing gratuitos y serán adecuados para pequeñas comunidades. ¡De todas formas todavĂ­a tendrás que configurar los registros SPF y DKIM en tus DNS!) - - ¡Esperamos que recibas esta prueba de entregabilidad de correo electrĂłnico correctamente! - - Buena suerte, - - Tus amigos de [Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "Email de Nueva versiĂłn" subject_template: "[%{email_prefix}] Nueva versiĂłn de Discourse, actualizaciĂłn disponible" @@ -2340,7 +2291,7 @@ es: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} account_suspended: title: "Cuenta Suspendida" subject_template: "[%{email_prefix}] Tu cuenta ha sido suspendida" diff --git a/config/locales/server.et.yml b/config/locales/server.et.yml index 92c9fe8522..08b5d54b6c 100644 --- a/config/locales/server.et.yml +++ b/config/locales/server.et.yml @@ -94,7 +94,7 @@ et: template: body: 'Järgmiste väljadega oli probleeme:' header: - one: 1 viga takistas mudeli %{model} salvestamist + one: '1 viga takistas mudeli %{model} salvestamist' other: '%{count} viga takistasid mudeli %{model} salvestamist' embed: load_from_remote: "Selle postituse laadimisel ilmnes ĂĽks viga" @@ -289,41 +289,41 @@ et: title: "Teretulemast Discourse'i" body: |2 - Selle märgistatud teema esimene lõik saab olema kõikidele uutele kĂĽlastajatele nähtav kui tervitustekst sinu avalehel. See on oluline! + Selle märgistatud teema esimene lõik saab olema kõikidele uutele kĂĽlastajatele nähtav kui tervitustekst sinu avalehel. See on oluline! - **Muuda see** enda kommuuni lĂĽhitutvustuseks: + **Muuda see** enda kommuuni lĂĽhitutvustuseks: - - Kellele see on? - - Mida siit leiab? - - Miks peaks keegi siia tulema? - - Kust saab rohkem infot (lingid, ressursid jne)? + - Kellele see on? + - Mida siit leiab? + - Miks peaks keegi siia tulema? + - Kust saab rohkem infot (lingid, ressursid jne)? - + - Ilmselt peaksid sa selle teema administreerimise alt sulgema :wrench: (ĂĽleval paremal ja allääres), et teadandele vastuseid ei kuhjuks. + Ilmselt peaksid sa selle teema administreerimise alt sulgema :wrench: (ĂĽleval paremal ja allääres), et teadandele vastuseid ei kuhjuks. lounge_welcome: title: "Teretulemast Lounge'i" body: |2 - Ă•nnitlused! :confetti_ball: + Ă•nnitlused! :confetti_ball: - Kui Sa seda teemat näed, oled hiljuti edutatud **pĂĽsiliikmeks** (usaldustase 3). + Kui Sa seda teemat näed, oled hiljuti edutatud **pĂĽsiliikmeks** (usaldustase 3). - NĂĽĂĽd võid … + NĂĽĂĽd võid … - * Muuta suvalise teema pealkirja - * Muuta suvalise teema foorumit - * Lasta kõiki oma viiteid järgida ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) on eemaldatud) - * Siseneda vaid usaldustasemel 3 ja kõrgemal olevatele kasutajatele mõeldud privaatse Salongi foorumisse - * Peita spämmi vaid ĂĽhe tähisega + * Muuta suvalise teema pealkirja + * Muuta suvalise teema foorumit + * Lasta kõiki oma viiteid järgida ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) on eemaldatud) + * Siseneda vaid usaldustasemel 3 ja kõrgemal olevatele kasutajatele mõeldud privaatse Salongi foorumisse + * Peita spämmi vaid ĂĽhe tähisega - Siin on [hetke pĂĽsiliikmete nimekiri](/badges/3/regular). Tervita neid kindlasti. + Siin on [hetke pĂĽsiliikmete nimekiri](/badges/3/regular). Tervita neid kindlasti. - Täname, et oled meie kogukonna tähtis liige! + Täname, et oled meie kogukonna tähtis liige! - (Täiendava info saamiseks usaldustasemete kohta [vaata teemat][trust]. Pea meeles, et vaid need liikmed kes jätkuvalt seatud nõudmisi täidavad pĂĽsivad pĂĽsiliikmetena.) + (Täiendava info saamiseks usaldustasemete kohta [vaata teemat][trust]. Pea meeles, et vaid need liikmed kes jätkuvalt seatud nõudmisi täidavad pĂĽsivad pĂĽsiliikmetena.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Liigist %{category}" replace_paragraph: "(Asenda see paragrahv oma uues kategoorias kategooria lĂĽhitutvustusega. See juhis ilmub siis kategooriate vaates. Seega, katsu hoida kirjeldus alla 200 tähemärgi. **Kuniks sa pole muutnud seda kirjeldust või kuni sa pole loonud teemasid, ei ole seda kategooriat kategooriate lehel näha.**)" @@ -493,7 +493,6 @@ et: long_form: 'tähistasin selle kui sobimatu' notify_user: title: 'Saada kasutajale @{{username}} sõnum' - description: 'Soovin selle isikuga tema postituse osas otse ja privaatselt suhelda.' long_form: 'saatsin kasutajale sõnumi' email_title: 'Sinu postitus teemas "%{title}"' email_body: "%{link}\n\n%{message}" @@ -732,7 +731,7 @@ et: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} signup_after_approval: subject_template: "Oled saidil %{site_name} heaks kiidetud!" page_not_found: diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml index 14abd92a92..e42fe05707 100644 --- a/config/locales/server.fa_IR.yml +++ b/config/locales/server.fa_IR.yml @@ -77,7 +77,6 @@ fa_IR: invalid: نامعتبر است is_invalid: "به نظر Ůاضح نیست، آیا این جمله کامل است؟" contains_censored_words: "شامل کلمات سانسŮر شده زیر است: %{censored_words}" - matches_censored_pattern: "شامل کلمات سانسŮر شده‌ی زیر است که با الگŮŰŚ کلمات سانسŮر شده سایت هماهنگ است: %{censored_words}" less_than: باید کمتر از %{count} باشد less_than_or_equal_to: باید کمتر ٠یا مساŮŰŚ %{count} باشد not_a_number: عدد نیست @@ -159,7 +158,6 @@ fa_IR: no_links_allowed: "Ů…ŘŞŘŁŘłŮŰŚŮ…ŘŚ کاربران تازه نمی‌تŮانند در نŮشته‌ها پیŮند بگذارند." too_many_links: other: "Ů…ŘŞŘŁŘłŮŰŚŮ…ŘŚ کاربران تازه تنها می‌تŮانند در هر نŮشته %{count} پیŮند بگذارند." - contains_blocked_words: "نŮشته‌ی شما حاŮŰŚ الŮاظی است که ممنŮŘą هستند." spamming_host: "Ů…ŘŞŘŁŘłŮانه شما نمی‌تŮانید پیŮندی با آن میزبان را بŮرستید." user_is_suspended: "کاربر تعلیق شده نمی‌تŮاند نŮشته‌ای ارسال کند." topic_not_found: "ŰŚÚ© مشکلی ŮجŮŘŻ دارد. شاید این Ů…ŮضŮŘą همزمان که شما در حال Ř®Ůاندن آن بŮŘŻŰŚŘŻ بسته شده یا پاک شده." @@ -353,41 +351,41 @@ fa_IR: title: "به ŘŻŰŚŘłÚ©Ůرس Ř®ŮŘ´ آمدید" body: |2 - اŮلین پاراگرا٠این Ů…ŮضŮŘą سنجاق شده ٠به عنŮان پیام Ř®Ůش‌آمد ÚŻŮŰŚŰŚ به تمام بازدید‌کنندگان نمایش داده می‌شŮŘŻ. که مهم است! + اŮلین پاراگرا٠این Ů…ŮضŮŘą سنجاق شده ٠به عنŮان پیام Ř®Ůش‌آمد ÚŻŮŰŚŰŚ به تمام بازدید‌کنندگان نمایش داده می‌شŮŘŻ. که مهم است! - این متن را به ŰŚÚ© ŘŞŮضیح مختصر درباره انجمن Ř®ŮŘŻ **تغییر دهید**: + این متن را به ŰŚÚ© ŘŞŮضیح مختصر درباره انجمن Ř®ŮŘŻ **تغییر دهید**: - - برای چه اŮرادی است؟ - - چه چیزی را در اینجا پیدا می‌کنند؟ - - چرا باید به اینجا بیایند؟ - - چه زمانی می‌تŮانند بیشتر بخŮانند (پیŮند‌ها، منابع Ů...)Řź - + - برای چه اŮرادی است؟ + - چه چیزی را در اینجا پیدا می‌کنند؟ + - چرا باید به اینجا بیایند؟ + - چه زمانی می‌تŮانند بیشتر بخŮانند (پیŮند‌ها، منابع Ů...)Řź + - شاید بخŮاهید این Ů…ŮضŮŘą را از طریق مدیریت ببندید :wrench: (در قسمت بالا سمت راست ٠پایین)ŘŚ بنابراین پاسخ‌ها در اعلان ها انباشته نمی‌شŮند. + شاید بخŮاهید این Ů…ŮضŮŘą را از طریق مدیریت ببندید :wrench: (در قسمت بالا سمت راست ٠پایین)ŘŚ بنابراین پاسخ‌ها در اعلان ها انباشته نمی‌شŮند. lounge_welcome: title: "به سالن Ř®ŮŘ´ آمدید" body: |2 - تبریک! :confetti_ball: + تبریک! :confetti_ball: - اگر می‌تŮانید این Ů…ŮضŮŘą را ببینید، یعنی به **ناظم** (سطح اعتماد 3) ارتقا پیدا کردید. + اگر می‌تŮانید این Ů…ŮضŮŘą را ببینید، یعنی به **ناظم** (سطح اعتماد 3) ارتقا پیدا کردید. - حالا می‌تŮانید … + حالا می‌تŮانید … - * عنŮاهر Ů…ŮضŮعی را Ůیرایش کنید - * دسته‌بندی هر Ů…ŮضŮعی را تغییر دهید - * تمام پیŮند‌های Ř®ŮŘŻ را به صŮرت follow قرار دهید. ([nofollow Ř®Ůدکار](http://en.wikipedia.org/wiki/Nofollow) حذ٠می‌شŮŘŻ) - * دسترسی به تالار های خصŮصی دسته‌بندی‌هایی که Ůقط به کاربران سطح 3 ٠بالاتر به آن دسترسی دارند - * Ů…Ř®ŮŰŚ سازی هرزنامه‌ با ŰŚÚ© پرچم + * عنŮاهر Ů…ŮضŮعی را Ůیرایش کنید + * دسته‌بندی هر Ů…ŮضŮعی را تغییر دهید + * تمام پیŮند‌های Ř®ŮŘŻ را به صŮرت follow قرار دهید. ([nofollow Ř®Ůدکار](http://en.wikipedia.org/wiki/Nofollow) حذ٠می‌شŮŘŻ) + * دسترسی به تالار های خصŮصی دسته‌بندی‌هایی که Ůقط به کاربران سطح 3 ٠بالاتر به آن دسترسی دارند + * Ů…Ř®ŮŰŚ سازی هرزنامه‌ با ŰŚÚ© پرچم - در اینجا [لیست Ůعلی کاربران ناظم](/badges/3/regular) را می‌تŮاند ببینید. مطمئن Ř´ŮŰŚŘŻ که سلام می‌کنید! + در اینجا [لیست Ůعلی کاربران ناظم](/badges/3/regular) را می‌تŮاند ببینید. مطمئن Ř´ŮŰŚŘŻ که سلام می‌کنید! - با تشکر از شما که بخش مهمی از این انجمن هستید! + با تشکر از شما که بخش مهمی از این انجمن هستید! - (برای اطلاعات بیشتر درباره سطŮŘ­ اعتماد [این Ů…ŮضŮŘą را ببینید][trust]. ŘŞŮجه داشته باشید که اعضا Ůقط در صŮرتی که شرایط را رعایت کنند در سطح اعتماد 3 باقی می‌مانند.) + (برای اطلاعات بیشتر درباره سطŮŘ­ اعتماد [این Ů…ŮضŮŘą را ببینید][trust]. ŘŞŮجه داشته باشید که اعضا Ůقط در صŮرتی که شرایط را رعایت کنند در سطح اعتماد 3 باقی می‌مانند.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "در رابطه با دسته‌بندی %{category}" replace_paragraph: "(به جای پاراگرا٠اŮŮ„ ŘŞŮضیحات Ú©Ůتاهی درباره دسته جدیدتان قرار بدهید. این راهنما در قسمت دسته ها نمایش داده Ů…ŰŚ Ř´Ůد٬ پس سعی کنید آن را زیر 200 کلمه نگاه دارید. **قبل از اینکه این متن را Ůیرایش کنید یا Ů…ŮضŮŘą جدیدی بŮجŮŘŻ بیاŮرید٬ این دسته در صŮحه دسته ها نمایش داده نمی Ř´ŮŘŻ.**)" @@ -546,8 +544,6 @@ fa_IR: long_form: 'به عنŮان نامناسب علامت گذاری شده' notify_user: title: 'Ůرستادن پیام به @{{username}}' - description: 'من می‌خŮاهم با این شخص به صŮرت مستقیم ٠محرمانه درباره نŮشته‌اش صحبت کنم.' - short_description: 'می‌خŮاهم با این شخص به صŮرت خصŮصی درباره‌ی نŮشته‌اش صحبت کنم.' long_form: 'به کاربر پیام داده Ř´ŘŻ' email_title: 'نŮشته ŰŚ شما در " %{title} "' email_body: "%{link}\n\n%{message}" @@ -801,7 +797,6 @@ fa_IR: poll_pop3_auth_error: "اتصال به سرŮر POP3 نامŮŮŮ‚ بŮŘŻŘŚ خطای اعتبار سنجی. لطŮا تنظیمات POP3 را بررسی کنید." site_settings: censored_words: "کلماتی که به صŮرت Ř®Ůدکار جایگزین Ů…ŰŚ Ř´Ůند با ■■■■" - censored_pattern: "الگŮŰŚ Regex به صŮرت Ř®Ůدکار با ■■■■ جا به جا می‌شŮŘŻ." delete_old_hidden_posts: "پاک کردن Ř®Ůدکار تمام نŮشته‌هایی که بیش از 30 رŮز بصŮرت پنهان باقی می‌مانند. " allow_user_locale: "اجازه انتخاب زبان به کاربران داده Ř´ŮŘŻ." set_locale_from_accept_language_header: "تنظیم زبان برای رابط کاربری کاربران ناشناس که از هدر مرŮرگر دریاŮŘŞ می‌شŮŘŻ. (تجربی، با Ú©Ř´ ناشناس کار نمی‌کند)" @@ -943,8 +938,6 @@ fa_IR: google_oauth2_client_id: "Client ID برای نرم اŮزار ÚŻŮÚŻŮ„ شما. " google_oauth2_client_secret: "Client secret برای بر نرم اŮزار ÚŻŮÚŻŮ„ شما. " enable_twitter_logins: "احراز هŮŰŚŘŞ ŘŞŰŚŮتر را Ůعال کن، به twitter_consumer_key Ů twitter_consumer_secret‌ نیاز است " - twitter_consumer_key: "Consumer key برای احراز هŮŰŚŘŞ در ŘŞŮییتر، که در http://dev.twitter.com ایجاد شده" - twitter_consumer_secret: "Consumer secret کننده برای احراز هŮŰŚŘŞ ŘŞŮییتر،‌ که در http://dev.twitter.com ایجاد شده" enable_instagram_logins: "Ůعالسازی ŮرŮŘŻ با اینستاگرام، نیازمند \ninstagram_consumer_key and instagram_consumer_secret" instagram_consumer_key: "Consumer key اینستاگرام" instagram_consumer_secret: "Consumer secret اینستاگرام" @@ -959,7 +952,6 @@ fa_IR: allow_restore: "اجازه بازیابی بده، که می‌تŮاند تمام اطلاعات سایت را جایگزین کند! نادرست را رها کن مگر اینکه قصد بازیابی نسخه پشتیبان را داری " maximum_backups: "حداکثر تعداد پشتیبان برای نگه داری بر رŮŰŚ ŘŻŰŚŘłÚ©. نسخه های پشتیبان قدیمی بطŮر Ř®Ůدکار پاک شده است" automatic_backups_enabled: "Ůعالسازی پشتیبان‌گیری Ř®Ůدکار در بازه زمانی مشخص" - backup_frequency: "هر چند Ůقت ŰŚÚ© بار نسخه پشتیبان میگیریم، به رŮز." enable_s3_backups: "نسخه پشتیبان را Ůقتی کامل Ř´ŘŻ به S3 بارگزاری کن. مهم: نیازمند اطلاعات ŮرŮŘŻ صحیح به S3 در تنظیمات Ůایل می‌باشد." s3_backup_bucket: "میزبان راه‌دŮر برای نگهداری از نسخه‌های پشتیبان. اخطار: مطمئن Ř´ŮŰŚŘŻ که میزبان خصŮصی است. " s3_disable_cleanup: "غیر‌Ůعال‌سازی حذ٠نسخه پشتیبان از S3 Ůقتی در هاست حذ٠می‌شŮŘŻ." @@ -1460,7 +1452,6 @@ fa_IR: test_mailer: title: "تست نامه‌رسان" subject_template: "[%{email_prefix}] تست ŘŞŘ­ŮŰŚŮ„ ایمیل" - text_body_template: "این ŰŚÚ© Ůرم ایمیل تست است\n\n[**%{base_url}**][0]\n\n-\t-مطمئن- Ř´ŮŰŚŘŻ که مقدار `notification email` از: را به درستی در تنظیمات سایت Ůارد کنید. **دامنه مشخص شده در “Ůرم” با ایمیل استŮاده شده برای ارسال معتبر‌سازی می‌شŮŘŻ.**\n-\tاز نحŮه نمایش ŘłŮرس ایمیل در کلاینت ایمیل Ř®ŮŘŻ مطلع Ř´ŮŰŚŘŻŘŚ بنابراین می‌تŮانید سربرگ‌های ایمیل را برای کار‌های مهم بررسی کنید. در جیمیل این گزینه “show original” در من٠کشŮŰŚŰŚ بالا سمت راست هر ایمیل است.\n-\t**مهم:** آیا ISP شما از Reverse DNS Ůارد شده در ارتباط با دامنه ٠آیپی شما که با ایمیل ارسال می‌شŮŘŻ پشتیبانی می‌کند؟ بررسی رکŮرد [Reverse PTD][2] اگر ISP شما مقدار reverse DNS pointer را به درستی Ůارد نکند، ایمیل‌های شما به احتمال زیاد ارسال نمی‌شŮند.\n-\tآیا [SPF record][8] دامنه شما صحیح است؟ [بررسی SPF record][1]. ŘŞŮجه داشته باشید که TXT مقدار رسمی record type برای SPF است.\n-\t آیا [DKIM record][3] دامنه شما صحیح است؟ این گزینه ارسال شدن ایمیل را به Ř´ŘŻŘŞ بهبŮŘŻ می‌بخشد. . [بررسی DKIM record][7]\n-\tاگر سرŮر ایمیل Ř®ŮŘŻ شخصی دارید، مطمئن Ř´ŮŰŚŘŻ که آیپی سرŮر ایمیل شما [در هیچ لیست سیاهی نیستند][4]. همچنین تایید کنید که ارسال ایمیل با نام کامل میزبان در ارسال پیام HELO انجام می‌شŮŘŻ. اگر نشŮŘŻŘŚ ŘŞŮسط سرŮر‌های ایمیل بسیاری رد می‌شŮŘŻ.\n-\tŘŞŮصیه می‌کنیم **ŰŚÚ© ایمیل تست به [mail-tester.com][mt] بŮرستید ** تا کارکرد تنظیمات بالا را تایید کنید.\n\n(راه *ساده* این است که ŰŚÚ© حساب‌کاربری رایگان در [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] یا [Mailjet][mj] بسازید، که امکان ارسال ایمیل برای انجمن‌های Ú©ŮÚ†Ú© را دارند. همچنان نیاز است که مقدار SPF Ů DKIM را در تنظیمات DNS Ř®ŮŘŻ Ůارد کنید!)\nامیدŮاریم تست ایمیل بدŮن Ů…Ř´Ú©Ů„ باشد\n\nŘŻŮستان شما در [Discourse](http://www.discourse.org)\n[0]: %{base_url}\n[1]: http://www.kitterman.com/spf/validate.html\n[2]: http://mxtoolbox.com/ReverseLookup.aspx\n[3]: http://www.dkim.org/\n[4]: http://whatismyipaddress.com/blacklist-check\n[7]: https://www.mail-tester.com/spf-dkim-check\n[8]: http://www.openspf.org/SPF_Record_Syntax\n[sg]: https://goo.gl/r1WMF6\n[sp]: https://www.sparkpost.com/\n[mg]: http://www.mailgun.com/\n[mj]: https://www.mailjet.com/pricing\n[mt]: http://www.mail-tester.com/\n" new_version_mailer: title: "نسخه جدید نامه‌رسان" subject_template: "[%{email_prefix}] نسخه جدید Discourse Ů…ŮجŮŘŻ است" @@ -1880,7 +1871,7 @@ fa_IR: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} account_suspended: title: "حساب کاربری معلق Ř´ŘŻ" subject_template: "[%{email_prefix}] حساب کاربری شما معلق شده است" diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index 3031f5181a..3520f05dec 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -82,7 +82,6 @@ fi: invalid: ei kelpaa is_invalid: "vaikuttaa epäselvältä, olihan se kokonainen virke?" contains_censored_words: "sisältää nämä sensuroidut sanat: %{censored_words}" - matches_censored_pattern: "sisältää nämä sivustolla sensuroidun säännöllisen lausekkeen täyttävät sanat: %{censored_words}" less_than: täytyy olla vähemmän kuin %{count} less_than_or_equal_to: täytyy olla yhtä suuri tai pienempi kuin %{count} not_a_number: ei ole numero @@ -105,7 +104,7 @@ fi: template: body: 'Seuraavien kenttien kanssa oli ongelmia:' header: - one: 1 virhe esti tallentamasta tätä %{model} + one: '1 virhe esti tallentamasta tätä %{model}' other: '%{count} virhettä esti tallentamasta tätä %{model}' embed: load_from_remote: "Viestin lataamisessa tapahtui virhe." @@ -177,7 +176,6 @@ fi: too_many_links: one: "Pahoittelut, uudet käyttäjät voivat laittaa vain yhden linkin viestiin." other: "Pahoittelut, uusi käyttäjä voi laittaa vain %{count} linkkiä viestiin." - contains_blocked_words: "Viestissäsi on kielletty sanoja." spamming_host: "Pahoittelut, linkit tuolle sivulle eivät ole sallittuja." user_is_suspended: "Hyllytetyt käyttäjät eivät saa luoda viestejä." topic_not_found: "Jotain on mennyt pieleen. Ehkä tämä ketju on suljettu tai poistettu sillä välin, kun katselit sitä?" @@ -380,41 +378,41 @@ fi: title: "Tervetuloa käyttämään Discoursea" body: |2 - Tämän kiinnitetyn ketjun ensimmäinen kappale on sivustolla vierailevalle näkyvä tervetulotoivotus. Se on tärkeä! + Tämän kiinnitetyn ketjun ensimmäinen kappale on sivustolla vierailevalle näkyvä tervetulotoivotus. Se on tärkeä! - **Muokkaa tästä** tiivis kuvaus yhteisöstäsi: + **Muokkaa tästä** tiivis kuvaus yhteisöstäsi: - - Kenelle se on suunnattu? - - Mistä täällä keskustellaan? - - Miksi heidän kannattaa käydä täällä? - - Mistä he löytävät lisätietoa (linkkejä, ohjeita jne.)? + - Kenelle se on suunnattu? + - Mistä täällä keskustellaan? + - Miksi heidän kannattaa käydä täällä? + - Mistä he löytävät lisätietoa (linkkejä, ohjeita jne.)? - + - Saattaa olla hyvä sulkea tämä ketju ylläpitäjän :wrench: -valikon kautta (oikealla ylhäällä sekä sivun pohjalla), jottei vastauksia kasaannu ilmoituksen alle. + Saattaa olla hyvä sulkea tämä ketju ylläpitäjän :wrench: -valikon kautta (oikealla ylhäällä sekä sivun pohjalla), jottei vastauksia kasaannu ilmoituksen alle. lounge_welcome: title: "Tervetuloa Loungeen" body: |2 - Onnittelut! :confetti_ball: + Onnittelut! :confetti_ball: - Jos näet tämän ketjun, sinut on juuri ylennetty **Mestariksi** (luottamustaso 3). + Jos näet tämän ketjun, sinut on juuri ylennetty **Mestariksi** (luottamustaso 3). - Voit nyt… + Voit nyt… - * Muokata minkä hyvänsä ketjun otsikkoa - * Siirtää minkä tahansa ketjun toiselle alueelle - * Lisätä linkkejä, joita hakukoneet seuraavat ([nofollow](http://en.wikipedia.org/wiki/Nofollow) on poistettu) - * Päästä suljetulle Lounge-alueelle, joka näkyy vain luottamustason 3 ja korkeammille käyttäjille - * Piilottaa roskapostiviestin yhdellä liputuksella + * Muokata minkä hyvänsä ketjun otsikkoa + * Siirtää minkä tahansa ketjun toiselle alueelle + * Lisätä linkkejä, joita hakukoneet seuraavat ([nofollow](http://en.wikipedia.org/wiki/Nofollow) on poistettu) + * Päästä suljetulle Lounge-alueelle, joka näkyy vain luottamustason 3 ja korkeammille käyttäjille + * Piilottaa roskapostiviestin yhdellä liputuksella - Tässä on nykyinen [lista mestareista](/badges/3/leader). Muista käydä moikkaamassa. + Tässä on nykyinen [lista mestareista](/badges/3/leader). Muista käydä moikkaamassa. - Kiitos, että olet tämän yhteisön arvokas jäsen! + Kiitos, että olet tämän yhteisön arvokas jäsen! - (Lisää tietoa luottamustasoista saat [tästä englanninkielisestä ketjusta][trust]. Huomaa, että pysyäksesi mestarina sinun pitää täyttää vaatimukset jatkossakin.) + (Lisää tietoa luottamustasoista saat [tästä englanninkielisestä ketjusta][trust]. Huomaa, että pysyäksesi mestarina sinun pitää täyttää vaatimukset jatkossakin.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Alueesta %{category}" replace_paragraph: "(Korvaa tämä kappale lyhyellä kuvauksella uudesta alueesta. Tämä kuvaus näytetään alueen valinnan yhteydessä, joten yritä pitää se alle 200 merkin pituisena. **Aluetta ei näytetä Keskustelualueet-sivulla ennen kuin olet muokannut tätä tekstiä tai aloittanut viestiketjuja.**)" @@ -600,8 +598,6 @@ fi: long_form: 'liputti tämän sopimattomaksi' notify_user: title: 'Lähetä käyttäjälle @{{username}} viesti.' - description: 'Haluan keskustella viestistä kirjoittajan kanssa kahden kesken.' - short_description: 'Haluan keskustella viestistä kirjoittajan kanssa suoraan kahden kesken.' long_form: 'lähetä käyttäjälle viesti' email_title: 'Viestisi ketjussa "%{title}"' email_body: "%{link}\n\n%{message}" @@ -860,7 +856,6 @@ fi: poll_pop3_auth_error: "Yhteys POP3-palvelimelle epäonnistuu autentikaatiovirheen vuoksi. Tarkista POP3-asetukset." site_settings: censored_words: "Sanat, jotka korvataan automaattisesti merkeillä ■■■■" - censored_pattern: "Säännöllinen lauseke, joka korvataan automaattisesti ■■■■" delete_old_hidden_posts: "Poista automaattisesti kaikki yli 30 päivää piilotettuna olleet viestit." allow_user_locale: "Salli käyttäjien vaihtaa käyttöliittymän kieli omista asetuksista" set_locale_from_accept_language_header: "Aseta sivuston kieli kirjautumattomille käyttäjille selaimen kielivalinnan perusteella. (KOKEELLINEN, ei toimi anonyymin välimuistin kanssa)" @@ -1017,8 +1012,6 @@ fi: google_oauth2_client_id: "Google-applikaatiosi Client ID." google_oauth2_client_secret: "Google-applikaatiosi Client secret." enable_twitter_logins: "Ota käyttöön Twitter kirjautuminen, vaatimuksena twitter_consumer_key ja twitter_consumer_secret" - twitter_consumer_key: "Consumer key Twitter autentikaatioon, rekisteröinti osoitteessa http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret key Twitter autentikaatioon, rekisteröinti osoitteessa http://dev.twitter.com" enable_instagram_logins: "Ota Instagram-autentikaatio käyttöön, vaatimuksena instagram_consumer_key ja instagram_consumer_secret" instagram_consumer_key: "Consumer key Instagram-autentikaatioon" instagram_consumer_secret: "Consumer secret Instagram-autentikaatioon" @@ -1034,7 +1027,6 @@ fi: allow_restore: "Salli palautus, joka korvaa KAIKEN sivuston datan! Jätä valitsematta, jos et aio palauttaa sivuston varmuuskopiota" maximum_backups: "Tallennettuna pidettävien varmuuskopioiden maksimimäärä. Vanhemmat varmuuskopiot poistetaan automaattisesti" automatic_backups_enabled: "Tee automaattinen varmuuskopiointi, kuten tiheysasetus on määritelty" - backup_frequency: "Kuinka usein luodaan sivuston varmuuskopio, päivissä." enable_s3_backups: "Lataa varmuuskopiot S3:een niiden valmistuttua. TĂ„RKEÄÄ: edellyttää, että toimivat S3 kirjautumistiedot on syötetty asetuksiin." s3_backup_bucket: "Amazon S3 bucket johon varmuuskopiot ladataan. VAROITUS: Varmista, että se on yksityinen." s3_disable_cleanup: "Ă„lä poista varmuuskopiota S3:sta, kun se poistetaan paikallisesti." @@ -1639,47 +1631,6 @@ fi: test_mailer: title: "Sähköpostipalvelun testaus" subject_template: "[%{email_prefix}] Sähköpostin toimitettavuustesti" - text_body_template: | - Tämä on testisähköposti osoitteesta - - [**%{base_url}**][0] - - Sähköpostien toimittaminen on monimutkaista. Tässä joitakin tärkeitä seikkoja, jotka sinun tulisi tarkistaa ensimmäisenä: - - - *Varmista*, että `ilmoitusten sähköpostiosoitteen` from: kenttä on asetettu oikein sivuston asetuksissa. **Sähköpostin validointi tapahtuu "from"-kentän sähköpostiosoitteen verkkotunnusta vastaan**. - - - Selvitä, miten sähköpostiohjelmassasi saa näkyville sähköpostin raakadatan, jotta voit tutkia viestin tunnistetiedot. Gmailissa tämä tapahtuu "näytä kokonaan" valinnalla viestin oikeassa yläkulmassa olevasta valikosta. - - - **TĂ„RKEÄÄ:** Onko palveluntarjoajallasi syötettynä reverse DNS -kenttä verkkotunnuksille ja IP-osoitteille, joista lähetät sähköpostia? [Kokeile Reverse PTR -tietuetta][2] täällä. Jos palveluntarjoajasi ei tarjoa kunnollista reverse DNS pointer -tietuetta, on hyvin epätodennäköistä, että mikään sähköpostisi menee perille. - - - Onko verkkotunnuksesi [SPF-tietue][8] oikein? [Kokeile SPF-tietuetta][1] täällä. Huomaa, että TXT on oikea tietuetyyppi SPF-tietueelle. - - - Onko verkkotunnuksesi [DKIM-tietue][3] oikein? Tämä parantaa merkittävästi sähköpostin toimitettavuutta. [Kokeile DKIM-tietuetta][7] täällä. - - - Jos käytät omaa sähköpostipalvelinta, tarkista että palvelimen IP-osoite [ei ole millään sähköpostin mustalla listalla][4]. Tarkista myös, että se varmasti lähettää oikean muotoisen isäntänimen HELO-viestissään, joka palauttaa oikean DNS-tietueen. Jos näin ei ole, monet sähköpostipalvelut hylkäävät viestin. - - - Suosittelemme vahvasti, että **lähetät testisähköpostin osoitteeseen [mail-tester.com][mt]** varmistaaksesi, että kaikki yllä mainittu toimii kunnolla. - - (*Helpoin* tapa on luoda ilmainen tili [Sendgridiin][md], [SparkPostiin][sp][Mailguniin][mg] tai [Mailjetiin][mj], joiden ilmaiset palvelut riittävät hyvin pienen yhteisön käyttöön. Muista silti asettaa SPF- ja DKIM-tietueet DNS:ään!) - - Toivottavasti tämä testisähköposti saapui perille OK! - - Onnea matkaan, - - Ystäväsi [Discourselta](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "Uusi versio" subject_template: "[%{email_prefix}] Uusi Discourse-versio, päivitys saatavilla" @@ -2316,7 +2267,7 @@ fi: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} account_suspended: title: "Tili hyllytetty" subject_template: "[%{email_prefix}] Tilisi on hyllytetty" diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index 3d4fd7cb83..b5e8979404 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -29,6 +29,7 @@ fr: loading: "Chargement" powered_by_html: 'PropulsĂ© par Discourse, nĂ©cessite l''activation de JavaScript ' log_in: "Se connecter" + submit: "Envoyer" purge_reason: "SupprimĂ© automatiquement comme compte abandonnĂ© et non activĂ©" disable_remote_images_download_reason: "Le tĂ©lĂ©chargement des images externes a Ă©tĂ© dĂ©sactivĂ© faute de place suffisante sur le disque." anonymous: "Anonyme" @@ -41,6 +42,8 @@ fr: default_subject: "Ce sujet a besoin d'un titre" show_trimmed_content: "Montrer le contenu raccourci" maximum_staged_user_per_email_reached: "Vous avez atteint le nombre maximal d'utilisateurs qui peuvent ĂŞtre créés par courriel." + no_subject: "(aucun objet)" + no_body: "(aucun texte)" errors: empty_email_error: "Se produit quand le courriel reçu Ă©tait vide." no_message_id_error: "Se produit quand le courriel n'a pas d'entĂŞte \"Message-Id\"." @@ -82,13 +85,13 @@ fr: invalid: est invalide is_invalid: "ne semble pas clair, est-ce une phrase complète ?" contains_censored_words: "contient les mots censurĂ©s suivants : %{censored_words}" - matches_censored_pattern: "contient les mots censurĂ©s suivants qui correspondent Ă  l'expression rĂ©gulière : %{censored_words}" less_than: doit ĂŞtre infĂ©rieure Ă  %{count} less_than_or_equal_to: doit ĂŞtre infĂ©rieur ou Ă©gal Ă  %{count} not_a_number: n'est pas un nombre not_an_integer: doit ĂŞtre un entier odd: doit ĂŞtre impair record_invalid: 'Validation Ă©chouĂ©e : %{errors}' + max_emojis: "limitĂ© Ă  %{max_emojis_count}emoji" restrict_dependent_destroy: one: "Impossible de supprimer l'enregistrement car certains %{record} existent et en dĂ©pendent" many: "Impossible de supprimer l'enregistrement car un %{record} existe et en dĂ©pend" @@ -105,7 +108,7 @@ fr: template: body: 'Des problèmes ont Ă©tĂ© rencontrĂ©s sur les champs suivants : ' header: - one: Une erreur empĂŞche ce %{model} d'ĂŞtre sauvegardĂ© + one: 'Une erreur empĂŞche ce %{model} d''ĂŞtre sauvegardĂ©' other: '%{count} erreurs empĂŞchent ce %{model} d''ĂŞtre sauvegardĂ©' embed: load_from_remote: "Il y a eu une erreur lors du chargement de ce message." @@ -116,6 +119,7 @@ fr: max_username_length_range: "Il n'est pas possible de dĂ©finir un maximum plus petit que le minimum." default_categories_already_selected: "Vous ne pouvez pas sĂ©lĂ©ctionner une catĂ©gorie qui est utilisĂ©e dans une autre liste." s3_upload_bucket_is_required: "Vous ne pouvez pas activer l'upload sur S3 avant d'avoir renseignĂ© le 's3_upload_bucket'." + conflicting_google_user_id: 'Le Google Account ID a changĂ© pour ce compte ; un responsable doit intervenir pour des raisons de sĂ©curitĂ©. Merci de contacter les responsables et les renvoyer vers
https://meta.discourse.org/t/76575' invite: not_found: "Votre jeton d'invitation est invalide. Veuillez contacter l'administrateur du site." user_exists: "Il n'y a pas besoin d'inviter %{email} qui a dĂ©jĂ  un compte !" @@ -129,6 +133,7 @@ fr: backup_file_should_be_tar_gz: "Le fichier de sauvegarde doit ĂŞtre une archive .tar.gz." not_enough_space_on_disk: "Il n'y a pas assez d'espace sur le disque pour charger cette sauvegarde." invalid_filename: "Le nom du fichier de sauvegarde contient des caractères invalides. Sont autorisĂ©s : a-z 0-9 . - _." + invalid_params: "Vous avez fourni des paramètres invalides pour la requĂŞte: %{message}" not_logged_in: "Vous devez ĂŞtre connectĂ© pour faire cela." not_found: "L'URL ou ressource demandĂ©e n'a pas Ă©tĂ© retrouvĂ©." invalid_access: "Vous n'avez pas la permission de voir cette ressource." @@ -165,6 +170,7 @@ fr: too_many_mentions_newuser: one: "DĂ©solĂ©, les nouveaux utilisateurs ne peuvent mentionner qu'un utilisateur dans un message." other: "DĂ©solĂ©, les nouveaux utilisateurs ne peuvent mentionner que %{count} utilisateurs dans un message." + no_images_allowed_trust: "DĂ©sole, il ne vous est pas possible d'inclure des images dans un message" no_images_allowed: "DĂ©solĂ©, les nouveaux utilisateurs ne peuvent pas ajouter d'image dans les messages." too_many_images: one: "DĂ©solĂ©, les nouveaux utilisateurs ne peuvent ajouter qu'une image dans un message." @@ -174,10 +180,11 @@ fr: one: "DĂ©solĂ©, les nouveaux utilisateurs ne peuvent ajouter qu'un fichier dans leurs messages." other: "DĂ©solĂ©, les nouveaux utilisateurs ne peuvent ajouter que %{count} fichiers dans leurs messages." no_links_allowed: "DĂ©solĂ©, les nouveaux utilisateurs ne peuvent pas insĂ©rer de liens dans leurs messages." + links_require_trust: "DĂ©solĂ©, il ne vous est pas possible d'inclure des liens dans vos messages." too_many_links: one: "DĂ©solĂ©, les nouveaux utilisateurs ne peuvent insĂ©rer qu'un seul lien par message." other: "DĂ©solĂ©, les nouveaux utilisateurs ne peuvent insĂ©rer que %{count} liens par message." - contains_blocked_words: "Votre message contient des mots non autorisĂ©s." + contains_blocked_words: "Votre message contient un mot qui n'est pas permis : %{word}" spamming_host: "DĂ©solĂ©, vous ne pouvez pas insĂ©rer de lien vers ce domaine." user_is_suspended: "Les utilisateurs suspendus ne sont pas autorisĂ©s Ă  poster de messages." topic_not_found: "Une erreur est survenue. Peut-ĂŞtre que ce sujet a Ă©tĂ© fermĂ© ou supprimĂ© pendant que vous le regardiez ?" @@ -209,6 +216,7 @@ fr: top_weekly: "Meilleurs sujets de la semaine" top_daily: "Meilleurs sujets du jour" posts: "Messages rĂ©cents" + private_posts: "Derniers messages personnels" group_posts: "Derniers messages de %{group_name}" group_mentions: "Dernières mentions de %{group_name}" user_posts: "Derniers messages par @%{username}" @@ -304,6 +312,12 @@ fr: Nous sommes dĂ©solĂ©s, les nouveaux membres sont limitĂ©s temporairement Ă  %{newuser_max_replies_per_topic} rĂ©ponses dans un mĂŞme topic. PlutĂ´t que d'ajouter une nouvelle rĂ©ponse, vous pouvez Ă©diter vos messages prĂ©cĂ©dents ou visiter d'autres topics. + reviving_old_topic: | + ### Relancer ce sujet ? + + La dernière rĂ©ponse Ă  ce sujet Ă©tait **%{time_ago}**. Votre rĂ©ponse remettre ce sujet en tĂŞte de liste et provoquera une notification Ă  tous les intervenants qui y ont pris part auparavant. + + Etes vous sĂ»r de vouloir continuer cette vieille conversation ? activerecord: attributes: category: @@ -320,9 +334,12 @@ fr: topic: attributes: base: + warning_requires_pm: "Vous ne pouvez associer des avertissements qu'Ă  des messages personnels." too_many_users: "Vous ne pouvez envoyer des avertissements qu'Ă  un utilisateur Ă  la fois." + cant_send_pm: "DĂ©solĂ©, vous ne pouvez pas envoyer un message personnel Ă  cet utilisateur." no_user_selected: "Vous devez choisir un utilisateur valide." reply_by_email_disabled: "RĂ©ponse par courriel dĂ©sactivĂ©e." + target_user_not_found: "Un des utilisateurs Ă  lequel vous envoyez ce message n'a pas Ă©tĂ© retrouvĂ©." featured_link: invalid: "est invalide. L'URL doit inclure http:// ou https://." invalid_category: "ne peut pas ĂŞtre modifiĂ© dans cette catĂ©gorie." @@ -336,6 +353,10 @@ fr: unique_characters: "a trop de caractères rĂ©pĂ©tĂ©s. Veuillez utiliser un mot de passe plus sĂ©curisĂ©." ip_address: signup_not_allowed: "L'inscription n'est pas autorisĂ©e Ă  partir de ce compte." + user_email: + attributes: + user_id: + reassigning_primary_email: "Il n'est pas permis de rĂ©assigner une adresse courriel Ă  un autre utilisateur." color_scheme_color: attributes: hex: @@ -379,46 +400,47 @@ fr: title: "Bienvenue sur Discourse" body: |2 - Le premier paragraphe de ce sujet Ă©pinglĂ© sera visible comme message de bienvenue Ă  tous les nouveaux visiteurs de votre page d'accueil. Il est important ! + Le premier paragraphe de ce sujet Ă©pinglĂ© sera visible comme message de bienvenue Ă  tous les nouveaux visiteurs de votre page d'accueil. Il est important ! - **Modifier le** en une brève description de votre communautĂ© : + **Modifier le** en une brève description de votre communautĂ© : - - Pour qui est-elle destinĂ©e ? - - Que peuvent-ils y trouver ? - - Pourquoi doivent-ils venir ici ? - - OĂą peuvent-ils en lire plus (liens, ressources, etc.) ? + - Pour qui est-elle destinĂ©e ? + - Que peuvent-ils y trouver ? + - Pourquoi doivent-ils venir ici ? + - OĂą peuvent-ils en lire plus (liens, ressources, etc.) ? - + - Vous voulez peut-ĂŞtre fermer ce sujet via l'administration :wrench: (dans le coin supĂ©rieur droit et le bas) afin que des rĂ©ponses ne s'accumulent pas après une annonce. + Vous voulez peut-ĂŞtre fermer ce sujet via l'administration :wrench: (dans le coin supĂ©rieur droit et le bas) afin que des rĂ©ponses ne s'accumulent pas après une annonce. lounge_welcome: title: "Bienvenue dans le salon" body: |2 - FĂ©licitation ! :confetti_ball: + FĂ©licitation ! :confetti_ball: - Si vous voyez ce sujet, c'est que vous avez rĂ©cemment Ă©tĂ© promu au niveau de confiance **habituĂ©** (niveau de confiance 3). + Si vous voyez ce sujet, c'est que vous avez rĂ©cemment Ă©tĂ© promu au niveau de confiance **habituĂ©** (niveau de confiance 3). - Vous pouvez dĂ©sormais … + Vous pouvez dĂ©sormais … - * Modifier le titre de tous les sujets - * Modifier la catĂ©gorie de tous les sujets - * Avoir vos liens suivis ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) est retirĂ©) - * Accès Ă  un salon privĂ© visible uniquement pour les membres de niveau de confiance 3 ou plus. - * Masquer un message indĂ©sirable avec seulement un drapeau. + * Modifier le titre de tous les sujets + * Modifier la catĂ©gorie de tous les sujets + * Avoir vos liens suivis ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) est retirĂ©) + * Accès Ă  un salon privĂ© visible uniquement pour les membres de niveau de confiance 3 ou plus. + * Masquer un message indĂ©sirable avec seulement un drapeau. - Voici la [liste courante des membres habituĂ©s](/badges/3/regular). N'hĂ©sitez pas Ă  venir dire bonjour ! + Voici la [liste courante des membres habituĂ©s](/badges/3/regular). N'hĂ©sitez pas Ă  venir dire bonjour ! - Merci d'ĂŞtre une part importante de cette communautĂ© ! + Merci d'ĂŞtre une part importante de cette communautĂ© ! - (Pour plus d'information sur les niveaux de confiance [voir ce sujet][trust]. Veuillez noter que seuls les membres qui continuerons de respecter les critères garderons ce niveau de confiance.) + (Pour plus d'information sur les niveaux de confiance [voir ce sujet][trust]. Veuillez noter que seuls les membres qui continuerons de respecter les critères garderons ce niveau de confiance.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Ă€ propos de la catĂ©gorie %{category}" replace_paragraph: "(Remplacez ce premier paragraphe par une brève description de votre nouvelle catĂ©gorie. Ce guide apparaĂ®tra dans la zone de sĂ©lection de la catĂ©gorie, alors essayez de rester en dessous de 200 caractères. **Cette catĂ©gorie n'apparaĂ®tra pas sur la page des catĂ©gories jusqu'Ă  ce que vous ayez modifiĂ© cette description ou créé des sujets.**" post_template: "%{replace_paragraph}\n\nUtilisez les paragraphes suivants pour une plus longue description ou pour Ă©tablir des règles :\n\n- Ă€ quoi sert cette catĂ©gorie ? Pourquoi les utilisateurs choisiraient-ils cette catĂ©gorie pour leur discussion ?\n\n- En quoi est-elle diffĂ©rente des autres catĂ©gories existantes ?\n\n- Quels sujets devraient figurer dans cette catĂ©gorie ?\n\n- Avons-nous besoin de cette catĂ©gorie ? Devrions-nous fusionner celle-ci avec une autre catĂ©gorie ?\n" errors: + not_found: "CatĂ©gorie introuvĂ©e !" uncategorized_parent: "Sans catĂ©gorie ne peut pas avoir de parent" self_parent: "Le parent d'une sous-catĂ©gorie ne peut pas ĂŞtre elle-mĂŞme" depth: "Vous ne pouvez pas imbriquer une sous-catĂ©gorie sous une autre" @@ -451,6 +473,7 @@ fr: broken: "Cette image ne fonctionne pas" rate_limiter: slow_down: "Vous avez rĂ©alisĂ© cette action un trop grand nombre de fois, essayez Ă  nouveau plus tard." + too_many_requests: "Vous avez effectuĂ© cette action trop de fois. Veuillez attendre %{time_left}avant de ressayer." by_type: first_day_replies_per_day: "Vous avez atteint le nombre maximum de rĂ©ponses qu'un nouvel utilisateur peut crĂ©er pour son premier jour. Patientez s'il vous plaĂ®t %{time_left} avant d'essayer Ă  nouveau." first_day_topics_per_day: "Vous avez atteint le nombre maximum de sujets qu'un nouvel utilisateur peut crĂ©er pour son premier jour. Patientez s'il vous plaĂ®t %{time_left} avant d'essayer Ă  nouveau." @@ -555,6 +578,9 @@ fr: title: 'RĂ©initialiser le mot de passe' success: "Vous avez modifiĂ© votre mot de passe avec succès et vous ĂŞtes maintenant connectĂ©." success_unapproved: "Vous avez modifiĂ© votre mot de passe avec succès." + email_login: + invalid_token: "DĂ©solĂ©, ce lien de connection courriel est trop vieux. SĂ©lĂ©ctionner le bouton 'Se connecter' et utiliser 'Mot de passe oubliĂ©' pour obtenir un nouveau lien." + title: "Connection courriel" change_email: confirmed: "Votre adresse de courriel a Ă©tĂ© mise Ă  jour." please_continue: "Continuer vers %{site_name}" @@ -599,8 +625,8 @@ fr: long_form: 'signalĂ© comme inappropriĂ©' notify_user: title: 'Envoyer un message Ă  @{{username}} ' - description: 'Je veux parler Ă  cette personne directement et de manière privĂ©e Ă  propos de son message.' - short_description: 'Je veux parler Ă  cette personne directement et de manière privĂ©e au sujet de son message' + description: 'J''aimerais parler Ă  cette personne directement et personnellement au sujet de leur message.' + short_description: 'J''aimerais parler Ă  cette personne directement et personnellement au sujet de leur message.' long_form: 'utilisateur contactĂ©' email_title: 'Votre message sur « %{title} »' email_body: "%{link}\n\n%{message}" @@ -854,22 +880,28 @@ fr: email_polling_errored_recently: one: "La vĂ©rification des courriels a gĂ©nĂ©rĂ© une erreur au cours des 24 dernières heures. VĂ©rifiez le journal pour plus de dĂ©tails." other: "La vĂ©rification des courriels a gĂ©nĂ©rĂ© %{count} erreurs au cours des 24 dernières heures. VĂ©rifiez le journal pour plus de dĂ©tails." + missing_mailgun_api_key: "Le serveur est configurĂ© pour envoyer des courriels via Mailgun mais vous n'avez pas fourni la clef API nĂ©cessaire pour vĂ©rifier les messages webhook." bad_favicon_url: "Impossible de charger la favicon. VĂ©rifiez le paramètre favicon_url dans les paramètres du site" poll_pop3_timeout: "La connexion vers le serveur POP3 a expirĂ©. Les courriels entrants n'ont pas pu ĂŞtre tĂ©lĂ©chargĂ©s. Veuillez vĂ©rifier les paramètres POP3 et votre fournisseur de service courriel." poll_pop3_auth_error: "La connexion vers le serveur POP3 Ă©choue avec une erreur d'authentification. Veuillez vĂ©rifier les paramètres POP3." + force_https_warning: "Votre site web utiliser le SSL. Mais `force_https` n'est pas encore activĂ© dans les paramètres du site." site_settings: censored_words: "Mots qui seront automatiquement remplacĂ©s par ■■■■" - censored_pattern: "Expressions rĂ©gulières qui seront automatiquement remplacĂ©es par ■■■■" delete_old_hidden_posts: "Supprimer automatiquement les messages cachĂ©s plus de 30 jours." + default_locale: "La langue par dĂ©faut de cette instance de Discourse" allow_user_locale: "Autoriser les utilisateurs Ă  choisir la langue de l'interface dans leurs prĂ©fĂ©rences" set_locale_from_accept_language_header: "configurer la langue de l'interface pour les visiteurs Ă  partir des entĂŞtes de langue de leur navigateur. (EXPÉRIMENTAL, ne fonctionne pas avec le cache anonyme)" + support_mixed_text_direction: "Permettre le mĂ©lange de textes gauche-droite et droite-gauche." min_post_length: "Longueur minimale autorisĂ©e des messages en nombre de caractères" min_first_post_length: "Longueur minimale d'un premier message (corps de sujet) en nombre de caractères" + min_personal_message_post_length: "Longueur minimale des messages en nombre de caractères" max_post_length: "Longueur maximale autorisĂ©e des messages en nombres de caractères" topic_featured_link_enabled: "Activer la crĂ©ation de sujets avec lien" show_topic_featured_link_in_digest: "Afficher les sujets avec lien dans le rĂ©sumĂ© par courriel." min_topic_title_length: "Longueur minimale autorisĂ©e des titres de sujet en nombre de caractères" max_topic_title_length: "Longueur maximale autorisĂ©e des titres de sujet en nombre de caractères" + min_personal_message_title_length: "Longueur minimale pour un titre de message en nombre de caractères" + max_emojis_in_title: "Nombre maximum d'emojis permis dans le titre d'un sujet" min_search_term_length: "Longueur minimale autorisĂ©e du texte saisie avant de lancer une recherche en nombre de caractères" search_tokenize_chinese_japanese_korean: "Forcer la tokenisation dans la recherche chinois/japonais/korĂ©en, mĂŞme sur des sites non-CJK." search_prefer_recent_posts: "Si la recherche dans votre forum est lente, cette option tente l'indĂ©xation des messages les plus rĂ©cents en premier" @@ -890,6 +922,7 @@ fr: download_remote_images_max_days_old: "Ne pas tĂ©lĂ©charger les images distantes pour les messages plus anciens que n jours." disabled_image_download_domains: "Les images distantes de ces domaines ne seront jamais tĂ©lĂ©chargĂ©es. Liste dĂ©limitĂ©e par des pipes (|)." editing_grace_period: "Pendant (n) secondes après la publication d'un message, la modification de ce dernier ne provoquera pas d'historisation." + staff_edit_locks_post: "Les messages seront vĂ©rouillĂ©s Ă  la modification si modifiĂ©s par des responsables" post_edit_time_limit: "L'auteur peut modifier ou supprimer ses messages pendant (n) minutes après leur publication. Mettre Ă  0 pour l'autoriser sans limite de temps." edit_history_visible_to_public: "Autoriser tout le monde Ă  voir les versions prĂ©cĂ©dentes d'un message modifiĂ©. Quand dĂ©sactivĂ©, seuls les responsables peuvent voir l'historique." delete_removed_posts_after: "Les messages retirĂ©s par leur auteur seront automatiquement supprimĂ©s après (n) heures. Pour une valeur renseignĂ©e Ă  0, les messages seront supprimĂ©s immĂ©diatement." @@ -905,22 +938,28 @@ fr: post_onebox_maxlength: "Longueur maximale d'un message emboĂ®tĂ© en nombre de caractères." onebox_domains_blacklist: "Une liste de domaines que ne seront jamais transformĂ©s en Onebox." inline_onebox_domains_whitelist: "Une liste de domaines qui seront transformĂ© en Onebox s'ils ont Ă©tĂ© liĂ©s sans titre" + enable_inline_onebox_on_all_domains: "Ignorer le paramètre inline_onebox_domain_whitelist et permettre des onebox 'inline' pour tous les domaines." max_oneboxes_per_post: "Nombre maximum de Onebox dans un message." logo_url: "L'image de votre logo situĂ© en haut Ă  gauche de votre site doit ĂŞtre de forme rectangulaire large. Si vous laissez ce champ vide, le nom de votre site apparaitra." digest_logo_url: "L'image alternative de votre logo utilisĂ©e en haut du rĂ©sumĂ© par courriel de votre site. Elle devrait idĂ©alement ĂŞtre en forme de large rectangle et ne pas ĂŞtre une image SVG. Si vide, `logo_url` sera utilisĂ©." logo_small_url: "Le petit logo situĂ© en haut Ă  gauche de votre site doit ĂŞtre de forme carrĂ©. Si vous laissez ce champ vide, un logo de maison apparaĂ®tra." favicon_url: "Un favicon pour votre site, voir http://fr.wikipedia.org/wiki/Favicon, pour fonctionner correctement Ă  travers un CDN il doit ĂŞtre PNG." + mobile_logo_url: "Logo personnalisĂ© utilisĂ© sur la version mobile de votre site. Si non renseignĂ©, `logo_url` sera utilisĂ©, par exemple : http://example.com/uploads/default/logo.png" large_icon_url: "Image utilisĂ©e comme logo sur Android. Taille recommandĂ©e : 512 px par 512 px." apple_touch_icon_url: "IcĂ´ne utilisĂ©e pour les appareils d'Apple. Taille recommandĂ©e 144 px par 144 px." notification_email: "L'adresse de courriel dans le champs De qui sera utilisĂ©e pour envoyer les courriels systèmes essentiels. Le nom de domaine spĂ©cifiĂ© doit avoir les informations SPF, DKIM et PTR inversĂ© renseignĂ©s correctement pour que le courriel arrive Ă  destination." email_custom_headers: "Une liste dĂ©limitĂ© par des (|) pipes d'entĂŞtes de courriel" email_subject: "Format du sujet personnalisable pour les courriels standards. Voir https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" force_https: "Forcer votre site en HTTPS uniquement. MISE EN GARDE : n'activez PAS cette fonction tant que vous n'avez pas vĂ©rifiĂ© que le HTTPS est complètement configurĂ© et fonctionne absolument partout ! Avez-vous vĂ©rifiĂ© que vos CDN, vos connexions via rĂ©seaux sociaux ainsi que tous les logos / dĂ©pendances tiers sont tous compatibles HTTPS eux aussi ?" + same_site_cookies: "Utiliser des cookies de mĂŞme site, qui Ă©liminent tous les vecteurs de Cross Site Request Forgery sur les navigateurs supportĂ©s (Lax ou Strict). Avertissement: Strict ne fonctionnera que sur des sites qui imposent le login et utilisent le SSO." summary_score_threshold: "Le score minimum requis pour qu'un message soit inclus dans le 'RĂ©sumĂ© de ce sujet'" summary_posts_required: "Nombre minimum de messages dans un sujet avant que le 'RĂ©sumĂ© du sujet' soit activĂ©" summary_likes_required: "Nombre de J'aime minimum dans un sujet avant que le 'RĂ©sumĂ© du sujet' soit activĂ©" summary_percent_filter: "Quand un utilisateur clique sur « RĂ©sumer ce sujet », montrer le top % des messages" summary_max_results: "Nombre maximum de messages retournĂ©s par « RĂ©sumer ce sujet »" + enable_personal_messages: "Autoriser les utilisateurs de niveau de confiance 1 Ă  crĂ©er des messages et Ă  rĂ©pondre (configurable via le niveau de confiance minimum pour envoyer des messages). Notez que les responsables peuvent toujours envoyer des messages." + enable_system_message_replies: "Permettre aux utilisateurs de rĂ©pondre aux messages système, mĂŞme si les messages personnels sont dĂ©sactivĂ©s." + enable_personal_email_messages: "Autoriser les utilisateurs de niveau de confiance 1 Ă  envoyer des messages courriel personnels et Ă  rĂ©pondre (configurable via le niveau de confiance minimum pour envoyer des messages). Notez que les responsables peuvent toujours envoyer des messages." enable_long_polling: "Utiliser les requĂŞtes longues pour le flux de notifications." long_polling_base_url: "Racine de l'URL utilisĂ©e pour les requĂŞtes longues (dans le cas de l'utilisation d'un CDN pour fournir du contenu dynamique, pensez Ă  le configurer en mode \"origin pull\") par exemple : http://origin.site.com" long_polling_interval: "DĂ©lai d'attente du serveur avant de rĂ©pondre aux clients lorsqu'il n'y a pas de donnĂ©es Ă  envoyer\n(rĂ©servĂ© aux utilisateurs connectĂ©s)" @@ -934,6 +973,10 @@ fr: tl2_additional_likes_per_day_multiplier: "Augmenter la limite de J'aime par jour pour les utilisateurs de niveau 2 (membres) en la multipliant par ce nombre" tl3_additional_likes_per_day_multiplier: "Augmenter la limite de J'aime par jour pour les utilisateurs de niveau 3 (rĂ©guliers) en la multipliant par ce nombre" tl4_additional_likes_per_day_multiplier: "Augmenter la limite de J'aime par jour pour les utilisateurs de niveau 4 (meneurs) en la multipliant par ce nombre" + num_spam_flags_to_silence_new_user: "Si le message d'un nouvel utilisateur obtient plusieurs signalements pour spam de la part de num_users_to_silence_new_user diffĂ©rents utilisateurs, masquer tous ses messages et l'empĂŞcher de poster Ă  l'avenir. 0 dĂ©sactive cette fonctionnalitĂ©." + num_users_to_silence_new_user: "Si les messages d'un nouvel utilisateur obtiennent num_spam_flags_to_silence_new_user signalements d'autant d'utilisateurs diffĂ©rents, masquer tous ses messages et l'empĂŞcher de poster Ă  l'avenir. 0 dĂ©sactive cette fonctionnalitĂ©." + num_tl3_flags_to_silence_new_user: "Si les messages d'un nouvel utilisateur obtiennent\nce nombre de signalements de la part de num_tl3_users_to_silence_new_user utilisateurs de niveau de confiance 3 diffĂ©rents, masquer tous ses messages et l'empĂŞcher de poster Ă  l'avenir. 0 dĂ©sactive cette fonctionnalitĂ©." + num_tl3_users_to_silence_new_user: "Si les messages d'un nouvel utilisateur num_tl3_flags_to_silence_new_user signalements de la part de plusieurs utilisateurs de niveau de confiance 3 diffĂ©rents, masquer tous ses messages et l'empĂŞcher de poster Ă  l'avenir. 0 dĂ©sactive cette fonctionnalitĂ©." notify_mods_when_user_silenced: "Si un utilisateur est automatiquement mis sous silence, envoyer un message Ă  tous les modĂ©rateurs." flag_sockpuppets: "Si un nouvel utilisateur rĂ©pond Ă  un sujet avec la mĂŞme adresse IP que le nouvel utilisateur qui a commencĂ© le sujet, alors leurs messages seront automatiquement marquĂ©s comme spam." traditional_markdown_linebreaks: "Utiliser le retour Ă  la ligne traditionnel dans Markdown, qui nĂ©cessite deux espaces pour un saut de ligne." @@ -1005,8 +1048,6 @@ fr: google_oauth2_client_id: "Identifiante du client de votre application Google." google_oauth2_client_secret: "ClĂ© secrète du client de votre application Google." enable_twitter_logins: "Activer l'authentification Twitter, nĂ©cessite twitter_consumer_key et twitter_consumer_secret" - twitter_consumer_key: "ClĂ© utilisateur pour l'authentification Twitter, enregistrĂ©e sur http://dev.twitter.com" - twitter_consumer_secret: "Secret utilisateur pour l'authentification Twitter, enregistrĂ© sur http://dev.twitter.com" enable_instagram_logins: "Activer l'authentification Instagram, nĂ©cessite instagram_consumer_key et instagram_consumer_secret" instagram_consumer_key: "« Consumer key » pour l'identification Instagram" instagram_consumer_secret: "« Consumer secret » pour l'identification Instagram" @@ -1021,7 +1062,6 @@ fr: allow_restore: "Autoriser la restauration, qui peut remplacer TOUTES les donnĂ©es du site ! Laissez Ă  faux, sauf si vous envisagez de faire restaurer une sauvegarde" maximum_backups: "Nombre maximum de sauvegardes Ă  conserver sur le disque. Les anciennes sauvegardes seront automatiquement supprimĂ©es" automatic_backups_enabled: "Activer les sauvegardes automatiques tels que dĂ©finis dans les frĂ©quences de sauvegarde" - backup_frequency: "FrĂ©quence de crĂ©ation des sauvegardes du site, en jours." enable_s3_backups: "Envoyer vos sauvegardes Ă  S3 lorsqu'elles sont terminĂ©es. IMPORTANT : Vous devez avoir renseignĂ© vos identifiants S3 dans les paramètres de fichiers." s3_backup_bucket: "Bucket distant qui contiendra les sauvegardes. ATTENTION: VĂ©rifiez que c'est un bucket privĂ©" s3_disable_cleanup: "DĂ©sactiver la suppression des sauvegardes de S3 lors de leur suppression locale." @@ -1551,47 +1591,6 @@ fr: test_mailer: title: "Envoyer un courriel de test" subject_template: "[%{email_prefix}] Test de dĂ©livrance d'email" - text_body_template: | - Ceci est un courriel de test de - - [**%{base_url}**][0] - - Assurer la distribution de courriel est compliquĂ©. Voici un certain nombre de points importants que vous devez vĂ©rifier au prĂ©alable : - - - Soyez *certain* de renseigner l'adresse `notification email` dans les paramètres. **Le nom de domaine renseignĂ© dans l'addresse "from" des courriels que vous envoyez sera le nom de domaine utilisĂ© pour la validation**. - - - Sachez comment consulter le format brut d'un courriel dans votre client de courriel et vĂ©rifier les entĂŞtes pour obtenir des indices importants. Dans Gmail, il s'agit de l'option "show original" dans le menu contextuel situĂ© en haut Ă  droite de chaque courriel. - - - **IMPORTANT :** Votre FAI a-t-il un enregistrement DNS inverse renseignĂ© pour associer les noms de domaine et les adresses IP depuis lesquelles vous envoyez des courriels ? [Testez votre enregistrement inverse PTR][2] ici. Si votre FAI n'a pas renseignĂ© correctement votre enregistrement DNS inverse, il est très improbable que vos courriels soient dĂ©livrĂ©s. - - - L'[enregistrement SPF][8] de votre nom de domaine est-il correct ? [Testez votre enregistrement SPF][1] ici. Veuillez noter que le type officiel d'enregistrement est TXT pour le SPF. - - - L'[enregistrement DKIM][3] de votre nom de domaine est-il correct ? Ceci augmentera de manière significative la bonne distribution de vos courriels. [Testez votre enregistrement DKIM][7] ici. - - - Si vous maintenez votre propre serveur de courriel, assurez-vous que les IPs de votre serveur ne sont sur [aucune liste noire de courriel][4]. VĂ©rifiez Ă©galement qu'il envoie un "fully qualified hostname" rĂ©solvable en DNS dans son message HELO. Si ce n'est pas le cas, vos courriels seront rejetĂ©s par de nombreux services de courriel. - - - Nous vous recommandons d'**envoyer un courriel de test Ă  [mail-tester.com][mt]** pour vĂ©rifier que tout ce qui est mentionnĂ© ci-dessus fonctionne bien. - - (La manière *la plus facile* est de crĂ©er un compte gratuit sur [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] ou [Mailjet][mj], qui ont des options gratuites et qui seront suffisants pour de petites communautĂ©s. Vous devrez malgrĂ© tout configurer les entrĂ©es SPF et DKIM dans votre DNS !) - - Nous espĂ©rons que vous avez bien reçu ce courriel de test de distribution ! - - Bonne chance, - - vos amis de [Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "Nouvelle version" subject_template: "[%{email_prefix}] Nouvelle version de Discourse, mise Ă  jour disponible" @@ -2110,7 +2109,7 @@ fr: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} account_suspended: title: "Compte suspendu" subject_template: "[%{email_prefix}] Votre compte a Ă©tĂ© suspendu" diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index e348560589..983672d2ee 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -78,7 +78,6 @@ he: invalid: ×ś× ×Ş×§×™×ź is_invalid: "נר××” ×ś× ×‘×¨×•×¨, ×”×ם ×–×” ×ž×©×¤× ×©×ś×ť?" contains_censored_words: "מכיל ×ת המילים המצונזרות הב×ות: %{censored_words}" - matches_censored_pattern: "מכיל ×ת המילים הב×ות שמת×ימות לבי×וי הרגולרי המצונזר הב×: %{censored_words}" less_than: חייב להיות פחות מ-%{count} less_than_or_equal_to: חייב להיות פחות ×ו שווה ל-%{count} not_a_number: ×ינו מספר @@ -101,7 +100,7 @@ he: template: body: 'היו בעיות עם השדות הב×ים:' header: - one: שגי××” מנעה מ-%{model} להישמר. + one: 'שגי××” מנעה מ-%{model} להישמר.' other: '%{count} שגי×ות מנעו מ-%{model} להישמר.' embed: load_from_remote: "×רעה שגי××” ב×עינת ×”×¤×•×ˇ× ×”×–×”." @@ -172,7 +171,6 @@ he: too_many_links: one: "מצ×ערים, משתמשים חדשים יכולים להוסיף רק קישור ×חד בפוס×." other: "מצ×ערים, משתמשים חדשים יכולים להוסיף רק %{count} קישורים בפוס×." - contains_blocked_words: "×”×¤×•×ˇ× ×©×ś×š מכיל מילים ×סורות." spamming_host: "סליחה ×ך ×ינכם יכולים להוסיף קישור ל×תר ×–×”." user_is_suspended: "משתמשים מושעים ×ינם מורשים לפרסם." topic_not_found: "משהו השתבש ×ולי × ×•×©× ×–×” נסגר ×ו נמחק בזמן שקר×תם ×ותו?" @@ -365,40 +363,40 @@ he: title: "ברוכים הב×ים לדיסקורס" body: |2 - הפסקה הר×שונה של × ×•×©× × ×˘×•×Ą ×–×” תר××” כהודעת ״ברוכים הב×ים״ לכל המבקרים החדשים ב×תר הבית שלכם. ×”×™× ×—×©×•×‘×”! + הפסקה הר×שונה של × ×•×©× × ×˘×•×Ą ×–×” תר××” כהודעת ״ברוכים הב×ים״ לכל המבקרים החדשים ב×תר הבית שלכם. ×”×™× ×—×©×•×‘×”! - **עירכו ×–×ת** לתי×ור קצר של הקהילה שלכם: + **עירכו ×–×ת** לתי×ור קצר של הקהילה שלכם: - - למי ×”×™× × ×•×˘×“×”? - - מה הם יכולים ×ś×ž×¦×•× ×›×ן? - - למה כד××™ להם ×ś×‘×•× ×ś×¤×”? - - ×יפה ×פשר ×ś×§×¨×•× ×˘×•×“ (קישורים, מקורות, וכד׳)? + - למי ×”×™× × ×•×˘×“×”? + - מה הם יכולים ×ś×ž×¦×•× ×›×ן? + - למה כד××™ להם ×ś×‘×•× ×ś×¤×”? + - ×יפה ×פשר ×ś×§×¨×•× ×˘×•×“ (קישורים, מקורות, וכד׳)? - + - ייתכן ותרצו לסגור × ×•×©× ×–×” ב×מצעות :wrench: הניהול (בפינות משמ×ל למעלה ולמ××”), כדי שתגובות ×ś× ×™×™×˘×¨×ž×• על גבי כרזה. + ייתכן ותרצו לסגור × ×•×©× ×–×” ב×מצעות :wrench: הניהול (בפינות משמ×ל למעלה ולמ××”), כדי שתגובות ×ś× ×™×™×˘×¨×ž×• על גבי כרזה. lounge_welcome: title: "ברוכים הב×ים לל×ונג'" body: |2 - ברכות!: confetti_ball + ברכות!: confetti_ball - ×ם ×תם רו×ים × ×•×©× ×–×”, קודמתם ל×חרונה ל**משתמשים** (trust level 3). + ×ם ×תם רו×ים × ×•×©× ×–×”, קודמתם ל×חרונה ל**משתמשים** (trust level 3). - ×תם יכולים עכשיו … - * לערוך כותרות של כל × ×•×©× - * לשנות ×ת ×”×§×גוריה של כל × ×•×©× - * ל×פשר מעקב ×חר כל הקישורים שלכם (([automatic nofollow]) (http://en.wikipedia.org/wiki/Nofollow) ) יוסר. - * לגשת לק×גוריית הל×ונג' הפר××™ הזמינה רק למשתמשים ברמת ×מון 3 ומעלה - * להסתיר ספ×ם בעזרת דגל ×חד + ×תם יכולים עכשיו … + * לערוך כותרות של כל × ×•×©× + * לשנות ×ת ×”×§×גוריה של כל × ×•×©× + * ל×פשר מעקב ×חר כל הקישורים שלכם (([automatic nofollow]) (http://en.wikipedia.org/wiki/Nofollow) ) יוסר. + * לגשת לק×גוריית הל×ונג' הפר××™ הזמינה רק למשתמשים ברמת ×מון 3 ומעלה + * להסתיר ספ×ם בעזרת דגל ×חד - ×”× ×” [רשימת החברים שלכם למעמד ×–×”(/badges/3/regular). ×תם מוזמנים להגיד שלום. + ×”× ×” [רשימת החברים שלכם למעמד ×–×”(/badges/3/regular). ×תם מוזמנים להגיד שלום. - תודה על החלק החשוב ש×תם נו×לים בקהילה שלנו! + תודה על החלק החשוב ש×תם נו×לים בקהילה שלנו! - למידע נוסף על רמות ×”×מון, [ר×ו × ×•×©× ×–×”][trust]. שימו לב בבקשה שרק חברים שממשיכים לעמוד בדירשות ל×ורך זמן נש×רים ברמת ×מון זו). + למידע נוסף על רמות ×”×מון, [ר×ו × ×•×©× ×–×”][trust]. שימו לב בבקשה שרק חברים שממשיכים לעמוד בדירשות ל×ורך זמן נש×רים ברמת ×מון זו). - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "×ודות ×”×§×גוריה %{category}" replace_paragraph: "(החליפו פסקה ר×שונה זו עם תי×ור קצר של ×”×§×גוריה החדשה שלכם. ×”× ×—×™×” זו תופיעה ב×זור בחירת ×”×§×גוריה, ××– נסו לשמור על פחות מ 200 ×ותיות. **עד שתערכו תי×ור ×–×” ×ו תיצרו נוש×ים, ×§×גוריה זו ×ś× ×Ş×•×¤×™×˘ בדף ×”×§×גוריות.**)" @@ -584,8 +582,6 @@ he: long_form: 'דוגלל ×›×ś× ×¨×וי' notify_user: title: 'שלחו הודעה ל @{{username}}' - description: '×נו מעוניינים לדבר עם ×דם ×–×” ישירות ובפר×יות בנוגע ×ś×¤×•×ˇ× ×©×ś×•.' - short_description: '×× ×™ רוצה לדבר עם ×דם ×–×” ישירות וב×ופן פר××™ בנוגע ×ś×¤×•×ˇ× ×©×ś×”×ť.' long_form: 'הודעה נשלחה למשתמש/ת' email_title: '×”×¤×•×ˇ× ×©×ś×›×ť ב"%{title}"' email_body: "%{link}\n\n%{message}" @@ -841,7 +837,6 @@ he: poll_pop3_auth_error: "החיבור לשרת POP3 נכשל בשל שגי×ת הזדהות. ×× × ×‘×“×§×• ×ת הגדרות ×”-POP3 שלכם." site_settings: censored_words: "מלים שיוחלפו ב×ופן ×ו×ומ××™ ב- ■■■■" - censored_pattern: "בי×וי רגולרי שיוחלף ×ו×ומ×ית עם ■■■■" delete_old_hidden_posts: "מחיקת ×ו×ומ×ית של פרסומים מוסתרים שנותרים מוסתרים במשך יותר מ-30 יום." allow_user_locale: "×פשרו למשתמשים לבחור ×ת הגדרות השפה שלהם בממשק המשתמש/ת" set_locale_from_accept_language_header: "קבעו ×ת שפת הממשק עבור משתמשים ×נונימיים לפי השפה בדפדפן. (× -×™-ס-×™-ו-× -×™, ×ś× ×˘×•×‘×“ עם cache ×נונימי)" @@ -984,8 +979,6 @@ he: google_oauth2_client_id: "זהות לקוח (Client ID) של ×פליקציית ×”-Google שלך." google_oauth2_client_secret: "קוד סודי של לקוח (client secret) של ×פליקציית Google." enable_twitter_logins: "×פשרו ×ימות Twitter, מצריך twitter_consumer_key ו twitter_consumer_secret" - twitter_consumer_key: "מפתח לשימוש ב×ימות Twitter, רשום ב http://dev.twitter.com" - twitter_consumer_secret: "סוד לשימוש ב×ימות Twitter, רשום ב http://dev.twitter.com" enable_instagram_logins: "×פשרו ×ימות ×ינס×גרם, מצריך instagram_consumer_key ו instagram_consumer_secret" instagram_consumer_key: "מפתח ל×ימות ×ינס×גרם" instagram_consumer_secret: "סוד ל×ימות ×ינס×גרם" @@ -1000,7 +993,6 @@ he: allow_restore: "×פשר שחזור, ×שר יכול להחליף ×ת כל(!) המידע ב×תר! הותירו על \"שלילי\" (false) ××ś× ×ם כן ×תם מתכננים לשחזר גיבוי." maximum_backups: "המספר המקסימלי של גיבויים לשמירה על הכונן. גיבויים ישנים יותר ימחקו ×ו×ומ×ית" automatic_backups_enabled: "הרץ גיבויים ×ו×ומ×ים כמו שמוגדר בתדירות הגיבויים" - backup_frequency: "ב×יזו תכיפות ×נחנו מגבים ×ת ×”×תר, בימים." enable_s3_backups: "העל×ת גיבויים ל-S3 ל×חר השלמתם. חשוב: דורש הזנת הרש×ות S3 תקפות להגדרות הקבצים." s3_backup_bucket: "הדלי המרוחק שבו לשמור גיבויים. ×זהרה: שימו לב ×©×”×•× ×“×ś×™ פר××™." s3_disable_cleanup: "ב×לו ×ת ההסרה של גיבויים מ S3 ×›×שר הם מוסרים מקומית." @@ -1521,47 +1513,6 @@ he: test_mailer: title: "שולח-מיילים לבדיקה" subject_template: "[%{email_prefix}] מייל בדיקת שליחתיות" - text_body_template: | - זהו מייל בדיקה מ - - [**%{base_url}**][0] - - הגעה של מיילים ליעד ×”×™× ×˘×™× ×™×™×ź מסובך. ×”× ×” כמה דברים חשובים ש×תם צריכים לבדוק קודם כל: - - - היו *ב×וחים* שקבעתם כמו שצריך ×ת כתובת ×” from: של `מייל ההתר××”` בהגדרות ×”×תר. **הדומיין שקבוע בכתובת ×” "from" של המיילים ש×תם שולחים ×”×•× ×”×“×•×ž×™×™×ź שמיילים ×™×ומתו מולו**. - - - דעו ×יך להסתכל על המקור הגולמי של המיילים בתוכנת המיילים שלכם, כדי שתוכלו לבחון ×ת כותרות המיילים לרמזים חשובים. בג׳ימייל, זו ×ופציית ״הצג מקור״ ×‘×Ş×¤×¨×™× ×”× ×¤×Ş×— בכותרת של כל מייל. - - - חשוב: **×”×ם ×צל ספק ×”××™× ××¨× × ×©×ś×›×ť רשומה רשומת DNS הפוכה כדי לקשר ×ת שמות הדומיין וכתובות ×” IP ש×תם שולחים מהן מיילים? [בידקו ×ת רשומת ×” Reverse PTR שלכם] [2] ×›×ן. ×ם ספק ×”××™× ××¨× × ×©×ś×›×ť ×ś× ×ž×›× ×™×ˇ ×ת המצביע לרשומת ×” DNS ההפוכה הנכון, ×–×” מ×וד ×ś× ×ˇ×‘×™×¨ שמיילים שלכם יגיעו ליעד. - - - ×”×ם [רשומת ×” SPF][8] שלכם נכונה? [בידקו ×ת רשומת ×” SPF שלכם][1] ×›×ן. שימו לב ש TXT ×–×” סוג הרשומה הרשמי והנכון ל SPF. - - - ×”×ם [רשומת ×” DKIM][3] שלכם נכונה? ×–×” ישפר משמעותית ×ת שליחת המיילים שלכם. [בידקו ×ת רשומת ×” DKIM שלכם][7] ×›×ן. - - - ×ם ×תם מריצים ×ת שרת המייל של עצמכם, ווד×ו שכתובות ×” IP של שרת המייל שלכם [×ś× × ×ž×¦×ות ב×ף רשימה שחורה][4]. ווד×ו גם שהשרת ב×וח שולח שם שרת ×ž×ś× (fully-qualified hostname) שעושה resolve ל DNS בהודעת ×” HELO שלו. ×ם ל×, ×–×” יגרום למיילים שלכם להדחות על ידי שירותי מייל רבים. - - - ×נחנו ממליצים בחום ש**תשלחו מייל בדיקה ל[mail-tester.com][mt]** כדי ×ś×•×•×“× ×©×›×ś מה שמוזכר למעלה עובד ×›×™×ות. - - (הדרך *הקלה* ×”×™× ×ś×™×¦×•×¨ חשבון חינמי ב [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] ×ו [Mailjet][mj], שיש להם תוכניות מיילים חינמיות נדיבות ויהיו בסדר לקהילות ×§×נות. עדיין תצ×רכו להקים רשומות SPF ו DKIM בשירות ×” DNS שלכם!) - - ×נחנו מקווים שקיבלתם ×ת מייל הבדיקה ×”×–×” בסדר! - - בהצלחה, - - חבריכם ב[Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "שולח-מיילים של גרסה חדשה" subject_template: "[%{email_prefix}] גרסת Discourse חדשה, עדכון זמין." @@ -1964,7 +1915,7 @@ he: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "סיכום קצר של %{site_link} מ××– ביקורך ×”×חרון ב-%{last_seen_at}" since_last_visit: "מ××– ביקורכם ×”×חרון" diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index f7cb2cefb9..de380220b5 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -82,7 +82,6 @@ it: invalid: non è valido is_invalid: "sembra poco chiaro, è una frase completa?" contains_censored_words: "contiene le seguenti parole censurate: %{censored_words}" - matches_censored_pattern: "contiene le seguenti parole che coincidono con la regexp di censura del sito: %{censored_words}" less_than: deve essere minore di %{count} less_than_or_equal_to: deve essere minore o uguale a %{count} not_a_number: non è un numero @@ -105,7 +104,7 @@ it: template: body: 'Sono stati riscontrati problemi con i seguenti campi:' header: - one: 1 errore ha impedito il salvataggio di questo %{model} + one: '1 errore ha impedito il salvataggio di questo %{model}' other: '%{count} errori hanno impedito il salvataggio di questo %{model}' embed: load_from_remote: "Si è verificato un errore nel caricamento del messaggio." @@ -177,7 +176,6 @@ it: too_many_links: one: "Spiacenti, i nuovi utenti possono inserire al massimo un collegamento in un messaggio." other: "Spiacenti, i nuovi utenti possono inserire al massimo %{count} collegamenti in un messaggio." - contains_blocked_words: "Il tuo messaggio contiene parole che non sono consentite." spamming_host: "Spiacenti, non puoi inserire un collegamento verso quel dominio." user_is_suspended: "Agli utenti sospesi non è permesso creare messaggi." topic_not_found: "Quancosa non ha funzionato. Forse questo argomento è stato chiuso o cancellato mentre lo leggevi." @@ -376,41 +374,41 @@ it: title: "Benvenuto in Discourse" body: |2 - Il primo paragrafo di questo argomento puntato sarĂ  visibile come messaggio di benvenuto a tutti i nuovi visitatori sulla tua homepage. Ă importante! + Il primo paragrafo di questo argomento puntato sarĂ  visibile come messaggio di benvenuto a tutti i nuovi visitatori sulla tua homepage. Ă importante! - **Modificalo** con una breve descrizione della tua comunitĂ : + **Modificalo** con una breve descrizione della tua comunitĂ : - - A chi è rivolto? - - Cosa possono trovare qui? - - Perchè dovrebbero venire qui? - - Dove possono leggere di piĂą (collegamenti, risorse, ecc)? + - A chi è rivolto? + - Cosa possono trovare qui? + - Perchè dovrebbero venire qui? + - Dove possono leggere di piĂą (collegamenti, risorse, ecc)? - + - Puoi chiudere questo argomento tramite la :wrench: per amministratori (in alto a destra e in basso), in modo che le risposte non si accumulino all'annuncio. + Puoi chiudere questo argomento tramite la :wrench: per amministratori (in alto a destra e in basso), in modo che le risposte non si accumulino all'annuncio. lounge_welcome: title: "Benvenuto nel Lounge" body: |2 - Congratulazioni! :confetti_ball: + Congratulazioni! :confetti_ball: - Se vedi questo argomento, sei stato recentemente promosso a **esperto** (livello di esperienza 3). + Se vedi questo argomento, sei stato recentemente promosso a **esperto** (livello di esperienza 3). - Adesso puoi … + Adesso puoi … - * Modificare il titolo di qualunque argomento - * Cambiare la categoria di qualunque argomento - * Seguire tutti i tuoi collegamenti (è rimosso il [nofollow automatico](http://it.wikipedia.org/wiki/Nofollow) ) - * Accedere alla categoria privata Lounge visibile solo agli utenti con livello di esperienza 3 o superiore - * Cancellare i messaggi spam con un singolo clic + * Modificare il titolo di qualunque argomento + * Cambiare la categoria di qualunque argomento + * Seguire tutti i tuoi collegamenti (è rimosso il [nofollow automatico](http://it.wikipedia.org/wiki/Nofollow) ) + * Accedere alla categoria privata Lounge visibile solo agli utenti con livello di esperienza 3 o superiore + * Cancellare i messaggi spam con un singolo clic - Ecco l'elenco degli altri [utenti esperti](/badges/3/regular). Vai a salutarli. + Ecco l'elenco degli altri [utenti esperti](/badges/3/regular). Vai a salutarli. - Grazie per essere una parte importante di questa comunitĂ ! + Grazie per essere una parte importante di questa comunitĂ ! - (Per maggiori informazioni sui livelli di esperienza, [leggi questa discussione][trust]. Nota che possono rimanere utenti esperti solo quegli utenti che continuano a soddisfare i criteri di selezione) + (Per maggiori informazioni sui livelli di esperienza, [leggi questa discussione][trust]. Nota che possono rimanere utenti esperti solo quegli utenti che continuano a soddisfare i criteri di selezione) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Definizione della categoria %{category}" replace_paragraph: "(Sostituisci questo primo paragrafo con una breve descrizione della nuova categoria. Questa guida apparirĂ  nell'area di selezione della categoria, perciò cerca di stare sotto i 200 caratteri. **FinchĂ© non modifichi questo testo o non crei argomenti, questa categoria non apparirĂ  sulla pagina delle categorie.**)" @@ -596,8 +594,6 @@ it: long_form: 'segnalato come inappropriato' notify_user: title: 'Invia un messaggio a @{{username}}' - description: 'Vorrei discutere direttamente e privatamente con l''autore di questo messaggio.' - short_description: 'Voglio parlare con questa persona direttamente e privatamente a proposito del suo messaggio.' long_form: 'messaggio inviato' email_title: 'Il tuo messaggio su "%{title}"' email_body: "%{link}\n\n%{message}" @@ -856,7 +852,6 @@ it: poll_pop3_auth_error: "La connessione al server POP3 è fallita per un errore di autenticazione. Per favore verifica la tua configurazione POP3." site_settings: censored_words: "Parole che saranno automaticamente sostituite con ■■■■" - censored_pattern: "Pattern regex che sarĂ  automaticamente sostituito con ■■■■" delete_old_hidden_posts: "Cancella automaticamente tutti i messaggi nascosti che restano nascosti per piĂą di 30 giorni." allow_user_locale: "Permetti agli utenti di personalizzare la lingua dell'interfaccia" set_locale_from_accept_language_header: "imposta la lingua di interfaccia per gli utenti anonimi in base ai language header del loro browser.\n(SPERIMENTALE, non funziona con cache anonima)" @@ -1015,8 +1010,6 @@ it: google_oauth2_client_id: "Client ID della tua applicazione Google." google_oauth2_client_secret: "Client secret della tua applicazione Google." enable_twitter_logins: "Abilita l'autenticazione con Twitter, richiede twitter_consumer_key e twitter_consumer_secret" - twitter_consumer_key: "Consumer key per autenticazione via Twitter, come registrata su http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret per autenticazione via Twitter, come registrata su http://dev.twitter.com" enable_instagram_logins: "Abilita l'autenticazione con Instagram, richiede la instagram_consumer_key e la instagram_consumer_secret" instagram_consumer_key: "La consumer key per autenticazione Instagram" instagram_consumer_secret: "La consumer secret per l'autenticazione Instagram" @@ -1031,7 +1024,6 @@ it: allow_restore: "Abilita il ripristino, che sostituisce TUTTI i dati del sito! Lascia a falso a meno che non hai intenzione di ripristinare un backup." maximum_backups: "Il numero massimo di backup da mantenere sul disco. I backup piĂą vecchi vengono automaticamente cancellati." automatic_backups_enabled: "Esegui backup automatici come definito nella frequenza di backup" - backup_frequency: "Con che frequenza, in giorni, effettuiamo il backup del sito." enable_s3_backups: "Carica i backup su S3 quando completati. IMPORTANTE: richiede che siano inserite valide credenziali S3 nelle impostazioni File." s3_backup_bucket: "Il bucket remoto che contiene i backup. ATTENZIONE: assicurati che sia un bucket privato." s3_disable_cleanup: "Disabilita la rimozione dei backup da S3 quando rimossi localmente." @@ -1575,47 +1567,6 @@ it: test_mailer: title: "Test Mailer" subject_template: "[%{email_prefix}] Test di Consegna Email" - text_body_template: | - Questa è una email di prova inviata da - - [**%{base_url}**][0] - - Il recapito di email è complesso. Ecco alcune cose importanti che dovresti controllare prima:: - - - Sii *certo* di aver impostato `notification email` from: indirizzo correttamente nelle impostazioni del tuo sito. **Il dominio specificato nell'indirizzo "from" delle email inviate è il dominio in cui verranno convalidate le tue email**. - - - Impara a visualizzare il contenuto originale delle email nel tuo client email, in questo modo potrai controllare la presenza di eventuali errori. In Gmail puoi usare il tasto "Mostra Originale" presente nel menĂą a tendina di ogni email. - - - **IMPORTANTE:** Il tuo ISP ha un record DNS inverso per associare il nome del dominio e l'indirizzo ip dal quale invii le email? [Controlla il tuo record inverso PTR][2] qui. Se il tuo ISP non inserisce il record DNS inverso appropriato è molto improbabile che le tue email verranno recapitate. - - - Il [record SPF][8] del tuo dominio è corretto? [Controlla il tuo record SPF][1] qui. Tieni presente che TXT è il tipo di record ufficiale corretto per SPF. - - - Il [record DKIM][3] del tuo dominio è corretto? Questo migliorerĂ  significativamente il recapito delle email. [Controlla il tuo record DKIM][7] qui. - - - Se usi un tuo mail server, verifica che gli IP del tuo mail server [non siano in nessuna blacklist][4]. Verifica inoltre che il tuo mail server stia inviando un hostname pienamente qualificato che abbia una risoluzione nel DNS nel suo messaggio HELO. Se questo non avviene, è molto provabile che le tue email verranno scartate da molte caselle email. - - - Ti raccomandiamo di **inviare una email di prova a [mail-tester.com][mt]** per verificare che tutte le operazioni sopra indicate funzionino correttamente. - - (Il modo piĂą *semplice* è quello di creare un account free su [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] o [Mailjet][mj], che hanno un generoso piano gratuito che andrĂ  bene per i forum piĂą piccoli. Attenzione che dovrai comunque impostare i tuoi record SPF e DKIM nel DNS!) - - Speriamo tu abbia ricevuto questo test di recapito email! - - Buona fortuna, - - I tuoi amici di [Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "Mailer Nuova Versione" subject_template: "[%{email_prefix}] Nuova versione di Discourse, aggiornamento disponibile" @@ -2199,7 +2150,7 @@ it: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} account_suspended: title: "Account Sospeso" subject_template: "[%{email_prefix}] Il tuo account è stato sospeso" diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index 20df5c6004..1891e69f2e 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -58,6 +58,7 @@ ja: has_already_been_used: "ăŻć—˘ă«ä˝żç”¨ă•れă¦ă„ăľă™" inclusion: ăŻä¸€č¦§ă«ă‚りăľă›ă‚“ invalid: ăŻć­Łă—ăŹă‚りăľă›ă‚“ + is_invalid: "文章ă«ä¸Ťé©ĺ‡ăŞć–‡ĺ­—ă€ăľăźăŻć–‡ĺ­—ĺ—ăŚă‚ă‚‹ă‚ă†ă§ă™ă€‚\n文章をもă†ä¸€ĺş¦ç˘şčŞŤă—ă¦ă‚„り直ă—ă¦ăŹă ă•ă„。" less_than: ăŻ%{count}ă‚り小ă•ă„値ă«ă—ă¦ăŹă ă•ă„ less_than_or_equal_to: ăŻ%{count}以下ă®ĺ€¤ă«ă—ă¦ăŹă ă•ă„ not_a_number: ăŻć•°ĺ€¤ă§ĺ…ĄĺŠ›ă—ă¦ăŹă ă•ă„ @@ -115,6 +116,7 @@ ja: topic_not_found: "問題ăŚç™şç”źă—ăľă—ăźă€‚ăă”ăクăŚă‚Żă­ăĽă‚şă—ăźă‹ă€é–˛č¦§ä¸­ă«ĺ‰Šé™¤ă•れăźĺŹŻč˝ć€§ăŚă‚りăľă™ă€‚" just_posted_that: "ăŻćś€čż‘ă®ćŠ•ç¨żă¨ĺ†…容ăŚă»ăĽä¸€ç·’ă§ă™" invalid_characters: "ăŻä¸Ťć­ŁăŞć–‡ĺ­—ă‚’ĺ«ă‚“ă§ă„ăľă™" + is_invalid: "文章ă«ä¸Ťé©ĺ‡ăŞć–‡ĺ­—ă€ăľăźăŻć–‡ĺ­—ĺ—ăŚă‚ă‚‹ă‚ă†ă§ă™ă€‚\n文章をもă†ä¸€ĺş¦ç˘şčŞŤă—ă¦ă‚„り直ă—ă¦ăŹă ă•ă„。" next_page: "次ă®ăšăĽă‚¸ →" prev_page: "↠前ă®ăšăĽă‚¸" page_num: "%{num} ăšăĽă‚¸" @@ -212,24 +214,24 @@ ja: title: "ă©ă‚¦ăłă‚¸ă¸ă‚ă†ă“ăť" body: |2 - 素晴らă—ă„! :confetti_ball: + 素晴らă—ă„! :confetti_ball: - ă“ă®ăă”ăクăŚč¦‹ăă‚‹ă¨ă„ă†äş‹ăŻă€**ă¬ă‚®ăĄă©ăĽ**ă¸ć‡ć Ľă—ăźă‚ă†ă§ă™ă­ďĽ(ăă©ă‚ąăă¬ă™ă«3)。 + ă“ă®ăă”ăクăŚč¦‹ăă‚‹ă¨ă„ă†äş‹ăŻă€**ă¬ă‚®ăĄă©ăĽ**ă¸ć‡ć Ľă—ăźă‚ă†ă§ă™ă­ďĽ(ăă©ă‚ąăă¬ă™ă«3)。 - ă“れă§ă‚ăŞăźăŻ.... … + ă“れă§ă‚ăŞăźăŻ.... … - * ăă”ăクă®ă‚żă‚¤ăă«ă‚’好ăŤă«ç·¨é›†ă™ă‚‹äş‹ăŚă§ăŤăľă™ă€‚ - * ăă”ăクă®ă‚«ă†ă‚´ăŞă‚’好ăŤă«ĺ¤‰ć›´ă™ă‚‹äş‹ăŚă§ăŤăľă™ă€‚ - * Have all your links followed ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) is removed) - * ăă©ă‚ąăă¬ă™ă«ăŚ3以上ăŚĺ©ç”¨ĺ‡şćťĄă‚‹ă—ă©ă‚¤ă™ăĽăă©ă‚¦ăłă‚¸ă¸ă®ă‚˘ă‚Żă‚»ă‚ą - * 一度ă®ĺ ±ĺ‘Šă§ă‚ąă‘ă ă‚’非表示ă«ă§ăŤăľă™ + * ăă”ăクă®ă‚żă‚¤ăă«ă‚’好ăŤă«ç·¨é›†ă™ă‚‹äş‹ăŚă§ăŤăľă™ă€‚ + * ăă”ăクă®ă‚«ă†ă‚´ăŞă‚’好ăŤă«ĺ¤‰ć›´ă™ă‚‹äş‹ăŚă§ăŤăľă™ă€‚ + * Have all your links followed ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) is removed) + * ăă©ă‚ąăă¬ă™ă«ăŚ3以上ăŚĺ©ç”¨ĺ‡şćťĄă‚‹ă—ă©ă‚¤ă™ăĽăă©ă‚¦ăłă‚¸ă¸ă®ă‚˘ă‚Żă‚»ă‚ą + * 一度ă®ĺ ±ĺ‘Šă§ă‚ąă‘ă ă‚’非表示ă«ă§ăŤăľă™ - [現在ă¬ă‚®ăĄă©ăĽă«ć‰€ĺ±žă—ă¦ă„ă‚‹äşşă®ä¸€č¦§](/badges/3/regular)ă‹ă‚‰ăŞă‚ąăを確認ă™ă‚‹äş‹ăŚă§ăŤăľă™ă€‚ + [現在ă¬ă‚®ăĄă©ăĽă«ć‰€ĺ±žă—ă¦ă„ă‚‹äşşă®ä¸€č¦§](/badges/3/regular)ă‹ă‚‰ăŞă‚ąăを確認ă™ă‚‹äş‹ăŚă§ăŤăľă™ă€‚ - コăźăĄă‹ă†ă‚Łă‚’支ăă¦ă„ăźă ăŤă€ă‚りăŚă¨ă†ă”ă–ă„ăľă™ďĽ - (ăă©ă‚ąăă¬ă™ă«ă«é–˘ă™ă‚‹č©łă—ă„説ćŽăŻ [ă“ăˇă‚‰][trust]ă‚’ă”覧ăŹă ă•ă„。 ă¬ă‚®ăĄă©ăĽă®ćťˇä»¶ă‚’満ăźă—ç¶šă‘ă‚‹ă“ă¨ă§ç¶­ćŚă™ă‚‹äş‹ăŚă§ăŤăľă™ă€‚) + コăźăĄă‹ă†ă‚Łă‚’支ăă¦ă„ăźă ăŤă€ă‚りăŚă¨ă†ă”ă–ă„ăľă™ďĽ + (ăă©ă‚ąăă¬ă™ă«ă«é–˘ă™ă‚‹č©łă—ă„説ćŽăŻ [ă“ăˇă‚‰][trust]ă‚’ă”覧ăŹă ă•ă„。 ă¬ă‚®ăĄă©ăĽă®ćťˇä»¶ă‚’満ăźă—ç¶šă‘ă‚‹ă“ă¨ă§ç¶­ćŚă™ă‚‹äş‹ăŚă§ăŤăľă™ă€‚) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "%{category}ă‚«ă†ă‚´ăŞă«ă¤ă„ă¦" errors: @@ -649,8 +651,6 @@ ja: google_oauth2_client_id: "ă‚ăŞăźă®Googleアă—ăŞă‚±ăĽă‚·ă§ăłă®ă‚Żă©ă‚¤ă‚˘ăłăID" google_oauth2_client_secret: "ă‚ăŞăźă® Google アă—ăŞă‚±ăĽă‚·ă§ăłă®ă‚Żă©ă‚¤ă‚˘ăłă Secret。" enable_twitter_logins: "Twitter 認証を有効ă«ă™ă‚‹ (twitter_consumer_key 㨠twitter_consumer_secret ă®č¨­ĺ®šăŚĺż…č¦)" - twitter_consumer_key: "Twitter 認証用㮠consumer key。http://dev.twitter.com ă§ĺ…Ąć‰‹ĺŹŻč˝" - twitter_consumer_secret: "Twitter 認証用㮠consumer secret。http://dev.twitter.com ă§ĺ…Ąć‰‹ĺŹŻč˝" enable_facebook_logins: "Facebook 認証を有効ă«ă™ă‚‹ (facebook_app_id 㨠facebook_app_secret ă®č¨­ĺ®šăŚĺż…č¦)" facebook_app_id: "Facebook 認証用㮠app id。https://developers.facebook.com/apps ă§ĺ…Ąć‰‹ĺŹŻč˝" facebook_app_secret: "Facebook 認証用㮠app secret。https://developers.facebook.com/apps ă§ĺ…Ąć‰‹ĺŹŻč˝" @@ -659,7 +659,6 @@ ja: github_client_secret: "Github 認証用㮠client secret。https://github.com/settings/applications ă§ĺ…Ąć‰‹ĺŹŻč˝" allow_restore: "ă™ăąă¦ă®ă‚µă‚¤ăă‡ăĽă‚żă‚’ç˝®ăŤćŹ›ăă‚‹ĺľ©ĺ…を許可ă—ăľă™\n。ăăクアăă—ă‹ă‚‰ă®ĺľ©ĺ…を行ă†ă¨ăŤä»Ąĺ¤–ăŻfalseă®ăľăľă«ă—ă¦ăŹă ă•ă„。" maximum_backups: "ă‡ă‚Łă‚ąă‚Żă«äżťĺ­ă™ă‚‹ăăクアăă—ă®ćś€ĺ¤§ć•°ă€‚古ă„ăăクアăă—ăŻč‡Şĺ‹•çš„ă«ĺ‰Šé™¤ă•れăľă™" - backup_frequency: "サイăă®ăăクアăă—を作ćă™ă‚‹é »ĺş¦ďĽć—ĄďĽ‰" enable_s3_backups: "完了時ă«S3ă«ăăクアăă—をアăă—ă­ăĽă‰ă—ăľă™ă€‚重č¦: 有効ăŞS3 credentialsăŚă•ァイă«č¨­ĺ®šă«ĺż…č¦ă§ă™" s3_backup_bucket: "ăăクアăă—ă‚’äżťćŚă™ă‚‹ăケăă。 警告: ĺż…ăšă—ă©ă‚¤ă™ăĽăăケăăă«ăŞăŁă¦ă„ă‚‹ă“ă¨ă‚’確認ă—ă¦ăŹă ă•ă„" active_user_rate_limit_secs: "'last_seen_at' ă•ィăĽă«ă‰ă‚’ć›´ć–°ă™ă‚‹é »ĺş¦ (ç§’)" @@ -839,6 +838,8 @@ ja: category: 'ă‚«ă†ă‚´ăŞ' topic: 'çµćžś' user: 'ă¦ăĽă‚¶' + sso: + timeout_expired: "アカウăłăă¸ă®ă­ă‚°ă‚¤ăłăŚă‚żă‚¤ă ă‚˘ă‚¦ăă«ăŞă‚Šăľă—ăźă€‚ă‚‚ă†ä¸€ĺş¦ăăă—ăšăĽă‚¸ă‹ă‚‰ă­ă‚°ă‚¤ăłă—ă¦ăŹă ă•ă„。" original_poster: "Original Poster" most_posts: "Most Posts" most_recent_poster: "Most Recent Poster" diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index 351eb5c9c6..39a904ff42 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -77,7 +77,6 @@ ko: invalid: ě¬ë°”르지 않음 is_invalid: "문장이 ë¶ë¶„명합ë‹ë‹¤." contains_censored_words: "ę˛€ě—´ëś ë‹¨ě–´ę°€ ěžěеë‹ë‹¤: %{censored_words}" - matches_censored_pattern: "해당 단어는 사이트ě—서 검열í•는 정규식을 포함í•ęł  ěžěеë‹ë‹¤: %{censored_words}" less_than: '%{count}보다 작아야 함' less_than_or_equal_to: '%{count}보다 ěž‘ę±°ë‚ ę°™ě•„ě•Ľ 함' not_a_number: ě«ěžę°€ ě•„ë‹ @@ -159,7 +158,6 @@ ko: no_links_allowed: "죄송합ë‹ë‹¤. 신규회ě›ěť€ ę¸€ě— ë§í¬ëĄĽ 넣을 ě 없습ë‹ë‹¤." too_many_links: other: "죄송합ë‹ë‹¤. 신규회ě›ěť€ 글 í•ë‚ě— ë§í¬ëĄĽ %{count}개까지 넣을 ě ěžěеë‹ë‹¤." - contains_blocked_words: "íŹ¬ěŠ¤íŠ¸ě— ę¸ě§€ 단어가 포함ëě–´ ěžěеë‹ë‹¤." spamming_host: "죄송합ë‹ë‹¤. 회ě›ë‹ěť€ ë§í¬ëĄĽ 첨부할 ě 없습ë‹ë‹¤." user_is_suspended: "가입이 ëł´ëĄëś 회ě›ěť€ 글을 쓸 ě 없습ë‹ë‹¤." topic_not_found: "뭔가 ěžëŞ»ë네요. ě•„ë§ ěť˝ë ¤ëŠ” ëŹ„ě¤‘ě— í† í”˝ěť´ ë‹«í”ę±°ë‚ ě§€ě›Śě§„ ę±´ ě•„ë‹ęąŚěš”?" @@ -355,23 +353,23 @@ ko: title: "Discourseě— ě¤ě‹  ę˛ěť„ í™ěí•©ë‹ë‹¤" body: |2 - ęł ě •ëś í† í”˝ěť ě˛« 문단은 í™íŽěť´ě§€ě— 들어ě¤ëŠ” 사람들ě—게 í™ě 메시지로 보이게 ë©ë‹ë‹¤. ę·¸ëžě„ś 매우 중요해요! + ęł ě •ëś í† í”˝ěť ě˛« 문단은 í™íŽěť´ě§€ě— 들어ě¤ëŠ” 사람들ě—게 í™ě 메시지로 보이게 ë©ë‹ë‹¤. ę·¸ëžě„ś 매우 중요해요! - 커뮤ë‹í‹°ëĄĽ 알릴 ě ěžëŹ„ëˇť **ěť´ 글을 편집í•세요** + 커뮤ë‹í‹°ëĄĽ 알릴 ě ěžëŹ„ëˇť **ěť´ 글을 편집í•세요** - - ë„구를 위한 커뮤ë‹í‹°ěť¸ę°€ěš”? - - 여기ě—서 무엇을 얻을 ě ěžë‚ěš”? - - ě—¬ę¸°ě— ě™ś 와야í•ë‚ěš”? - - 더 알고싶으면 ë­ ë´ě•Ľ í•ë‚ěš” (ë§í¬, ěžëŁŚ, ę¸°í€ ë“±ë“±)? + - ë„구를 위한 커뮤ë‹í‹°ěť¸ę°€ěš”? + - 여기ě—서 무엇을 얻을 ě ěžë‚ěš”? + - ě—¬ę¸°ě— ě™ś 와야í•ë‚ěš”? + - 더 알고싶으면 ë­ ë´ě•Ľ í•ë‚ěš” (ë§í¬, ěžëŁŚ, ę¸°í€ ë“±ë“±)? - + - ěť´ í† í”˝ě— ë‹µę¸€ěť´ 쌓이는 게 싫다면, 관리기능 :wrench:(우측 ěë‹¨ě— ěžě–´ěš”)ěť„ 통해 ěť´ 토픽을 ë‹«ěť„ ě ěžěеë‹ë‹¤. + ěť´ í† í”˝ě— ë‹µę¸€ěť´ 쌓이는 게 싫다면, 관리기능 :wrench:(우측 ěë‹¨ě— ěžě–´ěš”)ěť„ 통해 ěť´ 토픽을 ë‹«ěť„ ě ěžěеë‹ë‹¤. lounge_welcome: title: "ëťĽěš´ě§€ě— ě¤ě‹  ę˛ěť„ í™ěí•©ë‹ë‹¤." body: |2 - ě¶•í•í•©ë‹ë‹¤! :confetti_ball: ë‹ąě‹ ěť íšŚě›ë“±ę¸‰ěť´ **지도ěž** (회ě›ë“±ę¸‰ 3등급)로 ě¬ëťĽę°”기 ë•Śë¬¸ě— ěť´ 토픽을 볼 ě ěžę˛Ś ëě—습ë‹ë‹¤. 이제부터 ě•„ëžěť 행동들을 í•  ě ěžěеë‹ë‹¤ … * 토픽 제목을 ěě •í•  ě ěžěеë‹ë‹¤ * í† í”˝ěť ěą´í…Śęł ë¦¬ëĄĽ ěě •í•  ě ěžěеë‹ë‹¤ * ë§í¬ę°€ follow로 ě˛ë¦¬ë©ë‹ë‹¤ ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) ę°€ ě śę±°ë©ë‹ë‹¤) * 회ě›ë“±ę¸‰ 3 ěť´ě인 사용ěžę°€ ě ‘ę·Ľí•  ě ěžëŠ” 비공개 카테고리인 ëťĽěš´ě§€ě— ě ‘ę·Ľí•  ě ěžěеë‹ë‹¤ * 좋아요와 ě‹ ęł ěť ę°€ě¤‘ěąę°€ 높아집ë‹ë‹¤ [ě •ę·ś 멤버 목록](/badges/3/regular)ěť„ 확인할 ě ěžěеë‹ë‹¤. 인사í•는 ę˛ěť„ 잊지ë§ě„¸ěš”. ěť´ 커뮤ë‹í‹°ěť 중요한 ě—­í• ěť„ 해주신 ę˛ě— ę°ě‚¬ë“śë¦˝ë‹ë‹¤! (회ě›ë“±ę¸‰ě— 관한 더 ěžě„¸í•ś 정보를 보시려면, [ěť´ 토픽 확인í•세요][trust]. ę¸°ě¤€ě— ë§žëŠ” 사람만 ě •ę·ś 회ě›ěśĽëˇś ěžę˛©ěť´ ěś ě§€ë©ë‹ë‹¤.) [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + ě¶•í•í•©ë‹ë‹¤! :confetti_ball: ë‹ąě‹ ěť íšŚě›ë“±ę¸‰ěť´ **지도ěž** (회ě›ë“±ę¸‰ 3등급)로 ě¬ëťĽę°”기 ë•Śë¬¸ě— ěť´ 토픽을 볼 ě ěžę˛Ś ëě—습ë‹ë‹¤. 이제부터 ě•„ëžěť 행동들을 í•  ě ěžěеë‹ë‹¤ … * 토픽 제목을 ěě •í•  ě ěžěеë‹ë‹¤ * í† í”˝ěť ěą´í…Śęł ë¦¬ëĄĽ ěě •í•  ě ěžěеë‹ë‹¤ * ë§í¬ę°€ follow로 ě˛ë¦¬ë©ë‹ë‹¤ ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) ę°€ ě śę±°ë©ë‹ë‹¤) * 회ě›ë“±ę¸‰ 3 ěť´ě인 사용ěžę°€ ě ‘ę·Ľí•  ě ěžëŠ” 비공개 카테고리인 ëťĽěš´ě§€ě— ě ‘ę·Ľí•  ě ěžěеë‹ë‹¤ * 좋아요와 ě‹ ęł ěť ę°€ě¤‘ěąę°€ 높아집ë‹ë‹¤ [ě •ę·ś 멤버 목록](/badges/3/regular)ěť„ 확인할 ě ěžěеë‹ë‹¤. 인사í•는 ę˛ěť„ 잊지ë§ě„¸ěš”. ěť´ 커뮤ë‹í‹°ěť 중요한 ě—­í• ěť„ 해주신 ę˛ě— ę°ě‚¬ë“śë¦˝ë‹ë‹¤! (회ě›ë“±ę¸‰ě— 관한 더 ěžě„¸í•ś 정보를 보시려면, [ěť´ 토픽 확인í•세요][trust]. ę¸°ě¤€ě— ë§žëŠ” 사람만 ě •ę·ś 회ě›ěśĽëˇś ěžę˛©ěť´ ěś ě§€ë©ë‹ë‹¤.) [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "'%{category}' ěą´í…Śęł ë¦¬ěť ě„¤ëŞ…" replace_paragraph: "(ěť´ 문단을 ě로운 카테고리를 ę°„ë‹¨íž ě„¤ëŞ…í•는 내용으로 대체í•세요. ěť´ 가이드는 카테고리 ě„ąě… ěě—­ě— í‘śě‹śë므로, 200ěž ěť´í•로 ěś ě§€í•는 게 좋습ë‹ë‹¤. **ěť´ 설명을 편집í•ę±°ë‚ í† í”˝ěť„ ěťě„±í•기 전까지는, ěť´ 카테고리는 카테고리 íŽěť´ě§€ě— 표시ëě§€ 않습ë‹ë‹¤.**)" @@ -528,8 +526,6 @@ ko: long_form: 'ë¶€ě ě í•¨ěśĽëˇś ě‹ ęł í•기' notify_user: title: '@{{username}} ë‹ě—게 메시지를 ëł´ë…ë‹ë‹¤' - description: 'íŹ¬ěŠ¤íŠ¸ě— ëŚ€í•ě—¬ 작성ěžě™€ ě§ě ‘ 개인ě ěśĽëˇś 이야기í•ęł  싶습ë‹ë‹¤.' - short_description: 'íŹ¬ěŠ¤íŠ¸ě— ëŚ€í•ě—¬ 작성ěžě™€ ě§ě ‘ 개인ě ěśĽëˇś 이야기í•ęł  싶습ë‹ë‹¤.' long_form: '메세지한 ěś ě €' email_title: '"%{title}" ë‚´ěť ë‹ąě‹ ěť ę¸€' email_body: "%{link}\n\n%{message}" @@ -785,7 +781,6 @@ ko: poll_pop3_auth_error: "인증 실패로 POP3 연결이 실패í–습ë‹ë‹¤. POP3 설정을 확인í•세요." site_settings: censored_words: "단어는 ěžëŹ™ě ěśĽëˇś `■■■■` 로 대체 ë©ë‹ë‹¤." - censored_pattern: "ěžëŹ™ěśĽëˇś `■■■■` 로 대체ë는 정규표í„식" delete_old_hidden_posts: "30일이 지난 ě¨ę˛¨ě§„ 글은 ěžëŹ™ěśĽëˇś ě‚­ě śë©ë‹ë‹¤." allow_user_locale: "사용ěžě—게 ěžě‹ ěť´ ě›í•는 언어를 ě„ íť í—ěš©" set_locale_from_accept_language_header: "익명 사용ěžěť 인터íŽěť´ěФ 언어를 웹브라우저 언어 헤더를 기준으로 변경í•기(실í—ě ěť¸ 기능입ë‹ë‹¤. 익명 cache와 동작í•ě§€ 않습ë‹ë‹¤.)" @@ -930,8 +925,6 @@ ko: google_oauth2_client_id: "Google Applicationěť Client ID" google_oauth2_client_secret: "Google Applicationěť Client Secret" enable_twitter_logins: "Twitter 인증을 활성화합ë‹ë‹¤. twitter_consumer_key와 twitter_consumer_secret í•„ěš”." - twitter_consumer_key: "http://dev.twitter.comě— ë“±ëˇťëś Twitter 인증을 위한 Consumer key" - twitter_consumer_secret: "http://dev.twitter.comě— ë“±ëˇťëś Twitter 인증을 위한 Consumer secret" enable_instagram_logins: "인스í€ę·¸ëž¨ 인증 켜기, instagram_consumer_key 와 instagram_consumer_secret í•„ěš”" instagram_consumer_key: "인스í€ę·¸ëž¨ 인증을 위한 Consumer key " instagram_consumer_secret: "인스í€ę·¸ëž¨ 인증을 위한 Consumer secret" @@ -946,7 +939,6 @@ ko: allow_restore: "데이터 ëłµě›ěť„ í—용합ë‹ë‹¤. ěť´ ě‚¬ěť´íŠ¸ěť ëŞ¨ë“  데이터가 ëł€ę˛˝ë  ě ěžë‹¤. ë°±ě—…ěť´ë‚ ëłµě› ęł„íšŤěť´ 없다면 비활성화로 놔둡ë‹ë‹¤." maximum_backups: "디스í¬ě— 유지할 최대 백업 ę°śě. ě¤ëžëś 백업ěśěśĽëˇś ěžëŹ™ěśĽëˇś ě‚­ě śëśë‹¤." automatic_backups_enabled: "ě„¤ě •ëś ë°±ě—… 주기로 ěžëŹ™ 백업 실행" - backup_frequency: "í•ëŁ¨ě— ëŞ‡ ë˛ ë°±ě—…ěť„ 받을까요?" enable_s3_backups: "백업이 완료ëë©´ Amazon S3로 업로드합ë‹ë‹¤. 중요: s3 credentialsěť„ 기입ëě–´ěžëŠ”ě§€ 확인 í•„ěš”" s3_backup_bucket: "백업본을 유지할 s3 버켓 이름. ěŁĽěť : 프라이빗 버켓인지 ë°ë“śě‹ś 확인해야í•세요." s3_disable_cleanup: "로컬ě—서 백업 ě‚­ě ś 시 S3ě—서 백업 ě śę±°í•는 기능 í•´ě ś" @@ -1398,7 +1390,7 @@ ko: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "%{last_seen_at}부터 ě§€ę¸ęąŚě§€ %{site_link} 사이트 근황" since_last_visit: "ë§ě§€ë§‰ 방문 후 경과시간" diff --git a/config/locales/server.nb_NO.yml b/config/locales/server.nb_NO.yml index 2c91f12b9a..934588bf1f 100644 --- a/config/locales/server.nb_NO.yml +++ b/config/locales/server.nb_NO.yml @@ -96,7 +96,7 @@ nb_NO: template: body: 'Dette er problemene med de følgene feltene:' header: - one: 1 feil forhindret lagring av %{model} + one: '1 feil forhindret lagring av %{model}' other: '%{count} feil forhindret lagring av %{model}' embed: load_from_remote: "Det oppstod et problem med innlastingen av innlegget." @@ -303,25 +303,25 @@ nb_NO: title: "Velkommen til salongen" body: |2 - Gratulerer! :confetti_ball: + Gratulerer! :confetti_ball: - Hvis du kan se denne trĂĄden, er du nylig blitt forfremmet til **aktivt medlem** (tillitsnivĂĄ 3). + Hvis du kan se denne trĂĄden, er du nylig blitt forfremmet til **aktivt medlem** (tillitsnivĂĄ 3). - Du kan nĂĄ … + Du kan nĂĄ … - * Endre tittelen til hvilken som helst trĂĄd - * Endre kategorien til hvilken som helst trĂĄd - * FĂĄ lenkene dine fulgt opp ([automatisk nofollow](http://en.wikipedia.org/wiki/Nofollow) er fjernet) - * Se og delta i diskusjonene i en privat salong-kategori som bare er synlig for brukere med tillitsnivĂĄ 3 eller høyere. - * Skjule søppelpost med ett flagg. + * Endre tittelen til hvilken som helst trĂĄd + * Endre kategorien til hvilken som helst trĂĄd + * FĂĄ lenkene dine fulgt opp ([automatisk nofollow](http://en.wikipedia.org/wiki/Nofollow) er fjernet) + * Se og delta i diskusjonene i en privat salong-kategori som bare er synlig for brukere med tillitsnivĂĄ 3 eller høyere. + * Skjule søppelpost med ett flagg. - Her er den [oppdaterte listen over andre trofaste](/badges/3/regular). Si gjerne hei! + Her er den [oppdaterte listen over andre trofaste](/badges/3/regular). Si gjerne hei! - Takk for at du er en viktig del av fellesskapet vĂĄrt. + Takk for at du er en viktig del av fellesskapet vĂĄrt. - (For mer informasjon om tillitsnivĂĄ [se denne trĂĄden][tillit]. Merk deg at bare medlemmer som fortsetter ĂĄ oppfylle kravene over tid, vil forbli *trofaste*.) + (For mer informasjon om tillitsnivĂĄ [se denne trĂĄden][tillit]. Merk deg at bare medlemmer som fortsetter ĂĄ oppfylle kravene over tid, vil forbli *trofaste*.) - [tillit]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [tillit]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Om kategorien %{category} " replace_paragraph: "(Erstatt dette første avsnittet med en kort beskrivelse av den nye kategorien din. Denne teksten vil vises i oversikten over kategorier, sĂĄ prøv ĂĄ holde lengden under 200 bokstaver. **Inntil du endrer denne beskrivelsen eller oppretter trĂĄder, vil denne kategorien ikke vises pĂĄ kategorier-siden.**)" @@ -479,8 +479,6 @@ nb_NO: long_form: 'markerte dette som upassende' notify_user: title: 'Send en melding til @{{username}}' - description: 'Jeg vil snakke direkte og privat med denne personen om innlegget.' - short_description: 'Jeg vil snakke direkte og privat med denne personen om innlegget.' long_form: 'sendt melding til bruker' email_title: 'Ditt innlegg i "%{title}"' email_body: "%{link}\n\n%{message}" diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index 65352818d6..80c28a817b 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -77,7 +77,6 @@ nl: invalid: is ongeldig is_invalid: "lijkt onduidelijk, is het een volledige zin?" contains_censored_words: "bevat de volgende gecensureerde woorden: %{censored_words}" - matches_censored_pattern: "bevat de volgende woorden die met de gecensureerde regexp van de website overeenkomen: %{censored_words}" less_than: moet minder zijn dan %{count} less_than_or_equal_to: moet minder zijn dan of gelijk zijn aan %{count} not_a_number: is niet een getal @@ -100,7 +99,7 @@ nl: template: body: 'Er waren problemen met de volgende velden:' header: - one: 1 fout heeft voorkomen dat %{model} kon worden opgeslagen + one: '1 fout heeft voorkomen dat %{model} kon worden opgeslagen' other: '%{count} fouten hebben voorkomen dat %{model} kon worden opgeslagen' embed: load_from_remote: "Er is een fout opgetreden bij het laden van dat bericht." @@ -171,7 +170,6 @@ nl: too_many_links: one: "Sorry, nieuwe gebruikers kunnen maar één koppeling in een bericht plaatsen." other: "Sorry, nieuwe gebruikers kunnen maar %{count} koppelingen in een bericht plaatsen." - contains_blocked_words: "Je bericht bevat woorden die niet zijn toegestaan." spamming_host: "Sorry, u kunt geen koppeling naar die host plaatsen." user_is_suspended: "Geschorste gebruikers mogen geen berichten plaatsen." topic_not_found: "Er is iets fout gegaan. Misschien is het topic gesloten of verwijderd terwijl u het bekeek?" @@ -366,41 +364,41 @@ nl: title: "Welkom bij Discourse" body: |2 - De eerste alinea van dit vastgemaakte topic is op uw startpagina zichtbaar als een welkomstbericht voor alle nieuwe bezoekers. Dit is belangrijk! + De eerste alinea van dit vastgemaakte topic is op uw startpagina zichtbaar als een welkomstbericht voor alle nieuwe bezoekers. Dit is belangrijk! - **Bewerk dit** naar een korte beschrijving van uw gemeenschap: + **Bewerk dit** naar een korte beschrijving van uw gemeenschap: - - Voor wie is deze bestemd? - - Wat kunnen ze hier vinden? - - Waarom zouden ze hier moeten komen? - - Waar kunnen ze meer lezen (koppelingen, bronnen, etc.)? + - Voor wie is deze bestemd? + - Wat kunnen ze hier vinden? + - Waarom zouden ze hier moeten komen? + - Waar kunnen ze meer lezen (koppelingen, bronnen, etc.)? - + - Mogelijk wilt u dit topic sluiten via de :wrench: voor beheer (rechtsboven en -beneden), zodat er bij een aankondiging geen antwoorden opstapelen. + Mogelijk wilt u dit topic sluiten via de :wrench: voor beheer (rechtsboven en -beneden), zodat er bij een aankondiging geen antwoorden opstapelen. lounge_welcome: title: "Welkom in de Lounge" body: |2 - Gefeliciteerd! :confetti_ball: + Gefeliciteerd! :confetti_ball: - Als u dit bericht kunt zien, bent u onlangs gepromoveerd naar **vaste gebruiker** (vertrouwensniveau 3). + Als u dit bericht kunt zien, bent u onlangs gepromoveerd naar **vaste gebruiker** (vertrouwensniveau 3). - U kunt nu … + U kunt nu … - * De titel van elk topic bewerken - * De categorie van elk topic wijzigen - * Al uw koppelingen laten volgen ([automatische nofollow](http://nl.wikipedia.org/wiki/Nofollow) is verwijderd) - * Een privĂ©categorie Lounge benaderen, die alleen zichtbaar is voor gebruikers met vertrouwensniveau 3 en hoger - * Spam verbergen met één markering + * De titel van elk topic bewerken + * De categorie van elk topic wijzigen + * Al uw koppelingen laten volgen ([automatische nofollow](http://nl.wikipedia.org/wiki/Nofollow) is verwijderd) + * Een privĂ©categorie Lounge benaderen, die alleen zichtbaar is voor gebruikers met vertrouwensniveau 3 en hoger + * Spam verbergen met één markering - Hier is de [huidige lijst van vaste gebruikers](/badges/3/regular). Zeg zeker even hallo. + Hier is de [huidige lijst van vaste gebruikers](/badges/3/regular). Zeg zeker even hallo. - Bedankt dat u een belangrijk onderdeel bent van deze gemeenschap! + Bedankt dat u een belangrijk onderdeel bent van deze gemeenschap! - ([Bekijk dit topic][trust] voor meer informatie over vertrouwensniveaus. Let erop dat alleen leden die na verloop van tijd aan de voorwaarden blijven voldoen vaste gebruikers blijven.) + ([Bekijk dit topic][trust] voor meer informatie over vertrouwensniveaus. Let erop dat alleen leden die na verloop van tijd aan de voorwaarden blijven voldoen vaste gebruikers blijven.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Over de categorie %{category}" replace_paragraph: "(Vervang deze eerste alinea door een korte beschrijving van uw nieuwe categorie. Deze leidraad verschijnt in het categorieselectiegebied, dus probeer deze onder de 200 tekens te houden. **Voordat u deze beschrijving bewerkt of topics aanmaakt, verschijnt deze categorie niet op de categoriepagina.**)" @@ -583,8 +581,6 @@ nl: long_form: 'heeft dit als ongepast gemarkeerd' notify_user: title: '@{{username}} een bericht sturen' - description: 'I wil rechtstreeks en privĂ© met deze persoon over zijn of haar bericht praten.' - short_description: 'I wil rechtstreeks en privĂ© met deze persoon over zijn of haar bericht praten.' long_form: 'bericht verstuurd naar gebruiker' email_title: 'Uw bericht in ''%{title}''' email_body: "%{link}\n\n%{message}" @@ -838,7 +834,6 @@ nl: poll_pop3_auth_error: "Verbinding met de POP3-server is mislukt met een authenticatiefout. Controleer uw POP3-instellingen." site_settings: censored_words: "Woorden die automatisch door ■■■■ zullen worden vervangen" - censored_pattern: "Regex-patroon dat automatisch door ■■■■ zal worden vervangen" delete_old_hidden_posts: "Verborgen berichten die meer dan 30 dagen verborgen blijven automatisch verwijderen" allow_user_locale: "Gebruikers toestaan om een eigen voorkeur voor de interfacetaal te kiezen" set_locale_from_accept_language_header: "Taal van interface voor anonieme gebruikers instellen op basis van de taalheaders van hun webbrowser. (EXPERIMENTEEL, werkt niet met anonieme cache)" @@ -964,8 +959,6 @@ nl: google_oauth2_client_id: "Client-ID van uw Google-toepassing." google_oauth2_client_secret: "Client-geheim van uw Google-toepassing." enable_twitter_logins: "Twitter-authenticatie inschakelen; vereist twitter_consumer_key en twitter_consumer_secret." - twitter_consumer_key: "consumer_key (registreer op dev.twitter.com)" - twitter_consumer_secret: "consumer secret (registreer op dev.twitter.com)" enable_instagram_logins: "Instagram-authenticatie inschakelen; vereist instagram_consumer_key en instagram_consumer_secret." enable_facebook_logins: "Facebook-authenticatie inschakelen; vereist facebook_app_id en facebook_app_secret." facebook_app_id: "app_id (registreer op https://developers.facebook.com/apps)" @@ -977,7 +970,6 @@ nl: allow_restore: "Herstellen van data toestaan, waarbij ALLE site-data wordt overschreven! Laat op 'false' staan, tenzij je een back-up terug wil zetten." maximum_backups: "Het maximale aantal back-ups om op schijf te bewaren. Oudere back-ups worden automatisch verwijderd." automatic_backups_enabled: "Automatische back-ups uitvoeren volgens de opgegeven back-up frequentie." - backup_frequency: "Hoe vaak we een back-up van de site maken, in dagen." enable_s3_backups: "Upload voltooide back-ups naar S3. LET OP: Zorg ervoor dat je je S3 inloggegevens hebt ingevuld in de Bestanden instellingen." s3_backup_bucket: "De S3 bucket voor de backups. WAARSCHUWING: zorg er voor dat dit een privĂ©bucket is." s3_disable_cleanup: "Backups van S3 niet verwijderen als het lokaal wordt verwijdert." diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index c5ebe7974c..7ab72831f1 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -77,7 +77,6 @@ pl_PL: invalid: jest nieprawidĹ‚owy is_invalid: "wyglÄ…da niejasno, czy to caĹ‚a wypowiedz ?" contains_censored_words: "zawiera nastÄ™pujÄ…ce niedozwolone sĹ‚owa: %{censored_words}" - matches_censored_pattern: "zawiera nastÄ™pujÄ…ce niedozwolone sĹ‚owa: %{censored_words}" less_than: musi być mniejszy niĹĽ %{count} less_than_or_equal_to: musi być mniejszy lub rĂłwny %{count} not_a_number: nie jest liczbÄ… @@ -106,7 +105,7 @@ pl_PL: template: body: 'WystÄ…piĹ‚y problemy z nastÄ™pujÄ…cymi polami:' header: - one: 1 błąd uniemoĹĽliwiĹ‚ zapisanie %{model} + one: '1 błąd uniemoĹĽliwiĹ‚ zapisanie %{model}' few: '%{count} błędy uniemoĹĽliwiĹ‚y zapisanie %{model}' many: '%{count} błędĂłw uniemoĹĽliwiĹ‚o zapisanie %{model}' other: '%{count} błędĂłw uniemoĹĽliwiĹ‚o zapisanie %{model}' @@ -191,7 +190,6 @@ pl_PL: one: "Przepraszamy, nowi uĹĽytkownicy mogÄ… dodać tylko jeden link we wpisie." few: "Przepraszamy, nowi uĹĽytkownicy mogÄ… dodać tylko %{count} linki we wpisie." other: "Przepraszamy, nowi uĹĽytkownicy mogÄ… dodać tylko %{count} linkĂłw we wpisie." - contains_blocked_words: "TwĂłj wpis zawiera niedozwolone sĹ‚owa" spamming_host: "Przepraszamy, nie moĹĽesz umieĹ›cić linka do tej strony." user_is_suspended: "Zawieszeni uĹĽytkownicy nie mogÄ… wysyĹ‚ać wiadomoĹ›ci." topic_not_found: "CoĹ› poszĹ‚o nie tak. Być moĹĽe temat zostaĹ‚ zamkniÄ™ty lub usuniÄ™ty w miÄ™dzyczasie?" @@ -394,41 +392,41 @@ pl_PL: title: "Witaj w Discourse" body: |2 - Pierwszy paragraf tego przypiÄ™tego tematu bÄ™dzie widoczny jako wiadomość powitalna dla wszystkich odwiedzajÄ…cych stronÄ™ głównÄ…. Jest bardzo waĹĽny! + Pierwszy paragraf tego przypiÄ™tego tematu bÄ™dzie widoczny jako wiadomość powitalna dla wszystkich odwiedzajÄ…cych stronÄ™ głównÄ…. Jest bardzo waĹĽny! - **Edytuj go** ĹĽeby zwięźle opisać swoje spoĹ‚eczeĹ„stwo. + **Edytuj go** ĹĽeby zwięźle opisać swoje spoĹ‚eczeĹ„stwo. - - Dla kogo jest? - - Co mogą tutaj znaleźć? - - Dlaczego powinni tutaj przychodzić? - - Gdzie mogÄ… przeczytać wiÄ™cej (link, zasoby, itp.)? + - Dla kogo jest? + - Co mogą tutaj znaleźć? + - Dlaczego powinni tutaj przychodzić? + - Gdzie mogÄ… przeczytać wiÄ™cej (link, zasoby, itp.)? - + - RozwaĹĽ zamkniÄ™cie tego tematu poprzez administracyjny :wrench: (w prawym gĂłrnym i dolnym rogu), aby zapobiec niepotrzebnym odpowiedziom do ogĹ‚oszenia. + RozwaĹĽ zamkniÄ™cie tego tematu poprzez administracyjny :wrench: (w prawym gĂłrnym i dolnym rogu), aby zapobiec niepotrzebnym odpowiedziom do ogĹ‚oszenia. lounge_welcome: title: "Witaj w Salonie" body: |2 - Gratulacje! :confetti_ball: + Gratulacje! :confetti_ball: - JeĹ›li widzisz ten temat, twoje konto osiÄ…gnęło trzeci poziom zaufania. + JeĹ›li widzisz ten temat, twoje konto osiÄ…gnęło trzeci poziom zaufania. - Od teraz moĹĽesz … + Od teraz moĹĽesz … - * Edytować tytuĹ‚ kaĹĽdego tematu - * Przenieść temat do innej kategorii - * Twoje linki nie bÄ™dÄ… posiadać juĹĽ atrybutu [nofollow](http://en.wikipedia.org/wiki/Nofollow) - * Korzystać z prywatnego Salonu, kategorii ktĂłra jest widoczna jedynie dla uĹĽytkownikĂłw na 3 poziomie zaufania lub wyĹĽszym - * Przyznawać polubienia i flagi o wiÄ™kszej wadze + * Edytować tytuĹ‚ kaĹĽdego tematu + * Przenieść temat do innej kategorii + * Twoje linki nie bÄ™dÄ… posiadać juĹĽ atrybutu [nofollow](http://en.wikipedia.org/wiki/Nofollow) + * Korzystać z prywatnego Salonu, kategorii ktĂłra jest widoczna jedynie dla uĹĽytkownikĂłw na 3 poziomie zaufania lub wyĹĽszym + * Przyznawać polubienia i flagi o wiÄ™kszej wadze - MoĹĽesz zobaczyć [wszystkie osoby z tym poziomem zaufania](/badges/3/regular). Nie zapomnij siÄ™ przywitać. + MoĹĽesz zobaczyć [wszystkie osoby z tym poziomem zaufania](/badges/3/regular). Nie zapomnij siÄ™ przywitać. - DziÄ™kujemy, ĹĽe jesteĹ› tak aktywnÄ… częściÄ… naszej spoĹ‚ecznoĹ›ci! + DziÄ™kujemy, ĹĽe jesteĹ› tak aktywnÄ… częściÄ… naszej spoĹ‚ecznoĹ›ci! - (WiÄ™cej informacji o poziomach zaufania znajduje siÄ™ [pod tym adresem][trust]. PamiÄ™taj, ĹĽe trzeci poziom zaufania zaleĹĽy od utrzymywania aktywnoĹ›ci danego konta na okreĹ›lonym poziomie.) + (WiÄ™cej informacji o poziomach zaufania znajduje siÄ™ [pod tym adresem][trust]. PamiÄ™taj, ĹĽe trzeci poziom zaufania zaleĹĽy od utrzymywania aktywnoĹ›ci danego konta na okreĹ›lonym poziomie.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "O kategorii %{category}" replace_paragraph: "(ZamieĹ„ ten pierwszy paragraf na krĂłtki opis nowej kategorii. Opis ten bÄ™dzie wyĹ›wietlany na karcie wyboru kategorii, wiÄ™c postaraj siÄ™ utrzymać go krĂłtszego niĹĽ 200 znakĂłw. **DopĂłki nie zmienisz tego opisu, ani nie utworzysz tematu, kategoria nie bÄ™dzie wyĹ›wietlana na liĹ›cie kategorii.**)" @@ -666,8 +664,6 @@ pl_PL: long_form: 'oflagowano jako niewĹ‚aĹ›ciwe' notify_user: title: 'WyĹ›lij @{{username}} wiadomość' - description: 'ChciaĹ‚bym porozmawiać o tym poĹ›cie z tÄ… osobÄ… prywatnie' - short_description: 'ChciaĹ‚bym porozmawiać o tym poĹ›cie bezpoĹ›rednio z tÄ… osobÄ….' long_form: 'uĹĽytkownik zostaĹ‚ powiadomiony' email_title: 'TwĂłj wpis w "%{title}"' email_body: "%{link}\n\n%{message}" @@ -927,7 +923,6 @@ pl_PL: poll_pop3_auth_error: "Połączenie z serwerem POP3 nie powiodĹ‚o siÄ™ przez błąd uwierzytelnienia. ProszÄ™ sprawdĹş swoje ustawienia POP3." site_settings: censored_words: "Wskazane sĹ‚owa bÄ™dÄ… automatycznie zamieniane na ■■■■" - censored_pattern: "Wzorzec Regex ktĂłry bÄ™dzie automatycznie zamieniany na ■■■■" delete_old_hidden_posts: "Automatycznie kasuj wpisy ukryte dĹ‚uĹĽej niĹĽ 30 dni." allow_user_locale: "ZezwĂłl uĹĽytkownikom na zmianÄ™ jÄ™zyka interfejsu we wĹ‚asnych ustawieniach" set_locale_from_accept_language_header: "ustaw jÄ™zyk interfejsu dla niezalogowanych uĹĽytkownikĂłw na podstawie jÄ™zyka w nagłówku ich przeglÄ…darki. (EKSPERYMENTALNE, nie dziaĹ‚a z anonimowym cache)" @@ -1076,8 +1071,6 @@ pl_PL: google_oauth2_client_id: "Client ID twojej aplikacji w Google" google_oauth2_client_secret: "Client Secret twojej aplikacji w Google" enable_twitter_logins: "Włącz autoryzacjÄ™ Twitter, wymagane twitter_consumer_key oraz twitter_consumer_secret" - twitter_consumer_key: "Consumer key for Twitter authentication, registered at http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret for Twitter authentication, registered at http://dev.twitter.com" enable_instagram_logins: "Włącz uwierzytelnienie za pomocÄ… Instagramu, wymaga instagram_consumer_key i instagram_consumer_secret." instagram_consumer_key: "Klucz konsumenta dla uwierzytelnienia Instragram" instagram_consumer_secret: "Sekret konsumenta dla uwierzytelnienia Instragram" @@ -1092,7 +1085,6 @@ pl_PL: allow_restore: "Dopuść przywracanie, ktĂłre moĹĽe zamienić WSZYSTKIE dane strony! Zostaw faĹ‚sz, chyba ĹĽe planujesz przywrĂłcić kopiÄ™ zapasowÄ…" maximum_backups: "Maksymalna liczba kopii zapasowych do przechowywania na dysku. Starsze kopie zapasowe zostanÄ… automatycznie usuniÄ™te." automatic_backups_enabled: "Uruchom automatyczne kopie zapasowe zgodnie z ustawionÄ… czÄ™stotliwoĹ›ciÄ… kopii" - backup_frequency: "Jak czÄ™sto moĹĽemy utworzyć kopiÄ™ zapasowÄ… strony, w dniach." enable_s3_backups: "WysyĹ‚aj kopie zapasowe do S3 kiedy gotowe. WAĹ»NE: wymaga poprawnych poĹ›wiadczeĹ„ S3 wprowadzonych w ustawieniach PlikĂłw." s3_backup_bucket: "Zdalne wiadro do przechowywania kopii zapasowych. UWAGA: Upewnij siÄ™, ĹĽe jest to wiadro prywatne." s3_disable_cleanup: "Dezaktywuj usuwanie kopii zapasowych z S3 kiedy usuniÄ™te lokalnie." @@ -1625,47 +1617,6 @@ pl_PL: test_mailer: title: "Test PowiadomieĹ„" subject_template: "[%{site_name}] Test dostarczania poczty" - text_body_template: | - To testowy mail z serwisu: - - [**%{base_url}**][0] - - Mail nie bez powodu jest tak dĹ‚ugi! PowinieneĹ› jeszcze sprawdzić kilka rzeczy: - - - **Upewnij siÄ™**, ĹĽe prawidĹ‚owo ustawiĹ‚eĹ› pole "od" dla powiadomieĹ„ mailowych w ustawieniach serwisu. **WzglÄ™dem tej domeny twoje maile bÄ™dÄ… weryfikowane!** - - - Dowiedz siÄ™, jak wyĹ›wietlić surowy kod ĹşrĂłdĹ‚owy wiadomoĹ›ci mailowej w twoim programie pocztowym, tak abyĹ› mĂłgĹ‚ przeglÄ…dać nagłówki maili szukajÄ…c istotnych wskazĂłwek. W przypadku GMaila nad kaĹĽdym mailem, po rozwiniÄ™ciu menu, dostÄ™pna jest opcja "PokaĹĽ oryginaĹ‚". - - - **WAĹ»NE:** Czy twĂłj ISP skonfigurowaĹ‚ odwrotny rekord DNS (reverse DNS record), by powiÄ…zać nazwy domenowe z adresami IP, spod ktĂłrych wysyĹ‚asz maile? [SprawdĹş swĂłj odwrotny rekord PTR][2] tutaj. JeĹ›li ISP nie skonfigurowaĹ‚ tego prawidĹ‚owo, twoje maile mogÄ… nie docierać do adresatĂłw. - - - Czy [rekord SPF][8] twojej domeny jest prawidĹ‚owy? [SprawdĹş swĂłj rekord SPF][1] tutaj. PamiÄ™taj, ĹĽe oficjalnym prawidĹ‚owym typem rekordu SPF jest TXT. - - - Czy [rekord DKIM][3] twojej domeny jest prawidĹ‚owy? To znacznie zwiÄ™kszy prawdopodobieĹ„stwo docierania twoich maili do adresatĂłw. [SprawdĹş swĂłj rekord DKIM][7] tutaj. - - - JeĹ›li korzystasz z wĹ‚asnego serwera poczty, upewnij siÄ™, ĹĽe adresy IP twojego serwera [nie znajdujÄ… siÄ™ na czarnych listach][4]. Ponadto upewnij siÄ™, ĹĽe serwer wysyĹ‚a w peĹ‚ni kwalifikowanÄ… nazwÄ™ hosta, rozwiÄ…zywanÄ… przez serwery DNS w wiadomoĹ›ciach HELO. W przeciwnym wypadku wielu usĹ‚ugodawcĂłw mailowych bÄ™dzie odrzucać twoje maile. - - - Mocno zalecamy, byĹ› **wysĹ‚aĹ‚ testowego maila do [mail-tester.com][mt]**, aby potwierdzić, ĹĽe wszystko o czym wspomnieliĹ›my dziaĹ‚a prawidĹ‚owo. - - (*NajĹ‚atwiej* zaĹ‚oĹĽyć darmowe konto w jednym z serwisĂłw: [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] lub [Mailjet][mj]. MajÄ… one wiele bezpĹ‚atnych ofert mailingowych, w sam raz dla maĹ‚ej spoĹ‚ecznoĹ›ci. PamiÄ™taj, ĹĽe i tak musisz skonfigurować rekordy SPF i DKIM w twoim serwerze DNS!) - - Mamy nadziejÄ™, ĹĽe bez problemu otrzymaĹ‚eĹ› tego maila potwierdzajÄ…cego docieranie maili wysyĹ‚anych przez forum. - - Powodzenia, - - Koledzy z [Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "Nowa wersja powiadomieĹ„" subject_template: "[%{site_name}] Aktualizacja do nowej wersji Discourse jest dostÄ™pna." @@ -2162,7 +2113,7 @@ pl_PL: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "KrĂłtkie podsumowanie aktywnoĹ›ci na %{site_link} od twojej ostatniej wizyty w dniu %{last_seen_at}." since_last_visit: "Od twojej ostatniej wizyty" diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index c2d79d6f3b..fab2940b73 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -98,7 +98,7 @@ pt: template: body: 'Ocorreram problemas com os seguintes campos:' header: - one: 1 erro impediu este %{model} de ser gravado + one: '1 erro impediu este %{model} de ser gravado' other: '%{count} erros impediram este %{model} de ser gravado' embed: load_from_remote: "Ocorreu um erro no carregamento dessa mensagem." @@ -166,7 +166,6 @@ pt: too_many_links: one: "Pedimos desculpa, novos utilizadores podem colocar apenas 1 hiperligação numa mensagem." other: "Pedimos desculpa, novos utilizadores podem colocar apenas %{count} hiperligações numa mensagem." - contains_blocked_words: "A sua publicação contĂ©m palavras que nĂŁo sĂŁo permitidas." spamming_host: "Pedimos desculpa, nĂŁo pode colocar uma hiperligação para esse servidor." user_is_suspended: "Utilizadores suspensos nĂŁo tĂŞm permissĂŁo para publicar." topic_not_found: "Algo de errado ocorreu. Talvez este tĂłpico tenha sido fechado ou eliminado enquanto olhava para ele?" @@ -325,25 +324,25 @@ pt: title: "Bem-vindo ao SalĂŁo" body: |2+ - ParabĂ©ns! :confetti_ball: + ParabĂ©ns! :confetti_ball: - Se consegue ver este tĂłpico, entĂŁo Ă© porque foi recentemente promovido a **habitual** (NĂ­vel de Confiança 3). + Se consegue ver este tĂłpico, entĂŁo Ă© porque foi recentemente promovido a **habitual** (NĂ­vel de Confiança 3). - Pode agora … + Pode agora … - * Editar o tĂ­tulo de qualquer tĂłpico - * Modificar a categoria de qualquer tĂłpico - * Ter todas as suas hiperligações a serem seguidas ([nĂŁoseguimento automático](http://en.wikipedia.org/wiki/Nofollow) Ă© removido) - * Aceder a um salĂŁo privado de categorias, apenas visĂ­vel a utilizadores com NĂ­vel de Confiança 3 ou superior - * Esconder spam com uma Ăşnica sinalização + * Editar o tĂ­tulo de qualquer tĂłpico + * Modificar a categoria de qualquer tĂłpico + * Ter todas as suas hiperligações a serem seguidas ([nĂŁoseguimento automático](http://en.wikipedia.org/wiki/Nofollow) Ă© removido) + * Aceder a um salĂŁo privado de categorias, apenas visĂ­vel a utilizadores com NĂ­vel de Confiança 3 ou superior + * Esconder spam com uma Ăşnica sinalização - Aqui está a [lista actual de membros habituais](/badges/3/regular). Certifique-se que diz olá. + Aqui está a [lista actual de membros habituais](/badges/3/regular). Certifique-se que diz olá. - Obrigado por ser uma parte importante na nossa comunidade! + Obrigado por ser uma parte importante na nossa comunidade! - (Para mais informação acerca dos NĂ­veis de Confiança, [veja este tĂłpico][confiança]. Por favor tenha conhecimento que apenas membros que continuam a corresponder aos requisitos ao longo do tempo, irĂŁo manter-se habituais.) + (Para mais informação acerca dos NĂ­veis de Confiança, [veja este tĂłpico][confiança]. Por favor tenha conhecimento que apenas membros que continuam a corresponder aos requisitos ao longo do tempo, irĂŁo manter-se habituais.) - [confiança]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [confiança]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Acerca da categoria %{category}" @@ -523,8 +522,6 @@ pt: long_form: 'sinalizou isto como inapropriado' notify_user: title: 'Enviar uma mensagem a @{{username}}' - description: 'Quero falar com esta pessoa diretamente e em privado acerca da sua mensagem.' - short_description: 'Eu quero falar com esta pessoa diretamente e em privado sobre a sua publicação.' long_form: 'utilizador com mensagens' email_title: 'A sua mensagem em "%{title}"' email_body: "%{link}\n\n%{message}" @@ -773,7 +770,6 @@ pt: poll_pop3_auth_error: "A tentativa de ligação ao servidor POP3 está a falhar por motivos de erro de autenticação. Por favor verifique a sua configuração de POP3." site_settings: censored_words: "Palavras que serĂŁo automaticamente substituĂ­das por ■■■■" - censored_pattern: "ExpressĂŁo regular que será automaticamente substituĂ­da por ■■■■" delete_old_hidden_posts: "Eliminar automaticamente quaisquer mensagens ocultas que permaneçam escondidas por mais de 30 dias." allow_user_locale: "Permitir aos utilizadores escolherem a sua prĂłpria lĂ­ngua preferencial para a interface." set_locale_from_accept_language_header: "defina a lĂ­ngua de interface para utilizadores anĂłnimos com base nos cabeçalhos de lĂ­ngua dos seus navegadores. (EXPERIMENTAL, nĂŁo funciona com cache anĂłnima)" @@ -906,8 +902,6 @@ pt: google_oauth2_client_id: "ID do cliente da sua aplicação Google." google_oauth2_client_secret: "Chave do cliente da sua aplicação Google." enable_twitter_logins: "Ativar autenticação pelo Twitter, requer twitter_consumer_key e twitter_consumer_secret" - twitter_consumer_key: "Chave de consumidor para autenticação pelo Twitter, registada em http://dev.twitter.com" - twitter_consumer_secret: "Chave do Consumidor para a autenticação pelo Twitter, registada em http://dev.twitter.com" enable_instagram_logins: "Activar autenticação pelo Instagram, requer instagram_consumer_key e instagram_consumer_secret" instagram_consumer_key: "Chave de consumidor para a autenticação via Instagram" instagram_consumer_secret: "Segredo de consumidor para a autenticação via Instagram" @@ -922,7 +916,6 @@ pt: allow_restore: "Permitir o restauro, que pode substituir TODOS os dados do sĂ­tio. Deixar a falso a menos que planeie restaurar uma cĂłpia de segurança" maximum_backups: "Valor máximo de cĂłpias de segurança a serem guardadas em disco. CĂłpias de Segurança antigas sĂŁo automaticamente eliminadas." automatic_backups_enabled: "Executar cĂłpias de segurança automáticas de acordo com as definições de frequĂŞncia de cĂłpia de segurança" - backup_frequency: "Com que frequĂŞncia criamos cĂłpias de segurança do sĂ­tio, em dias." enable_s3_backups: "Carregar cĂłpias de segurança para S3 quando completo. IMPORTANTE: requer credenciais S3 válidas inseridas nas configurações dos ficheiros." s3_backup_bucket: "Balde remoto para guardar cĂłpias de segurança. AVISO: Certifique-se que este Ă© um balde privado." s3_disable_cleanup: "Desactive a remoção das cĂłpias de segurança do S3 quando removidas localmente." @@ -1356,48 +1349,6 @@ pt: Desculpe, esta hiperligação de transferĂŞncia da cĂłpia de segurança já foi utilizada ou expirou. admin_confirmation_mailer: subject_template: "[%{email_prefix}] Confirmar nova Conta de Administrador" - test_mailer: - text_body_template: | - Este Ă© um email de teste de - - [**%{base_url}**][0] - - A entregabilidade de email Ă© complicada. Aqui estĂŁo alguns pontos importantes a verificar primeiro: - - - *Certifique-se* que configurou o endereço de: do `email de notificação` corretamente nas configurações do seu sĂ­tio. **O domĂ­nio especificado no endereço “de” nos emails que envia Ă© o domĂ­nio que será validado**. - - - Conhecer como observar o cĂłdigo fonte dos emails no seu email cliente, de modo a que possa examinar cabeçalhos de email Ă  procura de pistas importantes. No Gmail, Ă© a opção “mostrar original” no menu pendente no canto superior direito de cada email. - - - **IMPORTANTE:** O seu ISP tem um registo de DNS inverso inserido para associar os nomes de domĂ­nio aos endereços IP de onde envia o seu email? [Teste o seu registo de PTR Inverso][2] aqui. Se o seu ISP nĂŁo inserir o registo de DNS inverso apropriado, Ă© muito improvável que qualquer um dos seus emails seja entregue. - - - Estará o [registo SPF][8] do seu domĂ­nio correto? [Teste o seu registo SPF][1] aqui. Note que TXT Ă© o tipo correcto de registo oficial para SPF. - - - Estará o [registo DKIM][3] do seu domĂ­nio correto? Isto irá melhorar significativamente a entregabilidade de emails. [Teste o seu registo DKIM][7] aqui. - - - Se correr o seu prĂłprio servidor de email, certifique-se que os IPs do seu servidor de email nĂŁo [estĂŁo em nenhuma lista negra de email] [4]. Verifique tambĂ©m que Ă© enviado um nome de servidor qualificado que resolve o DNS na sua mensagem HELO. Se nĂŁo for, isto irá fazer com que o seu email seja rejeitado por muitos serviços de email. - - - Recomendamos vivamente que **envie um email de teste para [mail-tester.com][mt]** para verificar que tudo mencionado em cima está a funcionar correctamente. - - (A maneira *fácil* Ă© criar uma conta grátis em [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] ou [Mailjet][mj], que tĂŞm pacotes grátis generosos de envio de emails e serĂŁo suficientes para pequenas comunidades. Mesmo assim irá precisar de configurar os registos SPF e DKIM no seu DNS!) - - Esperamos que tenha recebido este teste de entregabilidade de email sem problemas! - - Boa sorte, - - Dos seus amigos em [Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: http://dkimcore.org/tools/dkimrecordcheck.html - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://sendgrid.com/ - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: subject_template: "[%{email_prefix}] Nova versĂŁo do Discourse, disponĂ­vel atualização" new_version_mailer_with_notes: @@ -1690,7 +1641,7 @@ pt: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "Um breve sumário de %{site_link} desde a sua Ăşltima visita em %{last_seen_at}" since_last_visit: "Desde a sua Ăşltima visita" diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index 448090358f..816ff6f8fe 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -75,7 +75,6 @@ pt_BR: invalid: Ă© inválido is_invalid: "parece pouco claro; Ă© uma frase completa?" contains_censored_words: "contĂ©m as seguintes palavras censuradas: %{censored_words}" - matches_censored_pattern: "contĂ©m as seguintes palavras que se encaixam nos padrões de expressões regulares censuradas do site: %{censored_words}" less_than: precisa ser menor que %{count} less_than_or_equal_to: deve ser menor ou igual Ă  %{count} not_a_number: nĂŁo Ă© um nĂşmero @@ -98,7 +97,7 @@ pt_BR: template: body: 'Houve problemas com os seguintes campos:' header: - one: 1 erro impediu este %{model} de ser salvo + one: '1 erro impediu este %{model} de ser salvo' other: '%{count} erros impediram este %{model} de ser salvo' embed: load_from_remote: "Houve um erro ao carregar essa mensagem." @@ -327,41 +326,41 @@ pt_BR: title: "Bem vindo ao Discourse" body: |2 - O primeiro parágrafo deste tĂłpico fixo será visto como uma mensagem de boas vindas a todos os novos visitantes em sua página inicial. É muito importante! + O primeiro parágrafo deste tĂłpico fixo será visto como uma mensagem de boas vindas a todos os novos visitantes em sua página inicial. É muito importante! - **Edite isso** em uma breve descrição da sua comunidade: + **Edite isso** em uma breve descrição da sua comunidade: - - Para quem ela Ă©? - - O que será encontrado aqui? - - Porque as pessoas deveria vir aqui? - - Onde Ă© possĂ­vel saber mais a respeito (links, fontes, etc)? + - Para quem ela Ă©? + - O que será encontrado aqui? + - Porque as pessoas deveria vir aqui? + - Onde Ă© possĂ­vel saber mais a respeito (links, fontes, etc)? - + - VocĂŞ pode querer fechar este tĂłpico atravĂ©s do Administrador :wrench: (na parte superior direita e inferior), para que nĂŁo se acumulem respostas num tĂłpico de boas vindas. + VocĂŞ pode querer fechar este tĂłpico atravĂ©s do Administrador :wrench: (na parte superior direita e inferior), para que nĂŁo se acumulem respostas num tĂłpico de boas vindas. lounge_welcome: title: "Bem-vindo ao SalĂŁo" body: |2 - ParabĂ©ns! :confetti_ball: + ParabĂ©ns! :confetti_ball: - Se vocĂŞ pode ver neste tĂłpico, vocĂŞ foi recentemente promovido a **regular** (nĂ­vel de confiança 3). + Se vocĂŞ pode ver neste tĂłpico, vocĂŞ foi recentemente promovido a **regular** (nĂ­vel de confiança 3). - Agora vocĂŞ pode … + Agora vocĂŞ pode … - * Editar o tĂ­tulo de qualquer tĂłpico - * Alterar a categoria de qualquer assunto - * Ter todos os seus links seguidos ([nofollow automática] (http://en.wikipedia.org/wiki/Nofollow) Ă© removido) - * Acesso a categoria privada Sala visĂ­veis apenas para os utilizadores a nĂ­vel de confiança 3 e superior - * Esconder o spam com uma Ăşnica sinalização + * Editar o tĂ­tulo de qualquer tĂłpico + * Alterar a categoria de qualquer assunto + * Ter todos os seus links seguidos ([nofollow automática] (http://en.wikipedia.org/wiki/Nofollow) Ă© removido) + * Acesso a categoria privada Sala visĂ­veis apenas para os utilizadores a nĂ­vel de confiança 3 e superior + * Esconder o spam com uma Ăşnica sinalização - Aqui está o [lista atual de companheiros regulares] (/badges/3/regular). NĂŁo se esqueça de dizer oi. + Aqui está o [lista atual de companheiros regulares] (/badges/3/regular). NĂŁo se esqueça de dizer oi. - Obrigado por ser uma parte importante dessa comunidade! + Obrigado por ser uma parte importante dessa comunidade! - (Para obter mais informações sobre os nĂ­veis de confiança, [veja este tĂłpico][trust]. Por favor note que somente os membros que continuam a atender aos requisitos ao longo do tempo permanecerá regulares.) + (Para obter mais informações sobre os nĂ­veis de confiança, [veja este tĂłpico][trust]. Por favor note que somente os membros que continuam a atender aos requisitos ao longo do tempo permanecerá regulares.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Sobre a categoria %{category} " replace_paragraph: "(Substitua este primeiro parágrafo por uma curta descrição da sua nova categoria. Este guia vai aparecer na área de seleção de categoria, entĂŁo tente usar menos que 200 caracteres. **AtĂ© vocĂŞ editar essa descrição ou criar tĂłpicos, esta categoria nĂŁo aparece na página de categorias.**)" @@ -537,7 +536,6 @@ pt_BR: long_form: 'sinalizado como inapropriado' notify_user: title: 'Envie ao(Ă ) @ {{nome de usuário}} uma mensagem' - description: 'Eu quero falar com essa pessoa diretamente e em particular sobre o seu post.' long_form: 'mensagem privada' email_title: 'Sobre a sua postagem "%{title}"' email_body: "%{link}\n\n%{message}" @@ -785,7 +783,6 @@ pt_BR: poll_pop3_auth_error: "A conexĂŁo com o servidor POP3 está falhando com um erro de autenticação. Por favor, verifique suas configurações de POP3." site_settings: censored_words: "Palavras que serĂŁo substituĂ­dos automaticamente por ■■■■" - censored_pattern: "PadrĂŁo de expressĂŁo regular que será automaticamente substituĂ­do por ■■■■" delete_old_hidden_posts: "Auto-apagar todas as mensagens ocultas que ficar oculta por mais de 30 dias." allow_user_locale: "Permitir que os usuários escolham suas prĂłprias preferĂŞncias de idioma de interface" set_locale_from_accept_language_header: "define a lĂ­ngua da interface para os usuários anĂ´nimos de acordo com os cabeçalhos de lĂ­ngua de seus navegadores. (EXPERIMENTAL, nĂŁo funciona com cache anĂ´nimo)" @@ -914,8 +911,6 @@ pt_BR: google_oauth2_client_id: "Client ID da sua aplicação Google." google_oauth2_client_secret: "Client secret da sua aplicação Google." enable_twitter_logins: "Ativar autenticação pelo Twitter, requer twitter_consumer_key e twitter_consumer_secret" - twitter_consumer_key: "consumer key registrada em dev.twitter.com (usada para fazer autenticação via twitter)" - twitter_consumer_secret: "consumer secret registrada em dev.twitter.com (usada para fazer autenticação via twitter)" enable_instagram_logins: "Habilitar autenticação via Instagram. Requer instagram_consumer_key e instagram_consumer_secret" instagram_consumer_key: "Chave de consumidor para autenticação via Instagram" instagram_consumer_secret: "Segredo de consumidor para autenticação via Instagram" @@ -930,7 +925,6 @@ pt_BR: allow_restore: "Permitir restauração, que pode substituir todos os dados do site! Deixe falsa, a menos que vocĂŞ pretenda restaurar um backup" maximum_backups: "A quantidade máxima de backups para manter no disco. Backups mais antigos sĂŁo excluĂ­dos automaticamente" automatic_backups_enabled: "Realizar backups automáticos, como definido na frequĂŞncia de backup" - backup_frequency: "A frequĂŞncia com a qual criamos backups, em dias." enable_s3_backups: "Fazer upload dos backups para o S3 quando completado. IMPORTANTE: exige credenciais válidas do S3 configuradas nas Configurações de Arquivos." s3_backup_bucket: "O repositĂłrio remoto para realizar backups. AVISO: Certifique-se de que Ă© um repositĂłrio privado." s3_disable_cleanup: "Desabilitar a remoção de backups na S3 quando removidos localmente." @@ -1416,7 +1410,7 @@ pt_BR: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "Um breve resumo de %{site_link} desde que viu pela Ăşltima vez em %{last_seen_at}." since_last_visit: "Desde a sua Ăşltima visita" diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml index fbfb757e98..5ec482db5c 100644 --- a/config/locales/server.ro.yml +++ b/config/locales/server.ro.yml @@ -96,7 +96,7 @@ ro: template: body: 'Au apÄrut probleme cu urmÄtoarele câmpuri:' header: - one: O eroare a Ă®mpiedicat acest %{model} sÄ fie salvat + one: 'O eroare a Ă®mpiedicat acest %{model} sÄ fie salvat' few: '%{count} erori au Ă®mpiedicat acest %{model} sÄ fie salvat' other: '%{count} de erori au Ă®mpiedicat acest %{model} sÄ fie salvat' embed: @@ -314,25 +314,25 @@ ro: title: "Bine ai venit Ă®n sufragerie" body: |2 - FelicitÄri! :confetti_ball: + FelicitÄri! :confetti_ball: - DacÄ poČ›i vedea acest subiect, ai fost promovat recent la **utilizator frecvent** (nivel de Ă®ncredere 3). + DacÄ poČ›i vedea acest subiect, ai fost promovat recent la **utilizator frecvent** (nivel de Ă®ncredere 3). - Acum poČ›i sÄ … + Acum poČ›i sÄ … - * Editezi titlul oricÄrui subiect - * Schimbi categoria oricÄrui subiect - * Setezi ca toate link-urile tale sÄ fie urmÄrite ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) este dezactivat) - * Accesezi secČ›iunea privatÄ din categoria Sufragerie unde au acces numai utilizatorii de nivel 3 sau mai mare - * Ascunzi spam-ul folosind un singur marcaj de avertizare + * Editezi titlul oricÄrui subiect + * Schimbi categoria oricÄrui subiect + * Setezi ca toate link-urile tale sÄ fie urmÄrite ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) este dezactivat) + * Accesezi secČ›iunea privatÄ din categoria Sufragerie unde au acces numai utilizatorii de nivel 3 sau mai mare + * Ascunzi spam-ul folosind un singur marcaj de avertizare - Aici este [lista colegilor utilizatori ce au ajuns la nivelul de Ă®ncredere 3](/badges/3/regular). SalutÄ-i! + Aici este [lista colegilor utilizatori ce au ajuns la nivelul de Ă®ncredere 3](/badges/3/regular). SalutÄ-i! - ĂŽČ›i mulČ›umim cÄ eČ™ti o parte importantÄ a acestei comunitÄČ›i! + ĂŽČ›i mulČ›umim cÄ eČ™ti o parte importantÄ a acestei comunitÄČ›i! - (Pentru mai multe informaČ›ii cu privire la nivelurile de Ă®ncredere, [vezi subiectul Ästa][Ă®ncredere]. Čšine aminte cÄ doar membrii ce vor Ă®ndeplini constant criteriile cerute vor rÄmâne la nivelul de Ă®ncredere 3.) + (Pentru mai multe informaČ›ii cu privire la nivelurile de Ă®ncredere, [vezi subiectul Ästa][Ă®ncredere]. Čšine aminte cÄ doar membrii ce vor Ă®ndeplini constant criteriile cerute vor rÄmâne la nivelul de Ă®ncredere 3.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Despre categoria %{category}" replace_paragraph: "(ĂŽnlocuieČ™te acest prim paragraf cu o scurtÄ descriere a noii tale categorii. Acest Ă®ndreptar va apÄrea Ă®n zona de selecČ›ie a categoriei, aČ™a cÄ Ă®ncearcÄ sÄ Ă®l limitezi la maxim 200 de caractere. **AceastÄ categorie nu va apÄrea Ă®n pagina categoriilor pânÄ ce nu editezi aceastÄ descriere sau pânÄ ce nu creezi subiecte**)" @@ -535,7 +535,6 @@ ro: long_form: 'marcat ca necorespunzÄtor' notify_user: title: 'Trimite-i un mesaj lui @{{username}}' - description: 'Vreau sÄ Ă®i vorbesc direct Č™i Ă®n mod privat acestei persoane cu privire la postarea sa.' long_form: 'utilizator cÄruia i-a fost trimis mesaj' email_title: 'Postarea ta din "%{title}"' email_body: "%{link}\n\n%{message}" @@ -781,7 +780,6 @@ ro: poll_pop3_auth_error: "Conexiunea la serverul POP3 a eČ™uat cu o eroare de autentificare. Te rugÄm sÄ verifici setÄrile POP3." site_settings: censored_words: "Cuvintele vor fi Ă®nlocuite automat cu ■■■■" - censored_pattern: "Modelul Regex va fi Ă®nlocuit automat cu ■■■■" delete_old_hidden_posts: "Č™terge automat toate postÄrile ascunse care stau ascunse mai mult de 30 de zile." allow_user_locale: "Permite utilizatorilor sÄ-Č™i aleagÄ singuri limba pentru interfaČ›Ä" set_locale_from_accept_language_header: "seteazÄ limba interfeČ›ei pentru utilizatorii anonimi pe baza header-elor de limbÄ ale web browser-elor lor. (EXPERIMENTAL, nu funcČ›ioneazÄ cu cache anonim)" @@ -915,8 +913,6 @@ ro: google_oauth2_client_id: "ID client al aplicaČ›iei tale Google." google_oauth2_client_secret: "Secret client al aplicaČ›iei tale Google." enable_twitter_logins: "ActiveazÄ autentificarea cu Twitter, necesitÄ twitter_consumer_key and twitter_consumer_secret" - twitter_consumer_key: "Cheie consumator pentru autentificarea cu Twitter, Ă®nregistratÄ la http://dev.twitter.com" - twitter_consumer_secret: "Secret consumator pentru autentificarea cu Twitter, Ă®nregistrat la http://dev.twitter.com" enable_instagram_logins: "ActiveazÄ autentificarea prin Instagram, necesitÄ instagram_consumer_key Č™i instagram_consumer_secret" instagram_consumer_key: "Cheie consumator pentru autentificarea Instagram" instagram_consumer_secret: "Autentificare Instagram pe bazÄ de secret consumator" @@ -931,7 +927,6 @@ ro: allow_restore: "BifeazÄ-l doar dacÄ doreČ™ti sÄ restaurezi un fiČ™ier de backup. AtenČ›ie, restaurarea poate Č™terge/Ă®nlocui TOATE datele din site!" maximum_backups: "Maximul de fiČ™iere backup pÄstrate. FiČ™ierele backup vechi sunt Č™terse automat" automatic_backups_enabled: "RuleazÄ automat operaČ›iuni de backup dupÄ cum este definit la frecvenČ›a de backup" - backup_frequency: "Cât de frecvent facem backup la site, Ă®n zile." enable_s3_backups: "ĂŽncarcÄ fiČ™ierele backup pe s3 când sunt complete. IMPORTANT: sunt necesare date de autentificare valide pentru S3 Ă®n secČ›iunea FIČ™IERE." s3_backup_bucket: "Containerul (bucket) de la distanČ›Ä care Č›ine backupurile. ATENČšIE: AsgurÄ-te cÄ e un container privat." s3_disable_cleanup: "DezactiveazÄ eliminarea backup-urilor de pe S3 când sunt Č™terse local." @@ -1369,48 +1364,6 @@ ro: subject_template: "ConfirmÄ cÄ nu mai vrei sÄ primeČ™ti actualizÄri pe email de la %{site_title}" invite_password_instructions: subject_template: "SeteazÄ parolÄ pentru contul tÄu %{site_name}" - test_mailer: - text_body_template: | - Acesta este un email de test de la - - [**%{base_url}**][0] - - A te asigura cÄ emailul se poate livra este o chestiune complicatÄ. Aici sunt câteva lucruri pe care ar trebui sÄ le verifici Ă®nainte de toate: - - - AsigurÄ-te *cu rigurozitate* cÄ la setÄrile site-ului, câmpul from: din `emailul de notificare` are adresa corectÄ. **Domeniul menČ›ionat Ă®n adresa din câmpul "from" al emailurilor pe care le trimiČ›i este domeniul Ă®n raport cu care se va face validarea emailului tÄu**. - - - Č™ti cum sÄ vizualizezi sursa brutÄ a emailului Ă®n clientul tÄu de mail, astfel Ă®ncât sÄ poČ›i extrage indicii preČ›ioase din header. ĂŽn Gmail, acest lucru se face cu opČ›iunea "aratÄ originalul" din meniu drop-down aflat Ă®n partea din dreapta-sus a fiecÄrui mail. - - - **IMPORTANT:** ISP-ul tÄu are introdusÄ Ă®nregistrarea pentru reverse DNS astfel Ă®ncât sa asocieze numele domeniilor cu adresele IP de pe care trimiČ›i mailuri? [VerificÄ-Č›i Ă®nregistrarea Reverse PTR][2] aici. DacÄ ISP-ul tÄu nu a introdus corect Ă®nregistrarea reverse DNS, este foarte puČ›in probabil cÄ emailul Č›i se va livra corect. - - - [ĂŽnregistrarea SPF][8] a domeniului tÄu este corectÄ? [VerificÄ-Č›i Ă®nregistrarea SPF][1] aici. ObservÄ cÄ TXT reprezintÄ tipul de Ă®nregistrare corect pentru SPF. - - - [ĂŽnregistrarea DKIM][3] a domeniului tÄu este corectÄ? Aceasta îți poate creČ™te semnificativ capacitatea emailului de a fi livrat. [VerificÄ-Č›i Ă®nregistrarea DKIM][7] aici. - - - DacÄ operezi propriul tÄu server de mail, asigurÄ-te cÄ IP-urile sale nu sunt listate pe [nicio listÄ neagrÄ de emailuri][4]. VerificÄ, de asemenea, cÄ trimite un hostname calificat Ă®n Ă®ntregime, care se rezolvÄ Ă®n DNS prin mesajul sÄu HELO. ĂŽn caz contrar, multe servicii de mail îți vor respinge emailul. - - - ĂŽČ›i recomandÄm cÄlduros sÄ **trimiČ›i un mail de probÄ [mail-tester.com][mt]** pentru a verifica dacÄ toate cele de mai sus funcČ›ioneazÄ corect. - - (Modalitatea *uČ™oarÄ* de a face asta este sÄ Ă®Č›i faci un cont gratuit pe [SendGrid][sg], [SparkPost][sp], [Mailgun][mg] sau pe [Mailjet][mj], care au planuri generoase de email gratuit Č™i suficiente pentru comunitÄČ›i mici. ĂŽnsÄ tot va trebui sÄ Ă®Č›i setezi Ă®nregistrÄrile SPF Č™i DKIM Ă®n DNS!) - - SperÄm cÄ vei primi acest test de livrare a emailurilor cu calificativul OK! - - Mult noroc, - - Prietenii tÄi de la [Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://goo.gl/r1WMF6 - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ flag_reasons: off_topic: "Postarea ta a fost marcatÄ cu marcaj de avertizare **Ă®n afara subiectului**: comunitatea simte cÄ nu se potriveČ™te subiectului, aČ™a cum e el momentan definit de titlu Č™i de prima postare." inappropriate: "Postarea ta a fost marcatÄ cu marcaj de avertizare **inadecvat**: comunitatea simte cÄ este ofensatoare, abuzivÄ sau Ă®n contradicČ›ie cu [ghidul comunitÄČ›ii noastre](/guidelines)." @@ -1720,7 +1673,7 @@ ro: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "Un rezumat scurt al %{site_link} de la ultima ta vizitÄ din %{last_seen_at}." since_last_visit: "De la ultima ta vizitÄ" diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index bf41ab5ad0..187937dc4a 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -75,7 +75,6 @@ ru: invalid: неверный is_invalid: "Не ŃовŃем ŃŹŃно, это предложение закончено?" contains_censored_words: "Ńодержит ŃледŃющие запрещенные Ńлова %{censored_words}" - matches_censored_pattern: "Ńодержит запрещенные на Ńайте выражения, которые ŃоответŃтвŃŃŽŃ‚ ŃĐ°Đ±Đ»ĐľĐ˝Ń %{censored_words}" less_than: должен быть меньŃе %{count} less_than_or_equal_to: должен быть меньŃе или равен %{count} not_a_number: не чиŃло @@ -101,7 +100,7 @@ ru: template: body: 'ОбнарŃжены ĐľŃибки в ŃледŃющих полях:' header: - one: 1 ĐľŃибка препятŃтвŃет Ńохранению %{model} + one: '1 ĐľŃибка препятŃтвŃет Ńохранению %{model}' few: '%{count} ĐľŃибки препятŃтвŃет Ńохранению %{model}' other: '%{count} ĐľŃибок препятŃтвŃŃŽŃ‚ Ńохранению %{model}' embed: @@ -190,7 +189,6 @@ ru: few: "Đзвините, новые пользователи могŃŃ‚ размещать только %{count} ŃŃылок в Ńообщении." many: "Đзвините, новые пользователи могŃŃ‚ размещать только %{count} ŃŃылок в Ńообщении." other: "Đзвините, новые пользователи могŃŃ‚ размещать только %{count} ŃŃылок в Ńообщении." - contains_blocked_words: "Đ’Đ°Ń ĐżĐľŃŃ‚ Ńодержит Ńлова, которые не разреŃены." spamming_host: "Đзвините, вы не можете размеŃтить ŃŃŃ‹Đ»ĐşŃ Đ˛ этом Ńообщении." user_is_suspended: "Заблокированным пользователям запрещено пиŃать." topic_not_found: "Что-то поŃло не так. Возможно, эта тема была закрыта или заархивирована, пока вы ее читали?" @@ -360,25 +358,25 @@ ru: title: "Добро пожаловать в Фойе" body: |2 - Поздравляем! :confetti_ball: + Поздравляем! :confetti_ball: - Đ•Ńли вы видите данное Ńообщение, вы Đ´ĐľŃтигли Ńровня **regular** (Ńровень доверия 3). + Đ•Ńли вы видите данное Ńообщение, вы Đ´ĐľŃтигли Ńровня **regular** (Ńровень доверия 3). - ĐŁĐ˛Đ°Ń ĐżĐľŃŹĐ˛Đ¸Đ»Đ¸ŃŃŚ новые возможноŃти … + ĐŁĐ˛Đ°Ń ĐżĐľŃŹĐ˛Đ¸Đ»Đ¸ŃŃŚ новые возможноŃти … - * Đ’Ń‹ можете редактировать заголовок любого обŃŃждения - * Đ’Ń‹ можете менять категорию обŃŃждения - * С ваŃих ŃŃылок ŃнимаетŃŃŹ ограничения nofollow ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) is removed) - * Đ’Ń‹ полŃчили Đ´ĐľŃŃ‚ŃĐż Đş закрытым категориям, которые Đ´ĐľŃŃ‚Ńпны только для Ńровня доверия 3 и выŃе - * Đ’Ń‹ можете Ńкрывать нежелательные Ńообщения в один клик + * Đ’Ń‹ можете редактировать заголовок любого обŃŃждения + * Đ’Ń‹ можете менять категорию обŃŃждения + * С ваŃих ŃŃылок ŃнимаетŃŃŹ ограничения nofollow ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) is removed) + * Đ’Ń‹ полŃчили Đ´ĐľŃŃ‚ŃĐż Đş закрытым категориям, которые Đ´ĐľŃŃ‚Ńпны только для Ńровня доверия 3 и выŃе + * Đ’Ń‹ можете Ńкрывать нежелательные Ńообщения в один клик - С полным ŃпиŃком привелегий вы можете ознакомитьŃŃŹ Ń‚ŃŃ‚ [current list of fellow regulars](/badges/3/regular). ПожалŃĐąŃта прочитайте это. + С полным ŃпиŃком привелегий вы можете ознакомитьŃŃŹ Ń‚ŃŃ‚ [current list of fellow regulars](/badges/3/regular). ПожалŃĐąŃта прочитайте это. - СпаŃибо Вам за то что являетеŃŃŚ важным членом наŃего ŃообщеŃтва! + СпаŃибо Вам за то что являетеŃŃŚ важным членом наŃего ŃообщеŃтва! - (Для полŃчения более детальной информации об Ńронях доверия поŃмотрите Ń‚ŃŃ‚ [see this topic][trust]. Напоминаем что данный Ńровень доверия ŃохраняетŃŃŹ Đ´Đľ тех пор пока вы придерживаетеŃŃŚ этих правил.) + (Для полŃчения более детальной информации об Ńронях доверия поŃмотрите Ń‚ŃŃ‚ [see this topic][trust]. Напоминаем что данный Ńровень доверия ŃохраняетŃŃŹ Đ´Đľ тех пор пока вы придерживаетеŃŃŚ этих правил.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "ОпиŃание раздела %{category}" replace_paragraph: "(Замените первый параграф лаконичным опиŃанием новой категории. Данное опиŃание бŃдет отражено в облаŃти выбора категории, ĐżĐľŃŤŃ‚ĐľĐĽŃ Đ¶ĐµĐ»Đ°Ń‚ĐµĐ»ŃŚĐ˝Đľ ограничитьŃŃŹ 200 Ńимволами. ** Пока вы не измените данное опиŃание или Ńоздадите темы, данная категория не появитŃŃŹ на Ńтранице категорий.**)" @@ -601,7 +599,6 @@ ru: long_form: 'отметить как неŃмеŃтное' notify_user: title: 'Отправить @{{username}} личное Ńообщение' - description: 'ĐŻ Ń…ĐľŃ‡Ń ĐľĐ±ŃŃдить это Ńообщение Ń Đ°Đ˛Ń‚ĐľŃ€ĐľĐĽ приватно и не привлекая внимания модераторов.' long_form: 'оповещаемый пользователь' email_title: 'ВаŃе Ńообщение в теме "%{title}"' email_body: "%{link}\n\n%{message}\n" @@ -932,8 +929,6 @@ ru: google_oauth2_client_id: "Client ID для ваŃего Google приложения." google_oauth2_client_secret: "Client secret для Google приложения" enable_twitter_logins: "РазреŃить идентификацию Ń Twitter, требŃет twitter_consumer_key и twitter_consumer_secret" - twitter_consumer_key: "ПотребительŃкий ключ для идентификации Ń Twitter, зарегиŃтрированный на http://dev.twitter.com" - twitter_consumer_secret: "ПотребительŃкий Ńекрет для идентификации Ń Twitter, зарегиŃтрированный на http://dev.twitter.com" enable_facebook_logins: "РазреŃить идентификацию Ń Facebook, требŃет facebook_app_id и facebook_app_secret" facebook_app_id: "id приложения для идентификации Ń Facebook, зарегиŃтрированный на https://developers.facebook.com/apps" facebook_app_secret: "Ńекрет приложения для идентификации Ń Facebook, зарегиŃтрированный на https://developers.facebook.com/apps" @@ -943,7 +938,6 @@ ru: readonly_mode_during_backup: "Включить режим \"только для чтения\" во время выполнения резервного копирования" allow_restore: "Позволить импорт, который может заменить ВСЕ данные Ńайта. ĐžŃтавьте выключенным, еŃли не планирŃете воŃŃтанавливать резервнŃŃŽ копию" maximum_backups: "МакŃимальное количеŃтво резервных копий Đş Ńохранению. Более Ńтарые резервные копии бŃĐ´ŃŃ‚ автоматичеŃки Ńдалены." - backup_frequency: "Как чаŃто Ńоздавать резервные копии форŃма, в днях" enable_s3_backups: "ЗагрŃжать резервные копии на S3 по заверŃению. УбедитеŃŃŚ, что наŃтройки S3 заполнены." s3_backup_bucket: "ĐĐ´Ń€ĐµŃ ĐżĐ°ĐżĐşĐ¸ Ńдаленного Ńервера для резервных копий. Đ’ĐťĐĐśĐĐťĐĐ•: УбедитеŃŃŚ, что меŃто назначения защищено от поŃторонних." active_user_rate_limit_secs: "Как чаŃто ĐĽŃ‹ обновляем поле 'last_seen_at', в ŃекŃндах" diff --git a/config/locales/server.sk.yml b/config/locales/server.sk.yml index 4f555f3d0e..83a41d2e1d 100644 --- a/config/locales/server.sk.yml +++ b/config/locales/server.sk.yml @@ -27,6 +27,7 @@ sk: posts: "prĂ­spevky" loading: "NaÄŤĂ­tava sa" log_in: "Prihlásenie" + submit: "odošli" purge_reason: "Automaticky zmazanĂ˝ ako opustenĂ˝, dezaktivovanĂ˝ účet" disable_remote_images_download_reason: "SĹĄahovanie vzdialenĂ˝ch obrázkov je vypnutĂ© kvĂ´li nedostatku diskovĂ©ho priestoru." anonymous: "AnonymnĂ˝" @@ -39,6 +40,8 @@ sk: default_subject: "Táto tĂ©ma musĂ­ maĹĄ názov" show_trimmed_content: "UkázaĹĄ orezanĂ˝ obsah" maximum_staged_user_per_email_reached: "DosiahnutĂ˝ maximálny poÄŤet doÄŤasnĂ˝ch používateÄľov vytvorenĂ˝ch emailom." + no_subject: "(bez predmetu)" + no_body: "(bez tela)" errors: empty_email_error: "Nastane, keÄŹ prijmeme Ăşplne prázdny mail." no_message_id_error: "Nastane, keÄŹ v maili chĂ˝ba hlaviÄŤka 'Message-Id'." @@ -94,8 +97,8 @@ sk: template: body: 'Nastal problĂ©m s nasledujĂşcimi poloĹľkami:' header: - one: UloĹľenie %{model} zlyhalo kĂ´li chybe - few: UloĹľenie %{model} zlyhalo kĂ´li %{count} chybám + one: 'UloĹľenie %{model} zlyhalo kĂ´li chybe' + few: 'UloĹľenie %{model} zlyhalo kĂ´li %{count} chybám' other: 'UloĹľenie %{model} zlyhalo kĂ´li %{count} chybám ' embed: load_from_remote: "Nastala chyba pri naÄŤĂ­tanĂ­ prĂ­spevku" @@ -150,6 +153,7 @@ sk: one: "Ä˝utujeme, novĂ­ používatelia mĂ´Ĺľu zmieniĹĄ v prĂ­spevku maximálne jednĂ©ho používateÄľa." few: "Ä˝utujeme, novĂ­ používatelia mĂ´Ĺľu zmieniĹĄ v prĂ­spevku maximálne %{count} používatelov." other: "Ä˝utujeme, novĂ­ používatelia mĂ´Ĺľu zmieniĹĄ v prĂ­spevku maximálne %{count} používatelov." + no_images_allowed_trust: "Ä˝utujeme, do prĂ­spevku nemĂ´Ĺľete vloĹľiĹĄ obrázok" no_images_allowed: "Ä˝utujeme, novĂ­ používatelia nemĂ´Ĺľu vkladaĹĄ obrázky do prĂ­spevkov." too_many_images: one: "Ä˝utujeme, novĂ­ používatelia mĂ´Ĺľu vloĹľiĹĄ maximálne jeden obrázok do prĂ­spevku." @@ -299,25 +303,25 @@ sk: title: "VĂ­tajte v SalĂłne" body: |2 - Gratulácia! :confetti_ball: + Gratulácia! :confetti_ball: - Ak vidĂ­te tĂşto tĂ©mu, boli ste nedávno povýšenĂ˝ na **pravidelnĂ˝** (ĂşroveĹ dĂ´very 3). + Ak vidĂ­te tĂşto tĂ©mu, boli ste nedávno povýšenĂ˝ na **pravidelnĂ˝** (ĂşroveĹ dĂ´very 3). - Teraz mĂ´Ĺľete … + Teraz mĂ´Ĺľete … - * UpravovaĹĄ nadpis akejkoÄľvek tĂ©my - * MeniĹĄ kategĂłriu akejkoÄľvek tĂ©my - * MaĹĄ sledovanĂ© všetky Vaše odkazy ([automatickĂ© nofollow](http://en.wikipedia.org/wiki/Nofollow) je odstránenĂ©) - * PrĂ­stup do privátnej katogĂłgie Lounge prĂ­stupnej iba ÄŤlenom s ĂşrovĹou dĂ´very 3 a vyššou - * SkryĹĄ spam pomocou vlajky + * UpravovaĹĄ nadpis akejkoÄľvek tĂ©my + * MeniĹĄ kategĂłriu akejkoÄľvek tĂ©my + * MaĹĄ sledovanĂ© všetky Vaše odkazy ([automatickĂ© nofollow](http://en.wikipedia.org/wiki/Nofollow) je odstránenĂ©) + * PrĂ­stup do privátnej katogĂłgie Lounge prĂ­stupnej iba ÄŤlenom s ĂşrovĹou dĂ´very 3 a vyššou + * SkryĹĄ spam pomocou vlajky - Tu je [aktuálny zoznam pravidelnĂ˝ch ÄŤlenov](/badges/3/regular). UrÄŤite ich pozdravte. + Tu je [aktuálny zoznam pravidelnĂ˝ch ÄŤlenov](/badges/3/regular). UrÄŤite ich pozdravte. - ÄŽakujeme, Ĺľe ste dĂ´leĹľitou súčasĹĄou našej komunity! + ÄŽakujeme, Ĺľe ste dĂ´leĹľitou súčasĹĄou našej komunity! - (Pre viac informáciĂ­ o Ăşrovniach dĂ´very, [pozrite tĂşto tĂ©mu][trust]. ProsĂ­m nezabudnite, Ĺľe iba ÄŤlenovia, ktorĂ˝ neustále spÄşĹajĂş poĹľiadavky zostanĂş platnĂ˝mi ÄŤlenmy.) + (Pre viac informáciĂ­ o Ăşrovniach dĂ´very, [pozrite tĂşto tĂ©mu][trust]. ProsĂ­m nezabudnite, Ĺľe iba ÄŤlenovia, ktorĂ˝ neustále spÄşĹajĂş poĹľiadavky zostanĂş platnĂ˝mi ÄŤlenmy.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "O kategĂłrii: %{category} " replace_paragraph: "(NahraÄŹte tento prvĂ˝ odstavec struÄŤnĂ˝m popisom Vašej novej kategĂłrie. Tento návod sa objavĂ­ v zozname kategĂłriĂ­, tak sa snaĹľte ho udrĹľaĹĄ pod 200 znakov. **KategĂłria sa neobjavĂ­ v zozname kategĂłriĂ­ pokĂ˝m neupravĂ­te tento popis alebo v nej nevytvorĂ­te tĂ©my.**)" @@ -509,7 +513,6 @@ sk: long_form: 'toto oznaÄŤ ako nevhodnĂ©' notify_user: title: 'PoslaĹĄ @{{username}} správu' - description: 'Chcem sa s touto osobou rozprávaĹĄ o jeho prĂ­spevku priamo a sĂşkromne.' long_form: 'úživateÄľovi bola zaslaná správa' email_title: 'Váš prĂ­spevok v "%{title}"' email_body: "%{link}\n\n%{message}" @@ -835,8 +838,6 @@ sk: google_oauth2_client_id: "Client ID Vašej Google aplikácie." google_oauth2_client_secret: "Client secret Vašej Google aplikácie." enable_twitter_logins: "PovoliĹĄ autentifikáciu cez Twitter, vyĹľaduje twitter_consmer_key a twiiter_consumer_secret" - twitter_consumer_key: "Consumer key pre Twitter autentifikáciu, registrovanĂ˝ na http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret pre Twitter autentifikáciu, registrovanĂ© na http://dev.twitter.com" enable_facebook_logins: "PovoliĹĄ autentifikáciu pomocou Facebook, vyĹľaduje facebook_app_id a facebook_app_secret." facebook_app_id: "App id pre Facebook autentifikáciu, registrovanĂ© na https://developers.facebook.com/apps" facebook_app_secret: "App secret pre Facebook autentifikáciu, registrovanĂ© na https://developers.facebook.com/apps" @@ -847,7 +848,6 @@ sk: allow_restore: "UmoĹľniĹĄ obnovu, ktorá nahradĂ­ VĹ ETKY data na stránkach! Ponechajte prázdne okrem prĂ­padu ak chcete obnoviĹĄ zo zálohy." maximum_backups: "Maximálny poÄŤet záloh udrĹľiavanĂ˝ch na disku. Staršie zálohy budu automaticky vymazanĂ©" automatic_backups_enabled: "SpustiĹĄ automatickĂ© zálohy podÄľa nastavenia intervalu záloh" - backup_frequency: "Ako ÄŤasto máme vytváraĹĄ zálohu stránok, v dĹoch." enable_s3_backups: "Po dokonÄŤenĂ­ nahraĹĄ zálohy na S3. DĂ”LEĹ˝ITÉ: poĹľaduje správne nastavenie prĂ­stupovĂ˝ch Ăşdajov na S3 v menu SĂşbory." s3_backup_bucket: "VzdialenĂ˝ S3 bucket na uloĹľenie záloh. VAROVANIE: Uistite sa, Ĺľe ide o sĂşkromnĂ˝ S3 bucket." backup_time_of_day: "ÄŚas v UTC, kedy sa má spustiĹĄ záloha." diff --git a/config/locales/server.sl.yml b/config/locales/server.sl.yml new file mode 100644 index 0000000000..7aff724c68 --- /dev/null +++ b/config/locales/server.sl.yml @@ -0,0 +1,80 @@ +# encoding: utf-8 +# +# Never edit this file. It will be overwritten when translations are pulled from Transifex. +# +# To work with us on translations, join this project: +# https://www.transifex.com/projects/p/discourse-org/ + +sl: + dates: + short_date_no_year: "D MMM" + short_date: "D MMM, YYYY" + long_date: "D MMMM, YYYY h:mma" + datetime_formats: &datetime_formats + formats: + short: "%d-%m-%Y" + short_no_year: "%B %-d" + date_only: "%B %-d, %Y" + long: "%B %-d, %Y, %l:%M%P" + date: + month_names: [null, Januar, Februar, Marec, April, Maj, Junij, Julij, Avgust, September, Oktober, November, December] + <<: *datetime_formats + time: + am: "am" + pm: "pm" + <<: *datetime_formats + title: "Discourse" + topics: "Teme" + loading: "Nalagam" + log_in: "Vpis" + errors: &errors + messages: + taken: "je Ĺľe uporabljeno" + exclusion: je rezervirano + has_already_been_used: "je Ĺľe v uporabi" + inclusion: ni vkljuÄŤeno na seznam + invalid: je neveljavno + less_than: mora biti manj kot %{count} + less_than_or_equal_to: mora biti manj ali enako kot %{count} + not_a_number: ni številka + not_an_integer: mora biti celo število + odd: mora biti liho število + too_long: + one: je predolgo (najveÄŤ 1 znak) + two: je predolgo (najveÄŤ 2 znaka) + few: je predolgo (najveÄŤ %{count} znakov) + other: je predolgo (najveÄŤ %{count} znakov) + too_short: + one: je prekratko (minimum je 1 znak) + two: je prekratko (minimum sta 2 znaka) + few: je prekratko (minimum je %{count} znakov) + other: je prekratko (minimum je %{count} znakov) + reading_time: "ÄŚas branja" + likes: "VšeÄŤki" + embed: + start_discussion: "ZaÄŤni razpravo" + continue: "Nadaljuj razpravo" + next_page: "naslednja stran →" + prev_page: "↠prejšnja stran" + page_num: "Stran %{num}" + home_title: "Domov" + author_wrote: "%{author} je napisal/a:" + num_participants: "UdeleĹľenci:" + read_full_topic: "Preberi celo temo" + private_message_abbrev: "Msg" + rss_description: + latest: "Zadnje teme" + excerpt_image: "slika" + groups: + default_names: + everyone: "vsi" + admins: "administratorji" + moderators: "moderatorji" + site_settings: + avatar_sizes: "Seznam avtomatsko generiranih velikosti avatarjev." + activemodel: + errors: + <<: *errors + activerecord: + errors: + <<: *errors diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml index fccd25fc9d..bbd9834be9 100644 --- a/config/locales/server.sq.yml +++ b/config/locales/server.sq.yml @@ -168,25 +168,25 @@ sq: title: "MirĂ« se vini nĂ« Lounge" body: |2 - Congratulations! :confetti_ball: + Congratulations! :confetti_ball: - If you can see this topic, you were recently promoted to **regular** (trust level 3). + If you can see this topic, you were recently promoted to **regular** (trust level 3). - You can now … + You can now … - * Edit the title of any topic - * Change the category of any topic - * Have all your links followed ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) is removed) - * Access a private Lounge category only visible to users at trust level 3 and higher - * Hide spam with a single flag + * Edit the title of any topic + * Change the category of any topic + * Have all your links followed ([automatic nofollow](http://en.wikipedia.org/wiki/Nofollow) is removed) + * Access a private Lounge category only visible to users at trust level 3 and higher + * Hide spam with a single flag - Here's the [current list of fellow regulars](/badges/3/regular). Be sure to say hi. + Here's the [current list of fellow regulars](/badges/3/regular). Be sure to say hi. - Thanks for being an important part of this community! + Thanks for being an important part of this community! - (For more information on trust levels, [see this topic][trust]. Please note that only members who continue to meet the requirements over time will remain regulars.) + (For more information on trust levels, [see this topic][trust]. Please note that only members who continue to meet the requirements over time will remain regulars.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Rreth kategorisĂ« %{category}" errors: @@ -338,7 +338,6 @@ sq: long_form: 'sinjalizoi kĂ«tĂ« postim si tĂ« papĂ«rshtatshĂ«m' notify_user: title: 'DĂ«rgoji @{{username}} njĂ« mesazh' - description: 'DĂ«shiroj tĂ« flas me kĂ«tĂ« person drejtpĂ«rdrejt dhe privatish rreth kĂ«tij postimi.' long_form: 'messaged user' email_title: 'Postimi juaj nĂ« "%{title}"' email_body: "%{link}\n\n%{message}" @@ -643,8 +642,6 @@ sq: google_oauth2_client_id: "Client ID of your Google application." google_oauth2_client_secret: "Client secret of your Google application." enable_twitter_logins: "Enable Twitter authentication, requires twitter_consumer_key and twitter_consumer_secret" - twitter_consumer_key: "Consumer key for Twitter authentication, registered at http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret for Twitter authentication, registered at http://dev.twitter.com" enable_facebook_logins: "Enable Facebook authentication, requires facebook_app_id and facebook_app_secret" facebook_app_id: "App id for Facebook authentication, registered at https://developers.facebook.com/apps" facebook_app_secret: "App secret for Facebook authentication, registered at https://developers.facebook.com/apps" @@ -653,7 +650,6 @@ sq: github_client_secret: "Client secret for Github authentication, registered at https://github.com/settings/applications" allow_restore: "Allow restore, which can replace ALL site data! Leave false unless you plan to restore a backup" maximum_backups: "The maximum amount of backups to keep on disk. Older backups are automatically deleted" - backup_frequency: "How frequently we create a site backup, in days." enable_s3_backups: "Upload backups to S3 when complete. IMPORTANT: requires valid S3 credentials entered in Files settings." s3_backup_bucket: "The remote bucket to hold backups. WARNING: Make sure it is a private bucket." active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds" @@ -1105,7 +1101,7 @@ sq: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "NjĂ« pĂ«rmbledhje e shkurtĂ«r e temave tek %{site_link} qĂ« prej vizitĂ«s tuaj tĂ« fundit mĂ« %{last_seen_at}" subject_template: "[%{email_prefix}] PĂ«rmbledhja" diff --git a/config/locales/server.sr.yml b/config/locales/server.sr.yml index f2a4d6fdc8..b3cdca8487 100644 --- a/config/locales/server.sr.yml +++ b/config/locales/server.sr.yml @@ -37,7 +37,6 @@ sr: exclusion: je rezervisano greater_than: mora biti veće od %{count} contains_censored_words: "sadrĹľi sledeće cenzurisane reÄŤi: 1%{censored_words}" - matches_censored_pattern: "sadrĹľi sledeće reÄŤi koje se poklapaju sa registrom cenzurisanih reÄŤi: 1%{censored_wrods}" less_than: mora biti manje od 1%{count} not_a_number: nije broj not_an_integer: mora biti ceo broj @@ -63,7 +62,6 @@ sr: start_discussion: "ZapoÄŤnite diskusiju" continue: "Nastavite diskusiju" no_links_allowed: "Ĺ˝ao nam je, novi korisnici ne mogu postavljati linkove u svoje postove." - contains_blocked_words: "Vaš post, sadrĹľi reÄŤi koje nisu dozvoljene." user_is_suspended: "Suspendovanim korisnicima nije dozvoljeno da objavljuju postove." topic_not_found: "Nešto se dogodilo. MoĹľda je tema zatvorena ili izbrisana dok ste je gledali?" not_accepting_pms: "Ĺ˝ao nam je, %{username} trenutno ne prihvata privatne poruke." diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index 878bb0f76b..6b53e00825 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -70,7 +70,7 @@ sv: has_already_been_used: "har redan använts" inclusion: är inte inkluderad i listan invalid: är ogiltig - is_invalid: "Verkar oklart, det ären komplett mening" + is_invalid: "Verkar oklart, är det en komplett mening?" contains_censored_words: "innehĂĄller följande censurerade ord:%(censurerade_ord)" less_than: mĂĄste vara mindre än %{count} less_than_or_equal_to: mĂĄste vara mindre eller lika med %{count} @@ -94,7 +94,7 @@ sv: template: body: 'Det uppstod problem med följande fält:' header: - one: Ett fel förhindrade %{model} frĂĄn att sparas + one: 'Ett fel förhindrade %{model} frĂĄn att sparas' other: '%{count} fel förhindrade %{model} frĂĄn att sparas' embed: load_from_remote: "Det uppstod ett fel vid laddning av inlägget." @@ -113,7 +113,7 @@ sv: topic_invite: user_exists: "Tyvärr, den användaren har redan bjudits in. Du kan endast bjuda in en användare till ett ämne en gĂĄng." backup: - operation_already_running: "En operation är redan igĂĄng. Kan inte starta ett nytt jobb just nu." + operation_already_running: "En operation är redan igĂĄng och därför kan inte ett nytt jobb snartas just nu." backup_file_should_be_tar_gz: "Backup-filen ska vara ett .tar.gz arkiv." not_enough_space_on_disk: "Det finns inte tillräckligt mycket utrymme pĂĄ disken för att ladda upp denna backup." invalid_filename: "Backupfilnamnet innehĂĄller ogiltiga tecken. Giltiga tecken är a-z 0-9 . - _." @@ -297,25 +297,25 @@ sv: title: "Välkommen till loungen" body: |2 - Grattis! :confetti_ball: + Grattis! :confetti_ball: - Om du kan se det här ämnet sĂĄ har du nyligen blivit upphöjd till **regelbunden** användare (förtroendenivĂĄ 3). + Om du kan se det här ämnet sĂĄ har du nyligen blivit upphöjd till **regelbunden** användare (förtroendenivĂĄ 3). - Du kan nu … + Du kan nu … - * Redigera rubriken för alla ämnen - * Ă„ndra kategorin för alla ämnen - * FĂĄ alla dina länkar följda ([automatiskt nofollow](http://en.wikipedia.org/wiki/Nofollow) är borttagna) - * FĂĄ ĂĄtkomst till en privat Lounge-kategori som endast är synlig för användare som har förtroendenivĂĄ 3 eller högre - * Gömma spam med en enda flagga + * Redigera rubriken för alla ämnen + * Ă„ndra kategorin för alla ämnen + * FĂĄ alla dina länkar följda ([automatiskt nofollow](http://en.wikipedia.org/wiki/Nofollow) är borttagna) + * FĂĄ ĂĄtkomst till en privat Lounge-kategori som endast är synlig för användare som har förtroendenivĂĄ 3 eller högre + * Gömma spam med en enda flagga - Här är den [aktuella listan över regelbundna användare](/badges/3/regular). Glöm inte att hälsa. + Här är den [aktuella listan över regelbundna användare](/badges/3/regular). Glöm inte att hälsa. - Tack för att du är en viktig del av det här forumet! + Tack för att du är en viktig del av det här forumet! - (För mer information om förtroendenivĂĄer, [se det här ämnet][trust]. Notera att endast medlemmar som fortsätter att tillfredställa kraven över tid kommer att fortsätta vara regelbundna medlemmar.) + (För mer information om förtroendenivĂĄer, [se det här ämnet][trust]. Notera att endast medlemmar som fortsätter att tillfredställa kraven över tid kommer att fortsätta vara regelbundna medlemmar.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Om kategorin %{category}" replace_paragraph: "(Byt ut den här första paragrafen med en sammanfattad beskrivning av din nya kategori. Den här vägledningen kommer att dyka upp i kategorins urvalsomrĂĄde, sĂĄ försök att hĂĄlla det kortare än 200 tecken. **Tills du har redigerat den här beskrivningen eller skapat nya ämnen sĂĄ kommer den här kategorin inte att synas pĂĄ kategorisidan.**)" @@ -485,7 +485,6 @@ sv: long_form: 'flagga detta som olämpligt' notify_user: title: 'Skicka ett meddelande till @{{username}} ' - description: 'Jag vill prata med den här personen direkt och privat om inlägget. ' long_form: 'notifierad användare' email_title: 'Du skrev i "%{title}"' email_body: "%{link}\n\n%{message}" @@ -862,8 +861,6 @@ sv: google_oauth2_client_id: "Klient-ID för din Google-applikation." google_oauth2_client_secret: "Klienthemlighet för din Google-applikation." enable_twitter_logins: "Aktivera Twitter autentisering, kräver twitter_consumer_key och twitter_consumer_secret" - twitter_consumer_key: "Konsumentnyckel för Twitter autentisering, registrerad pĂĄ http://dev.twitter.com" - twitter_consumer_secret: "Konsumenthemlighet för Twitter autentisering, registrerad pĂĄ http://dev.twitter.com" enable_instagram_logins: "TillĂĄt Instagramautentisering, kräver instagram_consumer_key och instagram_consumer_secret" instagram_consumer_key: "Konsumentnyckel för Instagram-autentisering" instagram_consumer_secret: "Konsumenthemlighet för Instagram-autentisering" @@ -877,7 +874,6 @@ sv: allow_restore: "TillĂĄt ĂĄterställning, vilket kan ersätta ALL data pĂĄ webbplatsen! Lämna avbockad om du inte planerar att ĂĄterställa en säkerhetskopia" maximum_backups: "Högsta antal säkerhetskopior att spara pĂĄ disken. Ă„ldre säkerhetskopior raderas automatiskt" automatic_backups_enabled: "Kör automatiska säkerhetskopior som definierats i säkerhetskopieringsfrekvens" - backup_frequency: "Hur frekvent vi skapar en säkerhetskopia, i dagar." enable_s3_backups: "Ladda upp säkerhetskopior till S3 vid färdigställning. VIKTIGT: kräver giltiga S3-kreditiv inlagda i Webbplatsinställningar > Filer." s3_backup_bucket: "Den externa behĂĄllaren för säkerhetskopieringar. VARNING: Se till att det här en privat behĂĄllare." s3_disable_cleanup: "Inaktivera borttagande av säkerhetskopior frĂĄn S3 när de tagits bort lokalt." @@ -1582,7 +1578,7 @@ sv: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "En kortfattad sammanfattning av %{site_link} sedan ditt senaste besök den %{last_seen_at}" since_last_visit: "Sedan ditt senaste besök" diff --git a/config/locales/server.te.yml b/config/locales/server.te.yml index 5af4961c4e..519c938e98 100644 --- a/config/locales/server.te.yml +++ b/config/locales/server.te.yml @@ -56,7 +56,7 @@ te: template: body: 'ఠదిగŕ±ŕ°µ క్షేత్రాలతో సమస్య ఉంది' header: - one: ŕ°’ŕ°• దోషం వల్ల ŕ° %[model] భద్రపరŕ±ŕ°šŕ±ŕ°ź వీలవలేదౠ+ one: 'ŕ°’ŕ°• దోషం వల్ల ŕ° %[model] భద్రపరŕ±ŕ°šŕ±ŕ°ź వీలవలేదŕ±' other: '%{count} దోషాల వల్ల %{model} భద్రపరŕ±ŕ°šŕ±ŕ°ź వీలవలేదŕ±' embed: load_from_remote: "ŕ° ŕ°źŕ°Şŕ°ľ లోడింగŕ±ŕ°˛ŕ±‹ దోషం" diff --git a/config/locales/server.th.yml b/config/locales/server.th.yml index dd3fd19d26..5b7d32bf5a 100644 --- a/config/locales/server.th.yml +++ b/config/locales/server.th.yml @@ -98,7 +98,7 @@ th: user_posted_pm_staged: text_body_template: |2 - %{message} + %{message} page_not_found: see_more: "อืŕąŕ¸™" search_google: "ŕ¸ŕ¸ąŕą€ŕ¸ŕ¸´ŕą‰ŕ¸Ą" diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index 40c01b079b..d2bbee163f 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -72,15 +72,19 @@ tr_TR: one: "Kayıt silinemiyor çünkĂĽ bir bağımlı %{record} var" many: "Kayıt silinemiyor çünkĂĽ bir bağımlı %{record} var" too_long: + one: çok uzun (en fazla %{count} karakter) other: çok uzun (en fazla %{count} karakter) too_short: + one: çok kısa (en az %{count} karakter olabilir) other: çok kısa (en az %{count} karakter olabilir) wrong_length: + one: yanlış uzunlukta (%{count} karakter olmalı) other: yanlış uzunlukta (%{count} karakter olmalı) other_than: "%{count} dışında bir rakam olmalı" template: body: 'Ĺžu alanlarla ilgili problem yaĹźandı:' header: + one: '%{count} hata bu %{model} kaydının alınmasını engelledi' other: '%{count} hata bu %{model} kaydının alınmasını engelledi' embed: load_from_remote: "Gönderi yĂĽklenirken bir hata oluĹźtu." @@ -105,33 +109,41 @@ tr_TR: reading_time: "Okuma Zamanı" likes: "BeÄźeniler" too_many_replies: + one: "ĂśzgĂĽnĂĽz, yeni kullanıcılar geçici olarak aynı konu içinde sadece %{count} cevap ile sınırlılar. " other: "ĂśzgĂĽnĂĽz, yeni kullanıcılar geçici olarak aynı konu içinde sadece %{count} cevap ile sınırlılar. " embed: start_discussion: "Tartışma BaĹźlat" continue: "Tartışmaya Devam Et" referer: "Yönlendiren:" more_replies: + one: "%{count} cevap daha" other: "%{count} cevap daha" loading: "Tartışma YĂĽkleniyor..." permalink: "Kalıcı BaÄźlantı" imported_from: "Bu ilave bir tartışmadır, asıl konu adresi %{link}" in_reply_to: "â–¶ %{username}" replies: + one: "%{count} cevap" other: "%{count} cevap" no_mentions_allowed: "ĂśzgĂĽnĂĽz, diÄźer kullanıcılardan bahsedemezsiniz." too_many_mentions: + one: "ĂśzgĂĽnĂĽz, bir gönderide sadece %{count} kullanıcıdan bahsedebilirsiniz." other: "ĂśzgĂĽnĂĽz, bir gönderide sadece %{count} kullanıcıdan bahsedebilirsiniz." no_mentions_allowed_newuser: "ĂśzgĂĽnĂĽz, yeni kullanıcılar diÄźer kullanıcılardan bahsedemezler." too_many_mentions_newuser: + one: "ĂśzgĂĽnĂĽz, yeni kullanıcılar bir gönderide sadece %{count} kullanıcıdan bahsedebilirler." other: "ĂśzgĂĽnĂĽz, yeni kullanıcılar bir gönderide sadece %{count} kullanıcıdan bahsedebilirler." no_images_allowed: "ĂśzgĂĽnĂĽz, yeni kullanıcılar gönderilere resim koyamazlar." too_many_images: + one: "ĂśzgĂĽnĂĽz, yeni kullanıcılar bir gönderiye sadece %{count} resim koyabilirler. " other: "ĂśzgĂĽnĂĽz, yeni kullanıcılar bir gönderiye sadece %{count} resim koyabilirler. " no_attachments_allowed: "ĂśzgĂĽnĂĽz, yeni kullanıcılar gönderilere bir Ĺźey ekleyemezler." too_many_attachments: + one: "ĂśzgĂĽnĂĽz, yeni kullanıcılar bir gönderiye sadece %{count} eklenti koyabilirler." other: "ĂśzgĂĽnĂĽz, yeni kullanıcılar bir gönderiye sadece %{count} eklenti koyabilirler." no_links_allowed: "ĂśzgĂĽnĂĽz, yeni kullanıcılar gönderilere baÄźlantı ekleyemezler." too_many_links: + one: "ĂśzgĂĽnĂĽz, yeni kullanıcılar bir gönderiye sadece %{count} baÄźlantı ekleyebilirler." other: "ĂśzgĂĽnĂĽz, yeni kullanıcılar bir gönderiye sadece %{count} baÄźlantı ekleyebilirler." spamming_host: "ĂśzgĂĽnĂĽz bu sunucuya baÄźlantı veremezsiniz." user_is_suspended: "UzaklaĹźtırılmış kullanıcılar gönderi yapamazlar." @@ -191,6 +203,7 @@ tr_TR: trust_level_4: "gĂĽven_seviyesi_4" education: until_posts: + one: "%{count} gönderi" other: "%{count} gönderi" new-topic: | %{site_name} topluluÄźuna hoĹź geldiniz — **yeni bir sohbet baĹźlattığınız için teĹźekkĂĽr ederiz! ** @@ -255,25 +268,25 @@ tr_TR: title: "Lobiye hoĹź geldiniz" body: |2 - Tebrikler! :confetti_ball: + Tebrikler! :confetti_ball: - Bu konuyu görebiliyorsanız, yakın sĂĽre önce **mĂĽdavim** (gĂĽven seviyesi 3) seviyesine terfi etmiĹźsiniz demektir. + Bu konuyu görebiliyorsanız, yakın sĂĽre önce **mĂĽdavim** (gĂĽven seviyesi 3) seviyesine terfi etmiĹźsiniz demektir. - Artık … + Artık … - * Herhangi bir konunun baĹźlık bilgisini dĂĽzenleyebilirsiniz - * Herhangi bir konunun kategorisini deÄźiĹźtirebilirsiniz - * EklediÄźiniz linkler takip edilebilir durumunda olacaktır ([otomatik nofollow] (http://en.wikipedia.org/wiki/Nofollow) kaldırıldı) - * Sadece 3. gĂĽven seviyesi ve ĂĽzerindekilere gözĂĽken özel lobi kategorisine eriĹźebilirsiniz - * Tek bildirmeyle istenmeyen içerikli iletileri gizleyebilirsiniz + * Herhangi bir konunun baĹźlık bilgisini dĂĽzenleyebilirsiniz + * Herhangi bir konunun kategorisini deÄźiĹźtirebilirsiniz + * EklediÄźiniz linkler takip edilebilir durumunda olacaktır ([otomatik nofollow] (http://en.wikipedia.org/wiki/Nofollow) kaldırıldı) + * Sadece 3. gĂĽven seviyesi ve ĂĽzerindekilere gözĂĽken özel lobi kategorisine eriĹźebilirsiniz + * Tek bildirmeyle istenmeyen içerikli iletileri gizleyebilirsiniz - [Ĺžu anki mĂĽdavim seviyedeki kiĹźilerin listesi](/badges/3/regular). Merhaba demeyi unutmayın. + [Ĺžu anki mĂĽdavim seviyedeki kiĹźilerin listesi](/badges/3/regular). Merhaba demeyi unutmayın. - Bu topluluÄźun önemli bir parçası olduÄźunuz için teĹźekkĂĽr ederiz! + Bu topluluÄźun önemli bir parçası olduÄźunuz için teĹźekkĂĽr ederiz! - (GĂĽven seviyeleriyle ilgili daha fazla bilgi için, [bu konuya bakın][trust]. LĂĽtfen, sadece sĂĽrekli olarak kriterlere uyan ĂĽyelerin standart seviyede kalacağını unutmayın.) + (GĂĽven seviyeleriyle ilgili daha fazla bilgi için, [bu konuya bakın][trust]. LĂĽtfen, sadece sĂĽrekli olarak kriterlere uyan ĂĽyelerin standart seviyede kalacağını unutmayın.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "%{category} kategorisi hakkında " replace_paragraph: "(Bu paragrafın yerine yeni kategorinizin kısa bir açıklamasını girin. Buraya yazılanlar kategori seçim alanında görĂĽneceÄźi için, açıklamanızı 200 karakterin altında tutmaya çalışın.**Siz bu metni dĂĽzenleyene veya herhangi bir konu oluĹźturana kadar bu kategori, kategori sayfasında gözĂĽkmeyecek.**)" @@ -289,6 +302,7 @@ tr_TR: uncategorized: "Kategorisiz silinemez" has_subcategories: "Alt kategorileri bulunduÄźu için bu kategori silinemez." topic_exists: + one: "Bu kategoriyi silemezsiniz çünkĂĽ %{count} konusu bulunuyor. En eski konu: %{topic_link}." other: "Bu kategoriyi silemezsiniz çünkĂĽ %{count} konusu bulunuyor. En eski konu: %{topic_link}." topic_exists_no_oldest: "Bu kategoriyi silemezsiniz çünkĂĽ %{count} konusu bulunuyor." uncategorized_description: "Kategoriye ihtiyacı olmayan, ya da diÄźer kategorilere uymayan konular." @@ -319,59 +333,84 @@ tr_TR: live_post_counts: "Canlı gönderi sayımları için çok hızlı istek yapıyorsunuz. LĂĽtfen tekrar denemeden önce %{time_left} bekleyin." unsubscribe_via_email: "GĂĽnlĂĽk e-posta ile abonelikten çıkma limitinizi aĹźtınız. LĂĽtfen tekrar denemeden önce %{time_left} bekleyin." hours: + one: "%{count} saat" other: "%{count} saat" minutes: + one: "%{count} dakika" other: "%{count} dakika" seconds: + one: "%{count} saniye" other: "%{count} saniye" datetime: distance_in_words: half_a_minute: "< 1dk" less_than_x_seconds: + one: "< %{count}sn" other: "< %{count}sn" x_seconds: + one: "%{count}sn" other: "%{count}sn" less_than_x_minutes: + one: "< %{count}dk" other: "< %{count}dk" x_minutes: + one: "%{count}dk" other: "%{count}dk" about_x_hours: + one: "%{count}sa" other: "%{count}sa" x_days: + one: "%{count}gĂĽn" other: "%{count}gĂĽn" about_x_months: + one: "%{count}ay" other: "%{count}ay" x_months: + one: "%{count}ay" other: "%{count}ay" about_x_years: + one: "%{count}yıl" other: "%{count}yıl" over_x_years: + one: "> %{count}yıl" other: "> %{count}yıl" almost_x_years: + one: "%{count}yıl" other: "%{count}yıl" distance_in_words_verbose: half_a_minute: "Ĺźu anda" less_than_x_seconds: + one: "hemen Ĺźimdi" other: "hemen Ĺźimdi" x_seconds: + one: "%{count} saniye önce" other: "%{count} saniye önce" less_than_x_minutes: + one: "son %{count} dakika içerisinde" other: "son %{count} dakika içerisinde" x_minutes: + one: "%{count} dakika önce" other: "%{count} dakika önce" about_x_hours: + one: "%{count} saat önce" other: "%{count} saat önce" x_days: + one: "%{count} gĂĽn önce" other: "%{count} gĂĽn önce" about_x_months: + one: "yaklaşık %{count} ay önce" other: "yaklaşık %{count} ay önce" x_months: + one: "%{count} ay önce" other: "%{count} ay önce" about_x_years: + one: "yaklaşık %{count} yıl önce" other: "yaklaşık %{count} yıl önce" over_x_years: + one: "%{count} yıldan daha fazla önce" other: "%{count} yıldan daha fazla önce" almost_x_years: + one: "neredeyse %{count} yıl önce" other: "neredeyse %{count} yıl önce" password_reset: no_token: "ĂśzgĂĽnĂĽz, bu parola deÄźiĹźtirme baÄźlantısı çok eski. Yeni bir baÄźlantı almak için lĂĽtfen 'GiriĹź Yap' tuĹźuna basın ve 'Parolamı unuttum'u seçin." @@ -413,7 +452,6 @@ tr_TR: long_form: 'bunu uygunsuz olarak bildirdi' notify_user: title: '@{{username}} adlı kullanıcıya ileti gönder' - description: 'Bu kiĹźiyle gönderisi hakkında doÄźrudan ve gizli olarak konuĹźmak istiyorum.' long_form: 'ileti gönderilen kullanıcı' email_title: '"%{title}" baĹźlıklı gönderiniz' email_body: "%{link}\n\n%{message}" @@ -758,8 +796,6 @@ tr_TR: google_oauth2_client_id: "Google uygulamanıza ait MĂĽĹźteri ID'si." google_oauth2_client_secret: "Google uygulamanıza ait client secret deÄźeri." enable_twitter_logins: "Twitter doÄźrulamasını etkinleĹźtir. twitter_consumer_key ve twitter_consumer_secret gereklidir" - twitter_consumer_key: "Twitter doÄźrulaması için gereken, http://dev.twitter.com adresinde kayıtlı consumer key" - twitter_consumer_secret: "Twitter doÄźrulaması için gereken, http://dev.twitter.com adresinde kayıtlı consumer secret" enable_instagram_logins: "Instagram doÄźrulamasını etkinleĹźtir, instagram_consumer_key ve instagram_consumer_secret gereklidir" enable_facebook_logins: "Facebook doÄźrulamasını etkinleĹźtir. facebook_app_id ve facebook_app_secret gerekir" facebook_app_id: "Facebook kimlik doÄźrulama için gerekli, https://developers.facebook.com/apps sayfasında bulunan app id" @@ -771,7 +807,6 @@ tr_TR: allow_restore: "Geri almaya izin ver. Sitedeki TĂśM verileri deÄźiĹźtirebilir! Bir yedeklemeyi geri yĂĽklemeyi planlamıyorsanız devre dışı bırakın." maximum_backups: "Diskte tutulacak en fazla yedek sayısı. Eski yedekler otomatik olarak silinir." automatic_backups_enabled: "Yedek sıklığında tanımlandığı gibi otomatik yedek almayı çalıştır" - backup_frequency: "Hangi sıklıkta bir site yedeÄźi oluĹźtururuz, gĂĽn olarak." enable_s3_backups: "Tamamlanınca yedeklemeleri S3'e yĂĽkle. Ă–NEMLİ: Dosyalar ayarında doÄźru S3 girilmesini gerektirir" s3_backup_bucket: "Yedeklemelerin yĂĽklenmesi için uzak biriktirme yeri. UYARI: Ă–zel bir biriktirme yeri olduÄźundan emin olun" s3_disable_cleanup: "Yerel olarak silinen yedeklerin S3 sunucularından silinmesini kapat" @@ -1030,14 +1065,17 @@ tr_TR: not_seen_in_a_month: "HoĹź geldiniz! Bir sĂĽredir yoktunuz. Bunlar sizin yokluÄźunuzda en gözde olan konular." merge_posts: edit_reason: + one: "%{count} gönderi %{username} tarafından birleĹźtirildi" other: "%{count} gönderi %{username} tarafından birleĹźtirildi" errors: different_topics: "Farklı konulara ait gönderiler birleĹźtirilemez." different_users: "Farklı kullanıcılara ait gönderiler birleĹźtirilemez." move_posts: new_topic_moderator_post: + one: "%{count} gönderi yeni bir konu için ayıklandı: %{topic_link}" other: "%{count} gönderi yeni bir konu için ayıklandı: %{topic_link}" existing_topic_moderator_post: + one: "%{count} gönderi var olan bir konu içinde birleĹźtirildi: %{topic_link}" other: "%{count} gönderi var olan bir konu içinde birleĹźtirildi: %{topic_link}" change_owner: post_revision_text: "Sahiplik %{old_user} hesabından %{new_user} hesabına aktarıldı" @@ -1048,20 +1086,28 @@ tr_TR: closed_enabled: "Konu Ĺźimdi kapatıldı. Artık yeni cevap yazılmasına izin yok. " closed_disabled: "Konu Ĺźimdi açıldı. Yeni cevaplara izin var." autoclosed_message_max_posts: + one: "Bu ileti %{count} olan en fazla cevap limitini aĹźtığı için otomatik olarak kapatıldı." other: "Bu ileti %{count} olan en fazla cevap limitini aĹźtığı için otomatik olarak kapatıldı." autoclosed_topic_max_posts: + one: "Bu konu %{count} olan en fazla cevap limitini aĹźtığı için otomatik olarak kapatıldı." other: "Bu konu %{count} olan en fazla cevap limitini aĹźtığı için otomatik olarak kapatıldı." autoclosed_enabled_days: + one: "Bu konu %{count} gĂĽn sonunda konu otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." other: "Bu konu %{count} gĂĽn sonunda konu otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." autoclosed_enabled_hours: + one: "%{count} saat sonunda konu otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." other: "%{count} saat sonunda konu otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." autoclosed_enabled_minutes: + one: "%{count} dakika sonunda konu otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." other: "%{count} dakika sonunda konu otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." autoclosed_enabled_lastpost_days: + one: "Bu konu son cevaptan %{count} gĂĽn sonra otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." other: "Bu konu son cevaptan %{count} gĂĽn sonra otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." autoclosed_enabled_lastpost_hours: + one: "Bu konu son cevaptan %{count} saat sonra otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." other: "Bu konu son cevaptan %{count} saat sonra otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." autoclosed_enabled_lastpost_minutes: + one: "Bu konu son cevaptan %{count} dakika sonra otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." other: "Bu konu son cevaptan %{count} dakika sonra otomatik olarak kapatıldı. Yeni cevap girilmesine izin verilmiyor." autoclosed_disabled: "Konu Ĺźimdi açıldı. Yeni cevaplara izin var." autoclosed_disabled_lastpost: "Konu Ĺźimdi açıldı. Yeni cevaplara izin var." @@ -1110,6 +1156,7 @@ tr_TR: max_new_accounts_per_registration_ip: "Mevcut IP adresiniz ĂĽzerinden yeni kayıt iĹźlemine izin verilmiyor. (Belirlenen sınır aşıldı.) Bir görevli ile iletiĹźime geçin." flags_reminder: subject_template: + one: "İlgilenilmesi gereken %{count} bildirim var" other: "İlgilenilmesi gereken %{count} bildirim var" unsubscribe_mailer: subject_template: "%{site_title} ĂĽzerinden daha fazla e-posta gĂĽncellemesi almak istemediÄźinizi onaylayın" @@ -1200,6 +1247,7 @@ tr_TR: subject_template: "Yeni hesap askıda" pending_users_reminder: subject_template: + one: "%{count} kullanıcı onay bekliyor" other: "%{count} kullanıcı onay bekliyor" text_body_template: | Bu foruma giriĹź yapabilmek için onayınızı (ya da reddinizi) bekleyen yeni kullanıcı kayıtları var. @@ -1342,7 +1390,7 @@ tr_TR: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "%{last_seen_at} tarihindeki son giriĹźinizden beri %{site_link} sitesine olanların kısa bir özeti" since_last_visit: "Son ziyaretinizden bu yana" diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml index a702350c09..a4003c11b2 100644 --- a/config/locales/server.uk.yml +++ b/config/locales/server.uk.yml @@ -315,8 +315,6 @@ uk: min_password_length: "Мінімальна довжина пароля." enable_yahoo_logins: "УвімкнŃти вхід через Yahoo" enable_twitter_logins: "УвімкнŃти вхід через Twitter; потребŃŃ” twitter_consumer_key та twitter_consumer_secret" - twitter_consumer_key: "Consumer key для Đ˛Ń…ĐľĐ´Ń Ń‡ĐµŃ€ĐµĐ· Twitter, зареєŃтрований на http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret для Đ˛Ń…ĐľĐ´Ń Ń‡ĐµŃ€ĐµĐ· Twitter, зареєŃтрований на http://dev.twitter.com" enable_facebook_logins: "УвімкнŃти вхід через Facebook; потребŃŃ” facebook_app_id та facebook_app_secret" facebook_app_id: "App id для Đ˛Ń…ĐľĐ´Ń Ń‡ĐµŃ€ĐµĐ· Facebook, зареєŃтрований на https://developers.facebook.com/apps" facebook_app_secret: "App secret для Đ˛Ń…ĐľĐ´Ń Ń‡ĐµŃ€ĐµĐ· Facebook, зареєŃтрований на https://developers.facebook.com/apps" diff --git a/config/locales/server.vi.yml b/config/locales/server.vi.yml index c9bd715532..9102abbb4f 100644 --- a/config/locales/server.vi.yml +++ b/config/locales/server.vi.yml @@ -246,25 +246,25 @@ vi: title: "ChĂ o mừng bạn đến vá»›i Phòng khách" body: |2 - ChĂşc mừng! :confetti_ball: + ChĂşc mừng! :confetti_ball: - Náşżu bạn cĂł thá» xem chá»§ đỠnĂ y, bạn đã được thÄng lĂŞn báş­c **thường xuyĂŞn** (báş­c tin tưởng 3). + Náşżu bạn cĂł thá» xem chá»§ đỠnĂ y, bạn đã được thÄng lĂŞn báş­c **thường xuyĂŞn** (báş­c tin tưởng 3). - Bạn cĂł thá» … + Bạn cĂł thá» … - * Sá»­a tiĂŞu đỠcá»§a bất kì chá»§ đỠnĂ o - * Sá»­a chuyĂŞn mục cá»§a bất kì chá»§ đỠnĂ o - * Tất cả liĂŞn káşżt ở trạng thái follow ([những liĂŞn káşżt nofollow](http://en.wikipedia.org/wiki/Nofollow) sáş˝ được loại bỏ) - * Truy cáş­p vĂ o phòng khách dĂ nh riĂŞng cho thĂ nh viĂŞn vá»›i báş­c tin tưởng 3 hoáş·c cao hơn - * Ẩn bĂ i viáşżt spam vá»›i 1 láş§n đánh dấu. + * Sá»­a tiĂŞu đỠcá»§a bất kì chá»§ đỠnĂ o + * Sá»­a chuyĂŞn mục cá»§a bất kì chá»§ đỠnĂ o + * Tất cả liĂŞn káşżt ở trạng thái follow ([những liĂŞn káşżt nofollow](http://en.wikipedia.org/wiki/Nofollow) sáş˝ được loại bỏ) + * Truy cáş­p vĂ o phòng khách dĂ nh riĂŞng cho thĂ nh viĂŞn vá»›i báş­c tin tưởng 3 hoáş·c cao hơn + * Ẩn bĂ i viáşżt spam vá»›i 1 láş§n đánh dấu. - Äây lĂ  danh sách [cá»§a các thĂ nh viĂŞn thường xuyĂŞn](/badges/3/regular). HĂŁy chĂ o họ Ä‘i nĂ o. + Äây lĂ  danh sách [cá»§a các thĂ nh viĂŞn thường xuyĂŞn](/badges/3/regular). HĂŁy chĂ o họ Ä‘i nĂ o. - Cảm ơn vì đã trở thĂ nh má»™t pháş§n khĂ´ng thá» thiáşżu đối vá»›i cá»™ng đồng. + Cảm ơn vì đã trở thĂ nh má»™t pháş§n khĂ´ng thá» thiáşżu đối vá»›i cá»™ng đồng. - (Äá» biáşżt thĂŞm chi tiáşżt vá» báş­c tin tưởng, [xem chá»§ đỠnĂ y][trust]. HĂŁy nhá»› ráş±ng bạn phải tiáşżp tục đạt được các yĂŞu cáş§u đỠduy trì báş­c tin tưởng cá»§a mình.) + (Äá» biáşżt thĂŞm chi tiáşżt vá» báş­c tin tưởng, [xem chá»§ đỠnĂ y][trust]. HĂŁy nhá»› ráş±ng bạn phải tiáşżp tục đạt được các yĂŞu cáş§u đỠduy trì báş­c tin tưởng cá»§a mình.) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "Giá»›i thiệu chuyĂŞn mục %{category}" replace_paragraph: "(Thay tháşż Ä‘oạn nĂ y vá»›i miĂŞu tả vá» category má»›i. Bản hướng dáş«n nĂ y sáş˝ được xuất hiện trong vĂąng chọn cá»§a category, vì tháşż nĂł chỉ giá»›i hạn trong 200 kĂ­ tá»±. **Cho đến khi bạn chỉnh sá»­a pháş§n miĂŞu tả nĂ y hoáş·c tạo topic cho nĂł, category nĂ y sáş˝ khĂ´ng xuất hiện trĂŞn trang web.**)" @@ -404,7 +404,6 @@ vi: long_form: 'đánh dấu cái nĂ y khĂ´ng thĂ­ch hợp' notify_user: title: 'Gá»­i tin nhắn cho @{{username}}.' - description: 'TĂ´i muốn trao đổi riĂŞng, trá»±c tiáşżp vá»›i người nĂ y vá» bĂ i viáşżt cá»§a họ.' long_form: 'đã nhắn tin cho thĂ nh viĂŞn' email_title: 'BĂ i Ä‘Äng cá»§a bạn trong "%{title}"' email_body: "%{link}\n\n%{message}" @@ -723,8 +722,6 @@ vi: google_oauth2_client_id: "Client ID ứng dụng Google cá»§a bạn." google_oauth2_client_secret: "Client secret ứng dụng Google cá»§a bạn." enable_twitter_logins: "Cho phĂ©p chứng thá»±c qua Twitter, yĂŞu cáş§u twitter_consumer_key vĂ  twitter_consumser_secret" - twitter_consumer_key: "Consumer key cho chứng thá»±c Twitter, Ä‘Äng kĂ˝ tại http://dev.twitter.com" - twitter_consumer_secret: "Consumer secret cho chứng thá»±c Twitter, Ä‘Äng kĂ˝ tại http://dev.twitter.com" enable_instagram_logins: "Báş­t chứng thá»±c Instagram, yĂŞu cáş§u instagram_consumer_key vĂ  instagram_consumer_secret" instagram_consumer_key: "KhĂła người dĂąng đỠchứng thá»±c Instagram" instagram_consumer_secret: "KhĂła bảo máş­t đỠchứng thá»±c Instagram" @@ -737,7 +734,6 @@ vi: allow_restore: "Cho phĂ©p phục hồi, nĂł cĂł thá» thay tháşż TẤT CẢ dữ liệu trang web! Bỏ chọn, trừ khi bạn cĂł káşż hoạch phục hồi má»™t bản sao lưu" maximum_backups: "Số bản sao lưu tối Ä‘a lưu trong đĩa cứng. Những bản sao lưu cĹ© sáş˝ được xĂła tá»± động" automatic_backups_enabled: "Chạy sao lưu tá»± động như cấu hình trong táş§n số sao lưu" - backup_frequency: "Táş§n số sao lưu trang web, trong ngĂ y." enable_s3_backups: "Tải bản sao lưu lĂŞn S3 khi hoĂ n tất. QUAN TRỌNG: yĂŞu cáş§u chứng thá»±c S3 đã được nháş­p trong cấu hình File." s3_backup_bucket: "Äịa chỉ tách biệt lưu trữ backup. LĆŻU Ăť: đây phải lĂ  địa chỉ được giĂ nh riĂŞng." s3_disable_cleanup: "VĂ´ hiệu hĂła việc loại bỏ các bản sao lưu từ S3 khi lấy ra tại địa phương." diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index f52dc202c4..c23900a125 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -82,7 +82,6 @@ zh_CN: invalid: ć— ć• is_invalid: "似乎不清楚,这ćŻä¸€ä¸Şĺ®Ść•´çš„句ĺ­ďĽź" contains_censored_words: "包ĺ«äş†ä»Ąä¸‹ć•Źć„źčŻŤ:%{censored_words}" - matches_censored_pattern: "包ĺ«äş†ä»Ąä¸‹ç˝‘站过滤系统中的敏感词%{censored_words}" less_than: 必须小于 %{count} less_than_or_equal_to: 必须小于等于 %{count} not_a_number: 不ćŻć•°ĺ­— @@ -102,7 +101,7 @@ zh_CN: template: body: '有下ĺ—é—®é˘ďĽš' header: - other: 有 %{count} 个错误使得%{model}ć— ćł•č˘«äżťĺ­ + other: '有 %{count} 个错误使得%{model}无法被保ĺ­' embed: load_from_remote: "载入帖ĺ­ć—¶ĺ‡şé”™äş†ă€‚" site_settings: @@ -165,7 +164,6 @@ zh_CN: no_links_allowed: "抱歉,访客无法贴链接。" too_many_links: other: "抱歉,访客一次仅č˝č´´ %{count} 条链接。" - contains_blocked_words: "你的帖ĺ­ĺŚ…ĺ«čżťç¦čŻŤă€‚" spamming_host: "抱歉,你不č˝ć·»ĺŠ ä¸€ä¸Şé“ľćŽĄĺ°é‚Łä¸Şĺś°ĺť€çš„链接。" user_is_suspended: "被ĺ°ç¦çš„用ć·ä¸Ťĺ…许发贴。" topic_not_found: "出现问é˘ă€‚ć–许这个主é˘ĺś¨ä˝ çś‹çš„时候已经被关闭ć–ĺ é™¤äş†ă€‚" @@ -366,41 +364,41 @@ zh_CN: title: "ć¬˘čżŽćťĄĺ° Discourse" body: |2 - 置顶主é˘çš„第一段ćŻč®żĺ®˘ĺś¨ä¸»éˇµçś‹ĺ°çš„欢迎ç§äżˇă€‚čż™ĺľé‡Ťč¦ďĽ + 置顶主é˘çš„第一段ćŻč®żĺ®˘ĺś¨ä¸»éˇµçś‹ĺ°çš„欢迎ç§äżˇă€‚čż™ĺľé‡Ťč¦ďĽ - **编辑**ĺ®ä˝śä¸şç¤ľĺŚşçš„ç®€č¦ä»‹ç»ŤďĽš + **编辑**ĺ®ä˝śä¸şç¤ľĺŚşçš„ç®€č¦ä»‹ç»ŤďĽš - - 什äąäşşćłĺŹ‚ä¸ŽďĽź - - 他们č˝ĺś¨čż™ć‰ľĺ°ä»€äąďĽź - - 为什äąä»–们č¦ćťĄčż™ďĽź - - 他们č˝ĺś¨ĺ“Şé‡Śé…读更多的内容ďĽé“ľćŽĄă€čµ„ćşç­‰ďĽ‰ďĽź + - 什äąäşşćłĺŹ‚ä¸ŽďĽź + - 他们č˝ĺś¨čż™ć‰ľĺ°ä»€äąďĽź + - 为什äąä»–们č¦ćťĄčż™ďĽź + - 他们č˝ĺś¨ĺ“Şé‡Śé…读更多的内容ďĽé“ľćŽĄă€čµ„ćşç­‰ďĽ‰ďĽź - + - 你可č˝ćłč¦ĺś¨ç®ˇç† :wrench: ďĽĺŹłä¸Šč§’ďĽ‰ä¸­ĺ…łé—­čż™ä¸Şä¸»é˘ďĽŚčż™ć ·ĺ›žĺ¤Ťĺ°±ä¸ŤäĽšç§ŻĺŽ‹ĺś¨čż™ä¸Şé€šĺ‘Šä¸‹ă€‚ + 你可č˝ćłč¦ĺś¨ç®ˇç† :wrench: ďĽĺŹłä¸Šč§’ďĽ‰ä¸­ĺ…łé—­čż™ä¸Şä¸»é˘ďĽŚčż™ć ·ĺ›žĺ¤Ťĺ°±ä¸ŤäĽšç§ŻĺŽ‹ĺś¨čż™ä¸Şé€šĺ‘Šä¸‹ă€‚ lounge_welcome: title: "欢迎来ĺ°č´µĺ®ľĺ®¤" body: |2 - ć­ĺ–śďĽ :confetti_ball: + ć­ĺ–śďĽ :confetti_ball: - 如果你看ĺ°äş†čż™ä¸Şä¸»é˘ďĽŚčŻ´ćŽä˝ ĺ·˛ç»Źč˘«ćŹĺŤ‡č‡ł**常规**ďĽäżˇä»»ç­‰çş§3)了。 + 如果你看ĺ°äş†čż™ä¸Şä¸»é˘ďĽŚčŻ´ćŽä˝ ĺ·˛ç»Źč˘«ćŹĺŤ‡č‡ł**常规**ďĽäżˇä»»ç­‰çş§3)了。 - 你现在可以… + 你现在可以… - * 编辑任何主é˘çš„ć ‡é˘ - * 改ĺŹä»»ä˝•主é˘çš„ĺ†ç±» - * 让你的链接设置为 follow 属性ďĽ[自动 nofollow](http://en.wikipedia.org/wiki/Nofollow)é™ĺ¶ĺ·˛ç»Źç§»é™¤ďĽ‰ - * 访问一个只有信任等级3及更é«ć‰Ťč˝č§ĺ°çš„贵宾室ĺ†ç±» - * 一次标记即可éšč—ŹĺžĺśľäżˇćŻ + * 编辑任何主é˘çš„ć ‡é˘ + * 改ĺŹä»»ä˝•主é˘çš„ĺ†ç±» + * 让你的链接设置为 follow 属性ďĽ[自动 nofollow](http://en.wikipedia.org/wiki/Nofollow)é™ĺ¶ĺ·˛ç»Źç§»é™¤ďĽ‰ + * 访问一个只有信任等级3及更é«ć‰Ťč˝č§ĺ°çš„贵宾室ĺ†ç±» + * 一次标记即可éšč—ŹĺžĺśľäżˇćŻ - 这里ćŻ[目前达ĺ°ĺ¸¸č§„的用ć·ĺ—表](/badges/3/regular)。一定č¦ćťĄé—®ä¸ŞĺĄ˝ďĽ + 这里ćŻ[目前达ĺ°ĺ¸¸č§„的用ć·ĺ—表](/badges/3/regular)。一定č¦ćťĄé—®ä¸ŞĺĄ˝ďĽ - 感谢ć为社区的重č¦ä¸€ĺ‘ďĽ + 感谢ć为社区的重č¦ä¸€ĺ‘ďĽ - ďĽćłäş†č§Łĺ…łäşŽäżˇä»»ç­‰çş§çš„更多信ćŻďĽŚ[参č§čż™ä¸Şä¸»é˘][trust]。请注意ćĺ‘需č¦ĺś¨ĺ°†ćťĄäąźç»´ćŚä¸€ĺ®šçš„选择条件的ćĺ‘才č˝äżťćŚĺś¨ĺ¸¸č§„。) + ďĽćłäş†č§Łĺ…łäşŽäżˇä»»ç­‰çş§çš„更多信ćŻďĽŚ[参č§čż™ä¸Şä¸»é˘][trust]。请注意ćĺ‘需č¦ĺś¨ĺ°†ćťĄäąźç»´ćŚä¸€ĺ®šçš„选择条件的ćĺ‘才č˝äżťćŚĺś¨ĺ¸¸č§„。) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "关于“%{category}”ĺ†ç±»" replace_paragraph: "ďĽĺ°†ç¬¬ä¸€ć®µčŻťäż®ć”ąć你的新ĺ†ç±»çš„简述。这段文字将出现在用ć·é€‰ć‹©ĺ†ç±»çš„地方,所以尝试保ćŚĺś¨ 200 个字符内。 **除非你编辑了这段文字ć–者在这ĺ†ç±»ä¸­ĺ›ĺ»şäş†ä¸»é˘ďĽŚčż™ä¸Şĺ†ç±»ä¸ŤäĽšĺ‡şçŽ°ĺś¨ĺ†ç±»éˇµéť˘ä¸­ă€‚**)" @@ -560,8 +558,6 @@ zh_CN: long_form: '标记为不当内容' notify_user: title: 'ç»™@{{username}}发é€ä¸€ćťˇç§äżˇ' - description: 'ć‘ćłä¸Žć­¤äşşç§ä¸‹äş¤ćµä»–们的帖ĺ­ă€‚' - short_description: 'ć‘ćłč¦ç›´ćŽĄĺś°ç§ä¸‹é‡Śĺ’Śä˝śč€…沟通他们的帖ĺ­ă€‚' long_form: '以发é€ç§äżˇç»™ç”¨ć·' email_title: '你在“%{title}”中的帖ĺ­' email_body: "%{link}\n\n%{message}" @@ -819,7 +815,6 @@ zh_CN: poll_pop3_auth_error: "至 POP3 服务器的连接验čŻĺ¤±č´Ąă€‚请检查POP3 设置。" site_settings: censored_words: "将被自动替换为 ■■■■" - censored_pattern: "ć­Łĺ™čˇ¨čľľĺĽŹĺ°†č‡ŞĺŠ¨č˘«ć›żćŤ˘ä¸ş■■■■" delete_old_hidden_posts: "自动ĺ é™¤č˘«éšč—Źč¶…过 30 天的帖ĺ­ă€‚" allow_user_locale: "ĺ…许用ć·é€‰ć‹©ä»–们自己的语言界面" set_locale_from_accept_language_header: "为未登录用ć·ćŚ‰ç…§ä»–ä»¬çš„ćµŹč§ĺ™¨ĺŹ‘é€çš„请求头é¨č®ľç˝®ç•Śéť˘čŻ­č¨€ă€‚ďĽĺ®žéŞŚć€§ďĽŚć— ćł•ĺ’ŚĺŚżĺŤçĽ“ĺ­ĺ…±ĺŚä˝żç”¨ďĽ‰" @@ -978,8 +973,6 @@ zh_CN: google_oauth2_client_id: "Google 应用的 Client ID" google_oauth2_client_secret: "Google 应用的 Client secret" enable_twitter_logins: "ĺŻç”¨ Twitter ĺ¸ĺŹ·éŞŚčŻç™»ĺ˝•ďĽŚéś€č¦ twitter_consumer_key ĺ’Ś twitter_consumer_secret" - twitter_consumer_key: "Twitter ĺ¸ĺŹ·éŞŚčŻçš„客ć·ĺŻ†ĺŚ™ďĽConsumer keyďĽ‰ďĽŚĺ° http://dev.twitter.com 来注册获取" - twitter_consumer_secret: "Twitter ĺ¸ĺŹ·éŞŚčŻçš„客ć·ĺ݆ç ďĽConsumer secretďĽ‰ďĽŚĺ° http://dev.twitter.com 来注册获取" enable_instagram_logins: "ĺŻç”¨ Instagram 验čŻďĽŚéś€č¦ instagram_consumer_key ĺ’Ś instagram_consumer_secret" instagram_consumer_key: "Instagram 验čŻçš„ Consumer key" instagram_consumer_secret: "Instagram 验čŻçš„ Consumer secret" @@ -994,7 +987,6 @@ zh_CN: allow_restore: "ĺ…许导入数据,这将č˝ć›żćŤ˘ć‰€ćś‰ĺ…¨ç«™ć•°ćŤ®ďĽé™¤éťžä˝ č®ˇĺ’导入数据,ĺ¦ĺ™čŻ·äżťćŚč®ľç˝®ä¸ş false" maximum_backups: "çŁç›äżťĺ­çš„最大备份数量。č€çš„备份将自动ĺ é™¤" automatic_backups_enabled: "按照定义的备份频率čżčˇŚč‡ŞĺŠ¨ĺ¤‡ä»˝č®ˇĺ’" - backup_frequency: "自动ĺ›ĺ»şç«™ç‚ąĺ¤‡ä»˝çš„频率,以天为单位。" enable_s3_backups: "当完ć备份ĺŽä¸ŠäĽ ĺ¤‡ä»˝ĺ° S3。重č¦ďĽšéś€č¦ĺś¨ć–‡ä»¶č®ľç˝®ä¸­ĺˇ«ĺ†™ćś‰ć•çš„ S3 验čŻčµ„料。" s3_backup_bucket: "远端备份 bucket。警告:确认ĺ®ä˝żç§ćś‰çš„ bucket。" s3_disable_cleanup: "当在本地ĺ é™¤ĺ¤‡ä»˝ć—¶ä¸Ťĺ é™¤ S3 上的备份。" @@ -1583,47 +1575,6 @@ zh_CN: test_mailer: title: "测试发件人" subject_template: "[%{email_prefix}] 邮件可达性测试" - text_body_template: | - čż™ćŻä¸€ĺ°ćµ‹čŻ•ç”µĺ­é‚®ä»¶ďĽŚĺŹ‘č‡ŞďĽš - - [**%{base_url}**][0] - - 电ĺ­é‚®ä»¶ĺ†ĺŹ‘ĺľĺ¤Ťćť‚。以下ćŻä¸€äş›ä˝ ĺş”该检查的重点: - - - 确定你在站点设置中为 `notification email` 设置了正确的地址。**čż™ćŻçł»ç»źĺŹ‘é€é‚®ä»¶é‡Śçš„“发自”地址,并需č¦ä˝ çš„ĺźźĺŤé…Ťç˝®ć­Łçˇ®ä»Ąäľ›ç›¸ĺ…łéŞŚčŻă€‚**。 - - - 学习使用邮件客ć·ç«ŻćźĄçś‹ç”µĺ­é‚®ä»¶ćşä»Łç ďĽŚčż™ć ·ä˝ ĺŹŻä»Ąé€ščż‡ćźĄçś‹é‚®ä»¶ĺ¤´é¨ćťĄćŽ’ćźĄé‡Ťč¦çşżç´˘ă€‚在 Gmail 中,可以通过每一ĺ°é‚®ä»¶ä¸‹ć‹‰čŹśĺŤ•ä¸­çš„â€śćľç¤şĺŽźĺ§‹ç§äżˇâ€ťćťĄćźĄçś‹ă€‚ - - - **重č¦ďĽš** ä˝ çš„ ISP ćŻĺ¦ĺŻąä˝ ĺŹ‘é€é‚®ä»¶çš„ĺźźĺŤĺ’Ś IP ä˝śäş†ĺŹŤĺ‘ DNS č§ŁćžďĽźä˝ ĺŹŻä»Ąĺś¨ć­¤[测试你的反ĺ‘枚举指é’ďĽPTR)记录][2]。如果你的 ISP ć˛ˇćś‰č®ľç˝®ć­Łçˇ®çš„ĺŹŤĺ‘ DNS 记录,那äąĺľĺŹŻč˝ä˝ çš„任何电ĺ­é‚®ä»¶é˝ä¸ŤäĽšč˘«ć功发é€ă€‚ - - - 你的域ĺŤçš„[发件人策略框架ďĽSPF)记录][8]ćŻĺ¦ć­Łçˇ®ďĽźä˝ ĺŹŻä»Ąĺś¨ć­¤[测试你的SPF记录][1]。注意 TXT ćŻä¸€ç§Ťć­Łçˇ®çš„ĺ®ć–ą SPF 记录。 - - - ä˝ ĺźźĺŤçš„[ĺźźĺŤĺŻ†é’Ąčş«ä»˝čŻ†ĺ«é‚®ä»¶ďĽDKIM)记录][3]设置ćŻĺ¦ć­Łçˇ®ďĽźčż™ĺ°†ćľč‘—ćŹé«é‚®ä»¶ĺ†ĺŹ‘çš„ć功率。在此[测试你的 DKIM 记录][7]。 - - - 如果你自己čżčˇŚé‚®ä»¶ćśŤĺŠˇĺ™¨ďĽŚćŁ€ćźĄçˇ®č®¤ä˝ ĺŹ‘é€ç”µĺ­é‚®ä»¶çš„服务器 IP [不在任何邮件黑ĺŤĺŤ•中][4]。验čŻä˝ çš„ DNS 记录的 HELO ç§äżˇä¸­č®ľç˝®äş†ç¬¦ĺč¦ć±‚的主机ĺŤă€‚如果没有,那äąäĽšĺŻĽč‡´ĺľĺ¤šé‚®ä»¶ćśŤĺŠˇĺ•†ć‹’ć”¶ä˝ çš„é‚®ä»¶ă€‚ - - - ć‘们强ç建议你**发é€ćµ‹čŻ•é‚®ä»¶č‡ł [mail-tester.com][mt]**,这样可以检查上述设置ćŻĺ¦ć­Łçˇ®ă€‚ - - ďĽćś€ç®€ĺŤ•的方法ćŻĺś¨ [SendGrid][sg]ă€[SparkPost][sp]ă€[Mailgun][mg] ĺ’Ś [Mailjet][mj]注册免费账ć·ďĽŚä»–们的免费账ć·ĺŻąä¸€ä¸Şĺ°Źç¤ľĺŚşćŻč¶łĺ¤źçš„。不过你仍然需č¦ĺś¨ DNS 设置中设定 SPF ĺ’Ś DKIM 记录ďĽďĽ‰ - - ć‘们衷ĺżĺ¸Śćś›ä˝ ć功完ć邮件发é€ćµ‹čŻ•ďĽ - - 祝好čżďĽ - - 你的朋友,[Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://sendgrid.com/ - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "ć–°ç‰ćś¬ĺŹ‘ä»¶äşş" subject_template: "[%{email_prefix}] ć–° Discourse ç‰ćś¬ĺŹŻäľ›ĺŤ‡çş§" @@ -2243,7 +2194,7 @@ zh_CN: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} account_suspended: title: "č´¦ć·č˘«ç¦ç”¨" subject_template: "[%{email_prefix}] 你的账ć·ĺ·˛ç»Źč˘«ç¦ç”¨" diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index cec5b5db8f..0747eb5481 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -73,7 +73,6 @@ zh_TW: invalid: 無ć•çš„ is_invalid: "似乎不清楚,這ćŻĺ®Ść•´çš„句ĺ­ĺ—ŽďĽź" contains_censored_words: "包ĺ«ä¸‹ĺ—éŽćżľč©žĺ˝™: %{censored_words}" - matches_censored_pattern: "包ĺ«ä¸‹ĺ—éŽćżľč©žĺ˝™ďĽäľťç…§ç«™ć–ąč¨­ĺ®šçš„正規表é”式 %{censored_words})" less_than: ĺż…é ĺ°‘ć–Ľ %{count} less_than_or_equal_to: ĺż…é ĺ°‘ć–Ľć–等於 %{count} not_a_number: 不ćŻć•¸ĺ­— @@ -155,7 +154,6 @@ zh_TW: no_links_allowed: "抱歉,新用ć¶ä¸Ťč˝ĺś¨č˛Ľć–‡ä¸­ć”ľç˝®é€Łçµă€‚" too_many_links: other: "抱歉,新使用者只č˝ĺś¨ć–‡ç« ä¸­ć”ľç˝® %{count} 個連çµă€‚" - contains_blocked_words: "你的文章包ĺ«ç¦ç”¨çš„字詞" spamming_host: "抱歉,你不č˝ĺĽµč˛Ľč©˛ç¶˛ç«™äą‹é€Łçµă€‚" user_is_suspended: "被ĺść¬Šçš„用ć¶ç„ˇćł•張貼文章。" topic_not_found: "出現問題。也許這個話題被關閉ć–ĺŞé™¤ă€‚" @@ -328,41 +326,41 @@ zh_TW: title: "ć­ˇčżŽäľ†ĺ° Discourse" body: |2 - 置頂主題的第一段ćŻč¨Şĺ®˘ĺś¨ä¸»é çś‹ĺ°çš„歡迎ć¶ćŻă€‚這ĺľé‡Ťč¦ďĽ + 置頂主題的第一段ćŻč¨Şĺ®˘ĺś¨ä¸»é çś‹ĺ°çš„歡迎ć¶ćŻă€‚這ĺľé‡Ťč¦ďĽ - **編輯**ĺ®ä˝śç‚şç¤ľçľ¤çš„ç°ˇč¦ä»‹ç´ąďĽš + **編輯**ĺ®ä˝śç‚şç¤ľçľ¤çš„ç°ˇč¦ä»‹ç´ąďĽš - - 什麼人ćłĺŹč‡ďĽź - - 他們č˝ĺś¨é€™ć‰ľĺ°ä»€éşĽďĽź - - 為什麼他們č¦äľ†é€™ďĽź - - 他們č˝ĺś¨ĺ“ŞčŁˇé–˛č®€ć›´ĺ¤šçš„ĺ…§ĺ®ąďĽé€Łçµă€čł‡ćşç­‰ďĽ‰ďĽź + - 什麼人ćłĺŹč‡ďĽź + - 他們č˝ĺś¨é€™ć‰ľĺ°ä»€éşĽďĽź + - 為什麼他們č¦äľ†é€™ďĽź + - 他們č˝ĺś¨ĺ“ŞčŁˇé–˛č®€ć›´ĺ¤šçš„ĺ…§ĺ®ąďĽé€Łçµă€čł‡ćşç­‰ďĽ‰ďĽź - + - 你可č˝ćłč¦ĺś¨ç®ˇç† :wrench: ďĽĺŹłä¸Šč§’ďĽ‰ä¸­é—śé–‰é€™ĺ€‹ä¸»éˇŚďĽŚé€™ć¨Łĺ›žč¦†ĺ°±ä¸Ťćśç©ŤĺŁ“ĺś¨é€™ĺ€‹é€šĺ‘Šä¸‹ă€‚ + 你可č˝ćłč¦ĺś¨ç®ˇç† :wrench: ďĽĺŹłä¸Šč§’ďĽ‰ä¸­é—śé–‰é€™ĺ€‹ä¸»éˇŚďĽŚé€™ć¨Łĺ›žč¦†ĺ°±ä¸Ťćśç©ŤĺŁ“ĺś¨é€™ĺ€‹é€šĺ‘Šä¸‹ă€‚ lounge_welcome: title: "歡迎來ĺ°č˛´čł“室" body: |2 - ć­ĺ–śďĽ :confetti_ball: + ć­ĺ–śďĽ :confetti_ball: - 如果你看ĺ°äş†é€™ĺ€‹ä¸»éˇŚďĽŚčŞŞćŽä˝ ĺ·˛ç¶“被ćŹĺŤ‡č‡ł**常規**ďĽäżˇä»»ç­‰ç´š3)了。 + 如果你看ĺ°äş†é€™ĺ€‹ä¸»éˇŚďĽŚčŞŞćŽä˝ ĺ·˛ç¶“被ćŹĺŤ‡č‡ł**常規**ďĽäżˇä»»ç­‰ç´š3)了。 - 你現在可以… + 你現在可以… - * 編輯任何主題的標題 - * 改變任何主題的ĺ†éˇž - * 讓你的連çµč¨­ç˝®ç‚ş follow 屬性ďĽ[自動 nofollow](http://en.wikipedia.org/wiki/Nofollow)é™ĺ¶ĺ·˛ç¶“移除) - * 訪問一個只有信任等級3及更é«ć‰Ťč˝č¦‹ĺ°çš„貴賓室ĺ†éˇž - * 一次標č¨ĺŤłĺŹŻéš±č—ŹĺžĺśľäżˇćŻ + * 編輯任何主題的標題 + * 改變任何主題的ĺ†éˇž + * 讓你的連çµč¨­ç˝®ç‚ş follow 屬性ďĽ[自動 nofollow](http://en.wikipedia.org/wiki/Nofollow)é™ĺ¶ĺ·˛ç¶“移除) + * 訪問一個只有信任等級3及更é«ć‰Ťč˝č¦‹ĺ°çš„貴賓室ĺ†éˇž + * 一次標č¨ĺŤłĺŹŻéš±č—ŹĺžĺśľäżˇćŻ - 這裡ćŻ[目前é”ĺ°ĺ¸¸č¦Źçš„用ć¶ĺ—表](/badges/3/regular)。一定č¦äľ†ĺ•Źĺ€‹ĺĄ˝ďĽ + 這裡ćŻ[目前é”ĺ°ĺ¸¸č¦Źçš„用ć¶ĺ—表](/badges/3/regular)。一定č¦äľ†ĺ•Źĺ€‹ĺĄ˝ďĽ - 感謝ć為社群的重č¦ä¸€ĺ“ˇďĽ + 感謝ć為社群的重č¦ä¸€ĺ“ˇďĽ - ďĽćłçž­č§Łé—ść–Ľäżˇä»»ç­‰ç´šçš„更多信ćŻďĽŚ[ĺŹč¦‹é€™ĺ€‹ä¸»éˇŚ][trust]。請注意ć員需č¦ĺś¨ĺ°‡äľ†äąźç¶­ćŚä¸€ĺ®šçš„é¸ć“‡ć˘ťä»¶çš„ć員才č˝äżťćŚĺś¨ĺ¸¸č¦Źă€‚) + ďĽćłçž­č§Łé—ść–Ľäżˇä»»ç­‰ç´šçš„更多信ćŻďĽŚ[ĺŹč¦‹é€™ĺ€‹ä¸»éˇŚ][trust]。請注意ć員需č¦ĺś¨ĺ°‡äľ†äąźç¶­ćŚä¸€ĺ®šçš„é¸ć“‡ć˘ťä»¶çš„ć員才č˝äżťćŚĺś¨ĺ¸¸č¦Źă€‚) - [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 + [trust]: https://meta.discourse.org/t/what-do-user-trust-levels-do/4924 category: topic_prefix: "ĺ°Ťć–Ľĺ†éˇžďĽš%{category} 的定義" replace_paragraph: "ďĽĺ°‡ç¬¬ä¸€ć®µč©±äż®ć”ąć你的新ĺ†éˇžçš„簡述。這段文字將出現在用ć¶é¸ć“‡ĺ†éˇžçš„地方,所以ĺ—試保ćŚĺś¨ 200 個字ĺ…內。 **除非你編輯了這段文字ć–者在這ĺ†éˇžä¸­ĺ‰µĺ»şäş†ä¸»éˇŚďĽŚé€™ĺ€‹ĺ†éˇžä¸Ťćśĺ‡şçŹľĺś¨ĺ†éˇžé éť˘ä¸­ă€‚**)" @@ -511,7 +509,6 @@ zh_TW: long_form: '投訴為不當內容' notify_user: title: '給 @{{username}} é€ĺ‡şä¸€ĺ‰‡č¨ŠćŻ' - description: 'ć‘ćłč¦ç›´ćŽĄä¸”ç§ä¸‹ĺś°ĺ’Śé€™ä˝Ťćś‹ĺŹ‹č¨Žč«–ĺ…¶ć–‡ç« ă€‚' long_form: '傳訊給用ć¶' email_title: '你的文章在"%{title}"' email_body: "%{link}\n\n%{message}" @@ -758,7 +755,6 @@ zh_TW: poll_pop3_auth_error: "至 POP3 伺服器的連接驗證失敗。請檢查POP3 設置。" site_settings: censored_words: "將被自動替換為 ■■■■" - censored_pattern: "正則表é”式將自動被替換為■■■■" delete_old_hidden_posts: "自動ĺŞé™¤č˘«éš±č—Źč¶…éŽ 30 天的帖ĺ­ă€‚" allow_user_locale: "ĺ…許用ć¶é¸ć“‡č‡Şĺ·±çš„語言介面" set_locale_from_accept_language_header: "為未登錄用ć¶ćŚ‰ç…§ä»–ĺ€‘çš„ç€Źč¦˝ĺ™¨ç™Ľé€çš„請求頭é¨č¨­ç˝®ç•Śéť˘čŞžč¨€ă€‚ďĽĺŻ¦é©—ć€§ďĽŚç„ˇćł•ĺ’ŚĺŚżĺŤç·©ĺ­ĺ…±ĺŚä˝żç”¨ďĽ‰" @@ -896,8 +892,6 @@ zh_TW: google_oauth2_client_id: "ä˝ çš„ Google 應用程式的客ć¶ç«Ż ID" google_oauth2_client_secret: "ä˝ çš„ Google 應用程式的客ć¶ç«Ż secret" enable_twitter_logins: "啟用推特ďĽTwitterďĽ‰ĺ¸łč™źé©—č­‰ç™»ĺ…ĄďĽŚéś€č¦ twitter_consumer_key ĺ’Ś twitter_consumer_secret" - twitter_consumer_key: "推特帳號驗證的客ć¶ĺŻ†ĺŚ™ďĽConsumer keyďĽ‰ďĽŚĺ° http://dev.twitter.com 來註冊獲取" - twitter_consumer_secret: "推特帳號驗證的客ć¶ĺŻ†ç˘ĽďĽConsumer secretďĽ‰ďĽŚĺ° http://dev.twitter.com 來註冊獲取" enable_instagram_logins: "啟用 Instagram é©—č­‰ďĽŚéś€č¦ instagram_consumer_key ĺ’Ś instagram_consumer_secret" instagram_consumer_key: "Instagram 驗證的 Consumer key" instagram_consumer_secret: "Instagram 驗證的 Consumer secret" @@ -912,7 +906,6 @@ zh_TW: allow_restore: "ĺ…許還原資料,注意此動作可č˝č¦†č“‹ă€Ść‰€ćś‰ă€Ťç¶˛ç«™čł‡ć–™ďĽé™¤éťžä˝ č¨ç•«é‚„原備份檔,ĺ¦ĺ‰‡č«‹äżťćŚć­¤č¨­ĺ®šç‚ş false" maximum_backups: "çŁç˘źĺ‚™ä»˝çš„最大數量,čŠçš„ĺ°‡ćśč‡Şĺ‹•ĺŞé™¤" automatic_backups_enabled: "按照定義的備份頻率é‹čˇŚč‡Şĺ‹•備份č¨ĺŠ" - backup_frequency: "自動創建站點備份的頻率,以天為單位。" enable_s3_backups: "當完ćĺ‚™ä»˝ĺľŚä¸Šĺ‚łĺ‚™ä»˝ĺ° S3。重č¦ďĽšéś€č¦ĺś¨ć–‡ä»¶č¨­ĺ®šä¸­ĺˇ«ĺŻ«ćś‰ć•çš„ S3 驗證資料。" s3_backup_bucket: "é ç«Żĺ‚™ä»˝ bucket ,注意:請確定ćŻç§ćś‰çš„ bucket" s3_disable_cleanup: "當在本地ĺŞé™¤ĺ‚™ä»˝ć™‚不ĺŞé™¤ S3 上的備份。" @@ -1367,47 +1360,6 @@ zh_TW: test_mailer: title: "測試 Mailer" subject_template: "[%{email_prefix}] é›»ĺ­éµä»¶ç™Ľé€ć¸¬č©¦" - text_body_template: | - 這ćŻä¸€ĺ°ć¸¬č©¦é›»ĺ­éµä»¶ďĽŚç™Ľč‡ŞďĽš - - [**%{base_url}**][0] - - é›»ĺ­éµä»¶ĺ†ç™Ľĺľč¤‡é›śă€‚以下ćŻä¸€äş›ä˝ ć‡‰č©˛ćŞ˘ćźĄçš„é‡Ťé»žďĽš - - - 確定你在站點設置中為 `notification email` 設置了正確的地址。**這ćŻçł»çµ±ç™Ľé€éµä»¶čŁˇçš„â€śç™Ľč‡Şâ€ťĺś°ĺť€ďĽŚä¸¦éś€č¦ä˝ çš„ĺźźĺŤé…Ťç˝®ć­Łç˘şä»Ąäľ›ç›¸é—śé©—證。**。 - - - 學習使用éµä»¶ĺ®˘ć¶ç«ŻćźĄçś‹é›»ĺ­éµä»¶ĺŽźĺ§‹ç˘ĽďĽŚé€™ć¨Łä˝ ĺŹŻä»Ąé€šéŽćźĄçś‹éµä»¶é ­é¨äľ†ćŽ’ćźĄé‡Ťč¦ç·šç´˘ă€‚在 Gmail 中,可以通éŽćŻŹä¸€ĺ°éµä»¶ä¸‹ć‹‰čŹśĺ–®ä¸­çš„â€śéˇŻç¤şĺŽźĺ§‹ć¶ćŻâ€ťäľ†ćźĄçś‹ă€‚ - - - **重č¦ďĽš** ä˝ çš„ ISP ćŻĺ¦ĺ°Ťä˝ ç™Ľé€éµä»¶çš„ĺźźĺŤĺ’Ś IP ä˝śäş†ĺŹŤĺ‘ DNS č§ŁćžďĽźä˝ ĺŹŻä»Ąĺś¨ć­¤[測試你的反ĺ‘ćžšč‰ćŚ‡é‡ťďĽPTR)č¨éŚ„][2]。如果你的 ISP ć˛’ćś‰č¨­ç˝®ć­Łç˘şçš„ĺŹŤĺ‘ DNS č¨éŚ„ďĽŚé‚ŁéşĽĺľĺŹŻč˝ä˝ çš„任何電ĺ­éµä»¶é˝ä¸Ťćśč˘«ć功發é€ă€‚ - - - 你的域ĺŤçš„[發件人策略框架ďĽSPF)č¨éŚ„][8]ćŻĺ¦ć­Łç˘şďĽźä˝ ĺŹŻä»Ąĺś¨ć­¤[測試你的SPFč¨éŚ„][1]。注意 TXT ćŻä¸€ç¨®ć­Łç˘şçš„ĺ®ć–ą SPF č¨éŚ„ă€‚ - - - ä˝ ĺźźĺŤçš„[ĺźźĺŤĺŻ†é‘°čş«ä»˝č­ĺĄéµä»¶ďĽDKIM)č¨éŚ„][3]設置ćŻĺ¦ć­Łç˘şďĽźé€™ĺ°‡éˇŻč‘—ćŹé«éµä»¶ĺ†ç™Ľçš„ć功率。在此[測試你的 DKIM č¨éŚ„][7]。 - - - 如果你自己é‹čˇŚéµä»¶äĽşćśŤĺ™¨ďĽŚćŞ˘ćźĄç˘şčŞŤä˝ ç™Ľé€é›»ĺ­éµä»¶çš„伺服器 IP [不在任何éµä»¶é»‘ĺŤĺ–®ä¸­][4]。驗證你的 DNS č¨éŚ„çš„ HELO ć¶ćŻä¸­č¨­ç˝®äş†ç¬¦ĺč¦ć±‚的主機ĺŤă€‚如果沒有,那麼ćśĺ°Žč‡´ĺľĺ¤šéµä»¶ćśŤĺ‹™ĺ•†ć‹’ć”¶ä˝ çš„éµä»¶ă€‚ - - - ć‘們強ç建議你**發é€ć¸¬č©¦éµä»¶č‡ł [mail-tester.com][mt]**,這樣可以檢查上述設置ćŻĺ¦ć­Łç˘şă€‚ - - ďĽćś€ç°ˇĺ–®çš„ć–ąćł•ćŻĺś¨ [SendGrid][sg]ă€[SparkPost][sp]ă€[Mailgun][mg] ĺ’Ś [Mailjet][mj]註冊免費賬ć¶ďĽŚä»–們的免費賬ć¶ĺ°Ťä¸€ĺ€‹ĺ°Źç¤ľçľ¤ćŻč¶łĺ¤ çš„。不éŽä˝ ä»Ťç„¶éś€č¦ĺś¨ DNS 設置中設定 SPF ĺ’Ś DKIM č¨éŚ„ďĽďĽ‰ - - ć‘們衷ĺżĺ¸Śćś›ä˝ ć功完ćéµä»¶ç™Ľé€ć¸¬č©¦ďĽ - - 祝好é‹ďĽ - - 你的朋友,[Discourse](http://www.discourse.org) - - [0]: %{base_url} - [1]: http://www.kitterman.com/spf/validate.html - [2]: http://mxtoolbox.com/ReverseLookup.aspx - [3]: http://www.dkim.org/ - [4]: http://whatismyipaddress.com/blacklist-check - [7]: https://www.mail-tester.com/spf-dkim-check - [8]: http://www.openspf.org/SPF_Record_Syntax - [sg]: https://sendgrid.com/ - [sp]: https://www.sparkpost.com/ - [mg]: http://www.mailgun.com/ - [mj]: https://www.mailjet.com/pricing - [mt]: http://www.mail-tester.com/ new_version_mailer: title: "ć–°ç‰ćś¬ Mailer" subject_template: "[%{email_prefix}] 有可用的 Discourse ć›´ć–°" @@ -1804,7 +1756,7 @@ zh_TW: subject_template: "%{optional_re}%{topic_title}" text_body_template: |2 - %{message} + %{message} digest: why: "在你上一次於 %{last_seen_at} 訪問後,在 %{site_link} 上的ć‘č¦ă€‚" since_last_visit: "自你上次訪問至今" diff --git a/config/nginx.sample.conf b/config/nginx.sample.conf index 6253e04146..f7544fdce1 100644 --- a/config/nginx.sample.conf +++ b/config/nginx.sample.conf @@ -188,7 +188,7 @@ server { # This big block is needed so we can selectively enable # acceleration for backups and avatars # see note about repetition above - location ~ ^/(letter_avatar/|user_avatar|highlight-js|stylesheets|favicon/proxied|service-worker-.*.js) { + location ~ ^/(letter_avatar/|user_avatar|highlight-js|stylesheets|favicon/proxied|service-worker) { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/config/routes.rb b/config/routes.rb index a7409d3afd..de27e9079a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -129,6 +129,7 @@ Discourse::Application.routes.draw do get "tl3_requirements" put "anonymize" post "reset_bounce_score" + put "disable_second_factor" end get "users/:id.json" => 'users#show', defaults: { format: 'json' } get 'users/:id/:username' => 'users#show', constraints: { username: RouteFormat.username } @@ -302,6 +303,7 @@ Discourse::Application.routes.draw do get "session/current" => "session#current" get "session/csrf" => "session#csrf" get "session/email-login/:token" => "session#email_login" + post "session/email-login/:token" => "session#email_login" get "composer_messages" => "composer_messages#index" post "composer/parse_html" => "composer#parse_html" @@ -329,12 +331,16 @@ Discourse::Application.routes.draw do end end + post "#{root_path}/second_factors" => "users#create_second_factor" + put "#{root_path}/second_factor" => "users#update_second_factor" + 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" get "#{root_path}/admin-login/:token" => "users#admin_login" + put "#{root_path}/admin-login/:token" => "users#admin_login" post "#{root_path}/toggle-anon" => "users#toggle_anon" post "#{root_path}/read-faq" => "users#read_faq" get "#{root_path}/search/users" => "users#search_users" @@ -349,6 +355,7 @@ Discourse::Application.routes.draw do get "#{root_path}/activate-account/:token" => "users#activate_account" put({ "#{root_path}/activate-account/:token" => "users#perform_account_activation" }.merge(index == 1 ? { as: 'perform_activate_account' } : {})) get "#{root_path}/authorize-email/:token" => "users_email#confirm" + put "#{root_path}/authorize-email/:token" => "users_email#confirm" get({ "#{root_path}/confirm-admin/:token" => "users#confirm_admin", constraints: { token: /[0-9a-f]+/ } @@ -360,6 +367,7 @@ Discourse::Application.routes.draw do get "#{root_path}/:username/messages/:filter" => "user_actions#private_messages", constraints: { username: RouteFormat.username } get "#{root_path}/:username/messages/group/:group_name" => "user_actions#private_messages", constraints: { username: RouteFormat.username, group_name: RouteFormat.username } get "#{root_path}/:username/messages/group/:group_name/archive" => "user_actions#private_messages", constraints: { username: RouteFormat.username, group_name: RouteFormat.username } + get "#{root_path}/:username/messages/tag/:tag_id" => "user_actions#private_messages", constraints: StaffConstraint.new get "#{root_path}/:username.json" => "users#show", constraints: { username: RouteFormat.username }, defaults: { format: :json } get({ "#{root_path}/:username" => "users#show", constraints: { username: RouteFormat.username, format: /(json|html)/ } }.merge(index == 1 ? { as: 'user' } : {})) put "#{root_path}/:username" => "users#update", constraints: { username: RouteFormat.username }, defaults: { format: :json } @@ -380,6 +388,7 @@ Discourse::Application.routes.draw do put "#{root_path}/:username/preferences/badge_title" => "users#badge_title", constraints: { username: RouteFormat.username } get "#{root_path}/:username/preferences/username" => "users#preferences", constraints: { username: RouteFormat.username } put "#{root_path}/:username/preferences/username" => "users#username", constraints: { username: RouteFormat.username } + get "#{root_path}/:username/preferences/second-factor" => "users#preferences", constraints: { username: RouteFormat.username } delete "#{root_path}/:username/preferences/user_image" => "users#destroy_user_image", constraints: { username: RouteFormat.username } put "#{root_path}/:username/preferences/avatar/pick" => "users#pick_avatar", constraints: { username: RouteFormat.username } get "#{root_path}/:username/preferences/card-badge" => "users#card_badge", constraints: { username: RouteFormat.username } @@ -443,8 +452,6 @@ Discourse::Application.routes.draw do get "posts.rss" => "groups#posts_feed", format: :rss get "mentions.rss" => "groups#mentions_feed", format: :rss - get 'activity' => "groups#show" - get 'activity/:filter' => "groups#show" get 'members' get 'posts' get 'topics' @@ -460,6 +467,8 @@ Discourse::Application.routes.draw do end member do + get 'activity' => "groups#show" + get 'activity/:filter' => "groups#show" put "members" => "groups#add_members" delete "members" => "groups#remove_member" post "request_membership" => "groups#request_membership" @@ -533,6 +542,7 @@ Discourse::Application.routes.draw do put "category/:category_id/slug" => "categories#update_slug" get "categories_and_latest" => "categories#categories_and_latest" + get "categories_and_top" => "categories#categories_and_top" get "c/:id/show" => "categories#show" get "c/:category_slug/find_by_slug" => "categories#find_by_slug" @@ -589,20 +599,20 @@ Discourse::Application.routes.draw do resources :similar_topics get "topics/feature_stats" - get "topics/created-by/:username" => "list#topics_by", as: "topics_by", constraints: { username: RouteFormat.username } - get "topics/private-messages/:username" => "list#private_messages", as: "topics_private_messages", constraints: { username: RouteFormat.username } - get "topics/private-messages-sent/:username" => "list#private_messages_sent", as: "topics_private_messages_sent", constraints: { username: RouteFormat.username } - get "topics/private-messages-archive/:username" => "list#private_messages_archive", as: "topics_private_messages_archive", constraints: { username: RouteFormat.username } - get "topics/private-messages-unread/:username" => "list#private_messages_unread", as: "topics_private_messages_unread", constraints: { username: RouteFormat.username } - get "topics/private-messages-group/:username/:group_name.json" => "list#private_messages_group", as: "topics_private_messages_group", constraints: { - username: RouteFormat.username, - group_name: RouteFormat.username - } - get "topics/private-messages-group/:username/:group_name/archive.json" => "list#private_messages_group_archive", as: "topics_private_messages_group_archive", constraints: { - username: RouteFormat.username, - group_name: RouteFormat.username - } + scope "/topics", username: RouteFormat.username do + get "created-by/:username" => "list#topics_by", as: "topics_by" + get "private-messages/:username" => "list#private_messages", as: "topics_private_messages" + get "private-messages-sent/:username" => "list#private_messages_sent", as: "topics_private_messages_sent" + get "private-messages-archive/:username" => "list#private_messages_archive", as: "topics_private_messages_archive" + get "private-messages-unread/:username" => "list#private_messages_unread", as: "topics_private_messages_unread" + get "private-messages-tag/:username/:tag_id.json" => "list#private_messages_tag", as: "topics_private_messages_tag", constraints: StaffConstraint.new + + scope "/private-messages-group/:username", group_name: RouteFormat.username do + get ":group_name.json" => "list#private_messages_group", as: "topics_private_messages_group" + get ":group_name/archive.json" => "list#private_messages_group_archive", as: "topics_private_messages_group_archive" + end + end get 'embed/comments' => 'embed#comments' get 'embed/count' => 'embed#count' @@ -697,6 +707,12 @@ Discourse::Application.routes.draw do delete "draft" => "draft#destroy" if service_worker_asset = Rails.application.assets_manifest.assets['service-worker.js'] + # https://developers.google.com/web/fundamentals/codelabs/debugging-service-workers/ + # Normally the browser will wait until a user closes all tabs that contain the + # current site before updating to a new Service Worker. + # Support the old Service Worker path to avoid routing error filling up the + # logs. + get "/service-worker.js" => redirect(service_worker_asset), format: :js get service_worker_asset => "static#service_worker_asset", format: :js end diff --git a/config/site_settings.yml b/config/site_settings.yml index ea7d2140aa..e70c723bbf 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -255,6 +255,16 @@ login: default: false google_oauth2_client_id: '' google_oauth2_client_secret: '' + google_oauth2_prompt: + default: '' + type: list + choices: + - '' + - 'none' + - 'consent' + - 'select_account' + google_oauth2_hd: + default: '' enable_yahoo_logins: client: true default: false @@ -437,6 +447,8 @@ groups: enable_group_directory: client: true default: true + group_in_subject: + default: false posting: min_post_length: @@ -500,6 +512,7 @@ posting: client: true default: 2 min: 1 + max_emojis_in_title: 1 allow_uncategorized_topics: client: true default: true @@ -521,6 +534,7 @@ posting: client: true validator: "EnablePrivateEmailMessagesValidator" editing_grace_period: 300 + editing_grace_period_max_diff: 100 staff_edit_locks_post: false post_edit_time_limit: default: 86400 @@ -608,11 +622,6 @@ posting: type: list client: true delete_old_hidden_posts: true - censored_pattern: - client: true - default: '' - refresh: true - type: regex enable_emoji: default: true client: true @@ -697,6 +706,7 @@ email: validator: "ReplyByEmailEnabledValidator" reply_by_email_address: default: '' + validator: "ReplyByEmailAddressValidator" alternative_reply_by_email_addresses: default: '' validator: "AlternativeReplyByEmailAddressesValidator" @@ -715,7 +725,7 @@ email: pop3_polling_username: '' pop3_polling_password: '' log_mail_processing_failures: false - incoming_email_prefer_html: false + incoming_email_prefer_html: true email_in: default: false client: true @@ -794,6 +804,11 @@ files: default: 'jpg|jpeg|png|gif' refresh: true type: list + authorized_extensions_for_staff: + client: true + default: '' + refresh: true + type: list crawl_images: default: true max_image_width: @@ -903,6 +918,9 @@ trust: min_trust_to_post_links: default: 0 enum: 'TrustLevelSetting' + min_trust_to_post_images: + default: 0 + enum: 'TrustLevelSetting' allow_flagging_staff: true tl1_requires_topics_entered: 5 tl1_requires_read_posts: @@ -951,6 +969,8 @@ trust: tl3_links_no_follow: default: false client: true + trusted_users_can_edit_others: + default: true security: force_https: @@ -1200,6 +1220,8 @@ legal: faq_url: client: true default: '' + log_anonymizer_details: + default: true backups: enable_backups: @@ -1560,6 +1582,8 @@ tags: type: list client: true default: '' + allow_staff_to_tag_pms: + default: false suppress_overlapping_tags_in_list: default: false client: true diff --git a/db/fixtures/001_categories.rb b/db/fixtures/001_categories.rb index 4dc13268ac..98b0be1a32 100644 --- a/db/fixtures/001_categories.rb +++ b/db/fixtures/001_categories.rb @@ -28,8 +28,8 @@ end ColumnDropper.drop( table: 'categories', - after_migration: 'AddUploadsToCategories', - columns: ['logo_url', 'background_url'], + after_migration: 'AddSuppressFromLatestToCategories', + columns: ['logo_url', 'background_url', 'suppress_from_homepage'], on_drop: ->() { STDERR.puts 'Removing superflous categories columns!' } diff --git a/db/migrate/20180109222722_create_user_second_factors.rb b/db/migrate/20180109222722_create_user_second_factors.rb new file mode 100644 index 0000000000..ff038c1776 --- /dev/null +++ b/db/migrate/20180109222722_create_user_second_factors.rb @@ -0,0 +1,12 @@ +class CreateUserSecondFactors < ActiveRecord::Migration[5.1] + def change + create_table :user_second_factors do |t| + t.integer :user_id, null: false + t.integer :method, null: false + t.string :data, null: false + t.boolean :enabled, null: false, default: false + t.timestamp :last_used + t.timestamps + end + end +end diff --git a/db/migrate/20180118215249_create_theme_settings.rb b/db/migrate/20180118215249_create_theme_settings.rb new file mode 100644 index 0000000000..4803641ec2 --- /dev/null +++ b/db/migrate/20180118215249_create_theme_settings.rb @@ -0,0 +1,12 @@ +class CreateThemeSettings < ActiveRecord::Migration[5.1] + def change + create_table :theme_settings do |t| + t.string :name, limit: 255, null: false + t.integer :data_type, null: false + t.text :value + t.integer :theme_id, null: false + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20180221215641_add_suppress_from_latest_to_categories.rb b/db/migrate/20180221215641_add_suppress_from_latest_to_categories.rb new file mode 100644 index 0000000000..3a812a3c11 --- /dev/null +++ b/db/migrate/20180221215641_add_suppress_from_latest_to_categories.rb @@ -0,0 +1,11 @@ +class AddSuppressFromLatestToCategories < ActiveRecord::Migration[5.1] + def up + add_column :categories, :suppress_from_latest, :boolean, default: false + execute <<~SQL + UPDATE categories SET suppress_from_latest = suppress_from_homepage + SQL + end + def down + raise "can not be removed" + end +end diff --git a/db/migrate/20180223041147_fix_google_oauth2_prompt_data_type.rb b/db/migrate/20180223041147_fix_google_oauth2_prompt_data_type.rb new file mode 100644 index 0000000000..1317ec46c4 --- /dev/null +++ b/db/migrate/20180223041147_fix_google_oauth2_prompt_data_type.rb @@ -0,0 +1,16 @@ +class FixGoogleOauth2PromptDataType < ActiveRecord::Migration[5.1] + def up + sql = <<~SQL + UPDATE site_settings + SET data_type=#{SiteSettings::TypeSupervisor.types[:list]} + WHERE data_type=#{SiteSettings::TypeSupervisor.types[:enum]} + AND name='google_oauth2_prompt' + SQL + + execute sql + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20180223222415_remove_censored_pattern_site_setting.rb b/db/migrate/20180223222415_remove_censored_pattern_site_setting.rb new file mode 100644 index 0000000000..b82a300b9b --- /dev/null +++ b/db/migrate/20180223222415_remove_censored_pattern_site_setting.rb @@ -0,0 +1,19 @@ +class RemoveCensoredPatternSiteSetting < ActiveRecord::Migration[5.1] + def up + execute <<~SQL + INSERT INTO user_histories + (action, acting_user_id, subject, previous_value, + new_value, admin_only, created_at, updated_at) + SELECT 3, -1, 'censored_pattern', value, '', true, now(), now() + FROM site_settings + WHERE name = 'censored_pattern' + AND value != '' + SQL + + execute "DELETE FROM site_settings WHERE name = 'censored_pattern'" + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20180227161818_drop_unused_tables.rb b/db/migrate/20180227161818_drop_unused_tables.rb new file mode 100644 index 0000000000..1db8c57d55 --- /dev/null +++ b/db/migrate/20180227161818_drop_unused_tables.rb @@ -0,0 +1,10 @@ +class DropUnusedTables < ActiveRecord::Migration[5.1] + def up + drop_table :category_featured_users + drop_table :versions + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/docs/INSTALL-cloud.md b/docs/INSTALL-cloud.md index aaca49a3ad..3cee1f61fa 100644 --- a/docs/INSTALL-cloud.md +++ b/docs/INSTALL-cloud.md @@ -62,14 +62,17 @@ Launch the setup tool at Answer the following questions when prompted: Hostname for your Discourse? [discourse.example.com]: - Email address for admin account? [me@example.com]: + Email address for admin account(s)? [me@example.com,you@example.com]: SMTP server address? [smtp.example.com]: - SMTP user name? [postmaster@discourse.example.com]: - SMTP port [587]: - SMTP password? []: + SMTP port? [587]: + SMTP user name? [user@example.com]: + SMTP password? [pa$$word]: + Let's Encrypt account email? (ENTER to skip) [me@example.com]: This will generate an `app.yml` configuration file on your behalf, and then kicks off bootstrap. Bootstrapping takes between **2-8 minutes** to set up your Discourse. If you need to change these settings after bootstrapping, you can run `./discourse-setup` again (it will read your old values from the file) or edit `/containers/app.yml` with `nano` and then `./launcher rebuild app`, otherwise your changes will not take effect. +**NOTE:** You should not attempt to enable Let's Encrypt unless the DNS record for hostname resolves to your server. You can run `./discourse-setup` again later to make any changes. + ### Start Discourse Once bootstrapping is complete, your Discourse should be accessible in your web browser via the domain name `discourse.example.com` you entered earlier, provided you configured DNS. If not, you can visit the server IP directly, e.g. `http://192.168.1.1`. diff --git a/lib/admin_user_index_query.rb b/lib/admin_user_index_query.rb index b9d74e8a52..fd3da2a23a 100644 --- a/lib/admin_user_index_query.rb +++ b/lib/admin_user_index_query.rb @@ -63,7 +63,7 @@ class AdminUserIndexQuery if params[:stats].present? && params[:stats] == false klass.order(order.reject(&:blank?).join(",")) else - klass.includes(:user_stat).order(order.reject(&:blank?).join(",")) + klass.includes(:user_stat, :user_second_factor).order(order.reject(&:blank?).join(",")) end end diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb index c46ee178b3..06781518f3 100644 --- a/lib/auth/default_current_user_provider.rb +++ b/lib/auth/default_current_user_provider.rb @@ -5,15 +5,16 @@ require_dependency "rate_limiter" class Auth::DefaultCurrentUserProvider - CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER".freeze - API_KEY ||= "api_key".freeze - USER_API_KEY ||= "HTTP_USER_API_KEY".freeze - USER_API_CLIENT_ID ||= "HTTP_USER_API_CLIENT_ID".freeze - API_KEY_ENV ||= "_DISCOURSE_API".freeze - USER_API_KEY_ENV ||= "_DISCOURSE_USER_API".freeze - TOKEN_COOKIE ||= "_t".freeze - PATH_INFO ||= "PATH_INFO".freeze + CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER" + API_KEY ||= "api_key" + USER_API_KEY ||= "HTTP_USER_API_KEY" + USER_API_CLIENT_ID ||= "HTTP_USER_API_CLIENT_ID" + API_KEY_ENV ||= "_DISCOURSE_API" + USER_API_KEY_ENV ||= "_DISCOURSE_USER_API" + TOKEN_COOKIE ||= "_t" + PATH_INFO ||= "PATH_INFO" COOKIE_ATTEMPTS_PER_MIN ||= 10 + BAD_TOKEN ||= "_DISCOURSE_BAD_TOKEN" # do all current user initialization here def initialize(env) @@ -58,7 +59,8 @@ class Auth::DefaultCurrentUserProvider current_user = @user_token.try(:user) end - unless current_user + if !current_user + @env[BAD_TOKEN] = true begin limiter.performed! rescue RateLimiter::LimitExceeded @@ -69,6 +71,8 @@ class Auth::DefaultCurrentUserProvider ) end end + elsif @env['HTTP_DISCOURSE_LOGGED_IN'] + @env[BAD_TOKEN] = true end if current_user && should_update_last_seen? @@ -86,8 +90,11 @@ class Auth::DefaultCurrentUserProvider raise Discourse::InvalidAccess if current_user.suspended? || !current_user.active @env[API_KEY_ENV] = true - limiter_min = RateLimiter.new(nil, "admin_api_min_#{api_key}", GlobalSetting.max_admin_api_reqs_per_key_per_minute, 60) - limiter_min.performed! + # we do not run this rate limiter while profiling + if Rails.env != "profile" + limiter_min = RateLimiter.new(nil, "admin_api_min_#{api_key}", GlobalSetting.max_admin_api_reqs_per_key_per_minute, 60) + limiter_min.performed! + end end # user api key handling diff --git a/lib/auth/google_oauth2_authenticator.rb b/lib/auth/google_oauth2_authenticator.rb index dcee38d217..a280408193 100644 --- a/lib/auth/google_oauth2_authenticator.rb +++ b/lib/auth/google_oauth2_authenticator.rb @@ -51,15 +51,25 @@ class Auth::GoogleOAuth2Authenticator < Auth::Authenticator end def register_middleware(omniauth) + options = { + setup: lambda { |env| + strategy = env["omniauth.strategy"] + strategy.options[:client_id] = SiteSetting.google_oauth2_client_id + strategy.options[:client_secret] = SiteSetting.google_oauth2_client_secret + }, + skip_jwt: true + } + + if (google_oauth2_prompt = SiteSetting.google_oauth2_prompt).present? + options[:prompt] = google_oauth2_prompt.gsub("|", " ") + end + + google_oauth2_hd = SiteSetting.google_oauth2_hd + options[:hd] = google_oauth2_hd if google_oauth2_hd.present? + # jwt encoding is causing auth to fail in quite a few conditions # skipping - omniauth.provider :google_oauth2, - setup: lambda { |env| - strategy = env["omniauth.strategy"] - strategy.options[:client_id] = SiteSetting.google_oauth2_client_id - strategy.options[:client_secret] = SiteSetting.google_oauth2_client_secret - }, - skip_jwt: true + omniauth.provider :google_oauth2, options end protected diff --git a/lib/auth/result.rb b/lib/auth/result.rb index 911dca0a28..9f34d2c62e 100644 --- a/lib/auth/result.rb +++ b/lib/auth/result.rb @@ -4,7 +4,7 @@ class Auth::Result :awaiting_approval, :authenticated, :authenticator_name, :requires_invite, :not_allowed_from_ip_address, :admin_not_allowed_from_ip_address, :omit_username, - :skip_email_validation, :destination_url + :skip_email_validation, :destination_url, :omniauth_disallow_totp attr_accessor( :failed, @@ -42,13 +42,22 @@ class Auth::Result date: I18n.l(user.suspended_till, format: :date_only), reason: user.suspend_reason) } else - result = { - authenticated: !!authenticated, - awaiting_activation: !!awaiting_activation, - awaiting_approval: !!awaiting_approval, - not_allowed_from_ip_address: !!not_allowed_from_ip_address, - admin_not_allowed_from_ip_address: !!admin_not_allowed_from_ip_address - } + result = + if omniauth_disallow_totp + { + omniauth_disallow_totp: !!omniauth_disallow_totp, + email: email + } + else + { + authenticated: !!authenticated, + awaiting_activation: !!awaiting_activation, + awaiting_approval: !!awaiting_approval, + not_allowed_from_ip_address: !!not_allowed_from_ip_address, + admin_not_allowed_from_ip_address: !!admin_not_allowed_from_ip_address + } + end + result[:destination_url] = destination_url if authenticated && destination_url.present? result end diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb index c0c2b41377..fcffe92281 100644 --- a/lib/backup_restore/restorer.rb +++ b/lib/backup_restore/restorer.rb @@ -282,7 +282,7 @@ module BackupRestore def psql_command db_conf = BackupRestore.database_configuration - password_argument = "PGPASSWORD=#{db_conf.password}" if db_conf.password.present? + password_argument = "PGPASSWORD='#{db_conf.password}'" if db_conf.password.present? host_argument = "--host=#{db_conf.host}" if db_conf.host.present? port_argument = "--port=#{db_conf.port}" if db_conf.port.present? username_argument = "--username=#{db_conf.username}" if db_conf.username.present? diff --git a/lib/column_dropper.rb b/lib/column_dropper.rb index a291d212bc..31479077c7 100644 --- a/lib/column_dropper.rb +++ b/lib/column_dropper.rb @@ -6,7 +6,8 @@ class ColumnDropper raise ArgumentError.new("Invalid column name passed to drop #{column}") if column =~ /[^a-z0-9_]/i end - delay ||= Rails.env.production? ? 60 : 0 + # in production we need some extra delay to allow for slow migrations + delay ||= Rails.env.production? ? 3600 : 0 sql = <<~SQL SELECT 1 diff --git a/lib/composer_messages_finder.rb b/lib/composer_messages_finder.rb index 8b765c0e14..76c582dd57 100644 --- a/lib/composer_messages_finder.rb +++ b/lib/composer_messages_finder.rb @@ -20,7 +20,7 @@ class ComposerMessagesFinder # Determines whether to show the user education text def check_education_message - return if @topic && @topic.archetype == Archetype.private_message + return if @topic&.private_message? if creating_topic? count = @user.created_topic_count diff --git a/lib/discourse_tagging.rb b/lib/discourse_tagging.rb index 0fcf8bda9b..f9284f9751 100644 --- a/lib/discourse_tagging.rb +++ b/lib/discourse_tagging.rb @@ -4,10 +4,10 @@ module DiscourseTagging TAGS_FILTER_REGEXP = /[\/\?#\[\]@!\$&'\(\)\*\+,;=\.%\\`^\s|\{\}"<>]+/ # /?#[]@!$&'()*+,;=.%\`^|{}"<> def self.tag_topic_by_names(topic, guardian, tag_names_arg, append: false) - if SiteSetting.tagging_enabled + if guardian.can_tag?(topic) tag_names = DiscourseTagging.tags_for_saving(tag_names_arg, guardian) || [] - old_tag_names = topic.tags.map(&:name) || [] + old_tag_names = topic.tags.pluck(:name) || [] new_tag_names = tag_names - old_tag_names removed_tag_names = old_tag_names - tag_names diff --git a/lib/email/message_builder.rb b/lib/email/message_builder.rb index 9bc9cfd5dd..ac95582586 100644 --- a/lib/email/message_builder.rb +++ b/lib/email/message_builder.rb @@ -62,7 +62,7 @@ module Email subject = String.new(SiteSetting.email_subject) subject.gsub!("%{site_name}", @template_args[:email_prefix]) subject.gsub!("%{optional_re}", @opts[:add_re_to_subject] ? I18n.t('subject_re', @template_args) : '') - subject.gsub!("%{optional_pm}", @opts[:private_reply] ? I18n.t('subject_pm', @template_args) : '') + subject.gsub!("%{optional_pm}", @opts[:private_reply] ? @template_args[:subject_pm] : '') subject.gsub!("%{optional_cat}", @template_args[:show_category_in_subject] ? "[#{@template_args[:show_category_in_subject]}] " : '') subject.gsub!("%{topic_title}", @template_args[:topic_title]) if @template_args[:topic_title] # must be last for safety else diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index 19389398ca..2df138a986 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -238,11 +238,13 @@ module Email text_content_type = @mail.text_part&.content_type elsif @mail.content_type.to_s["text/html"] html = fix_charset(@mail) - else + elsif @mail.content_type.blank? || @mail.content_type["text/plain"] text = fix_charset(@mail) text_content_type = @mail.content_type end + return unless text.present? || html.present? + if text.present? text = trim_discourse_markers(text) text, elided_text = trim_reply_and_extract_elided(text) @@ -259,9 +261,14 @@ module Email end markdown, elided_markdown = if html.present? - markdown = HtmlToMarkdown.new(html, keep_img_tags: true, keep_cid_imgs: true).to_markdown - markdown = trim_discourse_markers(markdown) - trim_reply_and_extract_elided(markdown) + # use the first html extracter that matches + if html_extracter = HTML_EXTRACTERS.select { |_, r| html[r] }.min_by { |_, r| html =~ r } + self.send(:"extract_from_#{html_extracter[0]}", html) + else + markdown = HtmlToMarkdown.new(html, keep_img_tags: true, keep_cid_imgs: true).to_markdown + markdown = trim_discourse_markers(markdown) + trim_reply_and_extract_elided(markdown) + end end if text.blank? || (SiteSetting.incoming_email_prefer_html && markdown.present?) @@ -271,6 +278,68 @@ module Email end end + def to_markdown(html, elided_html) + markdown = HtmlToMarkdown.new(html, keep_img_tags: true, keep_cid_imgs: true).to_markdown + [EmailReplyTrimmer.trim(markdown), HtmlToMarkdown.new(elided_html).to_markdown] + end + + HTML_EXTRACTERS ||= [ + [:gmail, /class="gmail_/], + [:outlook, /id="(divRplyFwdMsg|Signature)"/], + [:word, /class="WordSection1"/], + [:exchange, /name="message(Body|Reply)Section"/], + [:apple_mail, /id="AppleMailSignature"/], + [:mozilla, /class="moz-/], + ] + + def extract_from_gmail(html) + doc = Nokogiri::HTML.fragment(html) + # GMail adds a bunch of 'gmail_' prefixed classes like: gmail_signature, gmail_extra, gmail_quote + # Just elide them all + elided = doc.css("*[class^='gmail_']").remove + to_markdown(doc.to_html, elided.to_html) + end + + def extract_from_outlook(html) + doc = Nokogiri::HTML.fragment(html) + # Outlook properly identifies the signature and any replied/forwarded email + # Use their id to remove them and anything that comes after + elided = doc.css("#Signature, #Signature ~ *, hr, #divRplyFwdMsg, #divRplyFwdMsg ~ *").remove + to_markdown(doc.to_html, elided.to_html) + end + + def extract_from_word(html) + doc = Nokogiri::HTML.fragment(html) + # Word (?) keeps the content in the 'WordSection1' class and uses

tags + # When there's something else (,
, etc..) there's high chance it's a signature or forwarded email + elided = doc.css(".WordSection1 > :not(p):not(ul):first-of-type, .WordSection1 > :not(p):not(ul):first-of-type ~ *").remove + to_markdown(doc.at(".WordSection1").to_html, elided.to_html) + end + + def extract_from_exchange(html) + doc = Nokogiri::HTML.fragment(html) + # Exchange is using the 'messageReplySection' class for forwarded emails + # And 'messageBodySection' for the actual email + elided = doc.css("div[name='messageReplySection']").remove + to_markdown(doc.css("div[name='messageBodySection'").to_html, elided.to_html) + end + + def extract_from_apple_mail(html) + doc = Nokogiri::HTML.fragment(html) + # AppleMail is the worst. It adds 'AppleMailSignature' ids (!) to several div/p with no deterministic rules + # Our best guess is to elide whatever comes after that. + elided = doc.css("#AppleMailSignature:last-of-type ~ *").remove + to_markdown(doc.to_html, elided.to_html) + end + + def extract_from_mozilla(html) + doc = Nokogiri::HTML.fragment(html) + # Mozilla (Thunderbird ?) properly identifies signature and forwarded emails + # Remove them and anything that comes after + elided = doc.css("*[class^='moz-'], *[class^='moz-'] ~ *").remove + to_markdown(doc.to_html, elided.to_html) + end + def trim_reply_and_extract_elided(text) return [text, ""] if @opts[:skip_trimming] EmailReplyTrimmer.trim(text, true) @@ -690,11 +759,17 @@ module Email raise InvalidPostAction.new(e) end + def is_whitelisted_attachment?(attachment) + attachment.content_type !~ SiteSetting.attachment_content_type_blacklist_regex && + attachment.filename !~ SiteSetting.attachment_filename_blacklist_regex + end + def attachments # strip blacklisted attachments (mostly signatures) - @attachments ||= @mail.attachments.select do |attachment| - attachment.content_type !~ SiteSetting.attachment_content_type_blacklist_regex && - attachment.filename !~ SiteSetting.attachment_filename_blacklist_regex + @attachments ||= begin + attachments = @mail.attachments.select { |attachment| is_whitelisted_attachment?(attachment) } + attachments << @mail if @mail.attachment? && is_whitelisted_attachment?(@mail) + attachments end end diff --git a/lib/email/sender.rb b/lib/email/sender.rb index 08e141a923..fbe78e1a40 100644 --- a/lib/email/sender.rb +++ b/lib/email/sender.rb @@ -109,7 +109,7 @@ module Email else @message.header['Message-ID'] = post_message_id @message.header['In-Reply-To'] = referenced_post_message_ids[0] || topic_message_id - @message.header['References'] = [referenced_post_message_ids, topic_message_id].flatten.compact.uniq + @message.header['References'] = [topic_message_id, referenced_post_message_ids].flatten.compact.uniq end # https://www.ietf.org/rfc/rfc2919.txt diff --git a/lib/emoji/db.json b/lib/emoji/db.json index 1f021752e4..4fd33ff3ba 100644 --- a/lib/emoji/db.json +++ b/lib/emoji/db.json @@ -6758,6 +6758,9 @@ ], "fleur_de_lis": [ "fleur-de-lis" + ], + "face_vomiting": [ + "puke" ] } } \ No newline at end of file diff --git a/lib/file_helper.rb b/lib/file_helper.rb index c8039bb9c5..7ef5952390 100644 --- a/lib/file_helper.rb +++ b/lib/file_helper.rb @@ -25,57 +25,55 @@ class FileHelper follow_redirect: false, read_timeout: 5, skip_rate_limit: false, - verbose: nil) - - # verbose logging is default while debugging onebox - verbose = verbose.nil? ? true : verbose + verbose: false) url = "https:" + url if url.start_with?("//") raise Discourse::InvalidParameters.new(:url) unless url =~ /^https?:\/\// - dest = FinalDestination.new( + tmp = nil + + fd = FinalDestination.new( url, max_redirects: follow_redirect ? 5 : 1, skip_rate_limit: skip_rate_limit, verbose: verbose ) - uri = dest.resolve - if !uri && dest.status_code.to_i >= 400 - # attempt error API compatability - io = FakeIO.new - io.status = [dest.status_code.to_s, ""] + fd.get do |response, chunk, uri| + if tmp.nil? + # error handling + if uri.blank? + if response.code.to_i >= 400 + # attempt error API compatibility + io = FakeIO.new + io.status = [response.code, ""] + raise OpenURI::HTTPError.new("#{response.code} Error", io) + else + log(:error, "FinalDestination did not work for: #{url}") if verbose + throw :done + end + end - # TODO perhaps translate and add Discourse::DownloadError - raise OpenURI::HTTPError.new("#{dest.status_code} Error", io) - end + # first run + tmp_file_ext = File.extname(uri.path) - unless uri - log(:error, "FinalDestination did not work for: #{url}") if verbose - return - end + if tmp_file_ext.blank? && response.content_type.present? + ext = MiniMime.lookup_by_content_type(response.content_type)&.extension + ext = "jpg" if ext == "jpe" + tmp_file_ext = "." + ext if ext.present? + end - downloaded = uri.open("rb", read_timeout: read_timeout) - - extension = File.extname(uri.path) - - if extension.blank? && downloaded.content_type.present? - ext = MiniMime.lookup_by_content_type(downloaded.content_type)&.extension - ext = "jpg" if ext == "jpe" - extension = "." + ext if ext.present? - end - - tmp = Tempfile.new([tmp_file_name, extension]) - - File.open(tmp.path, "wb") do |f| - while f.size <= max_file_size && data = downloaded.read(512.kilobytes) - f.write(data) + tmp = Tempfile.new([tmp_file_name, tmp_file_ext]) + tmp.binmode end + + tmp.write(chunk) + + throw :done if tmp.size > max_file_size end + tmp&.rewind tmp - ensure - downloaded&.close end def self.optimize_image!(filename) diff --git a/lib/final_destination.rb b/lib/final_destination.rb index 3c999e8a0e..46cdff3c01 100644 --- a/lib/final_destination.rb +++ b/lib/final_destination.rb @@ -73,7 +73,7 @@ class FinalDestination "Host" => @uri.hostname } - result['cookie'] = @cookie if @cookie + result['Cookie'] = @cookie if @cookie result end @@ -96,9 +96,7 @@ class FinalDestination uri = URI(uri.to_s) end - unless validate_uri - return nil - end + return nil unless validate_uri result, (location, cookie) = safe_get(uri, &blk) @@ -120,9 +118,7 @@ class FinalDestination return nil if !uri extra = nil - if cookie - extra = { 'Cookie' => cookie } - end + extra = { 'Cookie' => cookie } if cookie get(uri, redirects - 1, extra_headers: extra, &blk) elsif result == :ok @@ -164,7 +160,7 @@ class FinalDestination ) location = nil - headers = nil + response_headers = nil response_status = response.status.to_i @@ -172,7 +168,7 @@ class FinalDestination when 200 @status = :resolved return @uri - when 405, 406, 409, 501 + when 400, 405, 406, 409, 501 get_response = small_get(request_headers) response_status = get_response.code.to_i @@ -181,31 +177,29 @@ class FinalDestination return @uri end - headers = {} + response_headers = {} if cookie_val = get_response.get_fields('set-cookie') - headers['set-cookie'] = cookie_val.join + response_headers[:cookies] = cookie_val end - # TODO this is confusing why grab location for anything not - # between 300-400 ? if location_val = get_response.get_fields('location') - headers['location'] = location_val.join + response_headers[:location] = location_val.join end end - unless headers - headers = {} - response.headers.each do |k, v| - headers[k.to_s.downcase] = v - end + unless response_headers + response_headers = { + cookies: response.data[:cookies] || response.headers[:"set-cookie"], + location: response.headers[:location] + } end if (300..399).include?(response_status) - location = headers["location"] + location = response_headers[:location] end - if set_cookie = headers["set-cookie"] - @cookie = set_cookie + if cookies = response_headers[:cookies] + @cookie = Array.wrap(cookies).map { |c| c.split(';').first.strip }.join('; ') end if location @@ -253,7 +247,6 @@ class FinalDestination end def is_dest_valid? - return false unless @uri && @uri.host # Whitelisted hosts @@ -262,9 +255,7 @@ class FinalDestination hostname_matches?(Discourse.base_url_no_prefix) if SiteSetting.whitelist_internal_hosts.present? - SiteSetting.whitelist_internal_hosts.split('|').each do |h| - return true if @uri.hostname.downcase == h.downcase - end + return true if SiteSetting.whitelist_internal_hosts.split("|").any? { |h| h.downcase == @uri.hostname.downcase } end # Whitelisted hosts @@ -338,12 +329,10 @@ class FinalDestination protected def safe_get(uri) - result = nil unsafe_close = false safe_session(uri) do |http| - headers = request_headers.merge( 'Accept-Encoding' => 'gzip', 'Host' => uri.host @@ -357,9 +346,8 @@ class FinalDestination end if Net::HTTPSuccess === resp - resp.decode_content = true - resp.read_body { |chunk| + resp.read_body do |chunk| read_next = true catch(:done) do @@ -377,19 +365,19 @@ class FinalDestination http.finish raise StandardError end - } + end result = :ok + else + catch(:done) do + yield resp, nil, nil + end end end end result rescue StandardError - if unsafe_close - :ok - else - raise - end + unsafe_close ? :ok : raise end def safe_session(uri) diff --git a/lib/guardian.rb b/lib/guardian.rb index 19d9fe7823..c494d395f4 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -129,6 +129,16 @@ class Guardian alias :can_see_flags? :can_moderate? alias :can_close? :can_moderate? + def can_tag?(topic) + return false if topic.blank? + + topic.private_message? ? can_tag_pms? : can_tag_topics? + end + + def can_see_tags?(topic) + SiteSetting.tagging_enabled && topic.present? && (!topic.private_message? || can_tag_pms?) + end + def can_send_activation_email?(user) user && is_staff? && !SiteSetting.must_approve_users? end diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb index 4344e5fd34..81eb95f9f0 100644 --- a/lib/guardian/post_guardian.rb +++ b/lib/guardian/post_guardian.rb @@ -15,19 +15,22 @@ module PostGuardian return false if (action_key == :notify_user && !is_staff? && opts[:is_warning].present? && opts[:is_warning] == 'true') taken = opts[:taken_actions].try(:keys).to_a - is_flag = PostActionType.flag_types_without_custom[action_key] + is_flag = PostActionType.notify_flag_types[action_key] already_taken_this_action = taken.any? && taken.include?(PostActionType.types[action_key]) - already_did_flagging = taken.any? && (taken & PostActionType.flag_types_without_custom.values).any? + already_did_flagging = taken.any? && (taken & PostActionType.notify_flag_types.values).any? result = if authenticated? && post && !@user.anonymous? - # post made by staff, but we don't allow staff flags return false if is_flag && (!SiteSetting.allow_flagging_staff?) && post.user.staff? - return false if [:notify_user, :notify_moderators].include?(action_key) && - !SiteSetting.enable_personal_messages? + if [:notify_user, :notify_moderators].include?(action_key) && + (!SiteSetting.enable_personal_messages? || + !@user.has_trust_level?(SiteSetting.min_trust_to_send_messages)) + + return false + end # we allow flagging for trust level 1 and higher # always allowed for private messages @@ -37,20 +40,13 @@ module PostGuardian not(is_flag || already_taken_this_action) && # nothing except flagging on archived topics - not(post.topic.try(:archived?)) && + not(post.topic&.archived?) && # nothing except flagging on deleted posts not(post.trashed?) && # don't like your own stuff - not(action_key == :like && is_my_own?(post)) && - - # new users can't notify_user or notify_moderators because they are not allowed to send private messages - not((action_key == :notify_user || action_key == :notify_moderators) && - !@user.has_trust_level?(SiteSetting.min_trust_to_send_messages)) && - - # no voting more than once on single vote topics - not(action_key == :vote && opts[:voted_in_topic] && post.topic.has_meta_data_boolean?(:single_vote)) + not(action_key == :like && is_my_own?(post)) end !!result @@ -76,11 +72,6 @@ module PostGuardian return can_see_flags?(topic) if PostActionType.is_flag?(type_symbol) - if type_symbol == :vote - # We can see votes if the topic allows for public voting - return false if topic.has_meta_data_boolean?(:private_poll) - end - true end @@ -115,9 +106,13 @@ module PostGuardian # Must be staff to edit a locked post return false if post.locked? && !is_staff? - if is_staff? || @user.has_trust_level?(TrustLevel[4]) - return can_create_post?(post.topic) - end + return can_create_post?(post.topic) if ( + is_staff? || + ( + SiteSetting.trusted_users_can_edit_others? && + @user.has_trust_level?(TrustLevel[4]) + ) + ) if post.topic.archived? || post.user_deleted || post.deleted_at return false @@ -204,10 +199,6 @@ module PostGuardian can_see_post?(post) end - def can_vote?(post, opts = {}) - post_can_act?(post, :vote, opts: opts) - end - def can_change_post_owner? is_admin? end diff --git a/lib/guardian/tag_guardian.rb b/lib/guardian/tag_guardian.rb index b9315078df..84856d4d15 100644 --- a/lib/guardian/tag_guardian.rb +++ b/lib/guardian/tag_guardian.rb @@ -5,7 +5,11 @@ module TagGuardian end def can_tag_topics? - user && user.has_trust_level?(SiteSetting.min_trust_level_to_tag_topics.to_i) + user && SiteSetting.tagging_enabled && user.has_trust_level?(SiteSetting.min_trust_level_to_tag_topics.to_i) + end + + def can_tag_pms? + is_staff? && SiteSetting.tagging_enabled && SiteSetting.allow_staff_to_tag_pms end def can_admin_tags? diff --git a/lib/guardian/topic_guardian.rb b/lib/guardian/topic_guardian.rb index bf451ec2ba..014e707afd 100644 --- a/lib/guardian/topic_guardian.rb +++ b/lib/guardian/topic_guardian.rb @@ -50,10 +50,22 @@ module TopicGuardian return false if !can_create_topic_on_category?(topic.category) # TL4 users can edit archived topics, but can not edit private messages - return true if (topic.archived && !topic.private_message? && user.has_trust_level?(TrustLevel[4]) && can_create_post?(topic)) + return true if ( + SiteSetting.trusted_users_can_edit_others? && + topic.archived && + !topic.private_message? && + user.has_trust_level?(TrustLevel[4]) && + can_create_post?(topic) + ) # TL3 users can not edit archived topics and private messages - return true if (!topic.archived && !topic.private_message? && user.has_trust_level?(TrustLevel[3]) && can_create_post?(topic)) + return true if ( + SiteSetting.trusted_users_can_edit_others? && + !topic.archived && + !topic.private_message? && + user.has_trust_level?(TrustLevel[3]) && + can_create_post?(topic) + ) return false if topic.archived is_my_own?(topic) && !topic.edit_time_limit_expired? @@ -72,6 +84,7 @@ module TopicGuardian end def can_convert_topic?(topic) + return false unless SiteSetting.enable_personal_messages? return false if topic.blank? return false if topic && topic.trashed? return false if Category.where("topic_id = ?", topic.id).exists? diff --git a/lib/guardian/user_guardian.rb b/lib/guardian/user_guardian.rb index 26a1056262..0aef85c991 100644 --- a/lib/guardian/user_guardian.rb +++ b/lib/guardian/user_guardian.rb @@ -43,7 +43,7 @@ module UserGuardian if is_me?(user) user.post_count <= 1 else - is_staff? && (user.first_post_created_at.nil? || user.first_post_created_at > SiteSetting.delete_user_max_post_age.to_i.days.ago) + is_staff? && (user.first_post_created_at.nil? || user.post_count <= 5 || user.first_post_created_at > SiteSetting.delete_user_max_post_age.to_i.days.ago) end end @@ -72,4 +72,8 @@ module UserGuardian user == @user || is_staff? end + def can_disable_second_factor?(user) + user && can_administer_user?(user) + end + end diff --git a/lib/hijack.rb b/lib/hijack.rb index 9814ea4bef..e0641182d1 100644 --- a/lib/hijack.rb +++ b/lib/hijack.rb @@ -51,12 +51,14 @@ module Hijack instance.response.headers[k] = v end + view_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) begin instance.instance_eval(&blk) rescue => e # TODO we need to reuse our exception handling in ApplicationController Discourse.warn_exception(e, message: "Failed to process hijacked response correctly", env: env) end + view_runtime = Process.clock_gettime(Process::CLOCK_MONOTONIC) - view_start unless instance.response_body || response.committed? instance.status = 500 @@ -76,6 +78,10 @@ module Hijack headers['Content-Length'] = body.bytesize headers['Connection'] = "close" + if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN] + headers['Discourse-Logged-Out'] = '1' + end + status_string = Rack::Utils::HTTP_STATUS_CODES[response.status.to_i] || "Unknown" io.write "#{response.status} #{status_string}\r\n" @@ -94,6 +100,34 @@ module Hijack # happens if client terminated before we responded, ignore io = nil ensure + + if Rails.configuration.try(:lograge).try(:enabled) + if timings + db_runtime = 0 + if timings[:sql] + db_runtime = timings[:sql][:duration] + end + + subscriber = Lograge::RequestLogSubscriber.new + payload = ActiveSupport::HashWithIndifferentAccess.new( + controller: self.class.name, + action: action_name, + params: request.filtered_parameters, + headers: request.headers, + format: request.format.ref, + method: request.request_method, + path: request.fullpath, + view_runtime: view_runtime * 1000.0, + db_runtime: db_runtime * 1000.0, + timings: timings, + status: response.status + ) + + event = ActiveSupport::Notifications::Event.new("hijack", Time.now, Time.now + timings[:total_duration], "", payload) + subscriber.process_action(event) + end + end + MethodProfiler.clear Thread.current[Logster::Logger::LOGSTER_ENV] = nil diff --git a/lib/html_to_markdown.rb b/lib/html_to_markdown.rb index 99563379ae..3f362e6f94 100644 --- a/lib/html_to_markdown.rb +++ b/lib/html_to_markdown.rb @@ -75,11 +75,12 @@ class HtmlToMarkdown code = node.children.find { |c| c.name == "code" } code_class = code ? code["class"] : "" lang = code_class ? code_class[/lang-(\w+)/, 1] : "" - @stack << Block.new("pre") - @markdown << "```#{lang}\n" + pre = Block.new("pre") + pre.markdown = "```#{lang}\n" + @stack << pre traverse(node) + pre.markdown << "\n```\n" @markdown << format_block - @markdown << "```\n" end def visit_blockquote(node) diff --git a/lib/import_export/base_exporter.rb b/lib/import_export/base_exporter.rb index dec228fbc0..58d3b77e4e 100644 --- a/lib/import_export/base_exporter.rb +++ b/lib/import_export/base_exporter.rb @@ -5,7 +5,7 @@ module ImportExport CATEGORY_ATTRS = [:id, :name, :color, :created_at, :user_id, :slug, :description, :text_color, :auto_close_hours, :parent_category_id, :auto_close_based_on_last_post, - :topic_template, :suppress_from_homepage, :all_topics_wiki, :permissions_params] + :topic_template, :suppress_from_latest, :all_topics_wiki, :permissions_params] GROUP_ATTRS = [ :id, :name, :created_at, :mentionable_level, :messageable_level, :visibility_level, :automatic_membership_email_domains, :automatic_membership_retroactive, diff --git a/lib/method_profiler.rb b/lib/method_profiler.rb index 9ad59b007f..f624ea8f97 100644 --- a/lib/method_profiler.rb +++ b/lib/method_profiler.rb @@ -1,7 +1,16 @@ # see https://samsaffron.com/archive/2017/10/18/fastest-way-to-profile-a-method-in-ruby class MethodProfiler - def self.patch(klass, methods, name) + def self.patch(klass, methods, name, no_recurse: false) patches = methods.map do |method_name| + + recurse_protection = "" + if no_recurse + recurse_protection = <<~RUBY + return #{method_name}__mp_unpatched(*args, &blk) if @mp_recurse_protect_#{method_name} + @mp_recurse_protect_#{method_name} = true + RUBY + end + <<~RUBY unless defined?(#{method_name}__mp_unpatched) alias_method :#{method_name}__mp_unpatched, :#{method_name} @@ -9,6 +18,7 @@ class MethodProfiler unless prof = Thread.current[:_method_profiler] return #{method_name}__mp_unpatched(*args, &blk) end + #{recurse_protection} begin start = Process.clock_gettime(Process::CLOCK_MONOTONIC) #{method_name}__mp_unpatched(*args, &blk) @@ -16,6 +26,7 @@ class MethodProfiler data = (prof[:#{name}] ||= {duration: 0.0, calls: 0}) data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start data[:calls] += 1 + #{"@mp_recurse_protect_#{method_name} = false" if no_recurse} end end end diff --git a/lib/middleware/request_tracker.rb b/lib/middleware/request_tracker.rb index f0ce009a6c..2ad01b0521 100644 --- a/lib/middleware/request_tracker.rb +++ b/lib/middleware/request_tracker.rb @@ -24,6 +24,14 @@ class Middleware::RequestTracker MethodProfiler.patch(Redis::Client, [ :call, :call_pipeline ], :redis) + + MethodProfiler.patch(Net::HTTP, [ + :request + ], :net, no_recurse: true) + + MethodProfiler.patch(Excon::Connection, [ + :request + ], :net) @patched_instrumentation = true end @@ -161,8 +169,23 @@ class Middleware::RequestTracker if info && (headers = result[1]) headers["X-Runtime"] = "%0.6f" % info[:total_duration] end + + if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN] && (headers = result[1]) + headers['Discourse-Logged-Out'] = '1' + end + result ensure + if (limiters = env['DISCOURSE_RATE_LIMITERS']) && env['DISCOURSE_IS_ASSET_PATH'] + limiters.each(&:rollback!) + env['DISCOURSE_ASSET_RATE_LIMITERS'].each do |limiter| + begin + limiter.performed! + rescue RateLimiter::LimitExceeded + # skip + end + end + end log_request_info(env, result, info) unless env["discourse.request_tracker.skip"] end @@ -205,16 +228,35 @@ class Middleware::RequestTracker global: true ) + limiter_assets10 = RateLimiter.new( + nil, + "global_ip_limit_10_assets_#{ip}", + GlobalSetting.max_asset_reqs_per_ip_per_10_seconds, + 10, + global: true + ) + + env['DISCOURSE_RATE_LIMITERS'] = [limiter10, limiter60] + env['DISCOURSE_ASSET_RATE_LIMITERS'] = [limiter_assets10] + + warn = GlobalSetting.max_reqs_per_ip_mode == "warn" || + GlobalSetting.max_reqs_per_ip_mode == "warn+block" + + if !limiter_assets10.can_perform? + if warn + Rails.logger.warn("Global asset IP rate limit exceeded for #{ip}: 10 second rate limit, uri: #{env["REQUEST_URI"]}") + end + + return !(GlobalSetting.max_reqs_per_ip_mode == "warn") + end + type = 10 begin limiter10.performed! type = 60 limiter60.performed! rescue RateLimiter::LimitExceeded - if ( - GlobalSetting.max_reqs_per_ip_mode == "warn" || - GlobalSetting.max_reqs_per_ip_mode == "warn+block" - ) + if warn Rails.logger.warn("Global IP rate limit exceeded for #{ip}: #{type} second rate limit, uri: #{env["REQUEST_URI"]}") !(GlobalSetting.max_reqs_per_ip_mode == "warn") else diff --git a/lib/new_post_manager.rb b/lib/new_post_manager.rb index 39cf75cfaf..8eb03d32b6 100644 --- a/lib/new_post_manager.rb +++ b/lib/new_post_manager.rb @@ -133,9 +133,9 @@ class NewPostManager end def perform - if !self.class.exempt_user?(@user) && WordWatcher.new("#{@args[:title]} #{@args[:raw]}").should_block? + if !self.class.exempt_user?(@user) && matches = WordWatcher.new("#{@args[:title]} #{@args[:raw]}").should_block? result = NewPostResult.new(:created_post, false) - result.errors[:base] << I18n.t('contains_blocked_words') + result.errors[:base] << I18n.t('contains_blocked_words', word: matches[0]) return result end diff --git a/lib/onebox/templates/discourse_topic_onebox.hbs b/lib/onebox/templates/discourse_topic_onebox.hbs index 8b6d746b71..856d3342d4 100644 --- a/lib/onebox/templates/discourse_topic_onebox.hbs +++ b/lib/onebox/templates/discourse_topic_onebox.hbs @@ -1,4 +1,4 @@ -