diff --git a/.travis.yml b/.travis.yml index 62961abc4a..6fdc835355 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ env: - RUBY_GC_MALLOC_LIMIT=50000000 matrix: - "RAILS_MASTER=0" - - "RAILS42=1" - "RAILS_MASTER=1" addons: @@ -21,7 +20,6 @@ addons: matrix: allow_failures: - env: "RAILS_MASTER=1" - - env: "RAILS42=1" - rvm: rbx-2 fast_finish: true @@ -51,7 +49,6 @@ before_script: - bundle exec rake db:create db:migrate install: - - bash -c "if [ '$RAILS42' == '1' ]; then bundle update --retry=3 --jobs=3 rails rails-observers; fi" - bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails rails-observers seed-fu; fi" - bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi" diff --git a/Gemfile b/Gemfile index 2332689189..be623dde76 100644 --- a/Gemfile +++ b/Gemfile @@ -6,30 +6,19 @@ def rails_master? ENV["RAILS_MASTER"] == '1' end -def rails_42? - ENV["RAILS42"] == '1' -end - if rails_master? gem 'arel', git: 'https://github.com/rails/arel.git' gem 'rails', git: 'https://github.com/rails/rails.git' gem 'rails-observers', git: 'https://github.com/rails/rails-observers.git' gem 'seed-fu', git: 'https://github.com/SamSaffron/seed-fu.git', branch: 'discourse' -elsif rails_42? - gem 'rails', '~> 4.2.1' - gem 'rails-observers', git: 'https://github.com/rails/rails-observers.git' - gem 'seed-fu', '~> 2.3.5' else - gem 'rails', '~> 4.1.10' + gem 'rails', '~> 4.2' gem 'rails-observers' - gem 'seed-fu', '~> 2.3.3' + gem 'seed-fu', '~> 2.3.5' end -# Rails 4.1.6+ will relax the mail gem version requirement to `~> 2.5, >= 2.5.4`. -# However, mail gem 2.6.x currently does not work with discourse because of the -# reference to `Mail::RFC2822Parser` in `lib/email.rb`. This ensure discourse -# would continue to work with Rails 4.1.6+ when it is released. -gem 'mail', '~> 2.5.4' +gem 'mail' +gem 'mime-types', require: 'mime/types/columnar' #gem 'redis-rails' gem 'hiredis' @@ -48,7 +37,6 @@ gem 'babel-transpiler' gem 'message_bus' gem 'rails_multisite', path: 'vendor/gems/rails_multisite' -gem 'redcarpet', require: false gem 'fast_xs' gem 'fast_xor' diff --git a/Gemfile.lock b/Gemfile.lock index 46f0d552f3..d96defd9c0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,46 +6,53 @@ PATH GEM remote: https://rubygems.org/ specs: - actionmailer (4.1.10) - actionpack (= 4.1.10) - actionview (= 4.1.10) + actionmailer (4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) mail (~> 2.5, >= 2.5.4) - actionpack (4.1.10) - actionview (= 4.1.10) - activesupport (= 4.1.10) - rack (~> 1.5.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.4) + actionview (= 4.2.4) + activesupport (= 4.2.4) + rack (~> 1.6) rack-test (~> 0.6.2) - actionview (4.1.10) - activesupport (= 4.1.10) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.4) + activesupport (= 4.2.4) builder (~> 3.1) erubis (~> 2.7.0) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) active_model_serializers (0.8.3) activemodel (>= 3.0) - activemodel (4.1.10) - activesupport (= 4.1.10) + activejob (4.2.4) + activesupport (= 4.2.4) + globalid (>= 0.3.0) + activemodel (4.2.4) + activesupport (= 4.2.4) builder (~> 3.1) - activerecord (4.1.10) - activemodel (= 4.1.10) - activesupport (= 4.1.10) - arel (~> 5.0.0) - activesupport (4.1.10) - i18n (~> 0.6, >= 0.6.9) + activerecord (4.2.4) + activemodel (= 4.2.4) + activesupport (= 4.2.4) + arel (~> 6.0) + activesupport (4.2.4) + i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.1) + thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - annotate (2.6.6) - activerecord (>= 2.3.0) - rake (~> 10.4.2, >= 10.4.2) - arel (5.0.1.20140414130214) - aws-sdk (2.0.45) - aws-sdk-resources (= 2.0.45) - aws-sdk-core (2.0.45) - builder (~> 3.0) + annotate (2.6.10) + activerecord (>= 3.2, <= 4.3) + rake (~> 10.4) + arel (6.0.3) + aws-sdk (2.1.23) + aws-sdk-resources (= 2.1.23) + aws-sdk-core (2.1.23) jmespath (~> 1.0) - multi_json (~> 1.0) - aws-sdk-resources (2.0.45) - aws-sdk-core (= 2.0.45) + aws-sdk-resources (2.1.23) + aws-sdk-core (= 2.1.23) babel-source (5.8.19) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) @@ -60,22 +67,59 @@ GEM binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) - byebug (5.0.0) - columnize (= 0.9.0) - celluloid (0.16.0) - timers (~> 4.0.0) + byebug (6.0.2) + celluloid (0.17.1.2) + bundler + celluloid-essentials + celluloid-extras + celluloid-fsm + celluloid-pool + celluloid-supervision + dotenv + nenv + rspec-logsplit (>= 0.1.2) + timers (>= 4.1.1) + celluloid-essentials (0.20.2.1) + bundler + dotenv + nenv + rspec-logsplit (>= 0.1.2) + timers (>= 4.1.1) + celluloid-extras (0.20.1) + bundler + dotenv + nenv + rspec-logsplit (>= 0.1.2) + timers (>= 4.1.1) + celluloid-fsm (0.20.1) + bundler + dotenv + nenv + rspec-logsplit (>= 0.1.2) + timers (>= 4.1.1) + celluloid-pool (0.20.1) + bundler + dotenv + nenv + rspec-logsplit (>= 0.1.2) + timers (>= 4.1.1) + celluloid-supervision (0.20.1.1) + bundler + dotenv + nenv + rspec-logsplit (>= 0.1.2) + timers (>= 4.1.1) certified (1.0.0) coderay (1.1.0) - columnize (0.9.0) connection_pool (2.2.0) - crass (1.0.1) - daemons (1.2.2) + crass (1.0.2) + daemons (1.2.3) debug_inspector (0.0.2) diff-lcs (1.2.5) discourse-qunit-rails (0.0.8) railties docile (1.1.5) - dotenv (1.0.2) + dotenv (2.0.2) email_reply_parser (0.5.8) ember-data-source (1.0.0.beta.16.1) ember-source (~> 1.8) @@ -91,15 +135,15 @@ GEM railties (>= 3.1) ember-source (1.12.1) erubis (2.7.0) - eventmachine (1.0.7) - excon (0.45.3) - execjs (2.5.2) - exifr (1.2.2) + eventmachine (1.0.8) + excon (0.45.4) + execjs (2.6.0) + exifr (1.2.3.1) fabrication (2.9.8) fakeweb (1.3.0) faraday (0.9.1) multipart-post (>= 1.2, < 3) - fast_blank (0.0.2) + fast_blank (1.0.0) fast_stack (0.1.0) rake rake-compiler @@ -108,24 +152,25 @@ GEM rake-compiler fast_xs (0.8.0) fastimage_discourse (1.6.6) - ffi (1.9.6) + ffi (1.9.10) flamegraph (0.1.0) fast_stack - foreman (0.77.0) - dotenv (~> 1.0.2) + foreman (0.78.0) thor (~> 0.19.1) fspath (2.1.1) gctools (0.2.3) given_core (3.5.4) sorcerer (>= 0.3.7) + globalid (0.3.6) + activesupport (>= 4.1.0) guess_html_encoding (0.0.11) handlebars-source (2.0.0) - hashie (3.4.0) - highline (1.7.1) + hashie (3.4.2) + highline (1.7.7) hike (1.2.3) hiredis (0.6.0) - hitimes (1.2.2) - htmlentities (4.3.3) + hitimes (1.2.3) + htmlentities (4.3.4) i18n (0.7.0) image_optim (0.20.2) exifr (~> 1.1, >= 1.1.3) @@ -135,46 +180,47 @@ GEM progress (~> 3.0, >= 3.0.1) image_size (1.4.1) in_threads (1.3.1) - jmespath (1.0.2) - multi_json (~> 1.0) + jmespath (1.1.3) jquery-rails (3.1.2) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.8.3) - jwt (1.3.0) - kgio (2.9.3) + jwt (1.5.1) + kgio (2.10.0) librarian (0.1.2) highline thor (~> 0.15) - libv8 (3.16.14.7) + libv8 (3.16.14.11) listen (0.7.3) logster (1.0.0.3.pre) + loofah (2.0.3) + nokogiri (>= 1.5.9) lru_redux (1.1.0) - mail (2.5.4) - mime-types (~> 1.16) - treetop (~> 1.4.8) - memory_profiler (0.9.3) + mail (2.6.3) + mime-types (>= 1.16, < 3) + memory_profiler (0.9.4) message_bus (1.0.16) rack (>= 1.1.3) redis metaclass (0.0.4) method_source (0.8.2) - mime-types (1.25.1) + mime-types (2.6.2) mini_portile (0.6.2) - minitest (5.6.1) + minitest (5.8.0) mocha (1.1.0) metaclass (~> 0.0.1) - mock_redis (0.14.0) + mock_redis (0.15.2) moneta (0.8.0) - msgpack (0.5.11) + msgpack (0.6.2) multi_json (1.11.2) multi_xml (0.5.5) multipart-post (2.0.0) mustache (1.0.2) + nenv (0.2.0) netrc (0.10.3) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) - nokogumbo (1.2.0) + nokogumbo (1.4.1) nokogiri oauth (0.4.7) oauth2 (1.0.0) @@ -183,11 +229,11 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (~> 1.2) - oj (2.12.9) + oj (2.12.14) omniauth (1.2.2) hashie (>= 1.2, < 4) rack (~> 1.0) - omniauth-facebook (2.0.0) + omniauth-facebook (2.0.1) omniauth-oauth2 (~> 1.2) omniauth-github-discourse (1.1.2) omniauth (~> 1.0) @@ -195,20 +241,18 @@ GEM omniauth-google-oauth2 (0.2.5) omniauth (> 1.0) omniauth-oauth2 (~> 1.1) - omniauth-oauth (1.0.1) + omniauth-oauth (1.1.0) oauth omniauth (~> 1.0) - omniauth-oauth2 (1.2.0) - faraday (>= 0.8, < 0.10) - multi_json (~> 1.3) + omniauth-oauth2 (1.3.1) oauth2 (~> 1.0) omniauth (~> 1.2) omniauth-openid (1.0.1) omniauth (~> 1.0) rack-openid (~> 1.3.1) - omniauth-twitter (1.0.1) - multi_json (~> 1.3) - omniauth-oauth (~> 1.0) + omniauth-twitter (1.2.1) + json (~> 1.3) + omniauth-oauth (~> 1.1) onebox (1.5.26) moneta (~> 0.8) multi_json (~> 1.11) @@ -217,8 +261,7 @@ GEM openid-redis-store (0.0.2) redis ruby-openid - pg (0.18.1) - polyglot (0.3.5) + pg (0.18.3) progress (3.1.0) pry (0.10.1) coderay (~> 1.1.0) @@ -226,13 +269,12 @@ GEM slop (~> 3.4) pry-nav (0.2.4) pry (>= 0.9.10, < 0.11.0) - pry-rails (0.3.3) + pry-rails (0.3.4) pry (>= 0.9.10) - puma (2.11.1) - rack (>= 1.1, < 2.0) + puma (2.14.0) r2 (0.2.5) - rack (1.5.5) - rack-mini-profiler (0.9.6) + rack (1.6.4) + rack-mini-profiler (0.9.7) rack (>= 1.1.3) rack-openid (1.3.1) rack (>= 1.1.0) @@ -241,39 +283,47 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.1.10) - actionmailer (= 4.1.10) - actionpack (= 4.1.10) - actionview (= 4.1.10) - activemodel (= 4.1.10) - activerecord (= 4.1.10) - activesupport (= 4.1.10) + rails (4.2.4) + actionmailer (= 4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) + activemodel (= 4.2.4) + activerecord (= 4.2.4) + activesupport (= 4.2.4) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.10) - sprockets-rails (~> 2.0) + railties (= 4.2.4) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.7) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.2) + loofah (~> 2.0) rails-observers (0.1.2) activemodel (~> 4.0) - railties (4.1.10) - actionpack (= 4.1.10) - activesupport (= 4.1.10) + railties (4.2.4) + actionpack (= 4.2.4) + activesupport (= 4.2.4) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - raindrops (0.13.0) + raindrops (0.15.0) rake (10.4.2) - rake-compiler (0.9.4) + rake-compiler (0.9.5) rake - rb-fsevent (0.9.4) + rb-fsevent (0.9.6) rb-inotify (0.9.5) ffi (>= 0.5.0) rbtrace (0.4.7) ffi (>= 1.0.6) msgpack (>= 0.4.3) trollop (>= 1.16.2) - redcarpet (3.2.2) redis (3.2.1) redis-namespace (1.5.2) redis (~> 3.0, >= 3.0.4) - ref (1.0.5) + ref (2.0.0) rest-client (1.7.2) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) @@ -291,6 +341,7 @@ GEM rspec-given (3.5.4) given_core (= 3.5.4) rspec (>= 2.12) + rspec-logsplit (0.1.3) rspec-mocks (3.2.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.2.0) @@ -304,14 +355,14 @@ GEM rspec-support (~> 3.2.0) rspec-support (3.2.2) rtlit (0.0.5) - ruby-openid (2.5.0) + ruby-openid (2.7.0) ruby-readability (0.7.0) guess_html_encoding (>= 0.0.4) nokogiri (>= 1.6.0) - sanitize (3.1.2) - crass (~> 1.0.1) + sanitize (4.0.0) + crass (~> 1.0.2) nokogiri (>= 1.4.4) - nokogumbo (= 1.2.0) + nokogumbo (= 1.4.1) sass (3.2.19) sass-rails (4.0.5) railties (>= 4.0.0, < 5.0) @@ -327,8 +378,8 @@ GEM shoulda-context (1.2.1) shoulda-matchers (2.7.0) activesupport (>= 3.0.0) - sidekiq (3.4.2) - celluloid (~> 0.16.0) + sidekiq (3.5.0) + celluloid (~> 0.17.0) connection_pool (~> 2.2, >= 2.2.0) json (~> 1.0) redis (~> 3.2, >= 3.2.1) @@ -336,15 +387,15 @@ GEM sidekiq-statistic (1.1.0) sidekiq (~> 3.3, >= 3.3.4) simple-rss (1.3.1) - simplecov (0.9.1) + simplecov (0.10.0) docile (~> 1.1.0) - multi_json (~> 1.0) - simplecov-html (~> 0.8.0) - simplecov-html (0.8.0) - sinatra (1.4.5) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + sinatra (1.4.6) rack (~> 1.4) rack-protection (~> 1.4) - tilt (~> 1.3, >= 1.3.4) + tilt (>= 1.3, < 3) slop (3.6.0) sorcerer (1.0.2) spork (1.0.0rc4) @@ -364,29 +415,26 @@ GEM therubyracer (0.12.2) libv8 (~> 3.16.14.0) ref - thin (1.6.3) + thin (1.6.4) daemons (~> 1.0, >= 1.0.9) - eventmachine (~> 1.0) + eventmachine (~> 1.0, >= 1.0.4) rack (~> 1.0) thor (0.19.1) thread_safe (0.3.5) tilt (1.4.1) - timecop (0.7.3) - timers (4.0.1) + timecop (0.8.0) + timers (4.1.1) hitimes - treetop (1.4.15) - polyglot - polyglot (>= 0.3.1) trollop (2.1.1) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (2.7.1) + uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) unf (0.1.4) unf_ext unf_ext (0.0.6) - unicorn (4.8.3) + unicorn (4.9.0) kgio (~> 2.6) rack raindrops (~> 0.7) @@ -427,9 +475,10 @@ DEPENDENCIES listen (= 0.7.3) logster lru_redux - mail (~> 2.5.4) + mail memory_profiler message_bus + mime-types minitest mocha mock_redis @@ -453,14 +502,13 @@ DEPENDENCIES r2 (~> 0.2.5) rack-mini-profiler rack-protection - rails (~> 4.1.10) + rails (~> 4.2) rails-observers rails_multisite! rake rb-fsevent rb-inotify (~> 0.9) rbtrace - redcarpet redis rest-client rinku @@ -473,7 +521,7 @@ DEPENDENCIES sanitize sass sass-rails (~> 4.0.5) - seed-fu (~> 2.3.3) + seed-fu (~> 2.3.5) shoulda sidekiq sidekiq-statistic diff --git a/app/assets/javascripts/admin/components/embedding-setting.js.es6 b/app/assets/javascripts/admin/components/embedding-setting.js.es6 index 904afacfee..039e5d5dc3 100644 --- a/app/assets/javascripts/admin/components/embedding-setting.js.es6 +++ b/app/assets/javascripts/admin/components/embedding-setting.js.es6 @@ -6,6 +6,11 @@ export default Ember.Component.extend({ @computed('field') inputId(field) { return field.dasherize(); }, + @computed('placeholder') + placeholderValue(placeholder) { + return placeholder ? I18n.t(placeholder) : null; + }, + @computed('field') translationKey(field) { return `admin.embedding.${field}`; }, diff --git a/app/assets/javascripts/admin/components/screened_ip_address_form_component.js b/app/assets/javascripts/admin/components/screened_ip_address_form_component.js index 69c517f00b..1bf90b0227 100644 --- a/app/assets/javascripts/admin/components/screened_ip_address_form_component.js +++ b/app/assets/javascripts/admin/components/screened_ip_address_form_component.js @@ -18,14 +18,25 @@ Discourse.ScreenedIpAddressFormComponent = Ember.Component.extend({ formSubmitted: false, actionName: 'block', - actionNames: function() { - return [ - {id: 'block', name: I18n.t('admin.logs.screened_ips.actions.block')}, - {id: 'do_nothing', name: I18n.t('admin.logs.screened_ips.actions.do_nothing')}, - {id: 'allow_admin', name: I18n.t('admin.logs.screened_ips.actions.allow_admin')} - ]; + adminWhitelistEnabled: function() { + return Discourse.SiteSettings.use_admin_ip_whitelist; }.property(), + actionNames: function() { + if (this.get('adminWhitelistEnabled')) { + return [ + {id: 'block', name: I18n.t('admin.logs.screened_ips.actions.block')}, + {id: 'do_nothing', name: I18n.t('admin.logs.screened_ips.actions.do_nothing')}, + {id: 'allow_admin', name: I18n.t('admin.logs.screened_ips.actions.allow_admin')} + ]; + } else { + return [ + {id: 'block', name: I18n.t('admin.logs.screened_ips.actions.block')}, + {id: 'do_nothing', name: I18n.t('admin.logs.screened_ips.actions.do_nothing')} + ]; + } + }.property('adminWhitelistEnabled'), + actions: { submit: function() { if (!this.get('formSubmitted')) { diff --git a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 index 97b1c9ace9..b7c4302e87 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 @@ -5,7 +5,7 @@ export default Ember.ArrayController.extend({ onlyOverridden: false, filtered: Ember.computed.notEmpty('filter'), - filterContentNow: function(category) { + filterContentNow(category) { // If we have no content, don't bother filtering anything if (!!Ember.isEmpty(this.get('allSiteSettings'))) return; @@ -20,12 +20,13 @@ export default Ember.ArrayController.extend({ return; } - const self = this, - matchesGroupedByCategory = [{nameKey: 'all_results', name: I18n.t('admin.site_settings.categories.all_results'), siteSettings: []}]; + const all = {nameKey: 'all_results', name: I18n.t('admin.site_settings.categories.all_results'), siteSettings: []}; + const matchesGroupedByCategory = [all]; - this.get('allSiteSettings').forEach(function(settingsCategory) { - const matches = settingsCategory.siteSettings.filter(function(item) { - if (self.get('onlyOverridden') && !item.get('overridden')) return false; + const matches = []; + this.get('allSiteSettings').forEach(settingsCategory => { + const siteSettings = settingsCategory.siteSettings.filter(item => { + if (this.get('onlyOverridden') && !item.get('overridden')) return false; if (filter) { if (item.get('setting').toLowerCase().indexOf(filter) > -1) return true; if (item.get('setting').toLowerCase().replace(/_/g, ' ').indexOf(filter) > -1) return true; @@ -36,16 +37,20 @@ export default Ember.ArrayController.extend({ return true; } }); - if (matches.length > 0) { - matchesGroupedByCategory[0].siteSettings.pushObjects(matches); + if (siteSettings.length > 0) { + matches.pushObjects(siteSettings); matchesGroupedByCategory.pushObject({ nameKey: settingsCategory.nameKey, name: I18n.t('admin.site_settings.categories.' + settingsCategory.nameKey), - siteSettings: matches + siteSettings, + count: siteSettings.length }); } }); + all.siteSettings.pushObjects(matches.slice(0, 30)); + all.count = matches.length; + this.set('model', matchesGroupedByCategory); this.transitionToRoute("adminSiteSettingsCategory", category || "all_results"); }, @@ -60,10 +65,7 @@ export default Ember.ArrayController.extend({ actions: { clearFilter() { - this.setProperties({ - filter: '', - onlyOverridden: false - }); + this.setProperties({ filter: '', onlyOverridden: false }); }, toggleMenu() { diff --git a/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 index b85fbaf069..de9b3cc982 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 @@ -56,12 +56,12 @@ export default Ember.ArrayController.extend({ var badges = []; this.get('badges').forEach(function(badge) { - if (badge.get('multiple_grant') || !granted[badge.get('id')]) { + if (badge.get('enabled') && (badge.get('multiple_grant') || !granted[badge.get('id')])) { badges.push(badge); } }); - return _.sortBy(badges, "name"); + return _.sortBy(badges, badge => badge.get('displayName')); }.property('badges.@each', 'model.@each'), /** diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6 index 7cda05c820..e8db973a88 100644 --- a/app/assets/javascripts/admin/models/admin-user.js.es6 +++ b/app/assets/javascripts/admin/models/admin-user.js.es6 @@ -387,16 +387,16 @@ const AdminUser = Discourse.User.extend({ const buttons = [{ "label": I18n.t("composer.cancel"), - "class": "cancel", - "link": true - }, { - "label": I18n.t('admin.user.delete_dont_block'), "class": "btn", - "callback": function(){ performDestroy(false); } + "link": true }, { "label": '' + I18n.t('admin.user.delete_and_block'), "class": "btn btn-danger", "callback": function(){ performDestroy(true); } + }, { + "label": I18n.t('admin.user.delete_dont_block'), + "class": "btn btn-primary", + "callback": function(){ performDestroy(false); } }]; bootbox.dialog(message, buttons, { "classes": "delete-user-modal" }); diff --git a/app/assets/javascripts/admin/models/staff_action_log.js b/app/assets/javascripts/admin/models/staff_action_log.js index dc52a7b170..0dde773661 100644 --- a/app/assets/javascripts/admin/models/staff_action_log.js +++ b/app/assets/javascripts/admin/models/staff_action_log.js @@ -11,6 +11,7 @@ Discourse.StaffActionLog = Discourse.Model.extend({ formatted += this.format('admin.logs.ip_address', 'ip_address'); formatted += this.format('admin.logs.topic_id', 'topic_id'); formatted += this.format('admin.logs.post_id', 'post_id'); + formatted += this.format('admin.logs.category_id', 'category_id'); if (!this.get('useCustomModalForDetails')) { formatted += this.format('admin.logs.staff_actions.new_value', 'new_value'); formatted += this.format('admin.logs.staff_actions.previous_value', 'previous_value'); @@ -19,7 +20,7 @@ Discourse.StaffActionLog = Discourse.Model.extend({ if (this.get('details')) formatted += Handlebars.Utils.escapeExpression(this.get('details')) + '
'; } return formatted; - }.property('ip_address', 'email', 'topic_id', 'post_id'), + }.property('ip_address', 'email', 'topic_id', 'post_id', 'category_id'), format: function(label, propertyName) { if (this.get(propertyName)) { diff --git a/app/assets/javascripts/admin/templates/admin.hbs b/app/assets/javascripts/admin/templates/admin.hbs index c7746a6d4a..6c3008dd28 100644 --- a/app/assets/javascripts/admin/templates/admin.hbs +++ b/app/assets/javascripts/admin/templates/admin.hbs @@ -13,8 +13,8 @@ {{/if}} {{#if currentUser.admin}} {{nav-item route='adminGroups' label='admin.groups.title'}} + {{nav-item route='adminEmail' label='admin.email.title'}} {{/if}} - {{nav-item route='adminEmail' label='admin.email.title'}} {{nav-item route='adminFlags' label='admin.flags.title'}} {{nav-item route='adminLogs' label='admin.logs.title'}} {{#if currentUser.admin}} diff --git a/app/assets/javascripts/admin/templates/components/embedding-setting.hbs b/app/assets/javascripts/admin/templates/components/embedding-setting.hbs index 36dbb88692..e3a99c3202 100644 --- a/app/assets/javascripts/admin/templates/components/embedding-setting.hbs +++ b/app/assets/javascripts/admin/templates/components/embedding-setting.hbs @@ -5,7 +5,7 @@ {{else}} - {{input value=value id=inputId}} + {{input value=value id=inputId placeholder=placeholderValue}} {{/if}}
diff --git a/app/assets/javascripts/admin/templates/components/site-setting.hbs b/app/assets/javascripts/admin/templates/components/site-setting.hbs index be0d4d8a08..32510bef5f 100644 --- a/app/assets/javascripts/admin/templates/components/site-setting.hbs +++ b/app/assets/javascripts/admin/templates/components/site-setting.hbs @@ -2,7 +2,7 @@

{{unbound settingName}}

-{{component componentName setting=setting value=buffered.value validationMessage=validationMessage}} + {{component componentName setting=setting value=buffered.value validationMessage=validationMessage}}
{{#if dirty}}
diff --git a/app/assets/javascripts/admin/templates/dashboard.hbs b/app/assets/javascripts/admin/templates/dashboard.hbs index 9a3d98496a..87d9cb699d 100644 --- a/app/assets/javascripts/admin/templates/dashboard.hbs +++ b/app/assets/javascripts/admin/templates/dashboard.hbs @@ -145,7 +145,7 @@ {{i18n 'admin.dashboard.uploads'}} {{disk_space.uploads_used}} ({{i18n 'admin.dashboard.space_free' size=disk_space.uploads_free}}) - {{i18n 'admin.dashboard.backups'}} + {{#if currentUser.admin}}{{i18n 'admin.dashboard.backups'}}{{/if}} {{disk_space.backups_used}} ({{i18n 'admin.dashboard.space_free' size=disk_space.backups_free}}) {{/unless}} diff --git a/app/assets/javascripts/admin/templates/embedding.hbs b/app/assets/javascripts/admin/templates/embedding.hbs index 15d021a9c1..d41970fb84 100644 --- a/app/assets/javascripts/admin/templates/embedding.hbs +++ b/app/assets/javascripts/admin/templates/embedding.hbs @@ -46,8 +46,13 @@

{{i18n "admin.embedding.crawling_settings"}}

{{i18n "admin.embedding.crawling_description"}}

- {{embedding-setting field="embed_whitelist_selector" value=embedding.embed_whitelist_selector}} - {{embedding-setting field="embed_blacklist_selector" value=embedding.embed_blacklist_selector}} + {{embedding-setting field="embed_whitelist_selector" + value=embedding.embed_whitelist_selector + placeholder="admin.embedding.whitelist_example"}} + + {{embedding-setting field="embed_blacklist_selector" + value=embedding.embed_blacklist_selector + placeholder="admin.embedding.blacklist_example"}}
diff --git a/app/assets/javascripts/admin/templates/plugins-index.hbs b/app/assets/javascripts/admin/templates/plugins-index.hbs index 5236d9d999..b5928ae1ef 100644 --- a/app/assets/javascripts/admin/templates/plugins-index.hbs +++ b/app/assets/javascripts/admin/templates/plugins-index.hbs @@ -1,9 +1,11 @@ {{#if length}} - {{d-button label="admin.plugins.change_settings" - icon="gear" - class='settings-button pull-right' - action="showSettings"}} + {{#if currentUser.admin}} + {{d-button label="admin.plugins.change_settings" + icon="gear" + class='settings-button pull-right' + action="showSettings"}} + {{/if}}

{{i18n "admin.plugins.installed"}}

@@ -41,11 +43,10 @@ {{/if}} - {{#if plugin.enabled_setting}} - + {{#if currentUser.admin}} + {{#if plugin.enabled_setting}} + {{d-button action="showSettings" actionParam=plugin icon="gear" label="admin.plugins.change_settings_short"}} + {{/if}} {{/if}} diff --git a/app/assets/javascripts/admin/templates/site-settings-category.hbs b/app/assets/javascripts/admin/templates/site-settings-category.hbs index 8cb5aedaa3..3e8b088f69 100644 --- a/app/assets/javascripts/admin/templates/site-settings-category.hbs +++ b/app/assets/javascripts/admin/templates/site-settings-category.hbs @@ -1,6 +1,6 @@ {{#if filteredContent}}
- {{#each setting in filteredContent}} + {{#each filteredContent as |setting|}} {{site-setting setting=setting saveAction="saveSetting"}} {{/each}}
diff --git a/app/assets/javascripts/admin/templates/site-settings.hbs b/app/assets/javascripts/admin/templates/site-settings.hbs index b7f400a242..03042fef84 100644 --- a/app/assets/javascripts/admin/templates/site-settings.hbs +++ b/app/assets/javascripts/admin/templates/site-settings.hbs @@ -6,9 +6,9 @@
- + {{d-button action="toggleMenu" class="menu-toggle" icon="bars"}} {{text-field value=filter placeholderKey="type_to_filter" class="no-blur"}} - + {{d-button action="clearFilter" label="admin.site_settings.clear_filter"}}
@@ -19,7 +19,7 @@ {{#link-to 'adminSiteSettingsCategory' category.nameKey class=category.nameKey}} {{category.name}} {{#if filtered}} - ({{category.siteSettings.length}}) + ({{category.count}}) {{/if}} {{/link-to}} {{/link-to}} diff --git a/app/assets/javascripts/discourse/adapters/rest.js.es6 b/app/assets/javascripts/discourse/adapters/rest.js.es6 index 347f27b8a1..1404e76d50 100644 --- a/app/assets/javascripts/discourse/adapters/rest.js.es6 +++ b/app/assets/javascripts/discourse/adapters/rest.js.es6 @@ -24,9 +24,7 @@ export default Ember.Object.extend({ return "/"; }, - pathFor(store, type, findArgs) { - let path = this.basePath(store, type, findArgs) + Ember.String.underscore(store.pluralize(type)); - + appendQueryParams(path, findArgs) { if (findArgs) { if (typeof findArgs === "object") { const queryString = Object.keys(findArgs) @@ -34,17 +32,21 @@ export default Ember.Object.extend({ .map(k => k + "=" + encodeURIComponent(findArgs[k])); if (queryString.length) { - path += "?" + queryString.join('&'); + return path + "?" + queryString.join('&'); } } else { // It's serializable as a string if not an object - path += "/" + findArgs; + return path + "/" + findArgs; } } - return path; }, + pathFor(store, type, findArgs) { + let path = this.basePath(store, type, findArgs) + Ember.String.underscore(store.pluralize(type)); + return this.appendQueryParams(path, findArgs); + }, + findAll(store, type) { return ajax(this.pathFor(store, type)).catch(rethrow); }, diff --git a/app/assets/javascripts/discourse/components/actions-summary.js.es6 b/app/assets/javascripts/discourse/components/actions-summary.js.es6 index f817f6da58..55e404f0ad 100644 --- a/app/assets/javascripts/discourse/components/actions-summary.js.es6 +++ b/app/assets/javascripts/discourse/components/actions-summary.js.es6 @@ -56,7 +56,7 @@ export default Ember.Component.extend(StringBuffer, { if (postUrl) { key = key + "_with_url"; } // TODO postUrl might be uninitialized? pick a good default - buffer.push(" " + I18n.t(key, { icons: iconsHtml, postUrl: postUrl}) + "."); + buffer.push(" " + I18n.t(key, { icons: iconsHtml, postUrl }) + "."); } if (users.length === 0) { @@ -83,6 +83,8 @@ export default Ember.Component.extend(StringBuffer, { autoUpdatingRelativeAge(new Date(post.get('postDeletedAt'))) + ""); } + + buffer.push("
"); }, actionTypeById(actionTypeId) { diff --git a/app/assets/javascripts/discourse/components/category-chooser.js.es6 b/app/assets/javascripts/discourse/components/category-chooser.js.es6 index df61b75dde..f5b85e7992 100644 --- a/app/assets/javascripts/discourse/components/category-chooser.js.es6 +++ b/app/assets/javascripts/discourse/components/category-chooser.js.es6 @@ -36,7 +36,7 @@ export default ComboboxView.extend({ @computed("rootNone") none(rootNone) { - if (Discourse.User.currentProp('staff') || Discourse.SiteSettings.allow_uncategorized_topics) { + if (Discourse.SiteSettings.allow_uncategorized_topics) { if (rootNone) { return "category.none"; } else { diff --git a/app/assets/javascripts/discourse/components/d-link.js.es6 b/app/assets/javascripts/discourse/components/d-link.js.es6 index 79a55a9fef..c8fda35c20 100644 --- a/app/assets/javascripts/discourse/components/d-link.js.es6 +++ b/app/assets/javascripts/discourse/components/d-link.js.es6 @@ -28,10 +28,9 @@ export default Ember.Component.extend({ return ''; }, - @computed("title", "label") - translatedTitle(title, label) { - const text = title || label; - if (text) return I18n.t(text); + @computed("title") + translatedTitle(title) { + if (title) return I18n.t(title); }, click(e) { diff --git a/app/assets/javascripts/discourse/components/notification-item.js.es6 b/app/assets/javascripts/discourse/components/notification-item.js.es6 index 2aa93e141f..8b4730074d 100644 --- a/app/assets/javascripts/discourse/components/notification-item.js.es6 +++ b/app/assets/javascripts/discourse/components/notification-item.js.es6 @@ -22,8 +22,14 @@ export default Ember.Component.extend({ const it = this.get('notification'); const badgeId = it.get("data.badge_id"); if (badgeId) { - const badgeName = it.get("data.badge_name"); - return Discourse.getURL('/badges/' + badgeId + '/' + badgeName.replace(/[^A-Za-z0-9_]+/g, '-').toLowerCase()); + var badgeSlug = it.get("data.badge_slug"); + + if (!badgeSlug) { + const badgeName = it.get("data.badge_name"); + badgeSlug = badgeName.replace(/[^A-Za-z0-9_]+/g, '-').toLowerCase(); + } + + return Discourse.getURL('/badges/' + badgeId + '/' + badgeSlug); } const topicId = it.get('topic_id'); diff --git a/app/assets/javascripts/discourse/components/pagedown-editor.js.es6 b/app/assets/javascripts/discourse/components/pagedown-editor.js.es6 index 83baf1be41..6f04e77152 100644 --- a/app/assets/javascripts/discourse/components/pagedown-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/pagedown-editor.js.es6 @@ -1,21 +1,23 @@ +import { observes, on } from 'ember-addons/ember-computed-decorators'; import loadScript from 'discourse/lib/load-script'; export default Ember.Component.extend({ classNameBindings: [':pagedown-editor'], - _initializeWmd: function() { - const self = this; - loadScript('defer/html-sanitizer-bundle').then(function() { - self.$('.wmd-input').data('init', true); - self._editor = Discourse.Markdown.createEditor({ containerElement: self.element }); - self._editor.run(); - Ember.run.scheduleOnce('afterRender', self, self._refreshPreview); + @on("didInsertElement") + _initializeWmd() { + loadScript('defer/html-sanitizer-bundle').then(() => { + this.$('.wmd-input').data('init', true); + this._editor = Discourse.Markdown.createEditor({ containerElement: this.element }); + this._editor.run(); + Ember.run.scheduleOnce('afterRender', this, this._refreshPreview); }); - }.on('didInsertElement'), + }, - observeValue: function() { + @observes("value") + observeValue() { Ember.run.scheduleOnce('afterRender', this, this._refreshPreview); - }.observes('value'), + }, _refreshPreview() { this._editor.refreshPreview(); diff --git a/app/assets/javascripts/discourse/components/post-gutter.js.es6 b/app/assets/javascripts/discourse/components/post-gutter.js.es6 index 5b923febe3..f4ca4cac6f 100644 --- a/app/assets/javascripts/discourse/components/post-gutter.js.es6 +++ b/app/assets/javascripts/discourse/components/post-gutter.js.es6 @@ -1,22 +1,25 @@ -var MAX_SHOWN = 5; +const MAX_SHOWN = 5; import StringBuffer from 'discourse/mixins/string-buffer'; import { iconHTML } from 'discourse/helpers/fa-icon'; +import property from 'ember-addons/ember-computed-decorators'; -export default Em.Component.extend(StringBuffer, { +const { get, isEmpty, Component } = Ember; + +export default Component.extend(StringBuffer, { classNameBindings: [':gutter'], rerenderTriggers: ['expanded'], // Roll up links to avoid duplicates - collapsed: function() { - var seen = {}, - result = [], - links = this.get('links'); + @property('links') + collapsed(links) { + const seen = {}; + const result = []; - if (!Em.isEmpty(links)) { + if (!isEmpty(links)) { links.forEach(function(l) { - var title = Em.get(l, 'title'); + const title = get(l, 'title'); if (!seen[title]) { result.pushObject(l); seen[title] = true; @@ -24,52 +27,52 @@ export default Em.Component.extend(StringBuffer, { }); } return result; - }.property('links'), + }, - renderString: function(buffer) { - var links = this.get('collapsed'), - toRender = links, - collapsed = !this.get('expanded'); + renderString(buffer) { + const links = this.get('collapsed'); + const collapsed = !this.get('expanded'); - if (!Em.isEmpty(links)) { + if (!isEmpty(links)) { + let toRender = links; if (collapsed) { toRender = toRender.slice(0, MAX_SHOWN); } buffer.push("'); } if (this.get('canReplyAsNewTopic')) { - buffer.push("" + iconHTML('plus') + I18n.t('post.reply_as_new_topic') + ""); + buffer.push(`${iconHTML('plus')}${I18n.t('post.reply_as_new_topic')}`); } }, - click: function(e) { - var $target = $(e.target); + click(e) { + const $target = $(e.target); if ($target.hasClass('toggle-more')) { this.toggleProperty('expanded'); return false; diff --git a/app/assets/javascripts/discourse/components/who-liked.js.es6 b/app/assets/javascripts/discourse/components/who-liked.js.es6 index a26f468999..3d34e9afc1 100644 --- a/app/assets/javascripts/discourse/components/who-liked.js.es6 +++ b/app/assets/javascripts/discourse/components/who-liked.js.es6 @@ -5,7 +5,7 @@ export default Ember.Component.extend(StringBuffer, { renderString(buffer) { const users = this.get('users'); - if (users && users.length > 0) { + if (users && users.get('length') > 0) { buffer.push("
"); let iconsHtml = ""; users.forEach(function(u) { diff --git a/app/assets/javascripts/discourse/controllers/edit-category.js.es6 b/app/assets/javascripts/discourse/controllers/edit-category.js.es6 index bfda086b64..d77783bc0a 100644 --- a/app/assets/javascripts/discourse/controllers/edit-category.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-category.js.es6 @@ -1,5 +1,6 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; import DiscourseURL from 'discourse/lib/url'; +import { extractError } from 'discourse/lib/ajax-error'; // Modal for editing / creating a category export default Ember.Controller.extend(ModalFunctionality, { @@ -67,17 +68,13 @@ export default Ember.Controller.extend(ModalFunctionality, { this.set('saving', true); model.set('parentCategory', parentCategory); - self.set('saving', false); this.get('model').save().then(function(result) { + self.set('saving', false); self.send('closeModal'); model.setProperties({slug: result.category.slug, id: result.category.id }); DiscourseURL.redirectTo("/c/" + Discourse.Category.slugFor(model)); }).catch(function(error) { - if (error && error.responseText) { - self.flash($.parseJSON(error.responseText).errors[0], 'error'); - } else { - self.flash(I18n.t('generic_error'), 'error'); - } + self.flash(extractError(error), 'error'); self.set('saving', false); }); }, @@ -94,13 +91,7 @@ export default Ember.Controller.extend(ModalFunctionality, { self.send('closeModal'); DiscourseURL.redirectTo("/categories"); }, function(error){ - - if (error && error.responseText) { - self.flash($.parseJSON(error.responseText).errors[0]); - } else { - self.flash(I18n.t('generic_error')); - } - + self.flash(extractError(error), 'error'); self.send('reopenModal'); self.displayErrors([I18n.t("category.delete_error")]); self.set('deleting', false); diff --git a/app/assets/javascripts/discourse/controllers/quote-button.js.es6 b/app/assets/javascripts/discourse/controllers/quote-button.js.es6 index 8b416b6d50..bd50004977 100644 --- a/app/assets/javascripts/discourse/controllers/quote-button.js.es6 +++ b/app/assets/javascripts/discourse/controllers/quote-button.js.es6 @@ -1,5 +1,6 @@ import loadScript from 'discourse/lib/load-script'; import Quote from 'discourse/lib/quote'; +import property from 'ember-addons/ember-computed-decorators'; export default Ember.Controller.extend({ needs: ['topic', 'composer'], @@ -8,10 +9,15 @@ export default Ember.Controller.extend({ loadScript('defer/html-sanitizer-bundle'); }.on('init'), - // If the buffer is cleared, clear out other state (post) - bufferChanged: function() { - if (Ember.isEmpty(this.get('buffer'))) this.set('post', null); - }.observes('buffer'), + @property('buffer', 'postId') + post(buffer, postId) { + if (!postId || Ember.isEmpty(buffer)) { return null; } + + const postStream = this.get('controllers.topic.model.postStream'); + const post = postStream.findLoadedPost(postId); + + return post; + }, // Save the currently selected text and displays the // "quote reply" button @@ -26,8 +32,12 @@ export default Ember.Controller.extend({ } const selection = window.getSelection(); - // no selections - if (selection.isCollapsed) return; + + // no selections + if (selection.isCollapsed) { + this.set('buffer', ''); + return; + } // retrieve the selected range const range = selection.getRangeAt(0), @@ -85,16 +95,13 @@ export default Ember.Controller.extend({ }, quoteText() { - - const postStream = this.get('controllers.topic.model.postStream'); const postId = this.get('postId'); - const post = postStream.findLoadedPost(postId); + const post = this.get('post'); // defer load if needed, if in an expanded replies section if (!post) { - postStream.loadPost(postId).then(() => { - this.quoteText(); - }); + const postStream = this.get('controllers.topic.model.postStream'); + postStream.loadPost(postId).then(() => this.quoteText()); return; } @@ -110,7 +117,7 @@ export default Ember.Controller.extend({ draftKey: post.get('topic.draft_key') }; - if(post.get('post_number') === 1) { + if (post.get('post_number') === 1) { composerOpts.topic = post.get("topic"); } else { composerOpts.post = post; diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index fd7b397794..70f3a967ac 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -131,15 +131,15 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { draftSequence: topic.get('draft_sequence') }; + if (quotedText) { opts.quote = quotedText; } + if(post && post.get("post_number") !== 1){ opts.post = post; } else { opts.topic = topic; } - composerController.open(opts).then(function() { - composerController.appendText(quotedText); - }); + composerController.open(opts); } return false; }, @@ -410,12 +410,12 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { action: Discourse.Composer.CREATE_TOPIC, draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY, categoryId: this.get('category.id') - }).then(function() { + }).then(() => { return Em.isEmpty(quotedText) ? Discourse.Post.loadQuote(post.get('id')) : quotedText; - }).then(function(q) { - const postUrl = "" + location.protocol + "//" + location.host + post.get('url'), - postLink = "[" + Handlebars.escapeExpression(self.get('model.title')) + "](" + postUrl + ")"; - composerController.appendText(I18n.t("post.continue_discussion", { postLink: postLink }) + "\n\n" + q); + }).then(q => { + const postUrl = `${location.protocol}//${location.host}${post.get('url')}`, + postLink = `[${Handlebars.escapeExpression(self.get('model.title'))}](${postUrl})`; + composerController.appendText(`${I18n.t("post.continue_discussion", { postLink })}\n\n${q}`); }); }, diff --git a/app/assets/javascripts/discourse/controllers/user-card.js.es6 b/app/assets/javascripts/discourse/controllers/user-card.js.es6 index a7ade2d7f4..ce2e065a89 100644 --- a/app/assets/javascripts/discourse/controllers/user-card.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-card.js.es6 @@ -67,6 +67,7 @@ export default Ember.Controller.extend({ const args = { stats: false }; args.include_post_count_for = this.get('controllers.topic.model.id'); + args.skip_track_visit = true; return Discourse.User.findByUsername(username, args).then((user) => { if (user.topic_post_count) { diff --git a/app/assets/javascripts/discourse/dialects/quote_dialect.js b/app/assets/javascripts/discourse/dialects/quote_dialect.js index 28f8de131c..68395103f0 100644 --- a/app/assets/javascripts/discourse/dialects/quote_dialect.js +++ b/app/assets/javascripts/discourse/dialects/quote_dialect.js @@ -23,11 +23,11 @@ Discourse.BBCode.register('quote', {noWrap: true, singlePara: true}, function(co } var avatarImg; + var postNumber = parseInt(params['data-post'], 10); + var topicId = parseInt(params['data-topic'], 10); + if (options.lookupAvatarByPostNumber) { // client-side, we can retrieve the avatar from the post - var postNumber = parseInt(params['data-post'], 10); - var topicId = parseInt(params['data-topic'], 10); - avatarImg = options.lookupAvatarByPostNumber(postNumber, topicId); } else if (options.lookupAvatar) { // server-side, we need to lookup the avatar from the username @@ -39,12 +39,23 @@ Discourse.BBCode.register('quote', {noWrap: true, singlePara: true}, function(co return ['p', ['aside', params, ['blockquote'].concat(contents)]]; } - return ['aside', params, - ['div', {'class': 'title'}, - ['div', {'class': 'quote-controls'}], - avatarImg ? ['__RAW', avatarImg] : "", - username ? I18n.t('user.said', {username: username}) : "" - ], - ['blockquote'].concat(contents) - ]; + var header = [ 'div', {'class': 'title'}, + ['div', {'class': 'quote-controls'}], + avatarImg ? ['__RAW', avatarImg] : "", + username ? I18n.t('user.said', {username: username}) : "" + ]; + + if (options.topicId && postNumber && options.getTopicInfo && topicId !== options.topicId) { + var topicInfo = options.getTopicInfo(topicId); + if (topicInfo) { + var href = topicInfo.href; + if (postNumber > 0) { href += "/" + postNumber; } + // get rid of username said stuff + header.pop(); + header.push(['a', {'href': href}, topicInfo.title]); + } + } + + + return ['aside', params, header, ['blockquote'].concat(contents)]; }); diff --git a/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 b/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 index 92ab828c68..79157a123a 100644 --- a/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 @@ -29,10 +29,6 @@ export default { }); } - bus.subscribe("/notification-alert/" + user.get('id'), function(data){ - onNotification(data, user); - }); - bus.subscribe("/notification/" + user.get('id'), function(data) { const oldUnread = user.get('unread_notifications'); const oldPM = user.get('unread_private_messages'); @@ -85,7 +81,13 @@ export default { }); if (!Ember.testing) { - initDesktopNotifications(bus); + if (!Discourse.Mobile.mobileView) { + bus.subscribe("/notification-alert/" + user.get('id'), function(data){ + onNotification(data, user); + }); + + initDesktopNotifications(bus); + } } } } diff --git a/app/assets/javascripts/discourse/lib/Markdown.Editor.js b/app/assets/javascripts/discourse/lib/Markdown.Editor.js index 13ca4b52a4..2dd0acaef3 100644 --- a/app/assets/javascripts/discourse/lib/Markdown.Editor.js +++ b/app/assets/javascripts/discourse/lib/Markdown.Editor.js @@ -306,7 +306,8 @@ // end of Chunks function firstByClass(doc, containerElement, className) { - var elements = doc.getElementsByClassName(className); + var container = containerElement || doc; + var elements = container.getElementsByClassName(className); if (elements && elements.length) { return elements[0]; } diff --git a/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 b/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 index 7ea7a7b029..ee449e3ccd 100644 --- a/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 +++ b/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 @@ -160,13 +160,15 @@ var toolbar = function(selected){ var PER_ROW = 12, PER_PAGE = 60; -var bindEvents = function(page,offset){ +var bindEvents = function(page, offset, options) { var composerController = Discourse.__container__.lookup('controller:composer'); $('.emoji-page a').click(function(){ var title = $(this).attr('title'); trackEmojiUsage(title); - composerController.appendTextAtCursor(":" + title + ":", {space: true}); + + const prefix = options.skipPrefix ? "" : ":"; + composerController.appendTextAtCursor(`${prefix}${title}:`, {space: !options.skipPrefix}); closeSelector(); return false; }).hover(function(){ @@ -178,21 +180,21 @@ var bindEvents = function(page,offset){ }); $('.emoji-modal .nav .next a').click(function(){ - render(page, offset+PER_PAGE); + render(page, offset+PER_PAGE, options); }); $('.emoji-modal .nav .prev a').click(function(){ - render(page, offset-PER_PAGE); + render(page, offset-PER_PAGE, options); }); $('.emoji-modal .toolbar a').click(function(){ var p = parseInt($(this).data('group-id')); - render(p, 0); + render(p, 0, options); return false; }); }; -var render = function(page, offset){ +var render = function(page, offset, options) { localStorage.emojiPage = page; localStorage.emojiOffset = offset; @@ -222,10 +224,12 @@ var render = function(page, offset){ var rendered = Ember.TEMPLATES["emoji-toolbar.raw"](model); $('body').append(rendered); - bindEvents(page, offset); + bindEvents(page, offset, options); }; -var showSelector = function(){ +var showSelector = function(options) { + options = options || {}; + $('body').append('
'); $('.emoji-modal-wrapper').click(function(){ @@ -234,7 +238,7 @@ var showSelector = function(){ var page = parseInt(localStorage.emojiPage) || 0; var offset = parseInt(localStorage.emojiOffset) || 0; - render(page, offset); + render(page, offset, options); $('body, textarea').on('keydown.emoji', function(e){ if(e.which === 27){ diff --git a/app/assets/javascripts/discourse/lib/quote.js.es6 b/app/assets/javascripts/discourse/lib/quote.js.es6 index d7c95bac49..bf1d290a5f 100644 --- a/app/assets/javascripts/discourse/lib/quote.js.es6 +++ b/app/assets/javascripts/discourse/lib/quote.js.es6 @@ -3,15 +3,17 @@ export default { REGEXP: /\[quote=([^\]]*)\]((?:[\s\S](?!\[quote=[^\]]*\]))*?)\[\/quote\]/im, // Build the BBCode quote around the selected text - build: function(post, contents, opts) { + build(post, contents, opts) { var contents_hashed, result, sansQuotes, stripped, stripped_hashed, tmp; var full = opts && opts["full"]; var raw = opts && opts["raw"]; + if (!post) { return ""; } + if (!contents) contents = ""; sansQuotes = contents.replace(this.REGEXP, '').trim(); - if (sansQuotes.length === 0) return ""; + if (sansQuotes.length === 0) { return ""; } // Escape the content of the quote sansQuotes = sansQuotes.replace(/ Ember.Object.create(result)); }, hideRevision(postId, version) { @@ -419,16 +418,15 @@ Post.reopenClass({ }, loadQuote(postId) { - return Discourse.ajax("/posts/" + postId + ".json").then(function (result) { + return Discourse.ajax("/posts/" + postId + ".json").then(result => { const post = Discourse.Post.create(result); return Quote.build(post, post.get('raw'), {raw: true, full: true}); }); }, loadRawEmail(postId) { - return Discourse.ajax("/posts/" + postId + "/raw-email").then(function (result) { - return result.raw_email; - }); + return Discourse.ajax("/posts/" + postId + "/raw-email") + .then(result => result.raw_email); } }); diff --git a/app/assets/javascripts/discourse/pre-initializers/sniff-capabilities.js.es6 b/app/assets/javascripts/discourse/pre-initializers/sniff-capabilities.js.es6 index c683498050..d728c93f37 100644 --- a/app/assets/javascripts/discourse/pre-initializers/sniff-capabilities.js.es6 +++ b/app/assets/javascripts/discourse/pre-initializers/sniff-capabilities.js.es6 @@ -1,23 +1,24 @@ /*global Modernizr:true*/ -/** - Initializes an object that lets us know about our capabilities. -**/ +// Initializes an object that lets us know about our capabilities. export default { name: "sniff-capabilities", - initialize: function(container, application) { - var $html = $('html'), - touch = $html.hasClass('touch') || (Modernizr.prefixed("MaxTouchPoints", navigator) > 1), - caps = Ember.Object.create(); + initialize(container, application) { + const $html = $('html'), + touch = $html.hasClass('touch') || (Modernizr.prefixed("MaxTouchPoints", navigator) > 1), + caps = Ember.Object.create(); // Store the touch ability in our capabilities object caps.set('touch', touch); $html.addClass(touch ? 'discourse-touch' : 'discourse-no-touch'); - // Detect Android + // Detect Devices if (navigator) { - var ua = navigator.userAgent; - caps.set('android', ua && ua.indexOf('Android') !== -1); + const ua = navigator.userAgent; + if (ua) { + caps.set('android', ua.indexOf('Android') !== -1); + caps.set('winphone', ua.indexOf('Windows Phone') !== -1); + } } // We consider high res a device with 1280 horizontal pixels. High DPI tablets like diff --git a/app/assets/javascripts/discourse/routes/badges-show.js.es6 b/app/assets/javascripts/discourse/routes/badges-show.js.es6 index 5f835d76d0..42bc89e9a2 100644 --- a/app/assets/javascripts/discourse/routes/badges-show.js.es6 +++ b/app/assets/javascripts/discourse/routes/badges-show.js.es6 @@ -12,7 +12,7 @@ export default Discourse.Route.extend({ serialize(model) { return { id: model.get("id"), - slug: model.get("name").replace(/[^A-Za-z0-9_]+/g, "-").toLowerCase() + slug: model.get("slug") }; }, diff --git a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 index e3d2e89a8b..b0dfc941ca 100644 --- a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 @@ -63,9 +63,15 @@ export default (filter, params) => { setupController(controller, model) { const topics = this.get('topics'), - periodId = topics.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''); + periodId = topics.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''), + canCreateTopic = topics.get('can_create_topic'), + canCreateTopicOnCategory = model.get('permission') === Discourse.PermissionType.FULL; - this.controllerFor('navigation/category').set('canCreateTopic', topics.get('can_create_topic')); + this.controllerFor('navigation/category').setProperties({ + canCreateTopicOnCategory: canCreateTopicOnCategory, + cannotCreateTopicOnCategory: !canCreateTopicOnCategory, + canCreateTopic: canCreateTopic + }); this.controllerFor('discovery/topics').setProperties({ model: topics, category: model, @@ -74,7 +80,9 @@ export default (filter, params) => { noSubcategories: params && !!params.no_subcategories, order: topics.get('params.order'), ascending: topics.get('params.ascending'), - expandAllPinned: true + expandAllPinned: true, + canCreateTopic: canCreateTopic, + canCreateTopicOnCategory: canCreateTopicOnCategory }); this.searchService.set('searchContext', model.get('searchContext')); diff --git a/app/assets/javascripts/discourse/templates/components/pagedown-editor.hbs b/app/assets/javascripts/discourse/templates/components/pagedown-editor.hbs index b12fad0228..52e92ae1fe 100644 --- a/app/assets/javascripts/discourse/templates/components/pagedown-editor.hbs +++ b/app/assets/javascripts/discourse/templates/components/pagedown-editor.hbs @@ -1,4 +1,3 @@
{{textarea value=value class="wmd-input"}} -
-
+
diff --git a/app/assets/javascripts/discourse/templates/components/user-menu.hbs b/app/assets/javascripts/discourse/templates/components/user-menu.hbs index 0fa99d740a..e7247424b8 100644 --- a/app/assets/javascripts/discourse/templates/components/user-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/user-menu.hbs @@ -37,10 +37,10 @@ {{/conditional-loading-spinner}}
{{plugin-outlet "user-menu-bottom"}} - {{#if siteSettings.show_logout_in_header}} + {{/menu-panel}} diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs index f3e452a4ab..b57679afc7 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs @@ -21,14 +21,15 @@
{{period-chooser period=period action="changePeriod"}}
- {{/if}} - {{#if topicTrackingState.hasIncoming}} -
-
- {{count-i18n key="topic_count_" suffix=topicTrackingState.filter count=topicTrackingState.incomingCount}} - {{i18n 'click_to_show'}} + {{else}} + {{#if topicTrackingState.hasIncoming}} +
+
+ {{count-i18n key="topic_count_" suffix=topicTrackingState.filter count=topicTrackingState.incomingCount}} + {{i18n 'click_to_show'}} +
-
+ {{/if}} {{/if}} {{#if hasTopics}} @@ -67,7 +68,7 @@

{{footerMessage}} - {{#if model.can_create_topic}}{{i18n 'topic.suggest_create_topic'}}{{/if}} + {{#if canCreateTopicOnCategory}}{{i18n 'topic.suggest_create_topic'}}{{/if}}

{{else}} {{#if top}} diff --git a/app/assets/javascripts/discourse/templates/emoji-selector-autocomplete.raw.hbs b/app/assets/javascripts/discourse/templates/emoji-selector-autocomplete.raw.hbs index a01fbde0bd..ac1f2a4755 100644 --- a/app/assets/javascripts/discourse/templates/emoji-selector-autocomplete.raw.hbs +++ b/app/assets/javascripts/discourse/templates/emoji-selector-autocomplete.raw.hbs @@ -1,8 +1,14 @@
diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs index 22cbceea9c..ae964b46e3 100644 --- a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs @@ -3,13 +3,13 @@
{{period-chooser period=period action="changePeriod"}}
- {{/if}} - - {{#if topicTrackingState.hasIncoming}} -
- {{count-i18n key="topic_count_" suffix=topicTrackingState.filter count=topicTrackingState.incomingCount}} - {{i18n 'click_to_show'}} -
+ {{else}} + {{#if topicTrackingState.hasIncoming}} +
+ {{count-i18n key="topic_count_" suffix=topicTrackingState.filter count=topicTrackingState.incomingCount}} + {{i18n 'click_to_show'}} +
+ {{/if}} {{/if}} {{#if hasTopics}} diff --git a/app/assets/javascripts/discourse/templates/navigation/category.hbs b/app/assets/javascripts/discourse/templates/navigation/category.hbs index 7d874edf17..428abe827b 100644 --- a/app/assets/javascripts/discourse/templates/navigation/category.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/category.hbs @@ -11,7 +11,12 @@ {{/if}} {{#if canCreateTopic}} - {{d-button id="create-topic" class="btn-default" action="createTopic" icon="plus" label="topic.create"}} + {{d-button id="create-topic" + class="btn-default" + action="createTopic" + icon="plus" + label="topic.create" + disabled=cannotCreateTopicOnCategory}} {{/if}} {{#if canEditCategory}} diff --git a/app/assets/javascripts/discourse/templates/post.hbs b/app/assets/javascripts/discourse/templates/post.hbs index 6fdfcfcdd2..51f10eac79 100644 --- a/app/assets/javascripts/discourse/templates/post.hbs +++ b/app/assets/javascripts/discourse/templates/post.hbs @@ -12,12 +12,12 @@
- {{#if userDeleted}} - - {{else}} - {{raw "post/poster-avatar" post=this classNames="main-avatar"}} - {{/if}} - {{plugin-outlet "poster-avatar-bottom"}} + {{#if userDeleted}} + + {{else}} + {{raw "post/poster-avatar" post=this classNames="main-avatar"}} + {{/if}} + {{plugin-outlet "poster-avatar-bottom"}}
diff --git a/app/assets/javascripts/discourse/templates/user/user.hbs b/app/assets/javascripts/discourse/templates/user/user.hbs index 28634a17f6..a1f74c5dda 100644 --- a/app/assets/javascripts/discourse/templates/user/user.hbs +++ b/app/assets/javascripts/discourse/templates/user/user.hbs @@ -120,6 +120,7 @@ {{#if model.last_seen_at}}
{{i18n 'user.last_seen'}}
{{bound-date model.last_seen_at}}
{{/if}} +
{{i18n 'views'}}
{{model.profile_view_count}}
{{#if model.invited_by}}
{{i18n 'user.invited_by'}}
{{#link-to 'user' model.invited_by}}{{model.invited_by.username}}{{/link-to}}
{{/if}} diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6 index bef69bea33..62d3dad0ec 100644 --- a/app/assets/javascripts/discourse/views/composer.js.es6 +++ b/app/assets/javascripts/discourse/views/composer.js.es6 @@ -5,6 +5,7 @@ import positioningWorkaround from 'discourse/lib/safari-hacks'; import debounce from 'discourse/lib/debounce'; import { linkSeenMentions, fetchUnseenMentions } from 'discourse/lib/link-mentions'; import { headerHeight } from 'discourse/views/header'; +import { showSelector } from 'discourse/lib/emoji/emoji-toolbar'; const ComposerView = Ember.View.extend(Ember.Evented, { _lastKeyTimeout: null, @@ -185,13 +186,23 @@ const ComposerView = Ember.View.extend(Ember.Evented, { if (!this.siteSettings.enable_emoji) { return; } const template = this.container.lookup('template:emoji-selector-autocomplete.raw'); + this.$('.wmd-input').autocomplete({ template: template, key: ":", - transformComplete(v) { return v.code + ":"; }, - dataSource(term){ - return new Ember.RSVP.Promise(function(resolve) { - const full = ":" + term; + + transformComplete(v) { + if (v.code) { + return `${v.code}:`; + } else { + showSelector({ skipPrefix: true }); + return ""; + } + }, + + dataSource(term) { + return new Ember.RSVP.Promise(resolve => { + const full = `:${term}`; term = term.toLowerCase(); if (term === "") { @@ -205,10 +216,13 @@ const ComposerView = Ember.View.extend(Ember.Evented, { const options = Discourse.Emoji.search(term, {maxResults: 5}); return resolve(options); - }).then(function(list) { - return list.map(function(i) { - return {code: i, src: Discourse.Emoji.urlFor(i)}; - }); + }).then(list => list.map(code => { + return {code, src: Discourse.Emoji.urlFor(code)}; + })).then(list => { + if (list.length) { + list.push({ label: I18n.t("composer.more_emoji") }); + } + return list; }); } }); @@ -246,7 +260,7 @@ const ComposerView = Ember.View.extend(Ember.Evented, { }); - const options ={ + const options = { containerElement: this.element, lookupAvatarByPostNumber(postNumber, topicId) { const posts = controller.get('controllers.topic.model.postStream.posts'); diff --git a/app/assets/javascripts/discourse/views/post.js.es6 b/app/assets/javascripts/discourse/views/post.js.es6 index cceef1e3ce..dbab61f421 100644 --- a/app/assets/javascripts/discourse/views/post.js.es6 +++ b/app/assets/javascripts/discourse/views/post.js.es6 @@ -208,10 +208,15 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, { const likeAction = post.get('likeAction'); if (likeAction && likeAction.get('canToggle')) { const users = this.get('likedUsers'); - if (likeAction.toggle(post) && users.length) { - users.addObject(currentUser); + const store = this.get('controller.store'); + const action = store.createRecord('post-action-user', + currentUser.getProperties('id', 'username', 'avatar_template') + ); + + if (likeAction.toggle(post) && users.get('length')) { + users.addObject(action); } else { - users.removeObject(currentUser); + users.removeObject(action); } } }, @@ -221,7 +226,7 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, { const likeAction = post.get('likeAction'); if (likeAction) { const users = this.get('likedUsers'); - if (users.length) { + if (users.get('length')) { users.clear(); } else { likeAction.loadUsers(post).then(newUsers => this.set('likedUsers', newUsers)); diff --git a/app/assets/javascripts/discourse/views/quote-button.js.es6 b/app/assets/javascripts/discourse/views/quote-button.js.es6 index aa9fb1404b..8f4c5ab66d 100644 --- a/app/assets/javascripts/discourse/views/quote-button.js.es6 +++ b/app/assets/javascripts/discourse/views/quote-button.js.es6 @@ -34,7 +34,11 @@ export default Ember.View.extend({ // best we can do is debounce this so we dont keep locking up // the selection when we add the caret to measure where we place // the quote reply widget - if (navigator.userAgent.match(/Windows Phone/)) { + // + // Same hack applied to Android cause it has unreliable touchend + const caps = this.capabilities; + const android = caps.get('android'); + if (caps.get('winphone') || android) { onSelectionChanged = _.debounce(onSelectionChanged, 500); } @@ -58,12 +62,6 @@ export default Ember.View.extend({ view.selectText(e.target, controller); view.set('isMouseDown', false); }) - .on('touchstart.quote-button', function(){ - view.set('isTouchInProgress', true); - }) - .on('touchend.quote-button', function(){ - view.set('isTouchInProgress', false); - }) .on('selectionchange', function() { // there is no need to handle this event when the mouse is down // or if there a touch in progress @@ -71,6 +69,18 @@ export default Ember.View.extend({ // `selection.anchorNode` is used as a target onSelectionChanged(); }); + + // Android is dodgy, touchend often will not fire + // https://code.google.com/p/android/issues/detail?id=19827 + if (!android) { + $(document) + .on('touchstart.quote-button', function(){ + view.set('isTouchInProgress', true); + }) + .on('touchend.quote-button', function(){ + view.set('isTouchInProgress', false); + }); + } }, selectText(target, controller) { diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 757f7308ec..d1c49d58d1 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -73,6 +73,9 @@ //= require ./discourse/components/topic-notifications-button //= require ./discourse/lib/link-mentions //= require ./discourse/views/header +//= require ./discourse/dialects/dialect +//= require ./discourse/lib/emoji/emoji +//= require ./discourse/lib/emoji/emoji-toolbar //= require ./discourse/views/composer //= require ./discourse/lib/show-modal //= require ./discourse/lib/screen-track @@ -90,8 +93,6 @@ //= require ./discourse/helpers/loading-spinner //= require ./discourse/helpers/category-link //= require ./discourse/lib/export-result -//= require ./discourse/dialects/dialect -//= require ./discourse/lib/emoji/emoji //= require_tree ./discourse/lib //= require ./discourse/router diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 6007fd8305..7953f4eae8 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -180,7 +180,6 @@ td.flaggers td { .result-message { display: inline-block; padding-left: 10px; - padding-top: 5px; } .username { input[type=text] { diff --git a/app/assets/stylesheets/common/base/onebox.scss b/app/assets/stylesheets/common/base/onebox.scss index 2db1ab7206..b8b2acde7e 100644 --- a/app/assets/stylesheets/common/base/onebox.scss +++ b/app/assets/stylesheets/common/base/onebox.scss @@ -248,7 +248,7 @@ aside.onebox.twitterstatus .onebox-body { .thumbnail { float: left; } - .tweet { + p, .tweet { float: left; display: inline-block; white-space: pre-wrap; diff --git a/app/assets/stylesheets/common/base/topic.scss b/app/assets/stylesheets/common/base/topic.scss index 015b4b6871..a75a428a38 100644 --- a/app/assets/stylesheets/common/base/topic.scss +++ b/app/assets/stylesheets/common/base/topic.scss @@ -9,9 +9,6 @@ .badge-wrapper { float: left; - &.bullet { - margin-top: 5px; - } } } diff --git a/app/assets/stylesheets/common/components/badges.css.scss b/app/assets/stylesheets/common/components/badges.css.scss index 96eefe1577..6cad8c1029 100644 --- a/app/assets/stylesheets/common/components/badges.css.scss +++ b/app/assets/stylesheets/common/components/badges.css.scss @@ -189,13 +189,15 @@ } .badge-wrapper { &.bar { - margin: 5px 0; + padding: 5px 0; + width: 100%; .badge-category { max-width: 100px; } } &.bullet { - margin: 5px; + padding: 5px; + width: 100%; .badge-category { max-width: 100px; } diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 9bcc420850..100b443757 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -85,6 +85,9 @@ color: dark-light-choose(scale-color($tertiary, $lightness: -40%), scale-color($tertiary, $lightness: 40%)); } } + .badge-wrapper { + padding-left: 5px; + } } .composer-popup:nth-of-type(2) { @@ -221,7 +224,8 @@ } } .contents { - padding: 10px; + padding-left: 10px; + padding-top: 5px; min-width: 1280px; .form-element { position: relative; diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index 626ca0f6b5..625261bcc5 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -507,7 +507,7 @@ video { .extra-info-wrapper { overflow: hidden; - .star, .badge-wrapper, i, .topic-link:not(.loading) { + .badge-wrapper, i, .topic-link { -webkit-animation: fadein .7s; animation: fadein .7s; } @@ -651,13 +651,13 @@ blockquote { .reply-new { padding-left: 27px; - display:block; + display: inline-block; overflow:hidden; } .track-link { padding-left: 10px; - display: block; + display: inline-block; overflow: hidden; } @@ -707,7 +707,7 @@ $topic-avatar-width: 45px; } .small-action { - width: 755px; + max-width: 755px; border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -75%); } diff --git a/app/assets/stylesheets/desktop/topic.scss b/app/assets/stylesheets/desktop/topic.scss index c3929fd250..d8dc7ccf9d 100644 --- a/app/assets/stylesheets/desktop/topic.scss +++ b/app/assets/stylesheets/desktop/topic.scss @@ -123,11 +123,10 @@ a:hover.reply-new { border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); padding: 5px; background: $secondary; - @include box-shadow(0 0px 2px rgba(0,0,0, .2)); position: relative; - left: 345px; - width: 133px; + left: 340px; + width: 135px; padding: 5px; button.full { diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index 0fc61f2977..2bf11f2b5e 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -46,7 +46,7 @@ button { padding: 8px 10px; vertical-align: top; background: transparent; - color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%)); + color: dark-light-choose(scale-color($primary, $lightness: 75%), scale-color($secondary, $lightness: 25%)); float: left; &.hidden { display: none; @@ -80,7 +80,7 @@ button { /* shift post reply button to the right and make it black */ .post-controls button.create { float: right; - color: $primary; + color: dark-light-choose(scale-color($primary, $lightness: 20%), scale-color($secondary, $lightness: 80%)); } @@ -461,14 +461,10 @@ span.highlighted { } .topic-meta-data { - white-space: nowrap; - position: absolute; - width: 100%; - left: 0px; + margin-left: 50px; .names { margin: 5px 0 0 5px; line-height: 17px; - padding-left: 50px; span { display: block; } diff --git a/app/assets/stylesheets/mobile/topic.scss b/app/assets/stylesheets/mobile/topic.scss index 9e0d6e2164..bd0b706504 100644 --- a/app/assets/stylesheets/mobile/topic.scss +++ b/app/assets/stylesheets/mobile/topic.scss @@ -64,11 +64,10 @@ border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); padding: 5px; background: $secondary; - box-shadow: 0 0px 2px rgba(0,0,0, .2); position: absolute; bottom: 34px; - width: 133px; + width: 135px; button.full { width: 100%; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d9fe0a4976..e8376b6a28 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -76,17 +76,7 @@ class ApplicationController < ActionController::Base # If they hit the rate limiter rescue_from RateLimiter::LimitExceeded do |e| - - time_left = "" - if e.available_in < 1.minute.to_i - time_left = I18n.t("rate_limiter.seconds", count: e.available_in) - elsif e.available_in < 1.hour.to_i - time_left = I18n.t("rate_limiter.minutes", count: (e.available_in / 1.minute.to_i)) - else - time_left = I18n.t("rate_limiter.hours", count: (e.available_in / 1.hour.to_i)) - end - - render_json_error I18n.t("rate_limiter.too_many_requests", time_left: time_left), type: :rate_limit, status: 429 + render_json_error e.description, type: :rate_limit, status: 429 end rescue_from PG::ReadOnlySqlTransaction do |e| @@ -310,9 +300,6 @@ class ApplicationController < ActionController::Base def preload_current_user_data store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false))) report = TopicTrackingState.report(current_user.id) - if report.length >= SiteSetting.max_tracked_new_unread.to_i - TopicUser.cap_unread_later(current_user.id) - end serializer = ActiveModel::ArraySerializer.new(report, each_serializer: TopicTrackingStateSerializer) store_preloaded("topicTrackingStates", MultiJson.dump(serializer)) end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index b63ec503db..52437468d3 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -4,6 +4,7 @@ class CategoriesController < ApplicationController before_filter :ensure_logged_in, except: [:index, :show, :redirect] before_filter :fetch_category, only: [:show, :update, :destroy] + before_filter :initialize_staff_action_logger, only: [:create, :update, :destroy] skip_before_filter :check_xhr, only: [:index, :redirect] def redirect @@ -81,10 +82,18 @@ class CategoriesController < ApplicationController position = category_params.delete(:position) @category = Category.create(category_params.merge(user: current_user)) - return render_json_error(@category) unless @category.save - @category.move_to(position.to_i) if position - render_serialized(@category, CategorySerializer) + if @category.save + @category.move_to(position.to_i) if position + + Scheduler::Defer.later "Log staff action create category" do + @staff_action_logger.log_category_creation(@category) + end + + render_serialized(@category, CategorySerializer) + else + return render_json_error(@category) unless @category.save + end end def update @@ -103,8 +112,15 @@ class CategoriesController < ApplicationController end category_params.delete(:position) + old_permissions = Category.find(@category.id).permissions_params - cat.update_attributes(category_params) + if result = cat.update_attributes(category_params) + Scheduler::Defer.later "Log staff action change category settings" do + @staff_action_logger.log_category_settings_change(@category, category_params, old_permissions) + end + end + + result end end @@ -133,6 +149,10 @@ class CategoriesController < ApplicationController guardian.ensure_can_delete!(@category) @category.destroy + Scheduler::Defer.later "Log staff action delete category" do + @staff_action_logger.log_category_deletion(@category) + end + render json: success_json end @@ -175,4 +195,8 @@ class CategoriesController < ApplicationController def fetch_category @category = Category.find_by(slug: params[:id]) || Category.find_by(id: params[:id].to_i) end + + def initialize_staff_action_logger + @staff_action_logger = StaffActionLogger.new(current_user) + end end diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index d894ee1f37..844bc6ba6d 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -84,7 +84,7 @@ class ListController < ApplicationController end define_method("category_#{filter}") do - canonical_url "#{Discourse.base_url}#{@category.url}" + canonical_url "#{Discourse.base_url_no_prefix}#{@category.url}" self.send(filter, category: @category.id) end @@ -93,7 +93,7 @@ class ListController < ApplicationController end define_method("parent_category_category_#{filter}") do - canonical_url "#{Discourse.base_url}#{@category.url}" + canonical_url "#{Discourse.base_url_no_prefix}#{@category.url}" self.send(filter, category: @category.id) end @@ -228,11 +228,11 @@ class ListController < ApplicationController parent_category_id = nil if parent_slug_or_id.present? parent_category_id = Category.query_parent_category(parent_slug_or_id) - raise Discourse::NotFound if parent_category_id.blank? + redirect_or_not_found and return if parent_category_id.blank? end @category = Category.query_category(slug_or_id, parent_category_id) - raise Discourse::NotFound if !@category + redirect_or_not_found and return if !@category @description_meta = @category.description_text guardian.ensure_can_see!(@category) @@ -308,4 +308,23 @@ class ListController < ApplicationController periods end + def redirect_or_not_found + url = request.fullpath + permalink = Permalink.find_by_url(url) + + if permalink.present? + # permalink present, redirect to that URL + if permalink.external_url + redirect_to permalink.external_url, status: :moved_permanently + elsif permalink.target_url + redirect_to "#{Discourse::base_uri}#{permalink.target_url}", status: :moved_permanently + else + raise Discourse::NotFound + end + else + # redirect to 404 + raise Discourse::NotFound + end + end + end diff --git a/app/controllers/manifest_json_controller.rb b/app/controllers/manifest_json_controller.rb new file mode 100644 index 0000000000..5462ec1fd6 --- /dev/null +++ b/app/controllers/manifest_json_controller.rb @@ -0,0 +1,15 @@ +class ManifestJsonController < ApplicationController + layout false + skip_before_filter :preload_json, :check_xhr + + def index + manifest = { + short_name: SiteSetting.title, + display: 'browser', + orientation: 'portrait', + start_url: "#{Discourse.base_uri}/" + } + + render json: manifest.to_json + end +end diff --git a/app/controllers/post_action_users_controller.rb b/app/controllers/post_action_users_controller.rb new file mode 100644 index 0000000000..5ea95d3c32 --- /dev/null +++ b/app/controllers/post_action_users_controller.rb @@ -0,0 +1,22 @@ +require_dependency 'discourse' + +class PostActionUsersController < ApplicationController + def index + params.require(:post_action_type_id) + params.require(:id) + post_action_type_id = params[:post_action_type_id].to_i + + finder = Post.where(id: params[:id].to_i) + finder = finder.with_deleted if guardian.is_staff? + + post = finder.first + guardian.ensure_can_see!(post) + guardian.ensure_can_see_post_actors!(post.topic, post_action_type_id) + + post_actions = post.post_actions.where(post_action_type_id: post_action_type_id) + .includes(:user) + .order('post_actions.created_at asc') + + render_serialized(post_actions.to_a, PostActionUserSerializer, root: 'post_action_users') + end +end diff --git a/app/controllers/post_actions_controller.rb b/app/controllers/post_actions_controller.rb index 46e7159e6c..4a7585890e 100644 --- a/app/controllers/post_actions_controller.rb +++ b/app/controllers/post_actions_controller.rb @@ -1,8 +1,7 @@ require_dependency 'discourse' class PostActionsController < ApplicationController - - before_filter :ensure_logged_in, except: :users + before_filter :ensure_logged_in before_filter :fetch_post_from_params before_filter :fetch_post_action_type_id_from_params @@ -26,16 +25,6 @@ class PostActionsController < ApplicationController end end - def users - guardian.ensure_can_see_post_actors!(@post.topic, @post_action_type_id) - - post_actions = @post.post_actions.where(post_action_type_id: @post_action_type_id) - .includes(:user) - .order('post_actions.created_at asc') - - render_serialized(post_actions.to_a, PostActionUserSerializer) - end - def destroy post_action = current_user.post_actions.find_by(post_id: params[:id].to_i, post_action_type_id: @post_action_type_id, deleted_at: nil) raise Discourse::NotFound if post_action.blank? diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index d139479de0..d1eacdc891 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -83,7 +83,7 @@ class TopicsController < ApplicationController response.headers['X-Robots-Tag'] = 'noindex' end - canonical_url UrlHelper.absolute_without_cdn("#{Discourse.base_uri}#{@topic_view.canonical_path}") + canonical_url UrlHelper.absolute_without_cdn(@topic_view.canonical_path) perform_show_response diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index b5bb7c3e24..6fc780aabc 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -39,6 +39,10 @@ class UsersController < ApplicationController user_serializer.topic_post_count = {topic_id => Post.where(topic_id: topic_id, user_id: @user.id).count } end + if !params[:skip_track_visit] && (@user != current_user) + track_visit_to_user_profile + end + # This is a hack to get around a Rails issue where values with periods aren't handled correctly # when used as part of a route. if params[:external_id] and params[:external_id].ends_with? '.json' @@ -650,4 +654,14 @@ class UsersController < ApplicationController render json: { success: false, message: I18n.t(key) } end + def track_visit_to_user_profile + user_profile_id = @user.user_profile.id + ip = request.remote_ip + user_id = (current_user.id if current_user) + + Scheduler::Defer.later 'Track profile view visit' do + UserProfileView.add(user_profile_id, ip, user_id) + end + end + end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2dadd8db44..0743108354 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -121,7 +121,7 @@ module ApplicationHelper opts ||= {} opts[:image] ||= "#{Discourse.base_url}#{SiteSetting.logo_small_url}" - opts[:url] ||= "#{Discourse.base_url}#{request.fullpath}" + opts[:url] ||= "#{Discourse.base_url_no_prefix}#{request.fullpath}" # Use the correct scheme for open graph if opts[:image].present? && opts[:image].start_with?("//") diff --git a/app/jobs/regular/process_post.rb b/app/jobs/regular/process_post.rb index cac70c050f..0f44be4302 100644 --- a/app/jobs/regular/process_post.rb +++ b/app/jobs/regular/process_post.rb @@ -14,7 +14,9 @@ module Jobs recooked = nil if args[:cook].present? - recooked = post.cook(post.raw, topic_id: post.topic_id) + cooking_options = args[:cooking_options] || {} + cooking_options[:topic_id] = post.topic_id + recooked = post.cook(post.raw, cooking_options.symbolize_keys) post.update_column(:cooked, recooked) end diff --git a/app/jobs/scheduled/periodical_updates.rb b/app/jobs/scheduled/periodical_updates.rb index 625d67b8d8..b051b3f85d 100644 --- a/app/jobs/scheduled/periodical_updates.rb +++ b/app/jobs/scheduled/periodical_updates.rb @@ -33,9 +33,7 @@ module Jobs Discourse.handle_job_exception(hash[:ex], error_context(args, "Rebaking user id #{user_id}", user_id: user_id)) end - TopicUser.cap_unread_backlog! - - offset = (SiteSetting.max_tracked_new_unread * (2/5.0)).to_i + offset = (SiteSetting.max_new_topics).to_i last_new_topic = Topic.order('created_at desc').offset(offset).select(:created_at).first if last_new_topic SiteSetting.min_new_topics_time = last_new_topic.created_at.to_i diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index 52e91f644d..a5b73f5db7 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -175,6 +175,7 @@ class UserNotifications < ActionMailer::Base .where("post_number < ?", post.post_number) .where(user_deleted: false) .where(hidden: false) + .where(post_type: Topic.visible_post_types) .order('created_at desc') .limit(SiteSetting.email_posts_context) diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index 4253523763..33e34d9ffa 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -6,6 +6,7 @@ class AdminDashboardData GLOBAL_REPORTS ||= [ 'visits', 'signups', + 'profile_views', 'topics', 'posts', 'time_to_first_response', diff --git a/app/models/anon_site_json_cache_observer.rb b/app/models/anon_site_json_cache_observer.rb new file mode 100644 index 0000000000..cacb0d1431 --- /dev/null +++ b/app/models/anon_site_json_cache_observer.rb @@ -0,0 +1,12 @@ +class AnonSiteJsonCacheObserver < ActiveRecord::Observer + observe :category, :post_action_type, :user_field, :group + + def after_destroy(object) + Site.clear_anon_cache! + end + + def after_save(object) + Site.clear_anon_cache! + end + +end diff --git a/app/models/badge.rb b/app/models/badge.rb index 818b4dfae3..76393865dc 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -329,12 +329,38 @@ SQL Badge.find_each(&:reset_grant_count!) end + def display_name + if self.system? + key = "admin_js.badges.badge.#{i18n_name}.name" + I18n.t(key, default: self.name) + else + self.name + end + end + + def long_description + if self[:long_description].present? + self[:long_description] + else + key = "badges.long_descriptions.#{i18n_name}" + I18n.t(key, default: '') + end + end + + def slug + Slug.for(self.display_name, '-') + end + protected def ensure_not_system unless id self.id = [Badge.maximum(:id) + 1, 100].max end end + + def i18n_name + self.name.downcase.gsub(' ', '_') + end end # == Schema Information diff --git a/app/models/category.rb b/app/models/category.rb index 7e550aef28..bd5e85914f 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -193,13 +193,17 @@ SQL end def topic_url - topic_only_relative_url.try(:relative_url) + if has_attribute?("topic_slug") + Topic.relative_url(topic_id, read_attribute(:topic_slug)) + else + topic_only_relative_url.try(:relative_url) + end end def description_text return nil unless description - @@cache ||= LruRedux::ThreadSafeCache.new(100) + @@cache ||= LruRedux::ThreadSafeCache.new(1000) @@cache.getset(self.description) do Nokogiri::HTML(self.description).text end @@ -283,6 +287,14 @@ SQL set_permissions(permissions) end + def permissions_params + hash = {} + category_groups.includes(:group).each do |category_group| + hash[category_group.group_name] = category_group.permission_type + end + hash + end + def apply_permissions if @permissions category_groups.destroy_all @@ -370,7 +382,8 @@ SQL end def has_children? - id && Category.where(parent_category_id: id).exists? + @has_children ||= (id && Category.where(parent_category_id: id).exists?) ? :true : :false + @has_children == :true end def uncategorized? diff --git a/app/models/category_group.rb b/app/models/category_group.rb index fa9fd3a21e..849fcba026 100644 --- a/app/models/category_group.rb +++ b/app/models/category_group.rb @@ -2,6 +2,8 @@ class CategoryGroup < ActiveRecord::Base belongs_to :category belongs_to :group + delegate :name, to: :group, prefix: true + def self.permission_types @permission_types ||= Enum.new(:full, :create_post, :readonly) end diff --git a/app/models/color_scheme.rb b/app/models/color_scheme.rb index d4a1b02dec..7e43107940 100644 --- a/app/models/color_scheme.rb +++ b/app/models/color_scheme.rb @@ -1,7 +1,12 @@ require_dependency 'sass/discourse_stylesheets' +require_dependency 'distributed_cache' class ColorScheme < ActiveRecord::Base + def self.hex_cache + @hex_cache ||= DistributedCache.new("scheme_hex_for_name") + end + attr_accessor :is_base has_many :color_scheme_colors, -> { order('id ASC') }, dependent: :destroy @@ -12,6 +17,8 @@ class ColorScheme < ActiveRecord::Base after_destroy :destroy_versions after_save :publish_discourse_stylesheet + after_save :dump_hex_cache + after_destroy :dump_hex_cache validates_associated :color_scheme_colors @@ -64,8 +71,14 @@ class ColorScheme < ActiveRecord::Base end def self.hex_for_name(name) - # Can't use `where` here because base doesn't allow it - (enabled || base).colors.find {|c| c.name == name }.try(:hex) + val = begin + hex_cache[name] ||= begin + # Can't use `where` here because base doesn't allow it + (enabled || base).colors.find {|c| c.name == name }.try(:hex) || :nil + end + end + + val == :nil ? nil : val end def colors=(arr) @@ -101,6 +114,11 @@ class ColorScheme < ActiveRecord::Base DiscourseStylesheets.cache.clear end + + def dump_hex_cache + self.class.hex_cache.clear + end + end # == Schema Information diff --git a/app/models/group.rb b/app/models/group.rb index 27959ac728..c14f5bb758 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -15,6 +15,13 @@ class Group < ActiveRecord::Base after_save :update_primary_group after_save :update_title + after_save :expire_cache + after_destroy :expire_cache + + def expire_cache + ApplicationSerializer.expire_cache_fragment!("group_names") + end + validate :name_format_validator validates_uniqueness_of :name, case_sensitive: false diff --git a/app/models/post.rb b/app/models/post.rb index b441db2333..7799da3afc 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -91,7 +91,7 @@ class Post < ActiveRecord::Base end def limit_posts_per_day - if user.first_day_user? && post_number && post_number > 1 + if user && user.first_day_user? && post_number && post_number > 1 RateLimiter.new(user, "first-day-replies-per-day", SiteSetting.max_replies_in_first_day, 1.day.to_i) end end @@ -507,6 +507,7 @@ class Post < ActiveRecord::Base } args[:image_sizes] = image_sizes if image_sizes.present? args[:invalidate_oneboxes] = true if invalidate_oneboxes.present? + args[:cooking_options] = self.cooking_options Jobs.enqueue(:process_post, args) DiscourseEvent.trigger(:after_trigger_post_process, self) end diff --git a/app/models/post_action.rb b/app/models/post_action.rb index abbe3c5df2..6ce053f82c 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -66,7 +66,7 @@ class PostAction < ActiveRecord::Base end def self.counts_for(collection, user) - return {} if collection.blank? + return {} if collection.blank? || !user collection_ids = collection.map(&:id) user_id = user.try(:id) || 0 diff --git a/app/models/post_action_type.rb b/app/models/post_action_type.rb index 847c03a64e..2f4829c347 100644 --- a/app/models/post_action_type.rb +++ b/app/models/post_action_type.rb @@ -1,7 +1,17 @@ require_dependency 'enum' +require_dependency 'distributed_cache' class PostActionType < ActiveRecord::Base + after_save :expire_cache + after_destroy :expire_cache + + def expire_cache + ApplicationSerializer.expire_cache_fragment!("post_action_types") + ApplicationSerializer.expire_cache_fragment!("post_action_flag_types") + end + class << self + def ordered order('position asc') end diff --git a/app/models/post_analyzer.rb b/app/models/post_analyzer.rb index 4e2bf369e8..70f1bf0123 100644 --- a/app/models/post_analyzer.rb +++ b/app/models/post_analyzer.rb @@ -11,7 +11,7 @@ class PostAnalyzer def cook(*args) cooked = PrettyText.cook(*args) - result = Oneboxer.apply(cooked) do |url, _| + result = Oneboxer.apply(cooked, topic_id: @topic_id) do |url, _| Oneboxer.invalidate(url) if args.last[:invalidate_oneboxes] Oneboxer.cached_onebox url end diff --git a/app/models/report.rb b/app/models/report.rb index 204bc3e9bc..8def047bd6 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -58,7 +58,7 @@ class Report if filter == :page_view_total ApplicationRequest.where(req_type: [ ApplicationRequest.req_types.reject{|k,v| k =~ /mobile/}.map{|k,v| v if k =~ /page_view/}.compact - ]) + ].flatten) else ApplicationRequest.where(req_type: ApplicationRequest.req_types[filter]) end @@ -98,6 +98,14 @@ class Report report_about report, User.real, :count_by_signup_date end + def self.report_profile_views(report) + start_date = report.start_date.to_date + end_date = report.end_date.to_date + basic_report_about report, UserProfileView, :profile_views_by_day, start_date, end_date + report.total = UserProfile.sum(:views) + report.prev30Days = UserProfileView.where("viewed_at >= ? AND viewed_at < ?", start_date - 30.days, start_date + 1).count + end + def self.report_topics(report) basic_report_about report, Topic, :listable_count_per_day, report.start_date, report.end_date, report.category_id countable = Topic.listable_topics diff --git a/app/models/screened_ip_address.rb b/app/models/screened_ip_address.rb index 287a7dd061..82d63cdcd9 100644 --- a/app/models/screened_ip_address.rb +++ b/app/models/screened_ip_address.rb @@ -75,6 +75,7 @@ class ScreenedIpAddress < ActiveRecord::Base end def self.block_admin_login?(user, ip_address) + return false unless SiteSetting.use_admin_ip_whitelist return false if user.nil? return false if !user.admin? return false if ScreenedIpAddress.where(action_type: actions[:allow_admin]).count == 0 diff --git a/app/models/site.rb b/app/models/site.rb index c6a3313f17..3459947b73 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -13,14 +13,6 @@ class Site SiteSetting end - def post_action_types - PostActionType.ordered - end - - def topic_flag_types - post_action_types.where(name_key: ['inappropriate', 'spam', 'notify_moderators']) - end - def notification_types Notification.types end @@ -29,10 +21,6 @@ class Site TrustLevel.all end - def groups - @groups ||= Group.order(:name).map { |g| { id: g.id, name: g.name } } - end - def user_fields UserField.all end @@ -41,16 +29,22 @@ class Site @categories ||= begin categories = Category .secured(@guardian) - .includes(:topic_only_relative_url, :subcategories) + .joins('LEFT JOIN topics t on t.id = categories.topic_id') + .select('categories.*, t.slug topic_slug') .order(:position) - unless SiteSetting.allow_uncategorized_topics - categories = categories.where('categories.id <> ?', SiteSetting.uncategorized_category_id) - end - categories = categories.to_a - allowed_topic_create = Set.new(Category.topic_create_allowed(@guardian).pluck(:id)) + with_children = Set.new + categories.each do |c| + if c.parent_category_id + with_children << c.parent_category_id + end + end + + allowed_topic_create_ids = + @guardian.anonymous? ? [] : Category.topic_create_allowed(@guardian).pluck(:id) + allowed_topic_create = Set.new(allowed_topic_create_ids) by_id = {} @@ -62,7 +56,7 @@ class Site categories.each do |category| category.notification_level = category_user[category.id] category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create.include?(category.id) - category.has_children = category.subcategories.present? + category.has_children = with_children.include?(category.id) by_id[category.id] = category end @@ -91,8 +85,37 @@ class Site }.to_json end + seq = nil + + if guardian.anonymous? + seq = MessageBus.last_id('/site_json') + + cached_json, cached_seq, cached_version = $redis.mget('site_json', 'site_json_seq', 'site_json_version') + + if cached_json && seq == cached_seq.to_i && Discourse.git_version == cached_version + return cached_json + end + + end + site = Site.new(guardian) - MultiJson.dump(SiteSerializer.new(site, root: false, scope: guardian)) + json = MultiJson.dump(SiteSerializer.new(site, root: false, scope: guardian)) + + if guardian.anonymous? + $redis.multi do + $redis.setex 'site_json', 1800, json + $redis.set 'site_json_seq', seq + $redis.set 'site_json_version', Discourse.git_version + end + end + + json + end + + def self.clear_anon_cache! + # publishing forces the sequence up + # the cache is validated based on the sequence + MessageBus.publish('/site_json','') end end diff --git a/app/models/topic.rb b/app/models/topic.rb index 3cfbe2e5b0..47d7f41586 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -5,6 +5,7 @@ require_dependency 'rate_limiter' require_dependency 'text_sentinel' require_dependency 'text_cleaner' require_dependency 'archetype' +require_dependency 'html_prettify' class Topic < ActiveRecord::Base include ActionView::Helpers::SanitizeHelper @@ -164,6 +165,9 @@ class Topic < ActiveRecord::Base cancel_auto_close_job ensure_topic_has_a_category end + if title_changed? + write_attribute :fancy_title, Topic.fancy_title(title) + end end after_save do @@ -270,17 +274,28 @@ class Topic < ActiveRecord::Base apply_per_day_rate_limit_for("pms", :max_private_messages_per_day) end + def self.fancy_title(title) + escaped = ERB::Util.html_escape(title) + return unless escaped + HtmlPrettify.render(escaped) + end + def fancy_title - sanitized_title = ERB::Util.html_escape(title) + return ERB::Util.html_escape(title) unless SiteSetting.title_fancy_entities? - return unless sanitized_title - return sanitized_title unless SiteSetting.title_fancy_entities? + unless fancy_title = read_attribute(:fancy_title) - # We don't always have to require this, if fancy is disabled - # see: http://meta.discourse.org/t/pattern-for-defer-loading-gems-and-profiling-with-perftools-rb/4629 - require 'redcarpet' unless defined? Redcarpet + fancy_title = Topic.fancy_title(title) + write_attribute(:fancy_title, fancy_title) - Redcarpet::Render::SmartyPants.render(sanitized_title) + unless new_record? + # make sure data is set in table, this also allows us to change algorithm + # by simply nulling this column + exec_sql("UPDATE topics SET fancy_title = :fancy_title where id = :id", id: self.id, fancy_title: fancy_title) + end + end + + fancy_title end def pending_posts_count @@ -701,6 +716,7 @@ class Topic < ActiveRecord::Base def title=(t) slug = Slug.for(t.to_s) write_attribute(:slug, slug) + write_attribute(:fancy_title, nil) write_attribute(:title,t) end @@ -720,12 +736,16 @@ class Topic < ActiveRecord::Base self.class.url id, slug, post_number end - def relative_url(post_number=nil) + def self.relative_url(id, slug, post_number=nil) url = "#{Discourse.base_uri}/t/#{slug}/#{id}" url << "/#{post_number}" if post_number.to_i > 1 url end + def relative_url(post_number=nil) + Topic.relative_url(id, slug, post_number) + end + def unsubscribe_url "#{url}/unsubscribe" end diff --git a/app/models/topic_link.rb b/app/models/topic_link.rb index 3ed474d7c0..ce55d512c7 100644 --- a/app/models/topic_link.rb +++ b/app/models/topic_link.rb @@ -2,8 +2,14 @@ require 'uri' require_dependency 'slug' class TopicLink < ActiveRecord::Base - MAX_DOMAIN_LENGTH = 100 unless defined? MAX_DOMAIN_LENGTH - MAX_URL_LENGTH = 500 unless defined? MAX_URL_LENGTH + + def self.max_domain_length + 100 + end + + def self.max_url_length + 500 + end belongs_to :topic belongs_to :user @@ -147,8 +153,8 @@ class TopicLink < ActiveRecord::Base reflected_post = Post.find_by(topic_id: topic_id, post_number: post_number.to_i) end - next if url && url.length > MAX_URL_LENGTH - next if parsed && parsed.host && parsed.host.length > MAX_DOMAIN_LENGTH + url = url[0...TopicLink.max_url_length] + next if parsed && parsed.host && parsed.host.length > TopicLink.max_domain_length added_urls << url TopicLink.create(post_id: post.id, diff --git a/app/models/topic_link_click.rb b/app/models/topic_link_click.rb index 4a51ca5200..907db52b11 100644 --- a/app/models/topic_link_click.rb +++ b/app/models/topic_link_click.rb @@ -13,7 +13,7 @@ class TopicLinkClick < ActiveRecord::Base # Create a click from a URL and post_id def self.create_from(args={}) - url = args[:url] + url = args[:url][0...TopicLink.max_url_length] return nil if url.blank? uri = URI.parse(url) rescue nil diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb index a38b6ec6d8..5f96c2e262 100644 --- a/app/models/topic_tracking_state.rb +++ b/app/models/topic_tracking_state.rb @@ -128,13 +128,9 @@ class TopicTrackingState # cycles from usual requests # # - sql = report_raw_sql(topic_id: topic_id) - - sql = < 0 + UserProfile.find(user_profile_id).increment!(:views) + end + end + end + end + + def self.profile_views_by_day(start_date, end_date) + profile_views = self.where("viewed_at >= ? AND viewed_at < ?", start_date, end_date + 1.day) + profile_views.group("date(viewed_at)").order("date(viewed_at)").count + end +end diff --git a/app/serializers/application_serializer.rb b/app/serializers/application_serializer.rb index ad570267ad..245336ef75 100644 --- a/app/serializers/application_serializer.rb +++ b/app/serializers/application_serializer.rb @@ -1,3 +1,28 @@ +require 'distributed_cache' + class ApplicationSerializer < ActiveModel::Serializer embed :ids, include: true + + class CachedFragment + def initialize(json) + @json = json + end + def as_json(*_args) + @json + end + end + + def self.expire_cache_fragment!(name) + fragment_cache.delete(name) + end + + def self.fragment_cache + @cache ||= DistributedCache.new("am_serializer_fragment_cache") + end + + protected + + def cache_fragment(name) + ApplicationSerializer.fragment_cache[name] ||= yield + end end diff --git a/app/serializers/badge_serializer.rb b/app/serializers/badge_serializer.rb index 415404d5cd..ede876c908 100644 --- a/app/serializers/badge_serializer.rb +++ b/app/serializers/badge_serializer.rb @@ -1,7 +1,7 @@ class BadgeSerializer < ApplicationSerializer attributes :id, :name, :description, :grant_count, :allow_title, :multiple_grant, :icon, :image, :listable, :enabled, :badge_grouping_id, - :system, :long_description + :system, :long_description, :slug has_one :badge_type @@ -12,17 +12,4 @@ class BadgeSerializer < ApplicationSerializer def include_long_description? options[:include_long_description] end - - def long_description - if object.long_description.present? - object.long_description - else - key = "badges.long_descriptions.#{object.name.downcase.gsub(" ", "_")}" - if I18n.exists?(key) - I18n.t(key) - else - "" - end - end - end end diff --git a/app/serializers/basic_post_serializer.rb b/app/serializers/basic_post_serializer.rb index 8969d19a06..70153bca9b 100644 --- a/app/serializers/basic_post_serializer.rb +++ b/app/serializers/basic_post_serializer.rb @@ -9,15 +9,15 @@ class BasicPostSerializer < ApplicationSerializer :cooked_hidden def name - object.user.try(:name) + object.user && object.user.name end def username - object.user.try(:username) + object.user && object.user.username end def avatar_template - object.user.try(:avatar_template) + object.user && object.user.avatar_template end def cooked_hidden diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index a10f9dbf63..259c781d29 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -74,7 +74,7 @@ class PostSerializer < BasicPostSerializer end def topic_slug - object.try(:topic).try(:slug) + object.topic && object.topic.slug end def include_topic_title? diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb index dec52ed2f2..174008ce08 100644 --- a/app/serializers/site_serializer.rb +++ b/app/serializers/site_serializer.rb @@ -12,15 +12,35 @@ class SiteSerializer < ApplicationSerializer :is_readonly, :disabled_plugins, :user_field_max_length, - :suppressed_from_homepage_category_ids + :suppressed_from_homepage_category_ids, + :post_action_types, + :topic_flag_types has_many :categories, serializer: BasicCategorySerializer, embed: :objects - has_many :post_action_types, embed: :objects - has_many :topic_flag_types, serializer: TopicFlagTypeSerializer, embed: :objects has_many :trust_levels, embed: :objects has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer has_many :user_fields, embed: :objects, serialzer: UserFieldSerializer + def groups + cache_fragment("group_names") do + Group.order(:name).pluck(:id,:name).map { |id,name| { id: id, name: name } }.as_json + end + end + + def post_action_types + cache_fragment("post_action_types") do + ActiveModel::ArraySerializer.new(PostActionType.ordered).as_json + end + end + + def topic_flag_types + cache_fragment("post_action_flag_types") do + flags = PostActionType.ordered.where(name_key: ['inappropriate', 'spam', 'notify_moderators']) + ActiveModel::ArraySerializer.new(flags, each_serializer: TopicFlagTypeSerializer).as_json + end + + end + def default_archetype Archetype.default end diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index 9cda8e1c01..a109ff34a5 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -65,13 +65,13 @@ class TopicViewSerializer < ApplicationSerializer last_poster: BasicUserSerializer.new(object.topic.last_poster, scope: scope, root: false) } - if object.topic.allowed_users.present? + if object.topic.private_message? result[:allowed_users] = object.topic.allowed_users.map do |user| BasicUserSerializer.new(user, scope: scope, root: false) end end - if object.topic.allowed_groups.present? + if object.topic.private_message? result[:allowed_groups] = object.topic.allowed_groups.map do |ag| BasicGroupSerializer.new(ag, scope: scope, root: false) end @@ -215,8 +215,8 @@ class TopicViewSerializer < ApplicationSerializer object.topic_user.try(:bookmarked) end - def include_pending_posts_count - scope.user.staff? && NewPostManager.queue_enabled? + def include_pending_posts_count? + scope.is_staff? && NewPostManager.queue_enabled? end end diff --git a/app/serializers/user_history_serializer.rb b/app/serializers/user_history_serializer.rb index 2af3c7dc9b..039e25c61d 100644 --- a/app/serializers/user_history_serializer.rb +++ b/app/serializers/user_history_serializer.rb @@ -10,6 +10,7 @@ class UserHistorySerializer < ApplicationSerializer :new_value, :topic_id, :post_id, + :category_id, :action, :custom_type diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 334b20e2d2..550d5654d3 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -65,7 +65,8 @@ class UserSerializer < BasicUserSerializer :custom_fields, :user_fields, :topic_post_count, - :pending_count + :pending_count, + :profile_view_count has_one :invited_by, embed: :object, serializer: BasicUserSerializer has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer @@ -346,4 +347,8 @@ class UserSerializer < BasicUserSerializer 0 end + def profile_view_count + object.user_profile.views + end + end diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb index 670ada829e..b6d964a655 100644 --- a/app/services/badge_granter.rb +++ b/app/services/badge_granter.rb @@ -41,10 +41,12 @@ class BadgeGranter end if SiteSetting.enable_badges? - notification = @user.notifications.create( - notification_type: Notification.types[:granted_badge], - data: { badge_id: @badge.id, badge_name: @badge.name }.to_json) - user_badge.update_attributes notification_id: notification.id + I18n.with_locale(@user.effective_locale) do + notification = @user.notifications.create( + notification_type: Notification.types[:granted_badge], + data: { badge_id: @badge.id, badge_name: @badge.display_name, badge_slug: @badge.slug }.to_json) + user_badge.update_attributes notification_id: notification.id + end end end end diff --git a/app/services/random_topic_selector.rb b/app/services/random_topic_selector.rb index d412a23890..7486311cb6 100644 --- a/app/services/random_topic_selector.rb +++ b/app/services/random_topic_selector.rb @@ -40,16 +40,18 @@ class RandomTopicSelector results = [] - left = count + return results if count < 1 - while left > 0 - id = $redis.lpop key - break unless id - - results << id.to_i - left -= 1 + results = $redis.multi do + $redis.lrange(key, 0, count-1) + $redis.ltrim(key, count, -1) end + results = results[0] + results.map!(&:to_i) + + left = count - results.length + backfilled = false if left > 0 ids = backfill(category) diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb index cd681eea92..cbb56693a6 100644 --- a/app/services/staff_action_logger.rb +++ b/app/services/staff_action_logger.rb @@ -210,6 +210,64 @@ class StaffActionLogger })) end + def log_category_settings_change(category, category_params, old_permissions=nil) + validate_category(category) + + changed_attributes = category.previous_changes.slice(*category_params.keys) + + if old_permissions != category_params[:permissions] + changed_attributes.merge!({ permissions: [old_permissions.to_json, category_params[:permissions].to_json] }) + end + + changed_attributes.each do |key, value| + UserHistory.create(params.merge({ + action: UserHistory.actions[:change_category_settings], + category_id: category.id, + context: category.url, + subject: key, + previous_value: value[0], + new_value: value[1] + })) + end + end + + def log_category_deletion(category) + validate_category(category) + + details = [ + "created_at: #{category.created_at}", + "name: #{category.name}", + "permissions: #{category.permissions_params}" + ] + + if parent_category = category.parent_category + details << "parent_category: #{parent_category.name}" + end + + UserHistory.create(params.merge({ + action: UserHistory.actions[:delete_category], + category_id: category.id, + details: details.join("\n"), + context: category.url + })) + end + + def log_category_creation(category) + validate_category(category) + + details = [ + "created_at: #{category.created_at}", + "name: #{category.name}" + ] + + UserHistory.create(params.merge({ + action: UserHistory.actions[:create_category], + details: details.join("\n"), + category_id: category.id, + context: category.url + })) + end + private def params(opts=nil) @@ -217,4 +275,8 @@ class StaffActionLogger { acting_user_id: @admin.id, context: opts[:context] } end + def validate_category(category) + raise Discourse::InvalidParameters.new(:category) unless category && category.is_a?(Category) + end + end diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index a74f93bafe..0843f71338 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -1,13 +1,14 @@ - +<%- if SiteSetting.favicon_url.present? %> +<%- end %> +<%- if SiteSetting.apple_touch_icon_url.present? %> -<% if SiteSetting.apple_touch_icon_url != "/images/default-apple-touch-icon.png" %> +<%- end %> +<%- if (SiteSetting.apple_touch_icon_url != "/images/default-apple-touch-icon.png") && SiteSetting.apple_touch_icon_url.present? %> -<% end %> +<%- end %> - - <%= canonical_link_tag %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 8a29518367..65cf09b535 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -31,6 +31,7 @@ <%- end %> <%= render_google_universal_analytics_code %> + <%= yield :head %> diff --git a/config/application.rb b/config/application.rb index 203442efff..f1df9881c6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -122,10 +122,7 @@ module Discourse # see: http://stackoverflow.com/questions/11894180/how-does-one-correctly-add-custom-sql-dml-in-migrations/11894420#11894420 config.active_record.schema_format = :sql - if Rails.version >= "4.2.0" && Rails.version < "5.0.0" - # Opt-into the default behavior in Rails 5 - config.active_record.raise_in_transactional_callbacks = false - end + config.active_record.raise_in_transactional_callbacks = true # per https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet config.pbkdf2_iterations = 64000 diff --git a/config/environments/production.rb b/config/environments/production.rb index 013e37f597..c637fb67b1 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -10,7 +10,7 @@ Discourse::Application.configure do config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) - config.serve_static_assets = GlobalSetting.serve_static_assets + config.serve_static_files = GlobalSetting.serve_static_assets config.assets.js_compressor = :uglifier diff --git a/config/environments/profile.rb b/config/environments/profile.rb index 1784a23528..3f672a9c47 100644 --- a/config/environments/profile.rb +++ b/config/environments/profile.rb @@ -13,7 +13,7 @@ Discourse::Application.configure do config.action_controller.perform_caching = true # in profile mode we serve static assets - config.serve_static_assets = true + config.serve_static_files = true # Compress JavaScripts and CSS config.assets.compress = true diff --git a/config/environments/test.rb b/config/environments/test.rb index b885d72ab3..16cab97ce1 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -8,7 +8,7 @@ Discourse::Application.configure do config.cache_classes = true # Configure static asset server for tests with Cache-Control for performance - config.serve_static_assets = true + config.serve_static_files = true # Show full error reports and disable caching config.consider_all_requests_local = true diff --git a/config/initializers/04-message_bus.rb b/config/initializers/04-message_bus.rb index 00bf806d41..ec0a7f974e 100644 --- a/config/initializers/04-message_bus.rb +++ b/config/initializers/04-message_bus.rb @@ -51,7 +51,7 @@ end MessageBus.cache_assets = !Rails.env.development? MessageBus.enable_diagnostics -if Rails.env == "test" +if Rails.env == "test" || $0 =~ /rake$/ # disable keepalive in testing MessageBus.keepalive_interval = -1 end diff --git a/config/initializers/i18n.rb b/config/initializers/i18n.rb index 43b7299572..616d519bfc 100644 --- a/config/initializers/i18n.rb +++ b/config/initializers/i18n.rb @@ -22,16 +22,5 @@ class FallbackLocaleList < Hash end end -class NoFallbackLocaleList < FallbackLocaleList - def [](locale) - [locale] - end -end - - -if Rails.env.development? - I18n.fallbacks = NoFallbackLocaleList.new -else - I18n.fallbacks = FallbackLocaleList.new - I18n.config.missing_interpolation_argument_handler = proc { throw(:exception) } -end +I18n.fallbacks = FallbackLocaleList.new +I18n.config.missing_interpolation_argument_handler = proc { throw(:exception) } diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index 63764408b9..7dde4291a7 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -737,11 +737,13 @@ ar: server: "خطأ في السيرفر" forbidden: "غير مصرح" unknown: "خطأ" + not_found: "الصفحة غير متوفرة" desc: network: "الرجاء التحقق من اتصالك" network_fixed: "يبدوا أنه رجع" server: "رقم الخطأ: {{status}}" forbidden: "ليس لديك الصلاحية" + not_found: "عفوا، حاول التطبيق حمل URL الغير موجودة." unknown: "حدث خطأ ما" buttons: back: "الرجوع" @@ -780,10 +782,10 @@ ar: signup_cta: sign_up: "إشترك" hide_session: "ذكرني غدا" - hide_forever: "لا، شكراً" + hide_forever: "لا شكرا" hidden_for_session: "حسنا، أنا اسأل لك غدا. يمكنك دائماً استخدام '\"تسجيل الدخول\"' لإنشاء حساب، أيضا." intro: "مهلا هناك! : heart_eyes: يبدو أنك تتمتع المناقشة، ولكن لم تقم بالتسجيل للحصول على حساب." - value_prop: "عندما تقوم بإنشاء حساب، نستطيع تتبع بالضبط ما كنت قد قرأت، حيث كنت دائماً تأتي حق العودة حيث تركته. يمكنك أيضا الحصول على الاشعارات، هنا وعبر البريد الإلكتروني، كلما تقدم وظائف جديدة. ويمكنك مثل مشاركات لتبادل الحب.:heartbeat:" + value_prop: "عندما تنشئ حساب، ننحن نتذكر ما كنت تقرأه بالضبط، و سترجع دائما في المكان الذي تركته، و ستصلك الاشعارات ايضا، هنا و عبر البريد الإلكتروني، في اي وقت ينشأ فيه منشور جديد، و تستطيع أن تُعجب بالمنشورارت لتشارك ما يعجبك :heartbeat:" methods: sso: "التسجيل أمر سهل: كل ما تحتاجه حساب في الموقع الرئيسي." only_email: "التسجيل أمر سهل: كل ما تحتاجه هو بريد إلكتروني وكلمة مرور." @@ -1717,16 +1719,16 @@ ar: notifications: watching: title: "مشاهده " - description: "سوف تشاهد تلقائياً كل المواضيع الجديدة في هذه الفئات. سيتم نتبيهك لكل المشاركات والمواضيع الجديدة, وعدد الردود الجديدة على هذه المواضيع سيكون ظاهراً." + description: "تلقائيا ستشاهد جميع المواضيع الجديدة في هذه الفئة، سوف تتلقى تنبيها بكل المنشورات الجديدة في المواضيع، وعدد من الردود الجديدة." tracking: title: "تتبع " - description: "سوف تتابع تلقائياً كل المواضيع في هذه الفئات.وعدد الردود الجديدة على هذه المواضيعسكون ظاهراً." + description: "سوف تتعقب تلقائيا كل المواضيع الجديده في هذه الفئات. سوف يتم تنبيهك اذا ذكر شخص اسم المستخدم او قام برد عليك، و سيتم عرض الردود الجديدة. " regular: - title: "طبيعي" + title: "منتظم" description: "سوف تُنبه اذا قام أحد بالاشارة لاسمك \"@name\" أو الرد عليك." muted: title: "كتم" - description: "لن يتم إشعارك بأي جديد يخص هذا الموضوع ولن يظهرهذا الموضوع في تبويب المواضيع الغير مقروءة." + description: "لن يتم اعلامك باي مواضيع جديدة في هذه الفئة، ولن تظهر في صفحتك الخاصة بالمواضيع الغير مقروءه" flagging: title: 'شكرا لمساعدتك في إبقاء مجتمعنا نظيفاً.' private_reminder: 'التبليغات ذات خصوصية، تظهر فقط للمشرفين' @@ -2294,6 +2296,7 @@ ar: ip_address: "IP" topic_id: "رقم معرّف الموضوع" post_id: "رقم المشاركة" + category_id: "معرف الفئة" delete: 'حذف' edit: 'تعديل' save: 'حفظ' @@ -2334,6 +2337,9 @@ ar: impersonate: "إنتحال" anonymize_user: "مستخدم مجهول" roll_up: "عناوين IP المتغيرة المحظورة" + change_category_settings: "تغيير إعدادات الفئة" + delete_category: "حذف الفئة" + create_category: "أنشئ فئة" screened_emails: title: "عناوين بريد إلكتروني محجوبة." description: "عندما تتم محاول انشاء حساب جديد, سيتم التحقق من قائمة البريد الالكتروني وسيتم حظر التسجيل لهذا البريد واتخاذ اي اجراء متبع" @@ -2726,7 +2732,9 @@ ar: embed_username_key_from_feed: "مفتاح لسحب اسم عضو discourse من المغذي" embed_truncate: "بتر المشاركات المضمنة" embed_whitelist_selector: "منتقي CSS للعناصر التي تسمح في التضمينات." + whitelist_example: "مقالة، #قصة، .مشاركة" embed_blacklist_selector: "منتقي CSS للعناصر التي حذفت من التضمينات." + blacklist_example: ".وحدة إعلان، رأس الصفحة" feed_polling_enabled: "استورد المشاركات عبر RSS/ATOM" feed_polling_url: "URL مغذي RSS/ATOM يتقدم ببطء." save: "أحفظ الإعدادات المضمنة" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index 2083dcfee3..11099ab322 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -1044,11 +1044,8 @@ bs_BA: title: "Motrenje" tracking: title: "Praćenje" - regular: - title: "Regularno" muted: title: "Mutirano" - description: "You will not be notified of anything about new topics in these categories, and they will not appear on your unread tab." flagging: title: 'Zašto prijavljujete ovaj post?' action: 'Opomeni Post' diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 7699af6fb5..ad6882daf6 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -1368,16 +1368,12 @@ cs: notifications: watching: title: "Hlídání" - description: "Všechna nová témata v této kategorii budou automaticky Sledovaná. Na všechny nové příspěvky a témata budete upozorněni. Navíc ve výpisu uvidíte počet nepřečtených příspěvků." tracking: title: "Sledování" - description: "Všechna nová témata v této kategorii budou automaticky Hlídaná. Počet nových příspěvků se zobrazí vedle tématu." regular: - title: "Normální" description: "Budete informováni pokud někdo zmíní vaše @jméno nebo odpoví na váš příspěvek." muted: title: "Ztišený" - description: "Nebudete upozorněni na žádná nová témata v těchto kategoriích a ani se nebudou zobrazovat jako nepřečtené." flagging: title: 'Děkujeme, že pomáháte udržovat komunitu zdvořilou!' private_reminder: 'nahlášení jsou soukromá, viditelná pouze pro správce' diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 8b0887269c..4e52abdc93 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -109,6 +109,7 @@ da: google+: 'del dette link på Google+' email: 'send dette link i en e-mail' action_codes: + split_topic: "delte dette emne op %{when}" autoclosed: enabled: 'lukket %{when}' disabled: 'åbnet %{when}' @@ -117,6 +118,16 @@ da: disabled: 'åbnet %{when}' archived: enabled: 'arkiveret %{when}' + disabled: 'dearkiveret %{when}' + pinned: + enabled: 'fastgjort %{when}' + disabled: 'frigjort %{when}' + pinned_globally: + enabled: 'fastgjort globalt %{when}' + disabled: 'frigjort %{when}' + visible: + enabled: 'listet %{when}' + disabled: 'aflistet %{when}' topic_admin_menu: "administrationshandlinger på emne" emails_are_disabled: "Alle udgående emails er blevet deaktiveret globalt af en administrator. Ingen emailnotifikationer af nogen slags vil blive sendt." edit: 'redigér titel og kategori for dette emne' @@ -209,6 +220,7 @@ da: saved: "Gemt!" upload: "Upload" uploading: "Uploader…" + uploading_filename: "Uploader {{filename}}..." uploaded: "Uploadet!" enable: "Aktiver" disable: "Deaktiver" @@ -216,6 +228,7 @@ da: revert: "Gendan" failed: "Fejlet" switch_to_anon: "Anonym tilstand" + switch_from_anon: "Afbryd anonym tilstand" banner: close: "Afvis denne banner." edit: "Rediger dette banner >>" @@ -295,6 +308,7 @@ da: members_mods_and_admins: "Kun gruppe medlemmer, moderatore og administratore" everyone: "Alle" trust_levels: + title: "Tillidsniveau der automatisk tildeles medlemmer når de oprettes:" none: "Ingen" user_action_groups: '1': "Likes givet" @@ -316,7 +330,13 @@ da: no_subcategory: "ingen" category: "Kategori" reorder: + title: "Ret kategoriernes rækkefølge " + title_long: "Omorganiser listen over kategorier" + fix_order: "Lås placeringer" + fix_order_tooltip: "Ikke alle kategorier har et unikt positionsnummer, hvilket kan give uventede resultater." + save: "Gem rækkefølge" apply_all: "Anvend" + position: "Position" posts: "Indlæg" topics: "Emner" latest: "Seneste" @@ -366,15 +386,18 @@ da: trust_level: "Tillidsniveau" notifications: "Underretninger" desktop_notifications: + label: "Desktop-notifikationer" + not_supported: "Notifikationer understøttes ikke af denne browser. Beklager." + perm_default: "Slå notifikationer til" perm_denied_btn: "Tilladelse nægtet" - perm_denied_expl: "Du har ikke givet tilladelse til meddelelser. Brug din browser til at aktivere meddelelser og klik derefter på knappen, når du er færdig. (Computer: Ikonet længst til venstre i adresselinjen. Mobil: 'Site info'.)" - disable: "Deaktiver meddelelser" + perm_denied_expl: "Du har ikke givet tilladelse til notifikationer. Brug din browser til at aktivere notifikationer og klik derefter på knappen, når du er færdig. (Computer: Ikonet længst til venstre i adresselinjen. Mobil: 'Site info'.)" + disable: "Deaktiver notifikationer" currently_enabled: "(slået til)" - enable: "Aktiver meddelelser" + enable: "Aktiver notifikationer" currently_disabled: "(slået fra)" each_browser_note: "Bemærk: Du skal ændre indstillingen i alle dine browsere." dismiss_notifications: "Marker alle som læst" - dismiss_notifications_tooltip: "Marker alle ulæste meddelelser som læst" + dismiss_notifications_tooltip: "Marker alle ulæste notifikationer som læst" disable_jump_reply: "Ikke hop til mit indlæg efter jeg svarer" dynamic_favicon: "Vis nyt / opdateret emnetal på browserikon" edit_history_public: "Lad andre brugere se mine tidligere revisioner" @@ -537,9 +560,11 @@ da: truncated: "Viser de første {{count}} invitationer." redeemed: "Brugte invitationer" redeemed_tab: "Indløst" + redeemed_tab_with_count: "Indløst ({{count}})" redeemed_at: "Invitation brugt" pending: "Udestående invitationer" pending_tab: "Afventende" + pending_tab_with_count: "Ventende ({{count}})" topics_entered: "Emner åbnet" posts_read_count: "Indlæg læst" expired: "Denne invitation er forældet" @@ -552,6 +577,7 @@ da: account_age_days: "Kontoens alder i dage" create: "Send en invitation" generate_link: "Kopier invitations-link" + generated_link_message: '

Invitationslink genereret!

Invitationslinket er kun gyldigt for denne email-adresse: %{invitedEmail}

' bulk_invite: none: "Du har ikke inviteret nogen her endnu. Du kan sende individuelle invitationer eller invitere en masse mennesker på én gang ved at uploade en samlet liste over invitationer." text: "Masse invitering fra en fil" @@ -591,11 +617,13 @@ da: server: "Server fejl" forbidden: "Adgang nægtet" unknown: "Fejl" + not_found: "Side ikke fundet" desc: network: "Tjek din internetforbindelse." network_fixed: "Det ser ud som om den er tilbage." server: "Fejlkode: {{status}}" forbidden: "Du har ikke tilladelser til at se det" + not_found: "Ups, programmet forsøgte at indlæse en URL der ikke eksisterer." unknown: "Noget gik galt." buttons: back: "Gå tilbage" @@ -608,6 +636,9 @@ da: read_only_mode: enabled: "Skrivebeskyttet tilstand er aktiv. Du kan fortsætte med at læse indlæg, men du kan ikke ændre noget." login_disabled: "Log in er deaktiveret midlertidigt, da forummet er i \"kun læsnings\" tilstand." + too_few_topics_and_posts_notice: "Lad os få startet denne diskussion! Der er i øjeblikket %{currentTopics} / %{requiredTopics} emner og %{currentPosts} / %{requiredPosts} indlæg. Nye besøgende har brug for samtaler at læse og svare på." + too_few_topics_notice: "Lad os få startet denne diskussion ! Der er i øjeblikket %{currentTopics} / %{requiredTopics} emner. Nye besøgende har brug for samtaler at læse og svare på." + too_few_posts_notice: "Lad os få startet denne diskussion ! Der er i øjeblikket %{currentPosts} / %{requiredPosts} indlæg. Nye besøgende har brug for samtaler at læse og svare på." learn_more: "Læs mere…" year: 'år' year_desc: 'Indlæg oprettet i de seneste 365 dage' @@ -624,6 +655,21 @@ da: replies_lowercase: one: svar other: svar + signup_cta: + sign_up: "Tilmeld dig" + hide_session: "Mind mig om det i morgen" + hide_forever: "nej tak" + hidden_for_session: "OK, jeg spørger dig i morgen. Du kan også altid bruge \"Log på\" til at oprette en konto." + intro: "Hejsa! :heart_eyes: Det ser ud til, at du følger godt med i samtalerne, men du har endnu ikke oprettet en konto." + value_prop: "Når du opretter en konto, så kan vi huske hvad du har læst, så du altid kan fortsætte, hvor du er kommet til. Du får også notifikationer - her og på email - når nye interessante indlæg postes. Og du kan like indlæg og dele begejstringen. :heartbeat:" + methods: + sso: "Det er let at tilmelde sig: du har kun brug for en konto til hovedsitet." + only_email: "Det er nemt at tilmelde sig: du har kun brug for en email og en adgangskode." + only_other: "Brug din %{provider}-konto til at tilmelde dig." + one_and_email: "Brug din %{provider}-konto, eller email og password, til at tilmelde dig." + multiple_no_email: "Det er let at tilmelde sig: brug en af dine %{count} sociale konti" + multiple: "Det er nemt at tilmelde sig: brug en af dine %{count} sociale konti, eller en email og adgangskode." + unknown: "fejl under hentning af understøttede loginmetoder" summary: enabled_description: "Du ser et sammendrag af dette emne: kun de mest interessante indlæg som andre finder interresante." description: "Der er {{count}} svar." @@ -705,7 +751,10 @@ da: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + options: "Indstillinger" + whisper: "hvisken" add_warning: "Dette er en officiel advarsel." + toggle_whisper: "Slå hvisken til/fra" posting_not_on_topic: "Hvilket emne vil du svare på?" saving_draft_tip: "gemmer..." saved_draft_tip: "gemt" @@ -732,6 +781,7 @@ da: title_placeholder: "Hvad handler diskussionen om i korte træk?" edit_reason_placeholder: "hvorfor redigerer du?" show_edit_reason: "(tilføj en begrundelse for ændringen)" + reply_placeholder: "Skriv her. Brug Markdown, BBCode eller HTML til at formattere. Træk eller indsæt billeder." view_new_post: "Se dit nye indlæg." saving: "Gemmer…" saved: "Gemt!" @@ -775,7 +825,7 @@ da: units: "(# timer)" examples: 'Indtast antal timer (24).' notifications: - title: "notifikation ved @navns nævnelse, svar til dine indlæg og emner, beskeder, mv." + title: "notifikationer ved @navns nævnelse, svar på dine indlæg og emner, beskeder, mv." none: "Ikke i stand til at indlæse notifikationer for tiden." more: "se ældre notifikationer" total_flagged: "total markerede indlæg" @@ -792,6 +842,20 @@ da: moved_post: "

{{username}} moved {{description}}

" linked: "

{{username}} {{description}}

" granted_badge: "

Du blev tildelt '{{description}}'

" + alt: + mentioned: "Nævnt af" + quoted: "Citeret af" + replied: "Svaret" + posted: "Indlæg af" + edited: "Rediger dit indlæg af" + liked: "Likede dit indlæg" + private_message: "Privat besked fra" + invited_to_private_message: "Inviteret til en privat besked fra" + invited_to_topic: "Inviteret til et indlæg fra" + invitee_accepted: "Invitation accepteret af" + moved_post: "Dit indlæg blev flyttet af" + linked: "Link til dit indlæg" + granted_badge: "Badge tildelt" popup: mentioned: '{{username}} nævnte dig i "{{topic}}" - {{site_title}}' quoted: '{{username}} citerede dig i "{{topic}}" - {{site_title}}' @@ -809,10 +873,21 @@ da: local_tip: "vælg billeder fra din enhed" local_tip_with_attachments: "vælg billeder eller filer fra din enhed ({{authorized_extensions}})" hint: "(du kan også trække og slippe ind i editoren for at uploade dem)" + hint_for_supported_browsers: "du kan også bruge træk-og-slip eller indsætte billeder i editoren" uploading: "Uploader billede" select_file: "Vælg fil" image_link: "link som dit billede vil pege på" search: + sort_by: "Sorter efter" + relevance: "Relevans" + latest_post: "Seneste indlæg" + most_viewed: "Mest sete" + most_liked: "Mest likede" + select_all: "Vælg alle" + clear_all: "Ryd alle" + result_count: + one: "1 resultat for \"{{term}}\"" + other: "{{count}} reultater for \"{{term}}\"" title: "søg efter emner, indlæg, brugere eller kategorier" no_results: "Ingen resultater fundet." no_more_results: "Ikke flere resultater." @@ -825,6 +900,7 @@ da: topic: "Søg i dette emne" private_messages: "Søg i beskeder" hamburger_menu: "gå til en anden emneliste eller kategori" + new_item: "ny" go_back: 'gå tilbage' not_logged_in_user: 'bruger side, med oversigt over aktivitet og indstillinger' current_user: 'gå til brugerside' @@ -873,6 +949,9 @@ da: bookmarks: "Der er ikke flere bogmærkede emner." search: "Der er ikke flere søgeresultater." topic: + unsubscribe: + stop_notifications: "Du vil nu modtage færre notifikationer for {{title}}" + change_notification_state: "Din nuværende notifikationstilstand er" filter_to: "Vis {{post_count}} indlæg i emnet" create: 'Nyt emne' create_long: 'Opret et nyt emne i debatten' @@ -1012,15 +1091,20 @@ da: success_message: 'Du har nu rapporteret dette emne til administrator.' feature_topic: title: "Fremhæv dette emne" + pin: "Fastgør dette emne til toppen af kategorien {{categoryLink}} indtil" confirm_pin: "Du har allerede {{count}} fastgjorte emner. Fastgjorte emner kan være irriterende for nye og anonyme brugere. Er du sikker på du vil fastgøre et nyt emne i denne kategori?" unpin: "Fjern dette emne fra starten af listen i {{categoryLink}} kategorien." + unpin_until: "Fjern dette emne fra toppen af kategorien {{categoryLink}} eller vent til %{until}." pin_note: "Brugere kan unpinne emnet individuelt." + pin_validation: "Der skal angives en dato for at fastgøre dette emne" already_pinned: zero: "Der er ingen fastgjorte emner i {{categoryLink}}." one: "Emner fastgjorte i {{categoryLink}}: 1." other: "Fastgjorte emner i {{categoryLink}}: {{count}}." + pin_globally: "Fastgør dette emne til toppen af alle emnelister indtil" confirm_pin_globally: "Du har allerede {{count}} globalt fastgjorte emner. For mange fastgjorte emner kan være irriterende for nye og anonyme brugere. Er du sikker på du vil fastgøre et emne mere globalt?" unpin_globally: "Fjern dette emne fra toppen af alle emne lister." + unpin_globally_until: "Fjern dette emne fra toppen af alle emnelister eller vent til %{until}." global_pin_note: "Brugere kan unpinne emnet individuelt." already_pinned_globally: zero: "Der er ingen globalt fastgjorte emner." @@ -1089,6 +1173,12 @@ da: one: "Vælg den nye ejer af indlægget, oprindeligt skrevet af {{old_user}}." other: "Vælg den nye ejer af {{count}} indlæg, oprindeligt skrevet af {{old_user}}." instructions_warn: "Bemærk at tidligere notifikationer på dette emne vil ikke blive overført til den nye bruger.\n
Advarsel: På nuværende tidspunkt, vil ingen data der er afhængig af dette indlæg blive overført til den nye bruger. Brug forsigtigt." + change_timestamp: + title: "Ret tidsstempel" + action: "ret tidsstempel" + invalid_timestamp: "Tidsstempel kan ikke være i fremtiden" + error: "Der opstod en fejl under rettelsen af tidsstemplet for dette emne." + instructions: "Vælg venligst det nye tidsstempel for dette emne. Indlæg under emnet vil blive opdateret så de har samme tidsforskel." multi_select: select: 'vælg' selected: 'valgt ({{count}})' @@ -1101,6 +1191,8 @@ da: one: Du har valgt 1 indlæg. other: Du har valgt {{count}} indlæg. post: + reply: " {{replyAvatar}} {{usernameLink}}" + reply_topic: " {{link}}" quote_reply: "citér svar" edit: "Redigerer {{link}} {{replyAvatar}} {{username}}" edit_reason: "Reason: " @@ -1150,6 +1242,7 @@ da: no_value: "Nej" yes_value: "Ja" via_email: "dette indlæg blev oprettet via email" + whisper: "dette indlæg er en privat hvisken for moderatorer" wiki: about: "dette indlæg er en wiki; almindelige brugere kan redigere den" archetypes: @@ -1310,6 +1403,7 @@ da: topic_template: "Skabelon for emne" delete: 'Slet kategori' create: 'Ny kategori' + create_long: 'Opret en ny kategori' save: 'Gem kategori' slug: 'Kategori simpelt navn' slug_placeholder: '(Valgfri) gennemstregede-ord for URL' @@ -1332,6 +1426,7 @@ da: change_in_category_topic: "besøg kategoriemnet for at redigere beskrivelsen" already_used: 'This color has been used by another category' security: "Sikkerhed" + special_warning: "Advarsel: Denne kategori er forud-seedet og sikkerhedsindstillingerne kan ikke redigeres. Hvis du ikke ønsker at bruge denne kategori, bør du slette den snarere end at genbruge den til et andet formål." images: "Billeder" auto_close_label: "Luk automatisk emner efter:" auto_close_units: "timer" @@ -1339,6 +1434,7 @@ 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" allow_badges_label: "Tillad af badges bliver tildelt i denne kategori" edit_permissions: "Redigér tilladelser" add_permission: "Tilføj tilladelse" @@ -1351,16 +1447,16 @@ da: notifications: watching: title: "Kigger" - description: "Du overvåger automatisk alle emner i disse kategorier. Du vil få en notifikation ved alle nye indlæg og emner, og antallet af nye svar vil stå ved siden af emnerne." + description: "Du overvåger automatisk alle emner i disse kategorier. Du vil få en notifikation ved alle nye indlæg i alle emner, og antallet af nye svar vil blive vist." tracking: title: "Følger" - description: "Du vil automatisk følge alle nye emner i disse kategorier. En optælling af nye svar vises ved emnerne." + description: "Du følger automatisk alle nye emner i disse kategorier. Du vil blive underrettet hvis nogen nævner dit navn eller svarer dig, og antallet af nye svar vil blive vist." regular: - title: "Standard" + title: "Normal" description: "Du vil modtage en notifikation, hvis nogen nævner dit @name eller svarer dig." muted: title: "Ignoreret" - description: "Du ignorerer automatisk alle emner i disse kategorier. De vil heller ikke stå i sin \"ulæste\" indikation." + description: "Du vil aldrig blive underrettet om noget som helst angående nye emner i disse categorier, og de vil ikke blive vist under \"ulæste\"." flagging: title: 'Tak fordi du hjælper med at holde vores forum civiliseret!' private_reminder: 'flag er private, de er kun

synlige for personalet' @@ -1406,6 +1502,8 @@ da: help: "emnet er låst; det modtager ikke flere svar" archived: help: "emnet er arkiveret; det er frosset og kan ikke ændres" + locked_and_archived: + help: "Dette emne er lukket og arkiveret; der kan ikke længere postes nye indlæg, og emner kan ikke ændres." unpinned: title: "Ikke fastgjort" help: "Dette emne er ikke fastgjort for dig; det vil blive vist i den normale rækkefølge" @@ -1772,6 +1870,7 @@ da: header: "Header" top: "Top" footer: "Bund" + embedded_css: "Indlejret CSS" head_tag: text: "" title: "HTML som indsættes før -tagget" @@ -1863,6 +1962,7 @@ da: sent_test: "sednt!" delivery_method: "Leveringsmetode" preview_digest: "Forhåndsvisning af sammendrag" + preview_digest_desc: "Forhåndsvis indholdet af de opsamlings-emails der sendes til inaktive brugere" refresh: "Opdatér" format: "Format" html: "html" @@ -1888,6 +1988,7 @@ da: ip_address: "IP" topic_id: "Emne ID" post_id: "Indlægs ID" + category_id: "Kategori-id" delete: 'Slet' edit: 'Redigér' save: 'Gem' @@ -1927,6 +2028,10 @@ da: delete_post: "slet indlæg" impersonate: "Udgiv dig for bruger" anonymize_user: "anonymiser bruger" + roll_up: "rul IP-blokke op" + change_category_settings: "ret kategori-indstillinger" + delete_category: "slet kategori" + create_category: "opret kategori" screened_emails: title: "Blokerede e-mails" description: "Følgende e-mail-adresser kontrolleres når nogen prøver at oprette en konto, og oprettelsen vil enten blive blokeret, eller der vil blive foretaget en anden handling." @@ -2164,6 +2269,7 @@ da: field_types: text: 'Tekstfelt' confirm: 'Bekræft' + dropdown: "Rulleboks" site_text: none: "Vælg en indholdstype for at begynde at redigere." title: 'Tekstindhold' @@ -2175,7 +2281,7 @@ da: no_results: "Ingen resultater fundet." clear_filter: "Ryd" add_url: "tilføj URL" - add_host: "tilføj host" + add_host: "tilføj server" categories: all_results: 'Alle' required: 'Obligatoriske' @@ -2197,6 +2303,7 @@ da: backups: "Backups" login: "Brugernavn" plugins: "Plugins" + user_preferences: "Brugerpræferencer" badges: title: Badges new_badge: Nyt Badge @@ -2271,8 +2378,30 @@ da: image: "Billede" delete_confirm: "Er du sikker på du vil slette emotikonnet: %{name} ?" embedding: + get_started: "Hvis du vil indlejre Discourse på et andet website, skal du starte med at tilføje dets server." + confirm_delete: "Er du sikker på at du vil slette denne server?" + sample: "Tilføj denne HTML til dit site for at oprette og indlejre emner fra discourse. Erstat REPLACE_ME med den kanoniske URL for den side du indlejrer den på," + title: "Indlejring" + host: "Tilladte servere" + edit: "redigér" + category: "Opret i kategorien" + add_host: "Tilføj server" + settings: "Indlejrings-indstillinger" + feed_settings: "Feed-instillinger" + feed_description: "Hvis du angiver et RSS/ATOM-feed for dit site, kan det forbedre Discourses mulighed for at importere dit indhold." + crawling_settings: "Robot-indstillinger" + crawling_description: "Når Discourse opretter emner for dine indlæg, og der ikke er noget RSS/ATOM-feed, vil den forsøge at parse dit indhold ud fra din HTML. Det kan nogengange være en udfordring at udtrække dit indhold, så vi giver mulighed for at specificere CSS-regler for at gøre udtræk lettere." + embed_by_username: "Brugernavn for oprettelse af emne" + embed_post_limit: "Maksimalt antal indlæg der kan indlejres." + embed_username_key_from_feed: "Nøgle til at udtrække discourse-brugernavn fra feed" + embed_truncate: "Beskær de indlejrede indlæg" + embed_whitelist_selector: "CSS-selektorer for elementer der er tilladte i indlejringer" + whitelist_example: "article, #story, .post" + embed_blacklist_selector: "CSS-selektorer for elementer der fjernes fra indlejringer" + blacklist_example: ".ad-unit, header" feed_polling_enabled: "Importer indlæg via RSS/ATOM" feed_polling_url: "URL på RSS/ATOM feed der skal kravles" + save: "Gem indlejrings-indstillinger" permalink: title: "Permalinks" url: "URL" @@ -2303,6 +2432,8 @@ da: categories: 'g, c Kategorier' top: 'g, t Top' bookmarks: 'g, b Bogmærker' + profile: 'g, p Profil' + messages: 'g, m Beskeder' navigation: title: 'Navigation' jump: '# Gå til indlæg #' @@ -2321,6 +2452,7 @@ da: help: '? Åben keyboard hjælp' dismiss_new_posts: 'x, r Afvis alle Nye/Indlæg' dismiss_topics: 'x, t Afvis emner' + log_out: 'shift+z shift+z Log ud' actions: title: 'Handlinger' bookmark_topic: 'f Sæt bogmærke i emne' @@ -2454,3 +2586,15 @@ da: hot_link: name: Hot link description: Postede et eksternt link med mindst 300 klik + famous_link: + name: Berømt link + description: Postede et eksternt link med mindst 1000 klik + google_search: | +

Søg med Google

+

+

+

diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 5a57b22969..9bc5570f35 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -546,7 +546,7 @@ de: after_5_minutes: "nach 5 Minuten" after_10_minutes: "nach 10 Minuten" invited: - search: "schreib zum Suchen nach Einladungen..." + search: "zum Suchen nach Einladungen hier eingeben..." title: "Einladungen" user: "Eingeladener Benutzer" sent: "Gesendet" @@ -644,7 +644,7 @@ de: signup_cta: sign_up: "Registrieren" hide_session: "Erinnere mich morgen" - hide_forever: "Nein danke!" + hide_forever: "Nein danke" hidden_for_session: "In Ordnung, ich frag dich morgen wieder. Du kannst dir auch jederzeit mit „Anmelden“ ein Benutzerkonto erstellen." intro: "Hallo! :heart_eyes: Es sieht so aus, als würde dir die Diskussion gefallen. Du hast aber noch kein Benutzerkonto." value_prop: "Wenn du ein Benutzerkonto anlegst, merken wir uns, was du gelesen hast, damit du immer dort fortsetzten kannst, wo du aufgehört hast. Du kannst auch Benachrichtigungen – hier oder per E-Mail – erhalten, wenn neue Beiträge verfasst werden. Beiträge, die dir gefallen, kannst du mit einem Like versehen und diese Freude mit allen teilen. :heartbeat:" @@ -767,7 +767,7 @@ de: title_placeholder: "Um was geht es in dieser Diskussion? Schreib einen kurzen Satz." edit_reason_placeholder: "Warum bearbeitest du?" show_edit_reason: "(Bearbeitungsgrund hinzufügen)" - reply_placeholder: "Schreibe hier. Verwende Markdown, BBCode oder HTML zur Formatierung. Füge Bilder ein oder ziehe sie herein." + reply_placeholder: "Schreib hier. Verwende Markdown, BBCode oder HTML zur Formatierung. Füge Bilder ein oder ziehe sie herein." view_new_post: "Sieh deinen neuen Beitrag an." saving: "Speichere..." saved: "Gespeichert!" @@ -1424,16 +1424,13 @@ de: notifications: watching: title: "Beobachten" - description: "Du wirst automatisch alle neuen Themen in dieser Kategorie beobachten und über alle neuen Beiträge und Themen benachrichtigt werden. Die Anzahl der neuen Antworten wird bei den betroffenen Themen angezeigt." tracking: title: "Verfolgen" - description: "Du wirst automatisch allen neuen Themen in dieser Kategorie folgen. Die Anzahl der neuen Antworten wird bei den betroffenen Themen angezeigt." regular: title: "Normal" description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder dir antwortet." muted: title: "Stummgeschaltet" - description: "Du erhältst keine Benachrichtigungen über neue Themen in dieser Kategorie und die Themen werden nicht in deiner Liste ungelesener Themen aufscheinen." flagging: title: 'Danke für deine Mithilfe!' private_reminder: 'Meldungen sind vertraulich und nur für Mitarbeiter sichtbar' diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4dd0b0cf5c..1b1daeed99 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -619,7 +619,9 @@ en: user: "Invited User" sent: "Sent" none: "There are no pending invites to display." - truncated: "Showing the first {{count}} invites." + truncated: + one: "Showing the first invite." + other: "Showing the first {{count}} invites." redeemed: "Redeemed Invites" redeemed_tab: "Redeemed" redeemed_tab_with_count: "Redeemed ({{count}})" @@ -831,6 +833,7 @@ en: composer: emoji: "Emoji :smile:" + more_emoji: "more..." options: "Options" whisper: "whisper" @@ -2173,6 +2176,7 @@ en: ip_address: "IP" topic_id: "Topic ID" post_id: "Post ID" + category_id: "Category ID" delete: 'Delete' edit: 'Edit' save: 'Save' @@ -2213,6 +2217,9 @@ en: impersonate: "impersonate" anonymize_user: "anonymize user" roll_up: "roll up IP blocks" + change_category_settings: "change category settings" + delete_category: "delete category" + create_category: "create category" 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." @@ -2588,7 +2595,9 @@ en: embed_username_key_from_feed: "Key to pull discourse username from feed" embed_truncate: "Truncate the embedded posts" embed_whitelist_selector: "CSS selector for elements that are allowed in embeds" + whitelist_example: "article, #story, .post" embed_blacklist_selector: "CSS selector for elements that are removed from embeds" + blacklist_example: ".ad-unit, header" feed_polling_enabled: "Import posts via RSS/ATOM" feed_polling_url: "URL of RSS/ATOM feed to crawl" save: "Save Embedding Settings" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index 4b1f09b9f3..b56b946bdb 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -617,11 +617,13 @@ es: server: "Error del Servidor" forbidden: "Acceso Denegado" unknown: "Error" + not_found: "Página no encontrada" desc: network: "Por favor revisa tu conexión." network_fixed: "Parece que ha vuelto." server: "Código de error: {{status}}" forbidden: "No estás permitido para ver eso." + not_found: "¡Ups! la aplicación intentó cargar una URL inexistente." unknown: "Algo salió mal." buttons: back: "Volver Atrás" @@ -656,10 +658,10 @@ es: signup_cta: sign_up: "Registrarse" hide_session: "Recordar mañana" - hide_forever: "No gracias" + hide_forever: "no, gracias" hidden_for_session: "Vale, te preguntaremos mañana. Recuerda que también puedes usar el botón 'Iniciar sesión' para crear una cuenta en cualquier momento." intro: "¡Hola! :heart_eyes: Parece que estás interesado en las cosas que nuestros usuarios publican, pero no tienes una cuenta registrada." - value_prop: "Si estás registrado, podemos saber exactamente lo que has leído, para que siempre puedas retomar la lectura donde la dejaste. También puedes recibir notificaciones, por aquí o por email, cuando se publiquen nuevos mensajes. Ah, ¡y puedes darle a Me gusta a los mensajes! :heartbeat:" + value_prop: "Cuando te registras, recordamos lo que has leído, para que puedas volver justo donde estabas leyendo. También recibes notificaciones, por aquí y por email, cuando se publican nuevos mensajes. ¡También puedes darle a Me gusta a los mensajes! :heartbeat:" methods: sso: "Registrarse es gratis: sólo necesitas una cuenta de la web principal." only_email: "Registrarse es fácil: sólo necesitas un email y una contraseña." @@ -1445,16 +1447,16 @@ es: notifications: watching: title: "Vigilar" - description: "Seguirás automáticamente todos los nuevos temas en estas categorías. Se te notificará de cada nuevo post y tema, y además, se añadirá un contador de posts nuevos y sin leer al lado del tema." + description: "Vigilarás automáticamente todos los nuevos temas en estas categorías. Serás notificado por cada nuevo mensaje en cada tema, y verás una cuenta de las nuevas respuestas." tracking: title: "Seguir" - description: "Seguirás automáticamente todos los nuevos temas en estas categorías. Se añadirá un contador de posts nuevos y sin leer al lado del tema." + description: "Seguirás automáticamente todos los nuevos temas en estas categorías. Serás notificado si alguien menciona tu @nombre o te responde, y verás una cuenta de las nuevas respuestas." regular: title: "Normal" description: "Se te notificará solo si alguien menciona tu @nombre o te responde a un post." muted: title: "Silenciadas" - description: "No se te notificará de nuevos temas en estas categorías y no aparecerán en la pestaña de no leídos." + description: "Nunca serás notificado sobre nuevos temas en estas categorías, y no aparecerán en tu pestaña de no leído." flagging: title: '¡Gracias por ayudar a mantener una comunidad civilizada!' private_reminder: 'los reportes son privados, son visibles únicamente por los administradores' @@ -1986,6 +1988,7 @@ es: ip_address: "IP" topic_id: "ID del Tema" post_id: "ID del Post" + category_id: "ID de la categoría" delete: 'Eliminar' edit: 'Editar' save: 'Guardar' @@ -2026,6 +2029,9 @@ es: impersonate: "impersonar" anonymize_user: "anonimizar usuario" roll_up: "agrupar bloqueos de IP" + change_category_settings: "cambiar opciones de categoría" + delete_category: "eliminar categoría" + create_category: "crear categoría" screened_emails: title: "Correos bloqueados" description: "Cuando alguien trata de crear una cuenta nueva, los siguientes correos serán revisados y el registro será bloqueado, o alguna otra acción será realizada." @@ -2390,7 +2396,9 @@ es: embed_username_key_from_feed: "Clave para extraer usuario de discourse del feed" embed_truncate: "Truncar los posts insertados" embed_whitelist_selector: "Selector CSS para permitir elementos a embeber" + whitelist_example: "article, #story, .post" embed_blacklist_selector: "Selector CSS para restringir elementos a embeber" + blacklist_example: ".ad-unit, header" feed_polling_enabled: "Importar posts usando RSS/ATOM" feed_polling_url: "URL del feed RSS/ATOM del que extraer datos" save: "Guardar ajustes de Insertado" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 337852a60e..53bd89678e 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -1240,16 +1240,12 @@ fa_IR: notifications: watching: title: "در حال تماشا" - description: "شما به صورت خودکار تمام عناوین این دسته‌ها را مشاهده‌ خواهید کرد. به شما تمام عناوین و نوشته‌‌های جدید اطلاع رسانی خواهد شد، و تعداد نوشته‌های جدید هر عنوان در کنار آن نمایش داده می‌شود." tracking: title: "پیگیری" - description: "شما به صورت خودکار تمام عناوین جدید در این دسته‌ها را پیگیری خواهید کرد. تعداد نوشته های جدید در کنار عنواین نمایش داده می‌شود." regular: - title: "منظم" description: "در صورتی که فردی با @name به شما اشاره کند یا به شما پاسخی دهد به شما اطلاع داده خواهد شد." muted: title: "بی صدا شد" - description: "به شما همه چیز در رابطه با این دسته بندی اطلاع داده خواهد شد ، و آن ها در تب خوانده نشده پدیدار نمی شوند" flagging: title: 'تشکر برای کمک به نگه داشتن جامعه ما بصورت مدنی !' private_reminder: 'پرچم های خصوصی, فقط قابل مشاهده برای مدیران' diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index cb44381653..068a927619 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -109,6 +109,7 @@ fi: google+: 'jaa tämä linkki Google+:ssa' email: 'lähetä tämä linkki sähköpostissa' action_codes: + split_topic: "pilkkoi tämän ketjun %{when}" autoclosed: enabled: 'sulki %{when}' disabled: 'avasi %{when}' @@ -227,6 +228,7 @@ fi: revert: "Palauta" failed: "Epäonnistui" switch_to_anon: "Anonyymi tila" + switch_from_anon: "Poistu anonyymitilasta" banner: close: "Sulje tämä banneri." edit: "Muokkaa tätä banneria >>" @@ -615,11 +617,13 @@ fi: server: "Palvelinvirhe" forbidden: "Pääsy estetty" unknown: "Virhe" + not_found: "Sivua ei löytynyt" desc: network: "Tarkasta internetyhteytesi." network_fixed: "Näyttäisi palanneen takaisin." server: "Virhekoodi: {{status}}" forbidden: "Sinulla ei ole oikeutta katsoa tätä." + not_found: "Hups, ohjelma yritti ladata osoitteen, jota ei ole olemassa" unknown: "Jotain meni pieleen." buttons: back: "Mene takaisin" @@ -632,6 +636,9 @@ fi: read_only_mode: enabled: "Olet Vain luku -tilassa. Voit jatkaa selaamista, muttet välttämättä pysty vaikuttamaan sisältöön." login_disabled: "Kirjautuminen ei ole käytössä sivuston ollessa vain luku -tilassa." + too_few_topics_and_posts_notice: "Laitetaanpa keskustelu alulle! Tällä hetkellä palstalla on %{currentTopics} / %{requiredTopics} ketjua ja %{currentPosts} / %{requiredPosts} viestiä. Uudet kävijät tarvitsevat keskusteluita, joita lukea ja joihin vastata." + too_few_topics_notice: "Laitetaanpa keskustelu alulle! Tällä hetkellä palstalla on %{currentTopics} / %{requiredTopics} ketjua. Uudet kävijät tarvitsevat keskusteluita, joita lukea ja joihin vastata." + too_few_posts_notice: "Laitetaanpa keskustelu alulle! Tällä hetkellä palstalla on %{currentPosts} / %{requiredPosts} viestiä. Uudet kävijät tarvitsevat keskusteluita, joita lukea ja joihin vastata." learn_more: "opi lisää..." year: 'vuosi' year_desc: 'viimeisen 365 päivän aikana luodut ketjut' @@ -648,6 +655,21 @@ fi: replies_lowercase: one: vastaus other: vastauksia + signup_cta: + sign_up: "Luo tili" + hide_session: "Muistuta huomenna" + hide_forever: "ei kiitos" + hidden_for_session: "OK, kysyn huomenna uudestaan. Voit aina myös käyttää 'Kirjaudu sisään' -linkkiä luodaksesi tilin." + intro: "Hei siellä! :heart_eyes: Vaikuttaa siltä, että olet pitänyt keskusteluista, mutta et ole luonut omaa tiliä." + value_prop: "Kun luot tilin, muistamme mitä olet lukenut, jotta voit aina palata keskusteluissa takaisin oikeaan kohtaan. Saat myös ilmoituksia, täällä tai sähköpostilla, kun uusia viestejä kirjoitetaan. Voit myös tykätä viesteistä. :heartbeat:" + methods: + sso: "Tilin luominen on helppoa: tarvitset vain tunnuksen pääsivustolle." + only_email: "Tilin luominen on helppoa: tarvitset vain sähköpostiosoitteen ja salasanan." + only_other: "Käytä %{provider} -tiliäsi luodaksesi tunnuksen." + one_and_email: "Käytä %{provider} -tiliäsi, tai sähköpostia ja salasanaa luodaksesi tilin." + multiple_no_email: "Tilin luominen on helppoa: käytä mitä vain %{count} sosiaalisesta kirjautumisesta." + multiple: "Tilin luominen on helppoa: käytä mitä vain %{count} sosiaalisesta kirjautumisesta, tai sähköpostiosoitetta ja salasanaa." + unknown: "virhe haettaessa tuettuja kirjautumistapoja" summary: enabled_description: "Tarkastelet tiivistelmää tästä ketjusta, sen mielenkiintoisimpia viestejä käyttäjien toiminnan perusteella." description: "Tässä kejussa on {{count}} viestiä." @@ -729,7 +751,10 @@ fi: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + options: "Asetukset" + whisper: "kuiskaus" add_warning: "Tämä on virallinen varoitus." + toggle_whisper: "Vaihda kuiskaus" posting_not_on_topic: "Mihin ketjuun haluat vastata?" saving_draft_tip: "tallennetaan..." saved_draft_tip: "tallennettu" @@ -853,6 +878,16 @@ fi: select_file: "Valitse tiedosto" image_link: "linkki, johon kuvasi osoittaa" search: + sort_by: "Järjestä" + relevance: "Osuvuus" + latest_post: "Uusin viesti" + most_viewed: "Katselluin" + most_liked: "Tykätyin" + select_all: "Valitse kaikki" + clear_all: "Tyhjennä kaikki" + result_count: + one: "1 tulos haulle \"{{term}}\"" + other: "{{count}} tulosta haulle \"{{term}}\"" title: "etsi ketjuja, viestejä, käyttäjiä tai alueita" no_results: "Ei tuloksia." no_more_results: "Enempää tuloksia ei löytynyt." @@ -864,6 +899,8 @@ fi: category: "Etsi alueelta \"{{category}}\"" topic: "Etsi tästä ketjusta" private_messages: "Etsi viesteistä" + hamburger_menu: "siirry toiseen ketjuun tai alueelle" + new_item: "uusi" go_back: 'mene takaisin' not_logged_in_user: 'käyttäjäsivu, jossa on tiivistelmä käyttäjän viimeaikaisesta toiminnasta sekä käyttäjäasetukset' current_user: 'siirry omalle käyttäjäsivullesi' @@ -1059,6 +1096,7 @@ fi: unpin: "Älä enää pidä tätä ketjua {{categoryLink}}-aluen ylimmäisenä." unpin_until: "Poista nyt tämän ketjun kiinnitys alueen {{categoryLink}} ylimmäisenä, tai odota kunnes %{until}." pin_note: "Käyttäjät voivat poistaa ketjun kiinnityksen itseltään." + pin_validation: "Päivämäärä vaaditaan kiinnittämään tämä ketju" already_pinned: zero: "Alueella {{categoryLink}} ei ole kiinnitettyjä ketjuja." one: "Kiinnitettyjä ketjuja alueella {{categoryLink}}: 1." @@ -1135,6 +1173,12 @@ fi: one: "Valitse uusi omistaja viestille käyttäjältä {{old_user}}." other: "Valitse uusi omistaja {{count}} viestille käyttäjältä {{old_user}}." instructions_warn: "Huomaa, että viestin ilmoitukset eivät siirry uudelle käyttäjälle automaattisesti.
Varoitus: Tällä hetkellä mikään viestikohtainen data ei siirry uudelle käyttäjälle. Käytä varoen." + change_timestamp: + title: "Muuta aikaleimaa" + action: "muuta aikaleimaa" + invalid_timestamp: "Aikaleima ei voi olla tulevaisuudessa." + error: "Ketjun aikaleiman vaihtamisessa tapahtui virhe" + instructions: "Ole hyvä ja valitse ketjulle uusi aikaleima. Ketjun viestit päivitetään samalla aikaerolla." multi_select: select: 'valitse' selected: 'valittuna ({{count}})' @@ -1198,6 +1242,7 @@ fi: no_value: "Ei, säilytä" yes_value: "Kyllä, hylkää" via_email: "tämä viesti lähetettiin sähköpostitse" + whisper: "tämä viesti on yksityinen kuiskaus valvojille" wiki: about: "tämä viesti on wiki; peruskäyttäjät voivat muokata sitä" archetypes: @@ -1358,6 +1403,7 @@ fi: topic_template: "Ketjun sapluuna" delete: 'Poista alue' create: 'Uusi alue' + create_long: 'Luo uusi alue' save: 'Tallenna alue' slug: 'Alueen lyhenne' slug_placeholder: '(Valinnainen) url-lyhenne' @@ -1380,6 +1426,7 @@ fi: change_in_category_topic: "Muokkaa kuvausta" already_used: 'Tämä väri on jo käytössä toisella alueella' security: "Turvallisuus" + special_warning: "Varoitus: Tämä alue on esituotettu ja sen turvallisuusasetuksia ei voi muuttaa. Jos et halua käyttää sitä, poista se sen sijaan." images: "Kuvat" auto_close_label: "Sulje ketjut automaattisesti tämän ajan jälkeen:" auto_close_units: "tuntia" @@ -1387,6 +1434,7 @@ fi: email_in_allow_strangers: "Hyväksy viestejä anonyymeiltä käyttäjiltä joilla ei ole tiliä" email_in_disabled: "Uusien ketjujen luominen 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.' + suppress_from_homepage: "Vaimenna tämä ketju kotisivulta." allow_badges_label: "Salli arvomerkkien myöntäminen tältä alueelta" edit_permissions: "Muokkaa oikeuksia" add_permission: "Lisää oikeus" @@ -1399,16 +1447,16 @@ fi: notifications: watching: title: "Tarkkaile" - description: "Näiden alueiden kaikki uudet ketjut asetetaan automaattisesti tarkkailuun. Saat ilmoituksen kaikista uusista viesteistä ja ketjuista ja uusien viestien lukumäärä näytetään ketjun otsikon vieressä." + description: "Tarkkailet automaattisesti kaikkia uusia ketjuja näillä alueilla. Saat ilmoituksen jokaisesta uudesta viestistä jokaisessa ketjussa ja uusien vastausten lukumäärä näytetään. " tracking: title: "Seuraa" - description: "Näiden alueiden kaikki uudet ketjut asetetaan automaattisesti seurantaan. Uusien viestien lukumäärä näytetään ketjun otsikon vieressä." + description: "Seuraat automaattisesti kaikkia uusia ketjuja näillä alueilla. Saat ilmoituksen, jos joku mainitsee @nimesi tai vastaa sinulle ja uusien vastauksien lukumäärä näytetään." regular: title: "Tavallinen" description: "Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa sinulle." muted: title: "Vaimennettu" - description: "Et saa imoituksia uusista viesteistä näillä alueilla, eivätkä ne näy Lukemattomat-välilehdellä" + description: "Et saa imoituksia uusista viesteistä näillä alueilla, eivätkä ne näy Lukemattomat-välilehdellä." flagging: title: 'Kiitos avustasi yhteisön hyväksi!' private_reminder: 'liput ovat yksityisiä, ne näkyvät ainoastaan henkilökunnalle' @@ -1454,6 +1502,8 @@ fi: help: "Tämä ketju on suljettu; siihen ei voi enää vastata." archived: help: "Tämä ketju on arkistoitu; se on jäädytetty eikä sitä voi muuttaa" + locked_and_archived: + help: "Tämä ketju on suljettu ja arkistoitu, sihen ei voi enää vastata eikä sitä muuttaa" unpinned: title: "Kiinnitys poistettu" help: "Ketjun kiinnitys on poistettu sinulta; se näytetään tavallisessa järjestyksessä." @@ -1912,6 +1962,7 @@ fi: sent_test: "lähetetty!" delivery_method: "Lähetystapa" preview_digest: "Esikatsele tiivistelmä" + preview_digest_desc: "Esikatsele inaktiivisille käyttäjille lähetettyjen tiivistelmäsähköpostien sisältöä." refresh: "Päivitä" format: "Muotoilu" html: "html" @@ -1937,6 +1988,7 @@ fi: ip_address: "IP-osoite" topic_id: "Ketjun ID" post_id: "Viestin ID" + category_id: "Alueen ID" delete: 'Poista' edit: 'Muokkaa' save: 'Tallenna' @@ -1977,6 +2029,9 @@ fi: impersonate: "esiinny käyttäjänä" anonymize_user: "anonymisoi käyttäjä" roll_up: "Kääri IP estot" + change_category_settings: "muuta alueen asetuksia" + delete_category: "poista alue" + create_category: "luo alue" screened_emails: title: "Seulottavat sähköpostiosoitteet" description: "Uuden käyttäjätunnuksen luonnin yhteydessä annettua sähköpostiosoitetta verrataan alla olevaan listaan ja tarvittaessa tunnuksen luonti joko estetään tai suoritetaan muita toimenpiteitä." @@ -2248,6 +2303,7 @@ fi: backups: "Varmuuskopiot" login: "Kirjautuminen" plugins: "Lisäosat" + user_preferences: "Käyttäjäasetukset" badges: title: Arvomerkit new_badge: Uusi arvomerkki @@ -2321,6 +2377,31 @@ fi: name: "Nimi" image: "Kuva" delete_confirm: "Oletko varma, että haluat poistaa emojin :%{name}:?" + embedding: + get_started: "Jos haluat upottaa Discoursen toiselle sivustolle, aloita lisäämällä isäntä." + confirm_delete: "Oletko varma, että haluat poistaa tämän isännän?" + sample: "Käytä alla olevaa HTML-koodia sivustollasi luodaksesi ja upottaaksesi discourse ketjuja. Korvaa REPLACE_ME upotettavan sivun kanonisella URL-osoitteella." + title: "Upottaminen" + host: "Sallitut isännät" + edit: "muokkaa" + category: "Julkaise alueelle" + add_host: "Lisää isäntä" + settings: "Upotuksen asetukset" + feed_settings: "Syötteen asetukset" + feed_description: "Tarjoamalla RSS/ATOM syötteen sivustollesi, voit lisätä Discoursen kykyä tuoda sisältöä." + crawling_settings: "Crawlerin asetukset" + crawling_description: "Kun Discourse luo ketjuja kirjoituksistasi, se yrittää jäsentää kirjoitustesi sisältöä HTML:stä, jos RSS/ATOM syötettä ei ole tarjolla, Joskus kirjoitusten sisällön poimiminen on haastavaa, joten tarjoamme mahdollisuuden määrittää CSS sääntöjä sen helpottamiseksi." + embed_by_username: "Käyttäjänimi ketjun luomiseksi" + embed_post_limit: "Upotettavien viestien maksimimäärä" + embed_username_key_from_feed: "Avain, jolla erotetaan Discourse-käyttäjänimi syötteestä" + embed_truncate: "Typistä upotetut viestit" + embed_whitelist_selector: "CSS valitsin elementeille, jotka sallitaan upotetuissa viesteissä" + whitelist_example: "article, #story, .post" + embed_blacklist_selector: "CSS valitstin elementeille, jotka poistetaan upotetuista viesteistä" + blacklist_example: ".ad-unit, header" + feed_polling_enabled: "Tuo kirjoitukset RSS/ATOM syötteen avulla" + feed_polling_url: "RSS/ATOM syötteen URL" + save: "Tallenna upotusasetukset" permalink: title: "Ikilinkit" url: "URL" @@ -2351,6 +2432,8 @@ fi: categories: 'g, c Alueet' top: 'g, t Huiput' bookmarks: 'g, b Kirjanmerkit' + profile: 'g, p Profiili' + messages: 'g, m Viestit' navigation: title: 'Navigointi' jump: '# Siirry viestiin #' @@ -2362,12 +2445,14 @@ fi: title: 'Ohjelmisto' create: 'c Luo uusi ketju' notifications: 'n Avaa ilmoitukset' + hamburger_menu: '= Avaa valikko' user_profile_menu: 'p Avaa käyttäjätilin valikko' show_incoming_updated_topics: '. Näytä päivittyneet ketjut' search: '/ Etsi' help: '? Avaa näppäinoikoteiden apu' dismiss_new_posts: 'x, r Unohda uudet/viestit' dismiss_topics: 'x, t Unohda ketjut' + log_out: 'shift+z shift+z Kirjaudu ulos' actions: title: 'Toiminnot' bookmark_topic: 'f Vaihda kirjanmerkkeihin tai pois' @@ -2495,3 +2580,21 @@ fi: reader: name: Lukija description: Luki kaikki viestit ketjusta, jossa on yli 100 viestiä + popular_link: + name: Suosittu linkki + description: Postasi linkin ulkoiselle sivustolle, joka sai vähintään 50 klikkausta. + hot_link: + name: Kuuma linkki + description: Postasi linkin ulkoiselle sivustolle, joka sai vähintään 300 klikkausta. + famous_link: + name: Kuuluisa linkki + description: Postasi linkin ulkoiselle sivustolle, joka sai vähintään 1000 klikkausta. + google_search: | +

Etsi Googlella

+

+

+

diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index f6b80ec36e..84e11b5aea 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -109,6 +109,7 @@ fr: google+: 'partager ce lien sur Google+' email: 'envoyer ce lien dans un courriel' action_codes: + split_topic: "Ce sujet a été découpé %{when}" autoclosed: enabled: 'fermé %{when}' disabled: 'ouvert %{when}' @@ -227,6 +228,7 @@ fr: revert: "Rétablir" failed: "Echec" switch_to_anon: "Mode anonyme" + switch_from_anon: "Quitter le mode anonyme" banner: close: "Ignorer cette bannière." edit: "Éditer cette bannière >>" @@ -330,6 +332,7 @@ fr: reorder: title: "Réordonner les catégories" title_long: "Réorganiser la liste des catégories" + fix_order: "Corriger les positions" fix_order_tooltip: "Toutes les catégories n'ont pas une position unique. Cela peut provoquer des résultats non souhaités." save: "Enregistrer l'ordre" apply_all: "Appliquer" @@ -614,11 +617,13 @@ fr: server: "Erreur serveur" forbidden: "Accès refusé" unknown: "Erreur" + not_found: "Page introuvable" desc: network: "Veuillez vérifier votre connexion." network_fixed: "On dirait que c'est revenu." server: "Code d'erreur: {{status}}" forbidden: "Vous n'êtes pas autorisé à voir cela." + not_found: "Oups, l'application a essayé de charger une URL qui n'existe pas." unknown: "Une erreur est survenue." buttons: back: "Retour" @@ -631,6 +636,9 @@ fr: read_only_mode: enabled: "Le mode lecture seule est activé. Vous pouvez continuer à naviguer sur le site, mais ne pouvez pas prendre part aux discussions." login_disabled: "Impossible de se connecté quand le site est en mode lecture seule." + too_few_topics_and_posts_notice: "Démarrons cette discussion! Il y a actuellement %{currentTopics} / %{requiredTopics} sujets et %{currentPosts} / %{requiredPosts} messages. Les nouveaux visiteurs ont besoin de quelques conversations pour lire et répondre." + too_few_topics_notice: "Démarrons cette discussion ! Il y a actuellement %{currentTopics} / %{requiredTopics} sujets. Les nouveaux visiteurs ont besoin de quelques conversations à lire et répondre." + too_few_posts_notice: "Démarrons cette discussion ! Il y a actuellement %{currentPosts} / %{requiredPosts} messages. Les nouveaux visiteurs ont besoin de quelques conversations à lire et répondre." learn_more: "en savoir plus…" year: 'an' year_desc: 'sujets créés durant les 365 derniers jours' @@ -647,6 +655,21 @@ fr: replies_lowercase: one: réponse other: réponses + signup_cta: + sign_up: "S'inscrire" + hide_session: "Me le rappeler demain." + hide_forever: "non merci" + hidden_for_session: "Très bien, je vous proposerai demain. Vous pouvez toujours cliquer sur 'Se connecter' pour créer un compte." + intro: "Bonjour! :heart_eyes: Vous semblez apprécier la discussion, mais n'avez pas encore créé de compte." + value_prop: "Quand vous créez votre compte, nous stockons ce que vous avez lu pour vous positionner systématiquement sur le bon emplacement à votre retour. Vous avez également des notifications, ici et par courriel, quand de nouveaux messages sont postés. Et vous pouvez aimer les messages pour partager vos coups de cœurs. :heartbeat:" + methods: + sso: "S'inscrire est facile: vous avez juste besoin d'un compte sur le site principal." + only_email: "S'inscrire est facile: vous avez juste besoin d'une adresse mail et d'un mot de passe." + only_other: "Utilisez votre compte %{provider} pour vous inscrire." + one_and_email: "Utilisez votre compte %{provider}, ou un courriel et un mot de passe, pour vous inscrire." + multiple_no_email: "S'inscrire est facile : utilisez l'un de nos %{count} connecteurs sociaux." + multiple: "S'inscrire est facile : utilisez l'un de nos %{count} connecteurs sociaux, ou un courriel et un mot de passe." + unknown: "erreur lors du chargement des méthodes de connexion supportées" summary: enabled_description: "Vous visualisez un résumé de ce sujet : les messages importants choisis par la communauté." description: "Il y a {{count}} réponses." @@ -728,6 +751,8 @@ fr: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + options: "Options" + whisper: "murmure" add_warning: "Ceci est un avertissement officiel." posting_not_on_topic: "À quel sujet voulez-vous répondre ?" saving_draft_tip: "sauvegarde en cours..." @@ -852,8 +877,16 @@ fr: select_file: "Sélectionner Fichier" image_link: "lien vers lequel pointe l'image" search: + sort_by: "Tri par" + relevance: "Pertinence" + latest_post: "Dernier Message" + most_viewed: "Plus Vu" + most_liked: "Plus Aimé" select_all: "Tout sélectionner" clear_all: "Tout supprimer" + result_count: + one: "1 résultat pour \"{{term}}\"" + other: "{{count}} résultats pour \"{{term}}\"" title: "Rechercher les sujets, messages, utilisateurs ou catégories" no_results: "Aucun résultat." no_more_results: "Pas davantage de résultats." @@ -1212,6 +1245,7 @@ fr: no_value: "Non, le conserver" yes_value: "Oui, abandonner" via_email: "message depuis un courriel" + whisper: "ce message est un murmure privé pour les modérateurs" wiki: about: "ce message est en mode wiki; les utilisateurs de base peuvent le modifier" archetypes: @@ -1395,6 +1429,7 @@ fr: change_in_category_topic: "Éditer la description" already_used: 'Cette couleur est déjà utilisée par une autre catégorie' security: "Sécurité" + special_warning: "Avertissement : cette catégorie est une catégorie pré-remplie et les réglages de sécurité ne peuvent pas être modifiés. Si vous ne souhaitez pas utiliser cette catégorie, supprimez-là au lieu de détourner sa fonction." images: "Images" auto_close_label: "Fermer automatiquement après :" auto_close_units: "heures" @@ -1415,16 +1450,16 @@ fr: notifications: watching: title: "S'abonner" - description: "Vous surveillerez automatiquement les nouveaux sujets dans ces catégories. Vous serez notifié de tous les nouveaux messages et sujets, et le nombre de nouvelles réponses apparaîtra pour ces sujets." + description: "Vous surveillerez automatiquement tous les nouveaux sujets dans ces catégories. Vous serez averti pour tout nouveau message dans chaque sujet, et le nombre de nouvelles réponses sera affiché." tracking: title: "Suivi" - description: "Vous suivrez automatiquement tous les nouveaux sujets dans ces catégories. Le nombre de nouvelles réponses apparaîtra pour ces sujets." + description: "Vous surveillerez automatiquement tous les nouveaux sujets dans ces catégories. Vous serez averti si quelqu'un mentionne votre @nom ou vous répond, et le nombre de nouvelles réponses sera affiché." regular: title: "Normal" description: "Vous serez notifié si quelqu'un mentionne votre @pseudo ou vous répond." muted: title: "Silencieux" - description: "Vous ne recevrez aucune notification sur les sujets de ces catégories, et ils n'apparaîtront pas dans votre onglet \"non-lus\"." + description: "Vous ne serez jamais notifié concernant les nouveaux sujets dans ces catégories, et ils n'apparaîtront pas dans votre onglet non-lus." flagging: title: 'Merci de nous aider à garder notre communauté aimable !' private_reminder: 'les signalements sont privés, seulement visible aux modérateurs' @@ -1956,6 +1991,7 @@ fr: ip_address: "IP" topic_id: "Identifiant du sujet" post_id: "Identifiant du message" + category_id: "ID catégorie" delete: 'Supprimer' edit: 'Éditer' save: 'Sauvegarder' @@ -1996,6 +2032,9 @@ fr: impersonate: "incarner" anonymize_user: "rendre l'utilisateur anonyme" roll_up: "consolider des blocs d'IP" + change_category_settings: "modifier les paramètres de la catégorie" + delete_category: "supprimer catégorie" + create_category: "créer catégorie" screened_emails: title: "Courriels affichés" description: "Lorsque quelqu'un essaye de créé un nouveau compte, les adresses de courriel suivantes seront vérifiées et l'inscription sera bloquée, ou une autre action sera réalisée." diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 3f931cf4d0..f6c5b7accd 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -1414,16 +1414,12 @@ he: notifications: watching: title: "עוקב" - description: "תעקבו באופן אוטומטי אחרי כל הנושאים החדשים בקטגוריות אלה. תקבלו התראה על כל פרסום ונושא חדש." tracking: title: "רגיל+" - description: "בקטגוריות אלה סך הפרסומים החדשים שלא נקראו יופיע לצד שם הנושא." regular: - title: "רגיל" description: "תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלך או ישיב לפרסום שלך." muted: title: "מושתק" - description: "לא תקבל התראות בנוגע לנושאים חדשים בקטגוריות האלה, והם לא יופיעו בלשונית ה\"לא נקראו\" שלך." flagging: title: 'תודה על עזרתך לשמירה על תרבות הקהילה שלנו!' private_reminder: 'דגלים הם פרטיים וניתנים לצפייה ע"י הצוות בלבד' diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 0ddad6637a..3d047a5e18 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -825,8 +825,8 @@ it: edited: "Modifica il tuo messaggio da" liked: "Ha assegnato un \"Mi piace\" al tuo messaggio" private_message: "Messaggio privato da" - invited_to_private_message: "Invitato ad una conversazione privata da" - invited_to_topic: "Invitato ad un argomento da" + invited_to_private_message: "Invitato a un messaggio privato da" + invited_to_topic: "Invitato a un argomento da" invitee_accepted: "Invito accettato da" moved_post: "Il tuo messaggio è stato spostato da" linked: "Collegamento al tuo messaggio" @@ -916,7 +916,7 @@ it: search: "Non ci sono altri risultati di ricerca." topic: unsubscribe: - stop_notifications: "Da ora riceverai meno notifiche su {{title}}" + stop_notifications: "Da ora riceverai meno notifiche per {{title}}" change_notification_state: "Lo stato delle tue notifiche è" filter_to: "{{post_count}} suoi messaggi" create: 'Nuovo Argomento' @@ -1057,20 +1057,20 @@ it: success_message: 'Hai segnalato questo argomento con successo.' feature_topic: title: "Poni argomento in primo piano" - pin: "Metti questo argomento in cima alla categoria \"{{categoryLink}}\" fino al" + pin: "Poni questo argomento in cima alla categoria {{categoryLink}} fino a" confirm_pin: "Hai già {{count}} argomenti puntati. Troppi argomenti puntati potrebbero essere un peso per gli utenti nuovi o anonimi. Sicuro di voler puntare un altro argomento in questa categoria?" unpin: "Rimuovi questo argomento dalla cima della categoria {{categoryLink}}." - unpin_until: "Rimuovi questo argomento dalla cima della categoria {{categoryLink}} o attendi fino al %{until}." + unpin_until: "Rimuovi questo argomento dalla cima della categoria {{categoryLink}} o attendi fino a %{until}." pin_note: "Gli utenti possono spuntare gli argomenti individualmente per loro stessi." - pin_validation: "È richiesta una data per appuntare questo argomento" + pin_validation: "È richiesta una data per appuntare questo argomento." already_pinned: zero: "Non ci sono argomenti puntati in {{categoryLink}}." one: "Argomenti attualmente puntati in {{categoryLink}}: 1." other: "Argomenti attualmente puntati in {{categoryLink}}: {{count}}." - pin_globally: "Metti questo argomento in cima a tutte le liste di messaggi fino al" + pin_globally: "Poni questo argomento in cima a tutte le liste di argomenti fino a" confirm_pin_globally: "Hai già {{count}} argomenti puntati globalmente. Troppi argomenti puntati potrebbero essere un peso per gli utenti nuovi o anonimi. Sicuro di voler puntare un altro argomento globalmente?" unpin_globally: "Togli questo argomento dalla cima degli altri argomenti." - unpin_globally_until: "Rimuovi questo argomento dalla cima di tutte le liste di argomenti o attenti fino al %{until}." + unpin_globally_until: "Rimuovi questo argomento dalla cima di tutte le liste di argomenti o attendi fino a %{until}." global_pin_note: "Gli utenti possono spuntare gli argomenti autonomamente per loro stessi." already_pinned_globally: zero: "Non ci sono argomenti puntati globalmente." @@ -1097,7 +1097,7 @@ it: title: 'Invita' username_placeholder: "nome utente" action: 'Invia Invito' - help: 'invita altri su questo argomento via email o notifiche' + help: 'invita altri su questo argomento via email o tramite notifiche' to_forum: "Invieremo una breve email che permetterà al tuo amico di entrare subito cliccando un collegamento, senza bisogno di effettuare il collegamento." sso_enabled: "Inserisci il nome utente della persona che vorresti invitare su questo argomento." to_topic_blank: "Inserisci il nome utente o l'indirizzo email della persona che vorresti invitare su questo argomento." @@ -1412,16 +1412,12 @@ it: notifications: watching: title: "In osservazione" - description: "Osserverai automaticamente tutti i nuovi argomenti in queste categorie. Riceverai notifiche su tutti i nuovi messaggi e argomenti e per tali argomenti apparirà il conteggio delle nuove risposte." tracking: title: "Seguendo" - description: "Seguirai automaticamente tutti i nuovi argomenti in queste categorie. Per tali argomenti apparirà un conteggio delle nuove risposte." regular: - title: "Normale" description: "Riceverai una notifica se qualcuno menziona il tuo @nome o ti risponde." muted: title: "Silenziato" - description: "Non ti verrà notificato nulla sui nuovi argomenti di queste categorie, e non compariranno nella tua tab dei non letti." flagging: title: 'Grazie per aiutarci a mantenere la nostra comunità civile!' private_reminder: 'le segnalazioni sono private, visibili soltanto allo staff ' diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index f4ed722ac7..92b0db524b 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -1261,16 +1261,12 @@ ja: notifications: watching: title: "カテゴリ参加中" - description: "これらのカテゴリに新しく投稿されたトピックを自動的に参加します。これらのカテゴリに対して新しい投稿があった場合、登録されたメールアドレスと、コミュニティ内の通知ボックスに通知が届き、トピック一覧に新しい回答数がつきます。" tracking: title: "トラック中" - description: "これらのカテゴリの新規トピックを自動的にトラックします。トピックに対して新しい投稿があった場合、トピック一覧に新しい回答数がつきます。" regular: - title: "通常" description: "他ユーザからタグ付けをされた場合、またはあなたの投稿に回答が付いた場合に通知されます。" muted: title: "ミュート中" - description: "このカテゴリに投稿されたトピックについての通知を受け取りません。また、未読タブにも通知されません。" flagging: title: '私達のコミュニティの維持を助けてくれてありがとうごうざいます' private_reminder: 'フラグはプライベートです。スタッフのみが参照できます' diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 4d631210af..d68af672a4 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -1301,16 +1301,12 @@ ko: notifications: watching: title: "주시 중" - description: "이 카테고리 내의 새로운 글타래들을 지켜보도록 자동으로 설정됩니다. 새로운 글이나 글타래에 대하여 알림을 받게되며 글타래 옆에 읽지 않은 글의 수가 표시됩니다." tracking: title: "새 글 표시 중" - description: "이 카테고리 내의 새로운 글타래들을 추적하도록 자동으로 설정됩니다. 글타래 옆에 읽지 않은 글의 수가 표시됩니다." regular: - title: "알림: 일반" description: "누군가 내 @아아디 으로 멘션했거나 당신의 글에 답글이 달릴 때 알림을 받게 됩니다." muted: title: "알림 꺼짐" - description: "이 카테고리의 새로운 글타래에 대한 알림을 받지 않고 읽지 않\x1C은 탭에도 표시하지 않습니다." flagging: title: '우리 커뮤니티에 기여해 주셔서 감사합니다.' private_reminder: '신고는 오직 관리자만 볼 수 있습니다.' diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index bc95f1c0eb..c5ff73ac69 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -609,6 +609,13 @@ nb_NO: replies_lowercase: one: svar other: svar + signup_cta: + sign_up: "Registrer deg" + hide_session: "Spør meg igjen i morgen" + hide_forever: "nei takk" + hidden_for_session: "OK, jeg spør igjen i morgen. Du kan også registrere en konto når du vil!" + intro: "Hei du! :heart_eyes: Det ser ut som du følger diskusjonen, men ikke har registrert deg enda." + value_prop: "Når du registrerer deg husker vi hvor langt du har lest, så du starter på riktig sted neste gang du åpner en tråd. Du får også varsler, her og på e-post når det skjer ting i diskusjonene du vil følge. I tillegg kan du like innlegg :heartbeat:" summary: enabled_description: "Du ser for øyeblikket en oppsummering av dette emnet: de mest interessante innleggene i følge nettsamfunnet." description: "Det er {{count}} svar." @@ -1335,16 +1342,12 @@ nb_NO: notifications: watching: title: "Følger" - description: "Du vil automatisk følge alle nye emner i disse kategoriene. Du vil bli varslet om alle nye innlegg og emner. Antallet uleste og nye emner vil også vises." tracking: title: "Sporing" - description: "Du vil automatisk spore alle nye emner i disse kategoriene. Antallet nye svar vises for disse." regular: - title: "Vanlig" description: "Du vil bli varslet om noen nevner ditt @navn eller svarer deg." muted: title: "Dempet" - description: "Du vil ikke bli varslet om noe vedrørende disse emnene i disse kategoriene og de vil ikke vises i din ulest-liste." flagging: title: 'Takk for at du hjelper å holde forumet ryddig!' private_reminder: 'flagg er private, bare synlige for staben' diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 7474d3ec9d..c73fc17449 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -333,6 +333,7 @@ nl: title: "Categorieën Herschikken " title_long: "Reorganiseer de categorielijst" fix_order: "Posities fixen" + fix_order_tooltip: "Niet alle categorien hebben een unieke nummer, dit resulteert soms in onverwachte resultaten." save: "Volgorde Opslaan" apply_all: "Toepassen" position: "Positie" @@ -616,11 +617,13 @@ nl: server: "Serverfout" forbidden: "Toegang geweigerd" unknown: "Fout" + not_found: "Pagina niet gevonden" desc: network: "Controleer je verbinding." network_fixed: "Het lijkt er op dat het terug is" server: "Fout code: {{status}}" forbidden: "Je hebt geen toestemming om dit te bekijken." + not_found: "Oeps, de applicatie heeft geprobeerd een URL te laden die niet bestaat." unknown: "Er is iets mis gegaan" buttons: back: "Ga terug" @@ -655,8 +658,18 @@ nl: signup_cta: sign_up: "Aanmelden" hide_session: "Herrinner me morgen" - hide_forever: "Nee bedankt" + hide_forever: "nee dankje" hidden_for_session: "Ok, ik vraag het je morgen. Je kunt altijd 'Log in' gebruiken om in te loggen." + intro: "Hey! :heart_eyes: Praat mee in deze discussie, meld je aan met een account" + value_prop: "Wanneer je een account aangemaakt hebt, herinneren deze wat je gelezen hebt, zodat je direct door kan lezen vanaf waar je gestopt bent. Je krijgt ook notificaties, hier en via email, wanneer nieuwe posts gemaakt zijn. En je kan ook nog posts liken :heartbeat:" + methods: + sso: "Aanmelden gaat eenvoudig: alles wat je nodig hebt is een account op de hoofdsite." + only_email: "Aanmelden gaat eenvoudig: alles wat je nodig hebt is een email en wachtwoord." + only_other: "Gebruik je %{provider} account om aan te melden." + one_and_email: "Gebruik je %{provider} account of email en wachtwoord om aan te melden." + multiple_no_email: "Aanmelden gaat eenvoudig: gebruik een van de %{count} social logins." + multiple: "Aanmelden gaat eenvoudig: gebruik een van de %{count} social logins. of een email en wachtwoord." + unknown: "Fout bij ondersteunende login methoden" summary: enabled_description: "Je leest een samenvatting van dit topic: alleen de meeste interessante berichten zoals bepaald door de community. " description: "Er zijn {{count}} reacties." @@ -738,7 +751,10 @@ nl: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + options: "Opties" + whisper: "Fluister" add_warning: "Dit is een officiële waarschuwing." + toggle_whisper: "Schakel Fluistermode" posting_not_on_topic: "In welke topic wil je je antwoord plaatsen?" saving_draft_tip: "opslaan..." saved_draft_tip: "opgeslagen" @@ -765,6 +781,7 @@ nl: title_placeholder: "Waar gaat de discussie over in één korte zin?" edit_reason_placeholder: "vanwaar de wijziging?" show_edit_reason: "(geef een reden)" + reply_placeholder: "Typ hier. Gebruik Markdown, BBCode, of HTML om op te maken. Sleep of plak afbeeldingen." view_new_post: "Bekijk je nieuwe bericht." saving: "Opslaan..." saved: "Opgeslagen!" @@ -856,6 +873,7 @@ nl: local_tip: "selecteer afbeeldingen van uw apparaat" local_tip_with_attachments: "Selecteer afbeeldingen of bestanden van je apparaat ({{authorized_extensions}})" hint: "(je kan afbeeldingen ook slepen in de editor om deze te uploaden)" + hint_for_supported_browsers: "je kunt ook afbeeldingen slepen of plakken in de editor" uploading: "Uploaden" select_file: "Selecteer een bestand" image_link: "de link waar je afbeelding naar verwijst" @@ -867,6 +885,9 @@ nl: most_liked: "Meest geliked" select_all: "Selecteer Alles" clear_all: "Wis Alles" + result_count: + one: "1 resultaat voor \"{{term}}\"" + other: "{{count}} resultaat voor \"{{term}}\"" title: "zoek naar topics, berichten, gebruikers of categorieën" no_results: "Geen resultaten gevonden." no_more_results: "Geen resultaten meer gevonden." @@ -1221,6 +1242,7 @@ nl: no_value: "Nee, behouden" yes_value: "Ja, verwijderen" via_email: "dit bericht kwam binnen via e-mail" + whisper: "deze posts zijn alleen toegankelijk voor moderators" wiki: about: "deze discussie is een wiki; normale gebruikers kunnen hem aanpassen" archetypes: @@ -1404,6 +1426,7 @@ nl: change_in_category_topic: "Wijzig omschrijving" already_used: 'Deze kleur is al in gebruik door een andere categorie' security: "Beveiliging" + special_warning: "Waarschuwing: Dee catogorie is een vooringestelde categorie en de beveiligingsinstellingen kunnen hierdoor niet bewerkt worden. Als u deze categorie niet wenst te gebruiken, verwijder deze of herbestem deze." images: "Afbeeldingen" auto_close_label: "Sluit topics automatisch na:" auto_close_units: "uren" @@ -1424,12 +1447,12 @@ nl: notifications: watching: title: "In de gaten houden" - description: "Je ziet automatisch alle nieuwe topics in deze categorieën. Je ontvangt notificaties bij nieuwe berichten en topics, het aantal nieuwe reacties wordt voor deze topics weergegeven." + description: "Je krijgt automatisch alle nieuwe topics in deze categorie te zien. Je ontvangt notificaties bij nieuwe berichten en topics, naast het topic wordt het aantal nieuwe berichten weergegeven. " tracking: title: "Volgen" - description: "Je volgt automatisch alle topics in deze categorieën. Het aantal nieuwe reacties wordt voor deze topics weergegeven." + description: "Je ziet automatisch alle nieuwe topics in deze categorieën. Je ontvangt notificaties wanneer iemand je @name noemt of reageert op jou." regular: - title: "Regulier" + title: "Normaal" description: "Je krijgt een notificatie als iemand je @naam noemt of reageert op een bericht van jou." muted: title: "Genegeerd" @@ -1965,6 +1988,7 @@ nl: ip_address: "IP" topic_id: "Topic ID" post_id: "Bericht ID" + category_id: "Categorie ID" delete: 'Verwijder' edit: 'Wijzig' save: 'Opslaan' @@ -2005,6 +2029,9 @@ nl: impersonate: "Log in als gebruiker" anonymize_user: "maak gebruiker anoniem" roll_up: "groepeer verbannen IP-adressen" + change_category_settings: "verander categorie instellingen" + delete_category: "categorie verwijderen" + create_category: "categorie creeren" screened_emails: title: "Gescreende e-mails" description: "Nieuwe accounts met een van deze mailadressen worden geblokkeerd of een andere actie wordt ondernomen." @@ -2369,7 +2396,9 @@ nl: embed_username_key_from_feed: "Key voor de Discourse gebruikersnaam in de feed." embed_truncate: "Embedde berichten inkorten" embed_whitelist_selector: "CSS selector voor elementen die worden toegestaan bij embedding" + whitelist_example: "artikel #verhaal, .post" embed_blacklist_selector: "CSS selector voor elementen die worden verwijderd bij embedding" + blacklist_example: ".ad-unit, header" feed_polling_enabled: "Importeer berichten via RSS/ATOM" feed_polling_url: "URL van RSS/ATOM feed voor crawling" save: "Embedding Instellingen Opslaan " diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index e25fbb600b..987532e012 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -128,6 +128,7 @@ pl_PL: google+: 'udostępnij ten odnośnik na Google+' email: 'wyślij ten odnośnik przez email' action_codes: + split_topic: "podziel ten temat %{when}" autoclosed: enabled: 'zamknięcie %{when}' disabled: 'otworzenie %{when}' @@ -251,6 +252,7 @@ pl_PL: revert: "Przywróć" failed: "Niepowodzenie" switch_to_anon: "Tryb anonimowy" + switch_from_anon: "Zakończ tryb anonimowy" banner: close: "Zamknij ten baner." edit: "Edytuj ten baner >>" @@ -645,11 +647,13 @@ pl_PL: server: "błąd serwera" forbidden: "Brak dostępu" unknown: "Błąd" + not_found: "Nie znaleziono strony" desc: network: "Sprawdź swoje połączenie." network_fixed: "Chyba już w porządku." server: "Kod błędu: {{status}}" forbidden: "Nie możesz obejrzeć tego zasobu." + not_found: "Ups, aplikacja próbowała otworzyć URL który nie istnieje." unknown: "Coś poszło nie tak." buttons: back: "Cofnij" @@ -662,6 +666,9 @@ pl_PL: read_only_mode: enabled: "Aktywowani tryb tylko-do-odczytu. Możesz nadal przeglądać serwis, ale operacje zmieniające stan i treść mogą nie działać." login_disabled: "Logowanie jest zablokowane, gdy strona jest w trybie tylko do odczytu." + too_few_topics_and_posts_notice: "Pora rozruszać dyskusję! Aktualnie istnieje %{currentTopics} / %{requiredTopics} tematów i %{currentPosts} / %{requiredPosts} wpisów. Odwiedzający potrzebują więcej tematów i konwersacji do czytania i pisania na ich temat." + too_few_topics_notice: "Pora rozruszać dyskusję! Aktualnie istnieje %{currentTopics} / %{requiredTopics} tematów. Odwiedzający potrzebują więcej tematów i konwersacji do czytania i pisania na ich temat." + too_few_posts_notice: "Pora rozruszać dyskusję! Aktualnie istnieje %{currentPosts} / %{requiredPosts} wpisów. Odwiedzający potrzebują więcej tematów i konwersacji do czytania i pisania na ich temat." learn_more: "dowiedz się więcej…" year: 'rok' year_desc: 'tematy dodane w ciągu ostatnich 365 dni' @@ -679,6 +686,21 @@ pl_PL: one: odpowiedź few: odpowiedzi other: odpowiedzi + signup_cta: + sign_up: "Rejestracja" + hide_session: "Przypomnij mi jutro" + hide_forever: "nie, dziękuję" + hidden_for_session: "Ok, zapytamy jutro. Pamiętaj, że konto możesz w każdej chwili założyć klikając na 'Logowanie'." + intro: "Hej! :heart_eyes: Wygląda na to, że zainteresowała Cię dyskusja, ale nie posiadasz jeszcze konta." + value_prop: "Jeśli stworzysz konto, zapamiętamy przeczytane przez Ciebie wpisy i tematy, dzięki czemu zawsze powrócisz do odpowiedniego miejsca. Otrzymasz też powiadomienia o nowych wpisach. Dodatkowo możliwe będzie polubienie ciekawych wpisów :heartbeat:" + methods: + sso: "Rejestracja jest łatwa: wystarczy posiadać konto w naszym serwisie." + only_email: "Rejestracja jest łatwa: wystarczy podać e-mail oraz hasło." + only_other: "Użyj swojego konta %{provider} podczas rejestracji." + one_and_email: "Aby się zarejestrować, użyj swojego konta %{provider} lub adresu e-mail i hasła." + multiple_no_email: "Rejestracja jest łatwa: użyj jednego z %{count} serwisów społecznościowych." + multiple: "Rejestracja jest łatwa: użyj jednego z %{count} serwisów społecznościowych lub adresu e-mail i hasła." + unknown: "błąd podczas pobierania metod logowania" summary: enabled_description: "Przeglądasz podsumowanie tego tematu: widoczne są jedynie najbardziej wartościowe wpisy zdaniem uczestników. " description: "Istnieją {{count}} odpowiedzi." @@ -760,7 +782,10 @@ pl_PL: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + options: "Opcje" + whisper: "szept" add_warning: "To jest oficjalne ostrzeżenie." + toggle_whisper: "Przełącz szept" posting_not_on_topic: "W którym temacie chcesz odpowiedzieć?" saving_draft_tip: "zapisuję..." saved_draft_tip: "zapisano" @@ -884,8 +909,17 @@ pl_PL: select_file: "Wybierz plik" image_link: "odnośnik do którego Twój obraz będzie kierował" search: + sort_by: "Sortuj po" + relevance: "Trafność" + latest_post: "Aktualne wpisy" + most_viewed: "Popularne" + most_liked: "Lubiane" select_all: "Zaznacz wszystkie" clear_all: "Wyczyść wszystkie" + result_count: + one: "1 wynik dla \"{{term}}\"" + few: "{{count}} wyniki dla \"{{term}}\"" + other: "{{count}} wyników dla \"{{term}}\"" title: "szukaj tematów, wpisów, użytkowników lub kategorii" no_results: "Brak wyników wyszukiwania" no_more_results: "Nie znaleziono więcej wyników." @@ -1258,7 +1292,7 @@ pl_PL: no_value: "Nie, pozostaw" yes_value: "Tak, porzuć" via_email: "ten wpis został dodany emailem" - whisper: "ten wpis jest prywatną notatką dla moderatorów" + whisper: "ten wpis jest prywatnym szeptem do moderatorów" wiki: about: "to wpis typu Wiki: zwykli użytkownicy mogą go edytować" archetypes: @@ -1482,16 +1516,16 @@ pl_PL: notifications: watching: title: "Obserwuj wszystko" - description: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach. Dostaniesz powiadomienie o każdym nowym wpisie i temacie. Liczba nowych odpowiedzi będzie wyświetlana na liście tematów." + description: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach. Otrzymasz powiadomienie o każdym nowym wpisie i temacie. Wyświetlimy liczbę nowych odpowiedzi na liście tematów." tracking: title: "Śledzona" - description: "Będziesz automatycznie śledzić wszystkie tematy w tych kategoriach. Llicznik nowych odpowiedzi pojawi się obok ich tytułów." + description: "Będziesz automatycznie śledzić wszystkie tematy w tych kategoriach. Otrzymasz powiadomienie jeśli ktoś wspomni twój @login lub odpowie na twój wpis. Licznik nowych odpowiedzi pojawi się na liście tematów." regular: title: "Normalny" description: "Dostaniesz powiadomienie jedynie, gdy ktoś wspomni twoją @nazwę lub odpowie na twój wpis." muted: title: "Wyciszone" - description: "Nie będziesz powiadamiany o nowych tematach w tych kategoriach i nie będą się one pojawiać w karcie Nieprzeczytane." + description: "Nie otrzymasz powiadomień o tematach w tych kategoriach i nie będą się one pojawiać na karcie Nieprzeczytane." flagging: title: 'Dziękujemy za pomoc w utrzymaniu porządku w naszej społeczności!' private_reminder: 'oflagowania są poufne i widoczne jedynie dla obsługi serwisu' @@ -2026,6 +2060,7 @@ pl_PL: ip_address: "IP" topic_id: "ID tematu" post_id: "ID wpisu" + category_id: "ID kategorii" delete: 'Usuń' edit: 'Edytuj' save: 'Zapisz' @@ -2066,6 +2101,9 @@ pl_PL: impersonate: "udawanie użytkownika" anonymize_user: "anonimizuj użytkownika" roll_up: "zwiń bloki IP" + change_category_settings: "zmiana ustawień kategorii" + delete_category: "Usuń kategorię" + create_category: "Dodaj nową kategorię" screened_emails: title: "Ekranowane emaile" description: "Kiedy ktoś próbuje założyć nowe konto, jego adres email zostaje sprawdzony i rejestracja zostaje zablokowana, lub inna akcja jest podejmowana." @@ -2437,7 +2475,9 @@ pl_PL: embed_username_key_from_feed: "Klucz używany do pobrania nazwy użytkownika z kanału" embed_truncate: "Skracaj treść osadzanych wpisów" embed_whitelist_selector: "Selektor CSS elementów jakie mogą być osadzane" + whitelist_example: "article, #tresc, .wpis" embed_blacklist_selector: "Selektor CSS elementów jakie są usuwane podczas osadzania" + blacklist_example: ".reklama, header" feed_polling_enabled: "Importowanie wpisów via RSS/ATOM" feed_polling_url: "URL kanału RSS/ATOM" save: "Zapisz" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 593e8db406..d72fabb321 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -617,11 +617,13 @@ pt: server: "Erro de Servidor" forbidden: "Acesso Negado" unknown: "Erro" + not_found: "Página Não Encontrada" desc: network: "Por favor verifique a sua ligação." network_fixed: "Parece que está de volta." server: "Código de Erro: {{status}}" forbidden: "Não tem permissão para visualizar isso." + not_found: "Oops, a aplicação tentou carregar um URL que não existe." unknown: "Algo correu mal." buttons: back: "Voltar Atrás" @@ -656,10 +658,10 @@ pt: signup_cta: sign_up: "Inscrever-se" hide_session: "Lembrar-me amanhã" - hide_forever: "Não obrigado" + hide_forever: "não obrigado" hidden_for_session: "OK, Irei perguntar-lhe amanhã. Pode sempre usar 'Iniciar Sessão' para criar uma conta, também." intro: "Olá! :heart_eyes: Parece que está a gostar da discussão, mas não está inscrito para uma conta." - value_prop: "Quando cria uma conta, pode acompanhar exatamente o que leu, por isso volta sempre ao sítio onde ficou. Pode também obter notificações, aqui e por email, sempre que novas mensagens são feitas. E pode gostar de mensagens para partilhar o amor. :heartbeat:" + value_prop: "Quando cria uma conta, nós lembramo-nos exatamente do que leu, por isso volta sempre ao sítio onde ficou. Também recebe notificações, aqui ou por email, sempre que novas mensagens são feitas. E pode gostar de mensagens para partilhar o amor. :heartbeat:" methods: sso: "Inscrever-se é fácil: tudo o que precisa é uma conta no sítio principal." only_email: "Inscrever-se é fácil: tudo o que precisa é um email e uma palavra-passe." @@ -1445,16 +1447,16 @@ pt: notifications: watching: title: "A vigiar" - description: "Irá vigiar automaticamente todos os novos tópicos nestas categorias. Será notificado de todas as novas mensagens e tópicos, e uma contagem de novas respostas irá aparecer para estes tópicos." + description: "Irá vigiar automaticamente todos os novos tópicos nestas categorias. Irá ser notificado de cada nova mensagem em cada tópico, e uma contagem de novas respostas será exibida." tracking: title: "Acompanhar" - description: "Irá acompanhar automaticamente todos os novos tópicos nestas categorias. Uma contagem de novas respostas irá aparecer para estes tópicos." + description: "Irá acompanhar automaticamente todos os novos tópicos nestas categorias. Irá ser notificado se alguém mencionar o seu @nome ou lhe responder, e uma contagem de novas respostas será exibida." regular: - title: "Habitual" + title: "Normal" description: "Será notificado se alguém mencionar o seu @nome ou responder-lhe." muted: title: "Silenciado" - description: "Não será notificado relativamente acerca de novos tópicos nestas categorias, e não aparecerão no seu separador de não lidos." + description: "Nunca será notificado de nada acerca de novos tópicos nestas categorias, e estes não irão aparecer no seu separador de não lidos." flagging: title: 'Obrigado por ajudar a manter a nossa comunidade cívica!' private_reminder: 'as sinalizações são privadas, visíveis apenas para o pessoal' @@ -1986,6 +1988,7 @@ pt: ip_address: "IP" topic_id: "ID do Tópico" post_id: "ID da Mensagem" + category_id: "ID da Categoria" delete: 'Eliminar' edit: 'Editar' save: 'Guardar' @@ -2026,6 +2029,9 @@ pt: impersonate: "personificar" anonymize_user: "tornar utilizador anónimo" roll_up: "agregar blocos IP" + change_category_settings: "alterar configurações de categoria" + delete_category: "eliminar categoria" + create_category: "criar categoria" screened_emails: title: "Emails Filtrados" description: "Quando alguém tenta criar uma nova conta, os seguintes endereços de email serão verificados e o registo será bloqueado, ou outra ação será executada." @@ -2390,7 +2396,9 @@ pt: embed_username_key_from_feed: "Chave para puxar o nome de utilizador discouse do feed" embed_truncate: "Truncar as mensagens incorporadas" embed_whitelist_selector: "Seletor CSS para elementos que são permitidos nas incorporações" + whitelist_example: "artigo, #história, .mensagem" embed_blacklist_selector: "Seletor CSS para elementos que são removidos das incorporações" + blacklist_example: ".unidade-anuncio, cabeçalho" feed_polling_enabled: "Importar mensagens através de RSS/ATOM" feed_polling_url: "URL do feed RSS/ATOM para rastreio" save: "Guardar Configurações de Incorporação" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index d9e7b21341..ea03f64e3e 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -123,6 +123,7 @@ pt_BR: admin_title: "Admin" flags_title: "Sinalizações" show_more: "mostrar mais" + show_help: "opções" links: "Links" links_lowercase: one: "link" @@ -558,6 +559,7 @@ pt_BR: server: "Erro de Servidor" forbidden: "Acesso Negado" unknown: "Erro" + not_found: "Página não encontrada" desc: network: "Por favor verifique sua conexão." network_fixed: "Parece que voltou." @@ -591,6 +593,9 @@ pt_BR: replies_lowercase: one: resposta other: respostas + signup_cta: + hide_session: "Lembre-me amanhã" + hide_forever: "não obrigado" summary: enabled_description: "Você está vendo um sumário deste tópico: os posts mais interessantes conforme determinados pela comunidade." description: "Há {{count}} respostas." @@ -672,6 +677,7 @@ pt_BR: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + options: "Opções" add_warning: "Este é um aviso oficial." posting_not_on_topic: "Qual tópico você gostaria de responder?" saving_draft_tip: "gravando..." @@ -759,6 +765,8 @@ pt_BR: moved_post: "

{{username}} moved {{description}}

" linked: "

{{username}} {{description}}

" granted_badge: "

Adquirido '{{description}}'

" + alt: + mentioned: "Mencionado por" popup: mentioned: '{{username}} mencionou você em "{{topic}}" - {{site_title}}' quoted: '{{username}} citou você em "{{topic}}" - {{site_title}}' @@ -1312,16 +1320,12 @@ pt_BR: notifications: watching: title: "Observar" - description: "Você vai acompanhar automaticamente todos os novos tópicos dessas categorias. Você será notificado de todas as novas mensagens e tópicos. Além disso, a contagem de mensagens não lidas e novas também aparecerá ao lado do tópico." tracking: title: "Monitorar" - description: "Automaticamente monitora todos novos tópicos nestas categorias. Uma contagem de posts não lidos e novos aparecerá próximo ao tópico." regular: - title: "Normal" description: "Você será notificado se alguém mencionar o seu @nome ou responder à sua mensagem." muted: title: "Silenciar" - description: "Você não será notificado sobre novos tópicos dessas categorias e eles não vão aparecer na guia de mensagens não lidas." flagging: title: 'Obrigado por ajudar a manter a civilidade da nossa comunidade!' private_reminder: 'sinalizações são privadas, apenas ficam visíveis a moderação' diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index 7e3e9f054a..7b8c92dee4 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -1305,11 +1305,8 @@ ro: title: "Vizualizare" tracking: title: "Urmărire" - regular: - title: "Normal" muted: title: "Silențios" - description: "Nu veți fi niotificat de discuțiile noi din aceste categorii, ele nu vor apărea în tabul necitite." flagging: title: 'De ce marcați această postare ca fiind privată?' private_reminder: 'steagurile sunt private, vizibile numai personalului' diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 902f5de2b2..519387d17a 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -146,6 +146,26 @@ ru: facebook: 'Поделиться ссылкой через Facebook' google+: 'Поделиться ссылкой через Google+' email: 'Поделиться ссылкой по электронной почте' + action_codes: + split_topic: "Разделил эту тему %{when}" + autoclosed: + enabled: 'Закрыл тему %{when}' + disabled: 'Открыл тему %{when}' + closed: + enabled: 'Закрыл тему %{when}' + disabled: 'Открыл тему %{when}' + archived: + enabled: 'Заархивировал тему %{when}' + disabled: 'Разархивировал тему %{when}' + pinned: + enabled: 'Закрепил тему %{when}' + disabled: 'Открепил тему %{when}' + pinned_globally: + enabled: 'Закрепил тему глобально %{when}' + disabled: 'Открепил тему глобально %{when}' + visible: + enabled: 'Включил в списки %{when}' + disabled: 'Исключил из списков %{when}' topic_admin_menu: "действия администратора над темой" emails_are_disabled: "Все исходящие письма были глобально отключены администратором. Уведомления любого вида не будут отправляться на почту." edit: 'отредактировать название и раздел темы' @@ -161,6 +181,7 @@ ru: admin_title: "Админка" flags_title: "Жалобы" show_more: "показать дальше" + show_help: "Cправка" links: "Ссылки" links_lowercase: one: "ссылка" @@ -246,6 +267,7 @@ ru: saved: "Сохранено!" upload: "Загрузить" uploading: "Загрузка..." + uploading_filename: "Загрузка файла {{filename}}..." uploaded: "Загружено!" enable: "Включить" disable: "Отключить" @@ -253,6 +275,7 @@ ru: revert: "Вернуть" failed: "Проблема" switch_to_anon: "Анонимный режим" + switch_from_anon: "Выйти из анонимного режима" banner: close: "Больше не показывать это объявление." edit: "Редактировать это объявление >>" @@ -358,6 +381,14 @@ ru: all_subcategories: "Все подкатегории" no_subcategory: "Вне подкатегорий" category: "Раздел" + reorder: + title: "Упорядочивание разделов" + title_long: "Реорганизация списка разделов" + fix_order: "Зафиксировать порядковые номера" + fix_order_tooltip: "Не всем разделам назначен уникальный порядковый номер. Это может привести к непредсказуемому порядку разделов." + save: "Сохранить порядок" + apply_all: "Применить" + position: "Порядковый номер" posts: "Сообщения" topics: "Темы" latest: "Последние" @@ -404,9 +435,20 @@ ru: preferences: "Настройки" bookmarks: "Закладки" bio: "Обо мне" - invited_by: "Приглашен пользователем" - trust_level: "Уровень доверия" + invited_by: "Пригласил" + trust_level: "Уровень" notifications: "Уведомления" + desktop_notifications: + label: "Оповещения" + not_supported: "К сожалению, оповещения не поддерживаются этим браузером." + perm_default: "Включить оповещения" + perm_denied_btn: "Отказано в разрешении" + perm_denied_expl: "Вы запретили оповещения в вашем браузере. Вначале возобновите разрешение, а затем попробуйте еще раз." + disable: "Отключить оповещения" + currently_enabled: "(сейчас включены)" + enable: "Включить оповещения" + currently_disabled: "(сейчас отключены)" + each_browser_note: "Примечание: эта настройка устанавливается в каждом браузере индивидуально." dismiss_notifications: "Пометить все прочитанными" dismiss_notifications_tooltip: "Пометить все непрочитанные уведомления прочитанными" disable_jump_reply: "Не переходить к вашему новому сообщению после ответа" @@ -419,6 +461,7 @@ ru: admin: "{{user}} - админ" moderator_tooltip: "{{user}} - модератор" admin_tooltip: "{{user}} - админ" + blocked_tooltip: "Этот пользователь заблокирован" suspended_notice: "Пользователь заморожен до {{date}}." suspended_reason: "Причина:" github_profile: "Github" @@ -549,29 +592,35 @@ ru: never: "никогда" immediately: "немедленно" invited: - search: "введите текст для поиска приглашений..." + search: "Введите текст для поиска по приглашениям..." title: "Приглашения" - user: "Приглашенный пользователь" - truncated: "Отображаются первые {{count}} приглашений." + user: "Кто приглашен" + sent: "Когда" + none: "Приглашения, ожидающие одобрения, отсутствуют." + truncated: "Показаны первые {{count}} приглашений." redeemed: "Принятые приглашения" - redeemed_tab: "Принято" + redeemed_tab: "Принятые" + redeemed_tab_with_count: "Принятые ({{count}})" redeemed_at: "Принято" pending: "Еще не принятые приглашения" - pending_tab: "Ожидает одобрения" - topics_entered: "Просмотрено тем" - posts_read_count: "Прочитано сообщений" - expired: "Это истёкшее приглашение." - rescind: "Отменить приглашение" + pending_tab: "Ожидающие" + pending_tab_with_count: "Ожидающие ({{count}})" + topics_entered: "Просмотрел тем" + posts_read_count: "Прочитал сообщений" + expired: "Это приглашение истекло." + rescind: "Отозвать" rescinded: "Приглашение отозвано" reinvite: "Повторить приглашение" reinvited: "Приглашение выслано повторно" - time_read: "Время чтения" - days_visited: "Дней посещения" + time_read: "Времени читал" + days_visited: "Дней посещал" account_age_days: "Дней с момента регистрации" create: "Отправить приглашение" + generate_link: "Скопировать ссылку для приглашений" + generated_link_message: '

Пригласительная ссылка сгенерирована!

Эта ссылка действует только для следующего e-mail:%{invitedEmail}

' bulk_invite: - none: "Вы еще никого не приглашали сюда. Вы можете отправить индивидуальные приглашения или пригласить группу людей сразу загрузив групповой файл приглашений." - text: "Групповое приглашение из файла" + none: "Вы еще никого не приглашали на этот форум. Можно отправить индивидуальные приглашения по одному, или же пригласить сразу несколько людей из файла." + text: "Пригласить всех из файла" uploading: "Загрузка..." success: "Файл успешно загружен, вы получите сообщение, когда процесс будет завершен." error: "В процессе загрузки файла '{{filename}}' произошла ошибка: {{message}}" @@ -608,11 +657,13 @@ ru: server: "Ошибка сервера" forbidden: "Доступ закрыт" unknown: "Ошибка" + not_found: "Страница не найдена" desc: network: "Пожалуйста, проверьте ваше соединение." network_fixed: "Похоже, сеть появилась." server: "Ошибка: {{status}}" forbidden: "У вас нет доступа для просмотра этого." + not_found: "Упс, произошла попытка загрузить несуществующую ссылку" unknown: "Что-то пошло не так." buttons: back: "Вернуться" @@ -642,6 +693,21 @@ ru: one: ответ few: ответа other: ответов + signup_cta: + sign_up: "Зарегистрироваться" + hide_session: "Напомнить мне завтра" + hide_forever: "Нет, спасибо" + hidden_for_session: "Хорошо, напомню завтра. Кстати, зарегистрироваться можно также и с помощью кнопки \"Войти\"." + intro: "Привет! :heart_eyes: Кажется, форум пришелся вам по душе, но вы все еще не зарегистрировались." + value_prop: "После регистрации мы сможем запоминать, где вы закончили чтение, а когда вы заглянете в ту или иную тему снова, мы откроем ее там, где вы остановились в прошлый раз. Мы также сможем уведомлять вас о новых ответах в любимых темах в вашем личном кабинете или по электронной почте. А самое приятное - после регистрации можно ставить сердечки, тем самым выражая свою симпатию автору. :heartbeat:" + methods: + sso: "Регистрация очень простая: все, что вам нужно - это учетная запись на главном сайте." + only_email: "Регистрация очень простая: все, что вам нужно - это email и придумать пароль." + only_other: "Используйте вашу учетную запись в %{provider}, чтобы зарегистрироваться на форуме." + one_and_email: "Используйте вашу учетную запись в %{provider} или email и пароль, чтобы зарегистрироваться на форуме." + multiple_no_email: "Регистрация очень простая: используйте вход через любую из %{count} соцсетей." + multiple: "Регистрация очень простая: используйте вход через любую из %{count} социальных сетей, или email и пароль." + unknown: "Ошибка при получении списка поддерживаемых способов входа." summary: enabled_description: "Вы просматриваете выдержку из темы - только самые интересные сообщения по мнению сообщества." description: "Есть {{count}} ответ(ов)." @@ -663,7 +729,7 @@ ru: created: 'Создан' created_lowercase: 'создано' trust_level: 'Уровень доверия' - search_hint: 'псевдоним, e-mail или IP адрес' + search_hint: 'Псевдоним, e-mail или IP адрес' create_account: title: "Зарегистрироваться" failed: "Произошла ошибка. Возможно, этот Email уже используется. Попробуйте восстановить пароль" @@ -723,7 +789,10 @@ ru: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + options: "Опции" + whisper: "внутреннее сообщение" add_warning: "Это официальное предупреждение." + toggle_whisper: "Внутреннее сообщение" posting_not_on_topic: "В какой теме вы хотите ответить?" saving_draft_tip: "Сохранение..." saved_draft_tip: "сохранено" @@ -829,15 +898,29 @@ ru: select_file: "Выбрать файл" image_link: "ссылка, на которую будет указывать ваше изображение" search: - title: "поиск по темам, сообщениям, пользователям или разделам" + sort_by: "Сортировка" + relevance: "По смыслу" + latest_post: "С недавними сообщениями" + most_viewed: "Самые просматриваемые" + most_liked: "Больше всего симпатий" + select_all: "Выбрать все" + clear_all: "Сбросить все" + result_count: + one: "Найдено 1: \"{{term}}\"" + few: "Найдено {{count}}: \"{{term}}\"" + many: "Найдено {{count}}: \"{{term}}\"" + other: "Найдено {{count}}: \"{{term}}\"" + title: "Поиск по темам, сообщениям, псевдонимам и разделам" no_results: "Ничего не найдено." + no_more_results: "Больше ничего не найдено." + search_help: Справка по поиску searching: "Поиск ..." post_format: "#{{post_number}} от {{username}}" context: user: "Искать сообщения от @{{username}}" category: "Искать в разделе \"{{category}}\"" topic: "Искать в этой теме" - private_messages: "Поиск в сообщениях" + private_messages: "Искать в личных сообщениях" go_back: 'вернуться' not_logged_in_user: 'страница пользователя с историей его последней активности и настроек' current_user: 'перейти на вашу страницу пользователя' @@ -1195,6 +1278,7 @@ ru: no_value: "Нет, оставить" yes_value: "Да, отказаться" via_email: "это сообщение пришло с почты" + whisper: "Это внутреннее сообщение, т.е. оно видно только модераторам" wiki: about: "это вики-сообщение - любой пользователь может отредактировать его, чтобы улучшить, дополнить или исправить ошибки" archetypes: @@ -1432,16 +1516,12 @@ ru: notifications: watching: title: "Наблюдать" - description: "Автоматически наблюдать за всеми новыми темами в этих разделах и уведомлять меня о новых сообщениях в них. Счетчик новых сообщений будет появляться рядом с названием темы." tracking: title: "Следить" - description: "Автоматически следить за всеми новыми темами в этих разделах. Счетчик новых сообщений будет появляться рядом с названием темы." regular: - title: "Уведомлять" description: "Уведомлять, если кто-нибудь упомянет мой @псевдоним или ответит на мое сообщение." muted: title: "Без уведомлений" - description: "Не уведомлять меня о новых темах в этих разделах и не показывать новые темы на странице «Непрочитанные»." flagging: title: 'Спасибо за вашу помощь в поддержании порядка!' private_reminder: 'жалобы анонимны и видны только персоналу' @@ -1989,11 +2069,11 @@ ru: do_nothing: "ничего не делать" staff_actions: title: "Действия персонала" - instructions: "Кликните по имени пользователя и действиям для фильтрации списка. Кликните по аватару для перехода на страницу пользователя." + instructions: "Кликните по псевдониму или действиям для фильтрации списка. Кликните по аватару для перехода на страницу пользователя." clear_filters: "Показать все" staff_user: "Персонал" target_user: "Целевой пользователь" - subject: "Тема" + subject: "Субъект" when: "Когда" context: "Контекст" details: "Подробности" @@ -2007,7 +2087,7 @@ ru: actions: delete_user: "удаление пользователя" change_trust_level: "изменение уровня доверия" - change_username: "изменить имя пользователя" + change_username: "изменение псевдонима" change_site_setting: "изменение настройки сайта" change_site_customization: "изменение настройки сайта" delete_site_customization: "удаление настроек сайта" @@ -2018,9 +2098,12 @@ ru: check_email: "открыть e-mail" delete_topic: "удаление темы" delete_post: "удаление сообщения" - impersonate: "выдать себя за" - anonymize_user: "анонимизировать пользователя" - roll_up: "Сгруппировать IP адреса в подсети" + impersonate: "выдавание себя за" + anonymize_user: "анонимизация пользователя" + roll_up: "группирование IP адресов в подсети" + change_category_settings: "изменение настроек раздела" + delete_category: "удаление раздела" + create_category: "создание раздела" screened_emails: title: "Почтовые адреса" description: "Когда кто-то создает новую учетную запись, проверяется данный почтовый адрес и регистрация блокируется или производятся другие дополнительные действия." @@ -2256,21 +2339,23 @@ ru: delete: "Удалить" cancel: "Отмена" delete_confirm: "Вы уверены, что хотите удалить это поле?" + options: "Опции" required: title: "Обязательное во время регистрации?" - enabled: "обязательное" - disabled: "необязательное" + enabled: "Обязательное" + disabled: "Необязательное" editable: title: "Редактируемое после регистрации?" - enabled: "редактируемое" - disabled: "нередактируемое" + enabled: "Редактируемое" + disabled: "Нередактируемое" show_on_profile: - title: "Показать в публичном профиле?" - enabled: "показывается в профиле" - disabled: "не показывается в профиле" + title: "Показывать в публичном профиле?" + enabled: "Показывать в профиле" + disabled: "Не показывать в профиле" field_types: text: 'Текстовое поле' confirm: 'Подтверждение' + dropdown: "Выпадающий список" site_text: none: "Выберите секцию для редактирования." title: 'Текстовое содержание' @@ -2548,3 +2633,15 @@ ru: hot_link: name: Горячая ссылка description: Оставил внешнюю ссылку с более чем 300 кликов + famous_link: + name: Легендарная ссылка + description: Использовал внешнюю ссылку, которую открыли более 1000 раз + google_search: | +

Поиск через Google

+

+

+

diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index caed2c2f49..4078b644dd 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -1328,16 +1328,12 @@ sq: notifications: watching: title: "Watching" - description: "You will automatically watch all new topics in these categories. You will be notified of all new posts and topics, and a count of new replies will appear for these topics." tracking: title: "Tracking" - description: "You will automatically track all new topics in these categories. A count of new replies will appear for these topics." regular: - title: "Regular" description: "You will be notified if someone mentions your @name or replies to you." muted: title: "Muted" - description: "You will not be notified of anything about new topics in these categories, and they will not appear on your unread tab." flagging: title: 'Faleminderit për ndihmën që i jepni këtij komuniteti!' private_reminder: 'flags are private, only visible to staff' diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index b2608010ce..4660add837 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -1311,16 +1311,12 @@ sv: notifications: watching: title: "Bevakar" - description: "Du kommer automatiskt bevaka alla nya ämnen i dessa kategorier. Du notifieras om alla nya inlägg och ämnen, och en räknare över antalet nya svar visas för dessa ämnen." tracking: title: "Följer" - description: "Du kommer automatiskt att följa alla nya ämnen i dessa kategorier. Antalet olästa inlägg kommer att synas för dessa ämnen." regular: - title: "Vanlig" description: "Du notifieras om någon nämner ditt @namn eller svarar på ditt inlägg." muted: title: "Tystad" - description: "Du kommer inte att notifieras om något som rör nya ämnen i de här kategorierna, och de kommer inte att dyka upp i din olästa tabb." flagging: title: 'Tack för att du hjälper till att hålla vår gemenskap civiliserad!' private_reminder: 'flaggor är privata, endast synliga för funktionärer' diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index e9726bc6b3..179e09813f 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -1110,11 +1110,8 @@ te: title: "కన్నేసారు" tracking: title: "గమనిస్తున్నారు" - regular: - title: "రెగ్యులర్" muted: title: "నిశ్శబ్దం" - description: "ఈ వర్గాల్లో కొత్త విషయాల గురించి మీకు ప్రకటన రాదు. ఇంకా చదవి సంఖ్యలు కనిపించవు." flagging: title: 'మా కమ్యునిటీని నాగరికంగా ఉంచుటలో సహాయానికి ధన్యవాదములు' private_reminder: 'కేతనాలు ప్రైవేటు. కేవలం సిబ్బందికి మాత్రమే కనిపిస్తాయి' diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 7089f54029..3ce73d578c 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -1347,16 +1347,12 @@ tr_TR: notifications: watching: title: "Gözleniyor" - description: "Bu kategorilerdeki tüm yeni konuları otomatik olarak gözleyeceksiniz. Tüm yeni gönderi ve konularla ilgili bildiri alacaksınız, ayrıca okunmamış ve yeni gönderilerin sayısı ilgili konunun yanında belirecek." tracking: title: "Takip Ediliyor" - description: "Bu kategorilerdeki tüm yeni konuları otomatik olarak takip edeceksiniz. Okunmamış ve yeni gönderilerin sayısı ilgili konunun yanında belirecek." regular: - title: "Standart" description: "Birisi @isim şeklinde sizden bahsederse ya da gönderinize cevap verirse bildirim alacaksınız." muted: title: "Susturuldu" - description: "Bu kategorilerdeki yeni konular hakkında herhangi bir bildiri almayacaksınız ve okunmamışlar sekmenizde belirmeyecek. " flagging: title: 'Topluluğumuzun medeni kalmasına yardımcı olduğunuz için teşekkürler!' private_reminder: 'bayraklar özeldir, sadece görevlilere gözükür' diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index 1c30b78273..4bdeeae9f0 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -889,8 +889,6 @@ uk: title: "Слідкувати" tracking: title: "Стежити" - regular: - title: "Звичайний" muted: title: "Ігноровані" flagging: diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 540d6654c5..328c6d9358 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -587,11 +587,13 @@ zh_CN: server: "服务器错误" forbidden: "访问被阻止" unknown: "错误" + not_found: "没有找到网页" desc: network: "请检查你的网络连接。" network_fixed: "似乎恢复正常了。" server: "错误代码:{{status}}" forbidden: "你没有权限读这个。" + not_found: "噢!程式要加载的URL并不存在。" unknown: "出错了。" buttons: back: "返回" @@ -625,10 +627,10 @@ zh_CN: signup_cta: sign_up: "注册" hide_session: "明天提醒我" - hide_forever: "不,谢谢" + hide_forever: "不了" hidden_for_session: "好的,我会在明天提醒你。不过你任何时候都可以使用“登录”来创建账户。" intro: "你好!:heart_eyes: 看起来你挺喜欢这个讨论,但是你还没有注册账户。" - value_prop: "当你创建了账户,我们能准确地追踪你的阅读进度,所以你能够在下一次访问时知道你读过了什么。你也可以收到网页和邮件通知,特别是有新帖子的时候。并且你可以赞任何帖子来分享你的感谢。:heartbeat:" + value_prop: "当你创建了账户,我们能准确地追踪你的阅读进度,所以你能够在下一次访问时回到你上次阅读到的地方。你也可以在有新帖子的时候收到网页和邮件通知。并且你可以赞任何帖子来分享你的感谢。:heartbeat:" methods: sso: "注册很容易:你只要在主站点建立一个账户。" only_email: "注册很容易:只需要一个邮箱和密码。" @@ -1377,16 +1379,16 @@ zh_CN: notifications: watching: title: "关注" - description: "你将会自动监视这些分类中的所有新主题。你将会收到新帖子和新主题发布的通知,并且新帖数量也将在这些主题后显示。" + description: "你将会自动监视这些分类中的所有新主题。你会收到每个主题中的新帖子通知,并且新帖数量也将在每个主题后显示。" tracking: title: "追踪" - description: "你将会自动追踪这些分类中的所有新主题。新帖数量将在这些主题后显示。" + description: "你将会自动追踪这些分类中的所有新主题。你会在别人@你或回复你的帖子时才会收到通知,并且新帖数量也将在这些主题后显示。" regular: title: "常规" description: "如果某人@你或者回复你,你将收到通知。" muted: title: "免打扰" - description: "你不会收到这些分类中的任何新主题通知,并且他们将不会出现在你的未读列表中。" + description: "你不会收到这些分类的新主题的任何通知,他们也不会出现在你的未读标签中。" flagging: title: '感谢帮助社群远离邪恶!' private_reminder: '标记是不公开的,只有职员才可以见到' @@ -1909,6 +1911,7 @@ zh_CN: ip_address: "IP" topic_id: "主题 ID" post_id: "帖子 ID" + category_id: "分类 ID" delete: '删除' edit: '编辑' save: '保存' @@ -1949,6 +1952,9 @@ zh_CN: impersonate: "检视" anonymize_user: "匿名用户" roll_up: "回退 IP 封禁" + change_category_settings: "更改分类设置" + delete_category: "删除分类" + create_category: "创建分类" screened_emails: title: "被屏蔽的邮件地址" description: "当有人试图用以下邮件地址注册时,将受到阻止或其它系统操作。" @@ -2306,7 +2312,9 @@ zh_CN: embed_username_key_from_feed: "从流中拉取 Discourse 用户名的 Key " embed_truncate: "截断嵌入的帖子" embed_whitelist_selector: "使用 CSS 选择器选择允许的嵌入元素" + whitelist_example: "article, #story, .post" embed_blacklist_selector: "使用 CSS 选择器移除嵌入元素" + blacklist_example: ".ad-unit, header" feed_polling_enabled: "通过RSS/ATOM导入帖子" feed_polling_url: "用于抓取的 RSS/ATOM 流的 URL" save: "保存嵌入设置" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 092068234b..2256308bc7 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -639,7 +639,9 @@ zh_TW: emoji_one: "Emoji One" composer: emoji: "Emoji :smile:" + whisper: "密談" add_warning: "這是正式警告。" + toggle_whisper: "切換密談" posting_not_on_topic: "你想要回覆哪個討論話題?" saving_draft_tip: "正在儲存..." saved_draft_tip: "儲存完畢" @@ -1020,6 +1022,7 @@ zh_TW: no_value: "否" yes_value: "是" via_email: "本文章透過電子郵件送達" + whisper: "這文章是版主私人密談" wiki: about: "這篇文章設定為 wiki,一般用戶可以編輯它" archetypes: @@ -1205,11 +1208,8 @@ zh_TW: title: "關注" tracking: title: "追蹤" - regular: - title: "一般" muted: title: "靜音" - description: "你將不會收到這些分類中的新討論話題通知,它們也不會出現在你的未讀欄內。" flagging: title: '感謝幫助社群遠離邪惡!' private_reminder: '標記是不公開的,只有 工作人員才可看到' diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index 74f2f6b726..149c268cbe 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -342,8 +342,18 @@ ar: title: "قائد" change_failed_explanation: "حاولت تخفيض رتبة %{user_name} إلى '%{new_trust_level}'. أيضا مستوى الثقة لهم حاليا '%{current_trust_level}'. %{user_name} سيبقى في '%{current_trust_level}' - إذا رغبت في تخفيض رتبة عضو أنظر لمستوى الثقة أولاً." rate_limiter: - slow_down: "لقد أنجزت هذا العمل مرات كثيرة، حاول مجددا." + slow_down: "لقد أنجزت هذا العمل في مرات كثيرة، عاود المحاولة لاحقا." too_many_requests: "لدينا حد يومي لعدد المرات التي يمكن للعمل أن ينجز بها. رجاءا انتظر %{time_left} قبل المحاولة مجدداً." + by_type: + first_day_replies_per_day: "لقد وصلت للعدد الأقصى للردود التي يمكن للعضو الجديد إنشائها في يومهم الأول. رجاء انتظر %{time_left} قبل المحاولة مجددا." + first_day_topics_per_day: "لقد وصلت للعدد الأقصى للمواضيع التي يمكن للعضو الجديد إنشائها في يومهم الأول. رجاء انتظر %{time_left} قبل المحاولة مجددا." + create_topic: "أنت تنشأ مواضيع بسرعة عالية. رجاء انتظر %{time_left} قبل المحاولة مجددا." + create_post: "أنت ترد بسرعة عالية. رجاء انتظر %{time_left} قبل المحاولة مجددا." + topics_per_day: "لقد وصلت للعدد الأقصى للمواضيع الجديدة اليوم. رجاء انتظر %{time_left} قبل المحاولة مجددا." + pms_per_day: "لقد وصلت للعدد الأقصى للرسائل اليوم. رجاء انتظر %{time_left} قبل المحاولة مجددا." + create_like: "لقد وصلت للعدد الأقصى للإعجابات اليوم. رجاء انتظر %{time_left} قبل المحاولة مجددا." + create_bookmark: "لقد وصلت للعدد الأقصى للتفضيلات اليوم. رجاء انتظر %{time_left} قبل المحاولة مجددا." + edit_post: "لقد وصلت للعدد الأقصى للتعديلات اليوم. رجاء انتظر %{time_left} قبل المحاولة مجددا." hours: zero: "1 ساعة" one: "1 ساعة" @@ -632,6 +642,10 @@ ar: title: "الأعضاء جدد" xaxis: "يوم" yaxis: "عدد الأعضاء الجدد" + profile_views: + title: "مشاهدات ملف تعريف العضو" + xaxis: "يوم" + yaxis: "عدد مشاهدات ملف تعريف العضو" topics: title: "المواضيع" xaxis: "اليوم" @@ -901,6 +915,7 @@ ar: enable_noscript_support: "فعل دعم محرك بحث webcrawler عبر علامة غير نصية." allow_moderators_to_create_categories: "السماح للمشرفين إنشاء قسم جديد" cors_origins: "اسمح بالأصول للطلبات عبر المنشأ (CORS). كل أصل يجب أن يتضمن http:// أو https://. متغير env لـ DISCOURSE_ENABLE_CORS يجب أن يعين إلى true ليعمل CORS." + use_admin_ip_whitelist: "المدير فقط يمكنه تسجيل الدخول إذا كانت عناوين IP لهم معرفة في قائمة IP المحجوبة. (المدير > السجلات > IP المحجوبة)" top_menu: "حدد الأدوات التي تظهر في ملاحة الصفحة الرئيسية، وما ترتيبها. مثال الأخير|الجديد|غير مقروء|فئات|أعلى|مقروء|مشارك|مفضلات" post_menu: "تحديد العناصر التي تظهر في القائمة آخر، وبأي ترتيب. مثال مثل | تحرير | العلم | حذف | سهم | المرجعية | الرد" post_menu_hidden_items: "عناصر القائمة لإخفاء افتراضيا في القائمة آخر ما لم يتم النقر على القطع التوسع جرا." @@ -920,7 +935,7 @@ ar: email_token_valid_hours: "نسيت كلمه السر/ تنشيط رموز الحساب صالحه ل(n) ساعات" email_token_grace_period_hours: "نسيت كلمه السر/ تنشيط رموز الحساب لا تزال صالحة لفترة سماح بالـ(n) ساعات بعد استبدالها." enable_badges: "تفعيل نظام الشارات." - enable_whispers: "اسمح لمستخدمين باجراء محادثه سريه مع الطاقم " + enable_whispers: "السماح للموظفين الخصوصين التواصل ضمن موضوع.(تجريبي)" allow_index_in_robots_txt: "تحديد في ملف robots.txt أن يسمح هذا الموقع ليتم فهرستها من قبل محركات البحث على شبكة الإنترنت." email_domains_blacklist: "قائمة pipe-delimited المجالات البريد الإلكتروني الذي لا يسمح للمستخدمين تسجيل حسابات مع. مثال: mailinator.com | trashmail.net" email_domains_whitelist: "قائمة pipe-delimited من مجالات البريد الإلكتروني التي يجب على المستخدمين تسجيل حسابات مع. تحذير: لن يسمح للمستخدمين مع مجالات البريد الإلكتروني الأخرى غير المذكورة هنا!" @@ -1069,6 +1084,7 @@ ar: white_listed_spam_host_domains: "قائمة النطاقات المستثناة من اختبار مضيف البريد المزعج. لن يتم تقييد المستخدمين الجدد من إنشاء المشاركات مع الروابط إلى هذه النطاقات." staff_like_weight: "كم عدد مرات الترجيح الاضافيه لمنح اعجابات الطاقم " topic_view_duration_hours: "احسب عدد المواضيع التي تمت مشاهدتها مره واحده عبر ip/مستخدم كل N ساعات" + user_profile_view_duration_hours: "احسب عدد ملفات التعريف للعضو التي تمت مشاهدتها مرة لكل IP/عضو في N ساعات." levenshtein_distance_spammer_emails: "عند ربط رسائل البريد الإلكتروني spammer، الأرقام والحروف تختلف التي ستبقى تسمح بربط غامض." max_new_accounts_per_registration_ip: "اذا كان هناك بالفعل (N) مستوي ثقه الحسابات 0 من هذا IP ( و لم يكن عضو في الطاقم او TL2 او اعلى), توقف عن قبول تسجيلات الدخول الجديده من هذا IP" min_ban_entries_for_roll_up: "When clicking the Roll up button, will create a new subnet ban entry if there are at least (N) entries." diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml index 042e41c36f..85c97a5060 100644 --- a/config/locales/server.da.yml +++ b/config/locales/server.da.yml @@ -60,8 +60,18 @@ da: other_than: "skal være andet end %{count}" template: body: 'Der var problemer med følgende felter:' + header: + 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." + site_settings: + min_username_length_exists: "Du kan ikke sætte minimumslængden for brugernavne over det korteste eksisterende brugernavn." + min_username_length_range: "Du kan ikke sætte minimum til mere end maksimum." + max_username_length_exists: "Du kan ikke sætte maksimumlængden for brugernavne under det længste eksisterende brugernavn." + max_username_length_range: "Du kan ikke sætte maksimum til mindre end minimum." + default_categories_already_selected: "Du kan ikke vælge en kategori der er brugt i en anden liste." + s3_upload_bucket_is_required: "Du kan ikke oploade til S3 med mindre du har angivet 's3_upload_bucket'." bulk_invite: file_should_be_csv: "Den uploadede fil skal være i .csv eller .txt format." backup: @@ -133,6 +143,8 @@ da: posts: "Seneste indlæg" too_late_to_edit: "Dette indlæg er gammelt og kan ikke længere redigeres eller slettes." excerpt_image: "billede" + queue: + delete_reason: "Slettet via køen til moderation af indlæg." groups: errors: can_not_modify_automatic: "Du kan ikke modificere en automatisk gruppe" @@ -163,6 +175,14 @@ da: - Det er godt med kritik, men husk at kritisere *ideer*, ikke personer. For flere tips, [se vores retningslinier](/guidelines). Denne boks dukker kun op for dine første %{education_posts_text}. + avatar: | + ### Hvad med et billede til din konto? + + Du har oprettet et par emner og svar, men dit avatar er ikke så unik som du er -- det er bare et bogstav. + + Har du overvejet at **[gå til din brugerprofil](%{profile_path})** og uploade et billede, der repræsenterer dig? + + Det er lettere at følge diskussioner og finde interessant mennesker i samtaler, hvor alle har en unik avatar! dominating_topic: | ### Lad andre deltage i samtalen @@ -218,6 +238,7 @@ da: no_info_other: "
%{name} har ikke skrevet noget i “Om mig”-feltet endnu
" vip_category_name: "Lounge" vip_category_description: "En ekslusiv kategori der kun er tilgængelig for medlemmer med tillidsniveu 3 eller højere." + meta_category_name: "Site Feedback" meta_category_description: "Diskussion om dette site, hvordan det er indrettet, hvordan det virker og hvordan vi kan forbedre det." staff_category_name: "Staff" staff_category_description: "Privat gruppe for staff diskussioner. Emner er kun synlige for administratorer og moderatorer." @@ -231,9 +252,13 @@ da: uncategorized_parent: "Ukategoriserede kan ikke have en overordnede kategori" self_parent: "En underkategori kan ikke være sin egen overordnede kategori." depth: "Du kan ikke indlejre en underkategori i en anden underkategori." + email_in_already_exist: "Den indgående email-adresse '%{email_in}' bruges allerede til kategorien '%{category_name}'." cannot_delete: uncategorized: "Kan ikke slette Ukategoriseret" has_subcategories: "Kan ikke slette denne kategori, fordi den har under kategorier." + topic_exists: + one: "Kan ikke slette denne kategori, da den indeholder et emne. Ældste emne er %{topic_link}" + other: "Kan ikke slette denne kategori, da den indeholder %{count} emner. Ældste emne er %{topic_link}" topic_exists_no_oldest: "Kan ikke slette denne kategori fordi antallet af emner er %{count}" trust_levels: newuser: @@ -247,8 +272,11 @@ da: elder: title: "leder" rate_limiter: - slow_down: "Du har forsøgt denne handling for ofte, prøv igen senere" + slow_down: "Du har udført denne handling for mange gange, prøv igen senere" too_many_requests: "Vi har en daglig grænse for hvor mange gange den pågældende handling kan udføres. Vent venligst %{time_left} før du prøver igen." + by_type: + create_topic: "Du opretter emner for hurtigt. Vent venligst %{time_left} før du prøver igen." + create_post: "Du svarer for hurtigt. Vent venligst %{time_left} før du prøver igen." hours: one: "1 time" other: "%{count} timer" @@ -330,6 +358,7 @@ da: one: "næsten 1 år siden" other: "næsten %{count} år siden" password_reset: + no_token: "Beklager, dit kodeords-reset link er udløbet. Vælg 'Log ind' knappen og brug 'Jeg har glemt mit kodeord' for at få et nyt link." choose_new: "Vælg et nyt kodeord" choose: "Vælg et kodeord" update: 'opdatér kodeord' @@ -343,11 +372,13 @@ da: please_continue: "Fortsæt til %{site_name}" error: "Der opstod en fejl under opdateringen af din e-mail-adresse. Måske er adressen allerede i brug?" activation: + action: "Klik her for at aktivere din konto" already_done: "Beklager, dette bekræftelses-link er ikke længere gyldigt. Måske er din konto allerede aktiv?" please_continue: "Din nye konto er bekræftet; du bliver nu ledt til forsiden." continue_button: "Fortsæt til %{site_name}" welcome_to: "Velkommen til %{site_name}!" approval_required: "En moderator skal godkende din nye konto før du kan tilgå forummet. Du får en e-mail, når din konto er godkendt!" + missing_session: "Vi kan ikke se, om din konto blev oprettet, se venligst om du har aktiveret cookies." post_action_types: off_topic: title: 'Uden for emnet' @@ -364,12 +395,15 @@ da: description: 'Dette indlæg har indhold som en rimelig person ville opfatte som stødende, udtryk for misbrug eller som et brud på vores sammenholds retningslinjer.' long_form: 'markerede dette som stødende' notify_user: + title: 'Send besked til @{{username}}' description: 'Dette indlæg indeholder noget jeg gerne vil drøfte direkte og privat med vedkommende, Bliver ikke markeret.' + long_form: 'sendt besked til brugeren' email_title: 'Dit indlæg i "%{title}"' email_body: "%{link}\n\n%{message}" notify_moderators: title: "Noget andet" description: 'Dette indlæg kræver moderatorernes opmærksomhed baseret på en anden grund som ikke er nævnt ovenfor.' + long_form: 'markerede dette til gennemsyn' email_title: 'Et indlæg i "%{title}" kræver moderatorernes opmærksomhed' email_body: "%{link}\n\n%{message}" bookmark: @@ -394,12 +428,17 @@ da: long_form: 'markeret upassende' notify_moderators: title: "Noget andet" + long_form: 'markerede dette til gennemsyn' email_title: 'Emnet "%{title}" kræver moderator-opmærksomhed' email_body: "%{link}\n\n%{message}" + flagging: + you_must_edit: '

Dit indlæg er blevet markeret af fællesskabet. Se dine beskeder.

' + user_must_edit: '

Dette indlæg er blevet markert af fællesskabet, og er midlertidigt skjult.

' archetypes: regular: title: "Almindeligt emne" banner: + title: "Banneremne" message: make: "Dette emne er nu et banner-emne. Det optræder i toppen af alle sider indtil brugeren fjerner det." remove: "Dette emne er ikke mere et banner-emne. Det optræder ikke længere i toppen af alle sider." @@ -424,6 +463,10 @@ da: title: "Nye brugere" xaxis: "Dag" yaxis: "Antal nye brugere" + profile_views: + title: "Bruger Profil Visninger" + xaxis: "Dag" + yaxis: "Antag sete brugerprofiler" topics: title: "Emner" xaxis: "Dag" @@ -494,10 +537,12 @@ da: page_view_anon_reqs: title: "Anonym" xaxis: "Dag" + yaxis: "Anonym API Forespørgsler" page_view_logged_in_reqs: title: "Logget ind" xaxis: "Dag" page_view_crawler_reqs: + title: "Web Crawlers" xaxis: "Dag" page_view_total_reqs: title: "Total" @@ -527,7 +572,9 @@ da: xaxis: "Dag" yaxis: "Total" mobile_visits: + title: "Brugerbesøg" xaxis: "Dag" + yaxis: "Antal besøg" dashboard: rails_env_warning: "Din server kører i %{env}-tilstand." ruby_version_warning: "Du kører en version af Ruby 2.0.0 som har kendte problemer. Opgradér til patch-level 247 eller senere." @@ -608,11 +655,18 @@ da: github_client_secret: "Client secret til Github-login, oprettes på at https://github.com/settings/applications." active_user_rate_limit_secs: "Hvor ofte vi opdaterer feltet 'last_seen_at', i sekunder." previous_visit_timeout_hours: "Hvor lang tid et besøg varer før vi regner det med i det 'forrige' besøg, i timer." + max_likes_per_day: "Maksimalt antal likes per bruger per dag." clean_orphan_uploads_grace_period_hours: "Grace-periode (i timer) før et forældreløst upload bliver fjernet." purge_deleted_uploads_grace_period_days: "Grace-periode (i dage) før et slettet upload bliver fjernet." + tl2_requires_likes_received: "Hvor mange likes en bruger skal modtage inden forfremmelse til tillidsniveau 2." + tl2_requires_likes_given: "Hvor mange likes en bruger skal give inden forfremmelse til tillidsniveau 2." + tl3_requires_likes_given: "Det antal likes en bruger skal have givet i de seneste 100 dage for at kvalificere sig til forfremmelse til tillidsniveau 3." + tl3_requires_likes_received: "Det antal likes en bruger skal have modtaget i de seneste 100 dage for at kvalificere sig til forfremmelse til tillidsniveau 3." min_trust_to_create_topic: "Det mindste tillidsniveau der skal til for at oprette et nyt emne." title_fancy_entities: "Omdan almindelige ASCII-tegn i emnetitler til fancy HTML-entities, i stil med SmartyPants http://daringfireball.net/projects/smartypants/" title_prettify: "Undgå hyppige tastefejl i titlen, inklusive overforbrug af store bogstaver, første bogstav med småt, gentagne ! og ?, ekstra . i slutningen, etc." + topic_post_like_heat_low: "Når forholdet likes:indlæg overstiger dette tal, bliver antal indlæg let fremhævet." + topic_post_like_heat_medium: "Når forholdet likes:indlæg overstiger dette tal, bliver antal indlæg moderat fremhævet." faq_url: "Hvis du hoster en FAQ et andet sted kan du indtaste den fulde URL her." tos_url: "Hvis du hoster dine forretningsbetingelser et andet sted kan du indtaste den fulde URL her." privacy_policy_url: "Hvis du hoster din privatlivspolitik et andet sted kan du indtaste den fulde URL her." @@ -705,6 +759,10 @@ da: Der er nye brugere, som afventer godkendelse (eller afvisning) før de kan tilgå dette forum. [Gennemgå dem venligst på administrationssiden](%{base_url}/admin/users/list/pending). + download_remote_images_disabled: + subject_template: "Download af eksterne billeder er slået fra" + text_body_template: "Indstillingen `download_remote_images_to_local` blev slået fra fordi grænsen for bug af diskplads i `download_remote_images_threshold` blev nået." + subject_re: "Re:" user_notifications: previous_discussion: "Forrige svar" unsubscribe: @@ -760,6 +818,7 @@ da: %{respond_instructions} digest: why: "Et kort resume af %{site_link} siden dit sidste besøg %{last_seen_at}" + subject_template: "Sammenfatning af [%{site_name}]" new_activity: "Ny aktivitet på dine emner og indlæg:" top_topics: "Populære emner" other_new_topics: "Populære emner" @@ -767,6 +826,7 @@ da: click_here: "klik her" from: "%{site_name} opsummering" read_more: "Læs mere" + more_topics_category: "Flere nye emner: " forgot_password: subject_template: "[%{site_name}] Nulstil kodeord" text_body_template: | @@ -786,6 +846,8 @@ da: Klik på følgende for at vælge et kodeord.: %{base_url}/users/password-reset/%{email_token} + account_created: + subject_template: "[%{site_name}] Din nye konto" authorize_email: subject_template: "[%{site_name}] Bekræft din nye e-mail-adresse" text_body_template: | @@ -795,6 +857,7 @@ da: signup_after_approval: subject_template: "Du er blevet godkendt på %{site_name}!" signup: + subject_template: "[%{site_name}] Bekræft din nye konto" text_body_template: | Velkommen til %{site_name}! @@ -809,17 +872,33 @@ da: see_more: "Flere" search_title: "Søg på denne side" search_google: "Google" + terms_of_service: + title: "Vilkår" + signup_form_message: 'Jeg har læst og accepterer vilkårene.' deleted: 'slettet' upload: unauthorized: "Beklager, filen, som du forsøger at uploade er ikke autoriseret (autoriserede filendelser: %{authorized_extensions})." pasted_image_filename: "Indsat billede" + file_missing: "Du skal angive en fil til overførsel." + attachments: + too_large: "Beklager, men den fil du prøver at overføre er for stor (maksimumstørelsen er %{max_size_kb}KB)" images: + too_large: "Beklager, men billedet som du forsøger at uploade er for stort (den maksimale størrelse er %{max_size_kb}KB). Gør det venligst mindre og prøv igen." size_not_found: "Beklager, men vi kunne ikke fastslå billedets størrelse. Måske er dit billede ødelagt?" flag_reason: sockpuppet: "En ny bruger oprettede et emne, og en anden ny bruger med den samme IP-adresse svarede på det. Se indstillingen af flag_sockpuppets." email_log: anonymous_user: "Brugeren er anonym" seen_recently: "Bruger har været logget på for nyligt" + post_not_found: "Kan ikke finde et indlæg med id %{post_id}" + notification_already_read: "Den notifikation som denne email handler om er allerede læst" + post_deleted: "indlægget er blevet slettet af forfatteren" + user_suspended: "brugeren blev suspenderet" + already_read: "brugeren har allerede læst dette indlæg" + message_blank: "beskeden er tom" + message_to_blank: "message.to er tom" + text_part_body_blank: "text_part.body er tom" + body_blank: "brødtekst er tom" about: "Om" guidelines: "Retningslinjer" privacy: "Privatliv" @@ -827,8 +906,12 @@ da: csv_export: boolean_yes: "Ja" boolean_no: "Nej" + static_topic_first_reply: | + Rediger det første indlæg i dette emne for at ændre indholdet af siden %{page_name} guidelines_topic: title: "FAQ/Retningslinjer" + tos_topic: + title: "Vilkår" admin_login: success: "Email sendt" error: "Fejl!" diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index bbd860d349..982966a2bc 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -69,6 +69,8 @@ de: min_username_length_exists: "Die minimale Länge für den Benutzernamen kann nicht höher sein als der kürzeste Benutzername." min_username_length_range: "Du kannst das Minimum nicht höher setzen als das Maximum." max_username_length_exists: "Die maximale Länge für den Benutzernamen kann nicht kürzer sein als der längste Benutzername." + max_username_length_range: "Das Maximum darf nicht kleiner sein als das Minimum." + s3_upload_bucket_is_required: "Uploads auf Amazon S3 können nicht aktiviert werden, bevor der 's3_upload_bucket' eingetragen wurde." bulk_invite: file_should_be_csv: "Die hochgeladene Datei sollte im CSV oder TXT Format vorliegen." backup: @@ -180,6 +182,14 @@ de: - Kritik ist in Ordnung, aber bitte kritisiere nur *Ideen*, nicht Menschen. Beachte bitte auch [unsere Richtlinien](/guidelines). Dieser Hilfetext wird nur bei deinen ersten %{education_posts_text} Beiträgen angezeigt. + avatar: | + ### Wie wäre es mit einem Bild für Deinen Account? + + Du hast schon einige Themen und Antworten geschrieben, aber Dein Profilbild ist nicht so einzigartig, wie Du es bist -- es ist nur ein Buchstabe. + + Vielleicht schaust Du mal in **[Dein Benutzerprofil](%{profile_path})** und lädst dort ein Bild hoch, das etwas über Dich aussagt? + + Es ist einfacher, Diskussionen zu folgen und interessante Leute zu finden, wenn jeder ein eindeutiges Profilbild hat! sequential_replies: | ### Bitte antworte mehreren Beiträge gleichzeitig @@ -1140,6 +1150,20 @@ de: system_messages: post_hidden: subject_template: "Beitrag wegen Meldungen aus der Community versteckt" + text_body_template: | + Hallo, + + dies ist eine automatische Nachricht von %{site_name}, um Dich darüber zu informieren, dass Dein Beitrag verborgen worden ist. + + %{base_url}%{url} + + %{flag_reason} + + Mehrere Mitglieder der Gemeinschaft haben Deinen Beitrag gemeldet, bevor er verborgen wurde. Du solltest also Deinen Beitrag gemäß deren Rückmeldungen überarbeiten. **Du kannst Deinen Beitrag nach %{edit_delay} Minuten ändern; danach wird er automatisch wieder erscheinen.** + + Falls Dein Beitrag jedoch ein weiteres Mal von der Gemeinschaft gemeldet und verborgen wird, wird der Beitrag verborgen bleiben, bis ein Mitglied des Teams dies ändert – in diesem Fall können auch weitere Konsequenzen folgen, bis hin zu einer möglichen Sperrung Deines Accounts. + + Für weitereOrientierungshilfen wirf bitte einen Blick in unsere [Community-Richtlinien](%{base_url}/guidelines). welcome_user: subject_template: "Willkommen bei %{site_name}!" text_body_template: | diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 140c2ba0a1..5e4114522a 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -375,8 +375,19 @@ en: rate_limiter: - slow_down: "You have performed this action too many times, try again later" + slow_down: "You have performed this action too many times, try again later." too_many_requests: "We have a daily limit on how many times that action can be taken. Please wait %{time_left} before trying again." + by_type: + first_day_replies_per_day: "You've reached the maximum number of replies a new user can create on their first day. Please wait %{time_left} before trying again." + first_day_topics_per_day: "You've reached the maximum number of topics a new user can create on their first day. Please wait %{time_left} before trying again." + create_topic: "You're creating topics too quickly. Please wait %{time_left} before trying again." + create_post: "You're replying too quickly. Please wait %{time_left} before trying again." + topics_per_day: "You've reached the maximum number of new topics today. Please wait %{time_left} before trying again." + pms_per_day: "You've reached the maximum number of messages today. Please wait %{time_left} before trying again." + create_like: "You've reached the maximum number of likes today. Please wait %{time_left} before trying again." + create_bookmark: "You've reached the maximum number of bookmarks today. Please wait %{time_left} before trying again." + edit_post: "You've reached the maximun number of edits today. Please wait %{time_left} before trying again." + hours: one: "1 hour" other: "%{count} hours" @@ -576,6 +587,10 @@ en: title: "New Users" xaxis: "Day" yaxis: "Number of new users" + profile_views: + title: "User Profile Views" + xaxis: "Day" + yaxis: "Number of user profiles viewed" topics: title: "Topics" xaxis: "Day" @@ -858,6 +873,7 @@ en: enable_noscript_support: "Enable standard webcrawler search engine support via the noscript tag" allow_moderators_to_create_categories: "Allow moderators to create new categories" cors_origins: "Allowed origins for cross-origin requests (CORS). Each origin must include http:// or https://. The DISCOURSE_ENABLE_CORS env variable must be set to true to enable CORS." + use_admin_ip_whitelist: "Admins can only log in if they are at an IP address defined in the Screened IPs list (Admin > Logs > Screened Ips)." top_menu: "Determine which items appear in the homepage navigation, and in what order. Example latest|new|unread|categories|top|read|posted|bookmarks" post_menu: "Determine which items appear on the post menu, and in what order. Example like|edit|flag|delete|share|bookmark|reply" post_menu_hidden_items: "The menu items to hide by default in the post menu unless an expansion ellipsis is clicked on." @@ -1074,6 +1090,7 @@ en: white_listed_spam_host_domains: "A list of domains excluded from spam host testing. New users will never be restricted from creating posts with links to these domains." staff_like_weight: "How much extra weighting factor to give staff likes." topic_view_duration_hours: "Count a new topic view once per IP/User every N hours" + user_profile_view_duration_hours: "Count a new user profile view once per IP/User every N hours" levenshtein_distance_spammer_emails: "When matching spammer emails, number of characters difference that will still allow a fuzzy match." max_new_accounts_per_registration_ip: "If there are already (n) trust level 0 accounts from this IP (and none is a staff member or at TL2 or higher), stop accepting new signups from that IP." @@ -1183,8 +1200,6 @@ en: delete_drafts_older_than_n_days: Delete drafts older than (n) days. - show_logout_in_header: "Show log out in user dropdown in header" - vacuum_db_days: "Run VACUUM FULL ANALYZE to reclaim DB space after migrations (set to 0 to disable)" prevent_anons_from_downloading_files: "Prevent anonymous users from downloading attachments. WARNING: this will prevent any non-image site assets posted as attachments from working." diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index 385334271f..49c2a6f555 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -203,11 +203,11 @@ es: En vez de añadir otra respuesta, por favor, considera editar tus respuestas previas o visitar otros temas. reviving_old_topic: | - ### ¿Resucitar este tema? + ### ¿Revivir este tema? La última respuesta a este tema fue hace %{days} días. Tu post reactivará el tema subiéndolo a las primeras posiciones de la lista y notificará a aquellos involucrados en la conversación previa. - ¿Estás seguro de que quieres continuar esta antigua conversación? + ¿Estás seguro de que quieres continuar esta conversación antigua? activerecord: attributes: category: @@ -492,6 +492,10 @@ es: title: "Nuevos usuarios" xaxis: "Día" yaxis: "Número de usuarios nuevos" + profile_views: + title: "Visitas a perfil de usuario" + xaxis: "Día" + yaxis: "Número de perfiles de usuario vistos" topics: title: "Temas nuevos" xaxis: "Día" @@ -761,6 +765,7 @@ es: enable_noscript_support: "Habilitar el soporte de motor de búsqueda estándar webcrawler mediante la etiqueta noscript" allow_moderators_to_create_categories: "Permitir a los moderadores crear nuevas categorías" cors_origins: "Orígenes permitidos para las cross-origin requests (CORS). Cada origen debe incluir http:// or https://. La variable env DISCOURSE_ENABLE_CORS debe establecerse a verdadero para activar CORS." + use_admin_ip_whitelist: "Los admins solo pueden iniciar sesión si están en una dirección IP definida en la lista de Screened IPs (Admin > Logs > Screened Ips)." top_menu: "Determinar que items aparecen en la navegación de la home y en qué orden. Ejemplo latest|new|unread|categories|top|read|posted|bookmarks" post_menu: "Determina qué elementos aparecen en el menú de un post y en qué orden. Ejemplo: like|edit|flag|delete|share|bookmark|reply" post_menu_hidden_items: "Los elementos del menú a ocultar por defecto en el menú de cada post a menos que se haga clic en el botón para expandir las opciones." @@ -929,6 +934,7 @@ es: white_listed_spam_host_domains: "Una lista de dominios a excluir de las pruebas de spam. A los nuevos usuarios no se les restringirá la posibilidad de crear posts con enlaces a estos dominios." staff_like_weight: "Qué ponderación extra otorgan los me gusta provenientes de los miembros del Staff." topic_view_duration_hours: "Contar una visita a un nuevo tema por IP/Usuario cada N horas" + user_profile_view_duration_hours: "Contar una nueva visita de perfil por IP/Usuario cada N horas" levenshtein_distance_spammer_emails: "Al revisar coincidencias por correos electrónicos de spammers, qué número de caracteres permiten una coincidencia parcial." max_new_accounts_per_registration_ip: "Si ya hay (n) cuentas con nivel de confianza 0 desde esta IP (y ninguna es de un miembro del staff o de nivel de confianza 2 o más), prohibir nuevos registros desde esa IP." min_ban_entries_for_roll_up: "Al hacer clic en el botón Agrupar, se crea un nuevo rango de entradas para banear si hay al menos (N) entradas." @@ -997,7 +1003,7 @@ es: invites_per_page: "Número de invitaciones por defecto mostradas en la página de perfil del usuario." short_progress_text_threshold: "Después de que un número de posts en un tema alcance esta cifra, la barra de progreso sólo mostrará el número del post actual. Si cambias el ancho de la barra de progreso, deberías revisar este valor." default_code_lang: "Lenguaje de programación por defecto para aplicar el resaltado de la sintaxis en los bloques de código de GitHub (lang-auto, ruby, python etc.)" - warn_reviving_old_topic_age: "Cuando alguien publica en un tema cuya última respuesta fue hace este número de días o más, se le mostrará un aviso para desalentar el hecho de resucitar una antigua discusión. Deshabilita esta opción introduciendo el valor 0." + warn_reviving_old_topic_age: "Cuando alguien publica en un tema cuya última respuesta fue hace este número de días o más, se le mostrará un aviso para desalentar el hecho de revivir una antigua discusión. Deshabilita esta opción introduciendo el valor 0." autohighlight_all_code: "Forzar el resaltado de código a los bloques de código preformateado cuando no se especifique el lenguaje del código." highlighted_languages: "Incluye reglas resaltadas de sintaxis. (Advertencia: incluyendo demasiadas lenguages puede afectar al rendimiento) ver: https://highlightjs.org/static/demo/ para una demostración" feed_polling_enabled: "SOLO PARA EMBEBER: embeber feeds RSS/ATOM como posts." @@ -1012,7 +1018,6 @@ es: enable_cdn_js_debugging: "Permitir /logs mostrar los errores correctamente, añadiendo permisos crossorigin en todas las inclusiones de js" show_create_topics_notice: "Si el sitio tiene menos de 5 temas abiertos al público, mostrar un aviso pidiendo a los administradores crear más temas." delete_drafts_older_than_n_days: Eliminar borradores de más de (n) días de antigüedad. - show_logout_in_header: "Mostrar el botón para cerrar sesión en el desplegable de la cabecera" vacuum_db_days: "Correr VACUUM FULL ANALYZE para reclamar espacio en la base de datos después de las migraciones. (Poner en 0 para inhabilitar)" prevent_anons_from_downloading_files: "Impedir que los usuarios anónimos descarguen archivos. ADVERTENCIA: Esto impedirá que funcione cualquier recurso del sitio publicado como adjunto." slug_generation_method: "Elegir un método de generación de slug. 'encoded' generará cadenas con código porciento. 'none' hara que no se genere slug." @@ -1772,6 +1777,105 @@ es: Edita el primer post de este tema para cambiar el contenido de la página %{page_name}. guidelines_topic: title: "Preguntas Frecuentes / Directrices" + body: | + + + ## [Este es un lugar público para conversaciones civilizadas](#civilized) + + Por favor, trata este foro de debate con el mismo respeto con el que tratarías un parque. Este es un lugar donde compartir habilidades, conocimiento e intereses a través de debates y conversaciones. + + Estas no son unas normas estrictas y cerradas, sino una ayuda para el correcto desarrollo de la comunidad. Usa estas pautas para mantener este sitio como un lugar limpio y adecuado para poder debatir y conversar correctamente. + + + + ## [Aporta](#improve) + + Ayúdanos a mantener este foro como un buen lugar esforzándote para mejorar el debate de alguna manera, aunque sea mínima. Si no estás seguro de lo que tu mensaje aporta a la conversación, piensa sobre lo que quieres decir e intenta contestar después. + + El contenido de este foro nos importa, y queremos que actúes como si te importara a ti también. Sé respetuoso con los temas debatidos y con la gente que los debate, aunque no estés de acuerdo con algo de lo que digan. + + Una buena manera de de mejorar el foro es investigando un poco lo que está pasando o ya ha pasado. Por favor, echa un vistazo a los temas ya existentes antes de responder a uno o crearlo y así podrás ver quién opina lo mismo o tiene intereses parecidos a los tuyos. + + + + ## [Sé aceptable incluso cuando no estés de acuerdo](#agreeable) + + Cuando vayas a responder a algo con lo que no estás de acuerdo acuérdate de __criticar las ideas y no las personas__. Por favor, evita: + + + * Ridiculizar a los demás. + * Faltar al respeto e insultar. + * Responder al tono del mensaje en vez de a su contenido. + * Hacer comparaciones y contradicciones exageradas y/o fuera de lugar. + + En lugar de eso, da argumentos que aporten algo al tema, por favor. + + + + ## [Tu participación cuenta](#participate) + + Eres parte de la comunidad, y queremos que todo el mundo participe en ella. Crea o responde a los temas aportando algo y harás de este foro un lugar más interesante y agradable. + + Este foro ofrece herramientas que permiten a la comunidad identificar entre todas las mejores (y peores) contribuciones: "Me gusta", marcadores, reportes, distintivos, respuestas, ediciones, y demás. Usa todo esto para mejorar tu experiencia y la de los demás. + + Intentemos dejar esto mejor de lo que nos lo encontramos. + + + + ## [Si ves un problema, repórtalo](#flag-problems) + + Los moderadores tienen una autoridad especial: son los responsables de este foro. Pero tú lo eres también. Con tu ayuda, los moderadores pueden ser personas que faciliten el desarrollo de la comunidad en lugar de la policía del foro o el servicio de limpieza. + + Cuando veas un comportamiento inadecuado, no respondas. Eso hace que, al reconocer públicamente el contenido inadecuado, se siga publicando contenido de esa clase. Además, lo único que harás será perder energía y el tiempo de todos. En su lugar, repórtalo. Si un post es reportado lo suficiente, se tomarán medidas automáticamente o mediante la intervención de un moderador. + + Para el correcto funcionamiento de esta comunidad, los moderadores se reservan el derecho de eliminar cualquier contenido y cualquier cuenta, por cualquier razón en cualquier momento. Los moderadores no ven los temas antes de que sean publicados por lo que ellos y los operadores del foro no son responsables del contenido publicado por la comunidad. + + + + ## [Sé educado](#be-civil) + + Nada estropea más una conversación que la mala educación. + + * Sé educado. No publoques nada que una persona razonable consideraría ofensivo o abusivo. + * Mantén limpio el foro. No publiques nada obsceno o sexualmente explícito. + * Respeta. No molestes ni acoses a los demás usuarios, tampoco publiques su información privada y/o personal. + * Respeta el foro. No publiques spam ni nos estropees el foro. + + Como puedes ver, estos no son términos exactos con definiciones exactas. Evita siquiera la aparición de cualquiera de estas cosas en el foro. Si no estás seguro, piensa en cómo te sentirías si tu post apareciera en la portada de algún periódico importante. + + Este es un foro público y por tanto el contenido puede aparecer en los motores de búsqueda. El foro es para todos los públicos, así que debes tener cuidado con el vocabulario y el contenido. + + + + ## [Mantén limpio y organizado el foro](#keep-tidy) + + Esfuérzate para poner las cosas en su lugar correspondiente para que podamos estar más tiempo participando en la comunidad y menos tiempo limpiando y organizando. + + * No empieces un nuevo tema en la categoría equivocada. + * No publiques lo mismo en varios temas diferentes. + * No respondas con un mensaje sin contenido. + * No desvíes un tema en mitad de la discusión. + * No firmes tus mensajes — tu información y perfil están claramente visibles junto al post. + + En vez de responder “+1” or “Estoy de acuerdo”, usa el botón de Me gusta. En lugar de cambiar radicalmente la dirección de un tema, responde como nuevo tema. + + + + ## [Publica sólo lo que es tuyo](#stealing) + + No publiques nada que pertenezca a otra persona sin permiso. No debes facilitar enlaces, descipciones o métodos de robar la propiedad intelectual de los demás. (software, vídeo, audio, imágenes, etc.), o formas de violar las leyes. + + + + ## [Funcionamos gracias a gente como tú](#power) + + Este foro es operado por nuestro [equipo](/about) y *vosotros*, la comunidad. Si tienes alguna pregunta sobre cómo deberían funcionar las cosas aquí, abre un nuevo tema en la [categoría de Sugerencias](/c/site-feedback). Si pasa algo urgente que no puede ser resuelto con un tema o un reporte, contáctanos a través de [esta página](/about). + + + + ## [Términos del Servicio](#tos) + + Sí, estos temas aburren, pero debemos protegernos – y, por extensión, a ti y a tus datos – frente a gente poco amigable. Tenemos unos [Términos del Servicio](/tos) que describen nuestra forma de actuar (y la tuya), y los derechos con relación al contenido, la privacidad y las leyes, Para usar nuestros servicios, debes aceptar nuestros [Términos y Condiciones](/tos). tos_topic: title: "Términos de Servicio" privacy_topic: diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index 8dd89d08fe..7d38303e4e 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -65,6 +65,13 @@ fi: other: '%{count} virhettä esti tallentamasta tätä %{model}' embed: load_from_remote: "Viestin lataamisessa tapahtui virhe." + site_settings: + min_username_length_exists: "Et voi asettaa käyttäjänimen minimipituutta lyhyemmäksi, kuin lyhyin käyttäjänimi" + min_username_length_range: "Et voi asettaa minimi korkeammaksi kuin maksimi" + max_username_length_exists: "Et voi asettaa käyttäjänimen enimmäispituutta alle pisimmän käyttäjänimen." + max_username_length_range: "Et voi asettaa maksimia minimin alle." + default_categories_already_selected: "Et voi valita aluetta, joka on käytössä toisella listalla" + s3_upload_bucket_is_required: "Et voi ottaa s3 latausta käyttöön, jos et ole määrittänyt 's3_upload_bucket'." bulk_invite: file_should_be_csv: "Ladattavan tiedoston pitäisi olla csv- tai txt-muodossa." backup: @@ -401,6 +408,7 @@ fi: please_continue: "Jatka sivustolle %{site_name}" error: "Sähköpostiosoitteen vaihdossa tapahtui virhe. Ehkäpä tämä sähköpostiosoite on jo käytössä?" activation: + action: "Klikkaa tähän aktivoidaksesi tilisi" already_done: "Pahoittelut, tämän tilin varmennuslinkki ei ole enää voimassa. Ehkäpä tili on jo varmennettu?" please_continue: "Tilisi on nyt varmennettu; sivu ohjautuu palstan etusivulle." continue_button: "Jatka sivustolle %{site_name}" @@ -493,6 +501,10 @@ fi: title: "Uudet käyttäjät" xaxis: "Päivä" yaxis: "Uusien käyttäjien määrä" + profile_views: + title: "Käyttäjäprofiilin katselut" + xaxis: "Päivä" + yaxis: "Katseltujen käyttäjäprofiilien määrä" topics: title: "Ketjut" xaxis: "Päivä" @@ -635,6 +647,7 @@ fi: s3_config_warning: 'Palvelin on konfiguroitu tallentamaan tiedostot s3:een, mutta vähintään yksi arvoista s3_access_key_id, s3_secret_access_key tai s3_upload_bucket ei ole asetettu. Päivitä arvot sivuston asetuksissa.Voit lukea lisätietoja oppaasta "How to set up image uploads to S3?".' s3_backup_config_warning: 'Palvelin on konfiguroitu lataamaan varmuuskopiot s3:een, mutta vähintään yksi arvoista s3_access_key_id, s3_secret_access_key tai s3_backup_bucket ei ole asetettu. Päivitä arvot sivuston asetuksissa.Voit lukea lisätietoja oppaasta "How to set up image uploads to S3?".' image_magick_warning: 'Palvelin on konfiguroitu luomaan esikatselukuvia suurista kuvista, mutta ImageMagickia ei ole asennettu. Asenna ImageMagick paketinhallinnasta tai lataamalla uusin versio.' + failing_emails_warning: 'Epäonnistuneiden sähköpostitehtävien määrä on %{num_failed_jobs}. Tarkista app.yml ja varmista, että palvelimen asetukset ovat kunnossa. Katsele epäonnistuneita tehtäviä Sidekiqissa.' default_logo_warning: "Aseta sivustolle logot. Päivitä logo_url, logo_small_url, ja favicon_url sivuston asetuksissa." contact_email_missing: "Aseta yhteystietoihin sähköpostiosoite, josta sinut saa tavoitettua sivustoon liittyvissä kiireellisissä asioissa. Voit syöttää sen sivuston asetuksissa." contact_email_invalid: "Sivuston sähköpostiosoite ei kelpaa. Muokkaa sitä sivuston asetuksissa." @@ -719,6 +732,7 @@ fi: logo_url: "Logo sivustosi vasemmassa yläkulmassa. Sen pitäisi olla leveä suorakulmio muodoltaan. Jos jätetään tyhjäksi, näytetään sivuston otsikkoteksti." digest_logo_url: "Vaihtoehtoinen logo, jota käytetään sivustolta lähtevissä sähköpostitiivistelmissä. Sen pitäisi olle leveä suorakulmio muodoltaan. Jos jätetään tyhjäksi, sen tilalla käytetään 'logo_url'." logo_small_url: "Pieni logo sivuston vasemmassa reunassa, joka näytettään rullattaessa alaspäin. Sen pitäisi olla neliö muodoltaan. Jos jätetään tyhjäksi, näytetään sen tilalla koti-merkki." + favicon_url: "Palstan favicon, katso http://fi.wikipedia.org/wiki/Favicon, täytyy olla png toimiakseen CDN:n kanssa" mobile_logo_url: "Logo mobiilisivun vasemmassa ylälaidassa. Sen pitäisi olla neliön muotoinen. Jos jätetään tyhjäksi, 'logo_url' käytetään sen tilalla. Esim: http://example.com/uploads/default/logo.png" apple_touch_icon_url: "Applen laitteiden käyttämä ikoni, Suositeltu koko on 144px kertaa 144px." notification_email: "Sähköpostiosoite, josta kaikki tärkeät järjestelmän lähettämät sähköpostiviestit lähetetään. Verkkotunnuksen SPF, DKIM ja reverse PTR tietueiden täytyy olla kunnossa, jotta sähköpostit menevät perille." @@ -749,6 +763,7 @@ fi: notify_mods_when_user_blocked: "Jos käyttäjä estetään automaattisesti, lähetä viesti kaikille valvojille." flag_sockpuppets: "Jos uusi käyttäjä vastaa toisen uuden käyttäjän luomaan ketjun samasta IP osoitteesta, liputa molemmat viestit mahdolliseksi roskapostiksi." traditional_markdown_linebreaks: "Käytä perinteisiä rivinvaihtoja Markdownissa, joka vaatii kaksi perättäistä välilyöntiä rivin vaihtoon." + allow_html_tables: "Salli taulukoiden syöttäminen Markdowniin käyttäen HTML tageja, TABLE, THEAD, TD, TR, TH on valkolistattu (edellyttää kaikkien taulukoita sisältävien vanhojen viestien uudelleen rakentamisen)" post_undo_action_window_mins: "Kuinka monta minuuttia käyttäjällä on aikaa perua viestiin kohdistuva toimi (tykkäys, liputus, etc)." must_approve_users: "Henkilökunnan täytyy hyväksyä kaikki uudet tilit, ennen uusien käyttäjien päästämistä sivustolle. VAROITUS: tämän asetuksen valitseminen poistaa pääsyn kaikilta jo olemassa olevilta henkilökuntaan kuulumattomilta käyttäjiltä." ga_tracking_code: "Google analytics (ga.js) seurantakoodi, esim.: UA-12345678-9; katso http://google.com/analytics" @@ -759,6 +774,7 @@ fi: enable_noscript_support: "Ota käyttöön noscript-tagi hakukoneiden webcrawlereille" allow_moderators_to_create_categories: "Salli valvojien luoda uusia alueita" cors_origins: "Salli lähteet CORS-pyynnöille (cross-origin request). Jokaisen lähteen pitää sisältää http:// tai https://. DISCOURSE_ENABLE_CORS asetus pitää olla valittuna ottaaksesi CORSin käyttöön." + use_admin_ip_whitelist: "Ylläpitäjät voivat kirjautua vain IP osoitteista, jotka on määritetty Seulottavien IP:iden listassa (Ylläpito > Lokit > Seulottavat IP:t)" top_menu: "Mitkä painikkeet näytetään kotisivun navigointipalkissa, ja missä järjestyksessä. Esimerkiksi latest|new|unread|categories|top|read|posted|bookmarks" post_menu: "Mitkä painikkeet näytetään viestin valikossa, ja missä järjestyksessä. Esimerkiksi like|edit|flag|delete|share|bookmark|reply" post_menu_hidden_items: "Piilotettavat painikkeet viestin valikosta, kunnes '...' klikataan." @@ -791,6 +807,8 @@ fi: invite_passthrough_hours: "Kuinka pitkään käyttäjä voi käyttää vastatun kutsun avainta kirjautuakseen sisään, tunneissa" invite_only: "Julkinen rekisteröityminen on otettu pois käytöstä, uuden tilin luominen vaatii kutsun muilta käyttäjiltä tai henkilökunnalta." login_required: "Vaadi kirjautumista sivuston lukemiseen, epää anonyymi pääsy." + min_username_length: "Käyttäjänimen vähimmäispituus merkeissä." + max_username_length: "Käyttäjänimen enimmäispituus merkeissä." reserved_usernames: "Käyttäjänimet, joiden rekisteröintiä ei sallita." min_password_length: "Salasanan vähimmäispituus." block_common_passwords: "Älä salli salasanoja, jotka ovat 10 000 yleisimmän salasanan joukossa." @@ -805,6 +823,7 @@ fi: sso_not_approved_url: "Uudelleenohjaa hyväksymättömät SSO-tilit tähän osoitteeseen" enable_local_logins: "Ota käyttöön käyttäjätunnus/salasana -perusteinen kirjautuminen. (Huom: tämän täytyy olla käytössä kutsujen toimimiseksi)" allow_new_registrations: "Salli uusien käyttäjien rekisteröityminen. Ota tämä asetus pois käytöstä estääksesi uusien käyttäjätilien luomisen." + enable_signup_cta: "Näytä palaaville anonyymeille käyttäjille ilmoitus, jossa kehoitetaan heitä luomaan tili." enable_yahoo_logins: "Ota käyttöön Yahoo kirjautuminen" enable_google_oauth2_logins: "Ota käyttöön Google Oauth2 tunnistautuminen. Tämä on Googlen tällä hetkellä tukeva metodi. Vaatii key ja secret." google_oauth2_client_id: "Google-applikaatiosi Client ID." @@ -820,6 +839,7 @@ fi: github_client_secret: "Client secret Github autentikaatioon, rekisteröinti osoitteessa https://github.com/settings/applications" 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." @@ -852,6 +872,8 @@ fi: s3_region: "Amazon S3 region, jota käytetään kuvien sijoittamisessa." s3_cdn_url: "CDN URL, jota käytetään S3:ssa sijaitseville tiedostoille (esimerkiksi https://cdn.jossain.com). VAROITUS: tämän asetuksen muuttamisen jälkeen sinun täytyy rakentaa uudelleen kaikki vanhat viestit." avatar_sizes: "Profiilikuvista automaattisesti luotavat koot." + external_system_avatars_enabled: "Käytä ulkopuolista avatarpalvelua." + external_system_avatars_url: "Ulkoisen avatarpalvelun URL. Sallitut vaihdokset ovat {username} {first_letter} {color} {size}" enable_flash_video_onebox: "Ota käyttöön swf- ja flv-linkkien (Adobe Flash) onebox-tuki. VAROITUS: saattaa lisätä tietoturvariskejä." default_invitee_trust_level: "Oletus luottamustaso (0-4) kutsutuille käyttäjille." default_trust_level: "Uusien käyttäjien oletusarvoinen luottamustaso (0-4). VAROITUS! Tämän muuttaminen altistaa roskapostille." @@ -919,6 +941,8 @@ fi: newuser_spam_host_threshold: "Kuinka monta kertaa uusi käyttäjä voi lisätä linkin samaan isäntään `newuser_spam_host_posts` viesteissään, kunnes se tulkitaan roskapostin lähettämiseksi." white_listed_spam_host_domains: "Lista verkkotunnuksista, joita ei oteta huomioon roskapostin tunnistamisessa. Uusilla käyttäjillä ei ole rajoituksia linkkaamisessa näihin tunnuksiin." staff_like_weight: "Kuinka suuri ylimääräinen arvo on henkilökunnan tykkäyksillä." + topic_view_duration_hours: "Laske uusi ketjun katselu kerran per IP/käyttäjä joka N:s tunti" + user_profile_view_duration_hours: "Laske uusi profiilin katselu kerran per IP/käyttäjä joka N:s tunti" levenshtein_distance_spammer_emails: "Verrattaessa sähköpostiosoitteita tunnettuihin roskapostittajiin, näin monen merkin ero saa vielä aikaan löydöksen." max_new_accounts_per_registration_ip: "Jos samasta IP osoitteesta on jo (n) luottamustason 0 käyttäjätiliä (eikä yhtään henkilökunnan tai vähintään LT2), lakkaa hyväksymästä uusia rekisteröitymisiä tästä IP:stä." min_ban_entries_for_roll_up: "Kun Kääri-painiketta painetaan, luodaan IP-porttikielloista aliverkon kattavia kieltoja jos kieltoja on asettu vähintään (N) määrä." @@ -1011,6 +1035,21 @@ fi: approve_post_count: "Viestien lukumäärä, joka tarkastetaan uusilta käyttäjiltä" approve_unless_trust_level: "Tätä luottamustasoa alhaisempien käyttäjien viestit tarkastetaan" notify_about_queued_posts_after: "Jos hyväksyntää odottavia viestejä on odottanut näin monta tuntia, lähetä sähköposti contact_email osoitteeseen. Aseta 0 ottaaksesi pois käytöstä." + default_email_digest_frequency: "Kuinka usein käyttäjille lähetetään tiivistelmäsähköposti oletuksena." + default_email_private_messages: "Lähetä oletuksena sähköposti, kun joku lähettää käyttäjälle viestin." + default_email_direct: "Lähetä oletuksena sähköposti, kun joku lainaa/vastaa/mainitsee tai kutsuu käyttäjän." + default_email_mailing_list_mode: "Lähetä oletuksena sähköposti jokaisesta uudesta viestistä." + default_email_always: "Lähetä oletuksena sähköposti, vaikka käyttäjä on ollut aktiivinen palstalla." + default_other_new_topic_duration_minutes: "Yleinen oletusarvo sille, koska ketju tulkitaan uudeksi." + default_other_auto_track_topics_after_msecs: "Yleinen oletusarvo sille, missä ajassa ketjua aletaan seurata." + default_other_external_links_in_new_tab: "Avaa oletuksena ulkopuoliset linkit uudessa välilehdessä." + default_other_enable_quoting: "Ota oletuksena käyttöön lainaaminen valitsemalla tekstiä." + default_other_dynamic_favicon: "Näytä oletuksena uusien/päivittyneiden ketjujen määrä selaimen ikonissa." + default_other_disable_jump_reply: "Älä hyppää oletuksena uuteen vastaukseen käyttäjän lähetettyä sen." + default_other_edit_history_public: "Tee oletuksena viestien versioista julkisia." + default_categories_watching: "Lista oletuksena tarkkailtavista alueista." + default_categories_tracking: "Lista oletuksena seurattavista alueista." + default_categories_muted: "Lista oletuksena vaimennetuista alueista." errors: invalid_email: "Sähköpostiosoite ei kelpaa." invalid_username: "Tällä nimellä ei löydy käyttäjää." @@ -1132,6 +1171,10 @@ fi: characters: "täytyy koostua vain numeroista, kirjaimista ja alaviivoista" unique: "täytyy olla uniikki" blank: "pakollinen kenttä" + must_begin_with_alphanumeric: "täytyy alkaa kirjaimella, numerolla tai alaviivalla" + must_end_with_alphanumeric: "täytyy loppua kirjaimeen, numeroon tai alaviivaan" + must_not_contain_two_special_chars_in_seq: "ei saa sisältää peräkkäin kahta tai useampaa erikoismerkkiä (.-_)" + must_not_contain_confusing_suffix: "ei saa sisältää hämäävää liitettä, kuten .json tai .png etc." email: not_allowed: "ei sallita tältä sähköpostin palvelunatarjoajalta. Ole hyvä, ja käytä toista sähköpostiosoitetta." blocked: "ei ole sallittu." @@ -1485,6 +1528,7 @@ fi: Tälle sähköpostiosoitteelle ei löydy käyttäjätiliä. Yritä lähettää sähköposti toisesta osoitteesta tai ota yhteyttä henkilökuntaan. email_reject_empty: subject_template: "[%{site_name}] Sähköpostiongelma -- Ei sisältöä" + text_body_template: "Pahoittelemme, mutta sähköpostin lähettäminen tänne %{destination} (otsikolla %{former_title}) ei onnistunut.\n\nEmme löytäneet sähköpostiviestistäsi sisältöä. \n\nJos saat tämän viestin ja viestisi _sisälsi_ sisältöä, yritä uudestaan yksinkertaisemmalla muotoilulla.\n\n" email_reject_parsing: subject_template: "[%{site_name}] Sähköpostiongelma -- Tunnistamaton sisältö" text_body_template: | @@ -1606,6 +1650,10 @@ fi: download_remote_images_disabled: subject_template: "Linkattujen kuvien lataaminen on otettu pois käytöstä" text_body_template: "Asetus `download_remote_images_to_local` on otettu pois käytöstä, koska vapaan tilan rajoitus `download_remote_images_threshold` saavutettiin." + unsubscribe_link: | + Lopettaaksesi nämä viestit, vieraile [käyttäjäasetuksissasi](%{user_preferences_url}). + + Jos et halua enää saada ilmoituksia tästä kyseisestä ketjusta, [klikkaa tähän](%{unsubscribe_url}). subject_re: "VS:" subject_pm: "[YV]" user_notifications: @@ -1792,11 +1840,14 @@ fi: unauthorized: "Pahoittelut, tiedostomuoto ei ole sallittu (sallitut tiedostopäätteet: %{authorized_extensions})." pasted_image_filename: "Liitetty kuva" store_failure: "Latauksen #%{upload_id} käyttäjälle #%{user_id} tallentaminen epäonnistui." + file_missing: "Pahoittelut, sinun täytyy valita tiedosto joka ladataan." attachments: too_large: "Pahoittelut, tiedosto jonka latausta yritit on liian suuri ( suurin tiedostokoko on %{max_size_kb}KB)." images: too_large: "Pahoittelut, kuva jonka yritit ladata on liian suuri (suurin sallittu kuvakoko on %{max_size_kb}KB), pienennä kuvaa ja yritä uudestaan." size_not_found: "Pahoittelut, mutta emme pystyneet selvittämään kuvan kokoa. Ehkä kuvatiedosto on vahingoittunut?" + avatar: + missing: "Pahoittelut, mutta profiilikuvaa, jonka yritit valita ei ole palvelimella. Voitko yrittää ladata sen uudestaan?" flag_reason: sockpuppet: "Uusi käyttäjä loi ketjun ja toinen uusi käyttäjä samasta IP osoitteesta vastasi siihen. Katso asetus flag_sockpuppets." spam_hosts: "Tämä uusi käyttäjä yritti luoda useita viestejä, joissa oli linkkejä samaan verkkotunnukseen. Katso asetus newuser_spam_host_threshold." @@ -1832,6 +1883,29 @@ fi: title: "Käyttöehdot" privacy_topic: title: "Rekisteriseloste" + static: + search_help: | +

Vinkkejä

+

+

    +
  • Otsikon vastaavuutta priorisoidaan – jos olet epävarma, etsi otsikkoa
  • +
  • Uniikit, harvinaiset sanat tuottavat parhaan lopputuloksen
  • +
  • Kokeile etsimistä tietyn alueen, ketjun tai käyttäjän viesteistä/li> +
+

+

Vaihtoehdot/h3> +

+ + + + + + + +
order:viewsorder:latestorder:likes
status:openstatus:closedstatus:archivedstatus:norepliesstatus:single_user
category:foouser:foogroup:foobadge:foo
in:likesin:postedin:watchingin:trackingin:private
in:bookmarksin:first
posts_count:nummin_age:daysmax_age:days
+

+

+ sateenkaaria category:puistot status:open order:latest hakee ketjuja, joissa käytetään sanaa "sateenkaaria" alueella "puistot" ja joita ei ole suljettu tai arkistoitu järjestettynä ketjun viimeisimmän viestin päivämäärän mukaan.

admin_login: success: "Sähköposti lähetetty" error: "Virhe!" diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index 0d25be8244..94a192fcb2 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -1010,7 +1010,6 @@ fr: enable_cdn_js_debugging: "Autoriser /logs à afficher correctement les erreurs en ajoutant des permissions de crossorigin sur toutes les inclusions de js." show_create_topics_notice: "Si le site contient moins de 5 sujets publics, afficher un message pour demander aux administrateurs de créer d'autres sujets." delete_drafts_older_than_n_days: Supprimer les brouillons plus vieux que (n) jours. - show_logout_in_header: "Afficher le lien de déconnexion dans la liste utilisateur du header" vacuum_db_days: "Exécuter VACUUM FULL ANALYZE pour récupérer de l'espace dans la base de donnée après une migration (0 pour désactivé) " prevent_anons_from_downloading_files: "Refuser le téléchargement de pièces jointes aux utilisateurs anonymes. ATTENTION: cela empêchera de fonctionner les ressources envoyées en pièce jointe qui ne sont pas des images." slug_generation_method: "Choisissez une méthode de génération d'identifiant. \"encodé\" générera des chaines de caractères encodées avec des pourcentages. \"aucune\" désactivera complètement les identifiants." diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index 7cc94cc1f9..4b5a4cb31c 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -997,7 +997,6 @@ he: enable_cdn_js_debugging: "אפשרו ל-/logs להציג שגיאות בצורה נכונה באמצעות הוספת הרשאות לגישה בין אתרים (crossorigin permissions) בכל ה-js הכלולים." show_create_topics_notice: "אם לאתר פחות מ-5 נושאים פומביים, הציגו הודעה המבקשת מן המנהלים/מנהלות ליצור עוד נושאים." delete_drafts_older_than_n_days: Delete drafts older than (n) days. - show_logout_in_header: "Show log out in user dropdown in header" vacuum_db_days: "הריצו VACUUM FULL ANALYZE כדי להשיב שטח בסיס הנתונים אחרי העברות (כוונו ל-0 כדי לנטרל)" prevent_anons_from_downloading_files: "מונע ממשתמשים אנונימיים להוריד צרופות (attachments). אזהרה: דבר זה ימנע מכל משאב שאינו תמונה ופורסם כצרופה לעבוד." slug_generation_method: "Choose a slug generation method. 'encoded' will generate percent encoding string. 'none' will disable slug at all." diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index f2f2729300..0dd6cff0a9 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -395,7 +395,7 @@ it: email_title: 'Un messaggio "%{title}" richiede l''attenzione dei moderatori' email_body: "%{link}\n\n%{message}" bookmark: - title: 'Segnalibri' + title: 'Segnalibro' description: 'Messaggio nei segnalibri' long_form: 'Messaggio inserito nei segnalibri' like: @@ -880,7 +880,7 @@ it: pop3_polling_host: "L'host a cui collegarsi per ricevere email via POP3." pop3_polling_username: "Il nome utente dell'account POP3 per la ricezione di email." pop3_polling_password: "La password dell'account POP3 per la ricezione di email." - log_mail_processing_failures: "Traccia all'indirizzo http://yoursitename.com/logs tutti i fallimenti di processamento delle email" + log_mail_processing_failures: "Traccia tutti i fallimenti di lavorazione delle email all'indirizzo http://tuosito.com/logs " email_in: "Permetti agli utenti di pubblicare nuovi argomenti per email (richiede il pop3 polling). Configura gli indirizzi nella scheda \"Impostazioni\" di ciascuna categoria." email_in_min_trust: "Livello di esperienza minimo necessario affinché un utente possa pubblicare nuovi argomenti via email." email_prefix: "La [etichetta] utilizzata nell'oggetto delle email. Se non impostata sarà 'titolo'." @@ -893,7 +893,7 @@ it: allow_uploaded_avatars: "Permetti agli utenti di caricare immagini di profilo personalizzate." allow_animated_avatars: "Permetti agli utenti di utilizzare gif animate come immagini di profilo. ATTENZIONE: lanciare il comando rake \"avatars:refresh\" dopo aver cambiato questa impostazione." allow_animated_thumbnails: "Genera miniature animate delle gif animate." - default_avatars: "URL degli avatar che verranno utilizzati come predefiniti per tutti i nuovi utenti, fintanto che non li cambieranno esplicitamente." + default_avatars: "URL degli avatar che verranno utilizzati come predefiniti per i nuovi utenti, fintanto che non li cambieranno esplicitamente." automatically_download_gravatars: "Scarica i Gravatars per gli utenti quando viene creato l'account o quando viene modificata l'email" digest_topics: "Numero massimo di argomenti da mostrare nel riassunto email." allow_profile_backgrounds: "Permetti agli utenti di caricare immagini di sfondo per il profilo." diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index e359d44e12..7ea99340a1 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -494,6 +494,10 @@ nl: title: "Nieuwe Gebruikers" xaxis: "Dag" yaxis: "Aantal leden" + profile_views: + title: "Gebruikersprofielen bekeken" + xaxis: "Dag" + yaxis: "Aantal gebruikersprofielen bekeken" topics: title: "Topics" xaxis: "Dag" @@ -1165,6 +1169,8 @@ nl: subject_template: "Export succesvol afgerond." csv_export_failed: subject_template: "Export mislukt" + email_error_notification: + subject_template: "[%{site_name}] Email probleem -- POP authenticatie fout" too_many_spam_flags: subject_template: "Account geblokkeerd" text_body_template: | diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index 6b8a993c42..dd86929b14 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -731,6 +731,7 @@ pl_PL: topics_per_period_in_top_page: "Liczba tematów wyświetlanych w widoku 'Pokaż więcej' na ekranie popularnych wątków." email_token_valid_hours: "Tokeny resetujące hasło / aktywujące konto są ważne przez (n) godzin." enable_badges: "Włącz system odznak" + enable_whispers: "Zezwól użytkownikom na szepty do członków zespołu serwisu" port: "DEVELOPER ONLY! WARNING! Use this HTTP port rather than the default of port 80. Leave blank for default of 80." force_hostname: "DEVELOPER ONLY! WARNING! Specify a hostname in the URL. Leave blank for default." invite_expiry_days: "Jak długo klucz zaproszenie użytkownika jest ważny, w dniach." diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 19ff795b9f..f29c17aaba 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -314,8 +314,18 @@ pt: title: "líder" change_failed_explanation: "Tentou despromover %{user_name} para '%{new_trust_level}'. Contudo o Nível de Confiança é atualmente '%{current_trust_level}'. %{user_name} irá permanecer em '%{current_trust_level}' - se deseja despromover o utilizador, bloqueie o Nível de Confiança primeiro" rate_limiter: - slow_down: "Realizou esta ação demasiadas vezes, tente novamente mais tarde" + slow_down: "Realizou esta ação demasiadas vezes, tente novamente mais tarde." too_many_requests: "Possuímos um limite diário de número de vezes que uma ação pode ser tomada. Por favor aguarde %{time_left} antes de tentar novamente." + by_type: + first_day_replies_per_day: "Atingiu o número máximo de respostas que um novo utilizador pode criar no seu primeiro dia. Por favor espere %{time_left} antes de tentar novamente." + first_day_topics_per_day: "Atingiu o número máximo de tópicos que um novo utilizador pode criar no seu primeiro dia. Por favor espere %{time_left} antes de tentar novamente." + create_topic: "Está a criar tópicos demasiado rápido. Por favor espere %{time_left} antes de tentar novamente." + create_post: "Está a responder demasiado rápido. Por favor espere %{time_left} antes de tentar novamente." + topics_per_day: "Atingiu o número máximo de novos tópicos de hoje. Por favor espere %{time_left} antes de tentar novamente." + pms_per_day: "Atingiu o número máximo de mensagens de hoje. Por favor espere %{time_left} antes de tentar novamente." + create_like: "Atingiu o número máximo de gostos de hoje. Por favor espere %{time_left} antes de tentar novamente." + create_bookmark: "Atingiu o número máximo de marcações de hoje. Por favor espere %{time_left} antes de tentar novamente." + edit_post: "Atingiu o número máximo de edições de hoje. Por favor espere %{time_left} antes de tentar novamente." hours: one: "1 hora" other: "%{count} horas" @@ -504,6 +514,10 @@ pt: title: "Novos Utilizadores" xaxis: "Dia" yaxis: "Número de novos utilizadores" + profile_views: + title: "Visualizações do Perfil de Utilizador" + xaxis: "Dia" + yaxis: "Número de perfis de utilizador vistos" topics: title: "Tópicos" xaxis: "Dia" @@ -773,6 +787,7 @@ pt: enable_noscript_support: "Ativar o suporte básico para pesquisas em motores de pesquisa através da etiqueta noscript" allow_moderators_to_create_categories: "Permitir que os moderadores criem novas categorias" cors_origins: "Permitidos compartilhamentos de recursos de origem-cruzada (CORS). Cada origem deve incluir http:// ou https://. A variável de ambiente DISCOURSE_ENABLE_CORS tem que estar configurada a verdadeiro para ativar o CORS." + use_admin_ip_whitelist: "Os Administradores só podem iniciar sessão se estiverem num endereço IP definido na lista de IPs Rastreados (Admin > Logs > IPs Rastreados)." top_menu: "Determinar que elementos aparecem na navegação da página principal, e em que ordem. Exemplo últimos|novos|não lidos|categorias|topo|lidos|publicados|marcadores" post_menu: "Determine que itens irão aparecer no menu de mensagens, e em que ordem. Exemplo\ngostar|editar|sinalizar|eliminar|partilhar|marcar|responder" post_menu_hidden_items: "Os elementos do menu a serem escondidos por defeito no menu de mensagens a não ser que se clique na elipse de expansão." @@ -792,7 +807,7 @@ pt: email_token_valid_hours: "Os símbolos para palavra-passe esquecida /conta ativada são válidos por (n) horas." email_token_grace_period_hours: "Os símbolos para palavra-passe esquecida /conta ativada são válidos por um período de carência de (n) horas após serem recuperados" enable_badges: "Ativar o sistema de distintivos" - enable_whispers: "Permitir que os utilizadores sussurrem ao pessoal" + enable_whispers: "Permitir comunicação privada do pessoal dentro de tópico (experimental)" allow_index_in_robots_txt: "Especificar em robots.txt que este sítio permite ser indexado pelos motores de pesquisa." email_domains_blacklist: "Lista de domínios de email que os utilizadores não podem usar para registo de contas. Exemplo: mailinator.com|trashmail.net" email_domains_whitelist: "Lista de domínios de email que os utilizadores DEVEM usar para registar contas. AVISO: Utilizadores com domínios de email diferentes dos listados não serão permitidos!" @@ -941,6 +956,7 @@ pt: white_listed_spam_host_domains: "Lista de domínios excluídos dos testes de spam. Novos utilizadores nunca irão ser restritos da criação de mensagens com hiperligações a esses domínios." staff_like_weight: "Quanto é o fator de ponderação extra a dar aos gostos provenientes do pessoal." topic_view_duration_hours: "Contar uma nova visualização do tópico uma vez por IP/Utilizador a cada N horas" + user_profile_view_duration_hours: "Contar visualização de novo perfil de utilizador uma vez por IP/utilizador a cada N horas" levenshtein_distance_spammer_emails: "Ao fazer a correspondência de e-mails de spam, o número de caracteres de diferença que ainda permitirá uma correspondência difusa." max_new_accounts_per_registration_ip: "Se já há (n) contas com nível de confiança 0 a partir deste IP (e nenhum é um membro do pessoal ou em nível de confiança 2 ou superior), parar de aceitar novos registos a partir desse IP." min_ban_entries_for_roll_up: "Ao clicar no botão Agrupar, irá criar uma nova entrada de sub-rede banida se houver pelo menos (N) entradas." @@ -1025,7 +1041,6 @@ pt: enable_cdn_js_debugging: "Permitir que /logs exiba erros próprios ao adicionar permissões de origem-cruzada em todos os js incluídos." show_create_topics_notice: "Se o sítio tem menos de 5 tópicos públicos, mostrar um aviso pedindo aos administradores a criação de mais tópicos." delete_drafts_older_than_n_days: Eliminar rascunhos mais antigos que (n) days. - show_logout_in_header: "Mostrar Terminar Sessão no menu suspenso do utilizador no cabeçalho" vacuum_db_days: "Executar VACUUM FULL ANALYZE para reclamar espaço na Base de Dados após a migração (configurar a 0 para desativar)" prevent_anons_from_downloading_files: "Previna que utilizadores anónimos descarreguem anexos. AVISO: isto irá fazer com que quaisquer atributos (que não sejam imagens) publicados como anexos não funcionem." slug_generation_method: "Escolha um método de geração slug. 'encoded' irá gerar sequências de caracteres com código percentual. 'none' irá desativar slug por completo." diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index 02b701b952..649a8f842c 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -69,6 +69,13 @@ ru: other: '%{count} ошибок препятствуют сохранению %{model}' embed: load_from_remote: "Произошла ошибка при загрузке сообщения." + site_settings: + min_username_length_exists: "Нельзя установить минимальную длину псевдонимов, превышующую самый короткий уже существующий псевдоним." + min_username_length_range: "Нельзя установить минимум больше максимума." + max_username_length_exists: "Нельзя установить максимальную длину псевдонимов меньше, чем длина уже существующего самого длинного псевдонима." + max_username_length_range: "Нельзя установить максимум ниже минимума." + default_categories_already_selected: "Нельзя выбрать раздел, используемый в другом списке." + s3_upload_bucket_is_required: "Нельзя включить загрузки на S3, пока не задана настройка 's3_upload_bucket'." bulk_invite: file_should_be_csv: "Загружаемый файл должен быть в формате CSV или TXT." backup: @@ -192,14 +199,22 @@ ru: - Конструктивная критика приветствуется, но не забывайте критиковать *идеи*, а не людей. Больше информации вы найдете в нашем [руководстве пользователя](/guidelines). Данное окно будет показываться пока вы создаете свои первые %{education_posts_text}. + avatar: | + ### Как насчет вашей личной картинки? + + Мы видим, что вы уже написали несколько тем и сообщений, но ваша аватарка не особо привлекает - сейчас на ней просто изображена буква из вашего псевдонима или имени. + + Рекомендуем проследовать на **[страницу вашей учетной записи](%{profile_path})** и загрузить картинку, которая что-то о вас говорит. Это может быть или смайлик, или ваша фотография, или любая другая картинка. + + На самом деле, следить за дискуссиями и находить интересных собеседников намного проще, когда у всех форумчан есть своя уникальная картинка профиля! Ваша аватарка - это ваш характер и облик на форуме. sequential_replies: | - ### Рекомендуем вам совмещать ответы в одном сообщении + ### Лучше совмещать несколько ответов в один - Вместо множества отдельных сообщений с ответами, пожалуйста, используйте одно сообщение, которое включает в себя цитирование предыдущих сообщений или обращения по имени участника через механизм @имя. + Вместо того, чтобы писать и отправлять много отдельных ответов подряд, можно ответить всем по порядку в одном сообщении. Для этого нужно вставлять цитату, затем ответ на нее, затем снова цитату, затем ответ, и т.д. Также, можно упоминать участников диалога через их @псевдонимы. - Вы можете изменить свой предыдущий ответ добавив цитирование просто выделяя требуемый текст и нажимая на всплывающую кнопку ответить цитированием. + Вместо нового ответа, сейчас можно начать редактировать свой предыдущий ответ и просто добавить в него новые цитаты и ответы та них. Для этого нужно выделить требуемый текст и нажать на появившуюся кнопку ответить цитированием. - Для большинства участников проще читать темы, где есть небольшое число больших ответов вместо большого числа маленьких индивидуальных. + Для большинства читатей, намного проще читать темы, в которых длинные ответы и их мало, чем когда много коротеньких ответов. dominating_topic: |2 Данная тема является для вас важной – количество ваших ответов превышает %{percent}% процентов. @@ -426,6 +441,7 @@ ru: many: "почти %{count} лет назад" other: "почти %{count} лет назад" password_reset: + no_token: "К сожалению, данная ссылка на изменение пароля устарела. Нажмите на кнопку \"Войти\", а затем на \"Я забыл свой пароль\", чтобы сгенерировать новую ссылку для изменения пароля." choose_new: "Придумайте новый пароль" choose: "Придумайте пароль" update: 'Обновить пароль' @@ -439,6 +455,7 @@ ru: please_continue: "Перейти на %{site_name}" error: "При смене электронного адреса произошла ошибка. Возможно, этот адрес уже используется?" activation: + action: "Нажмите сюда, чтобы активировать вашу учетную запись" already_done: "Извините, ссылка на активацию учетной записи устарела. Возможно, ваша учетная запись уже активирована?" please_continue: "Ваша новая учетная запись успешно активирована, вы будете перенаправлены на главную страницу." continue_button: "Перейти на %{site_name}" @@ -531,6 +548,10 @@ ru: title: "Регистрации" xaxis: "Дата" yaxis: "Количество регистраций" + profile_views: + title: "Просмотры профилей" + xaxis: "Дата" + yaxis: "Количество просмотров профилей пользователей" topics: title: "Темы" xaxis: "Дата" @@ -779,6 +800,7 @@ ru: notify_mods_when_user_blocked: "Отправить сообщение всем модераторам, если пользователь заблокирован автоматически." flag_sockpuppets: "Если новый пользователь отвечает на тему с того же IP адреса как и новый пользователь, кто начал тему, отметить оба их сообщения как возможный спам." traditional_markdown_linebreaks: "Использовать стандартный способ переноса строки в Markdown: строка должна заканчиваться двумя пробелами, чтобы перенос строки после нее сработал." + allow_html_tables: "Разрешить создание таблиц в разметке Markdown с использованием HTML тегов. Разрешенные теги: TABLE, THEAD, TD, TR, TH (потребуется заново произвести HTML-обработку всех старых сообщений с таблицами)" post_undo_action_window_mins: "Количество минут, в течение которых пользователь может отменить действия, связанные с сообщениями: 'Мне нравится', 'Жалоба' и др." must_approve_users: "Персонал должен подтвердить регистрации новых пользователей перед тем, как им будет разрешен доступ к сайту.\nВНИМАНИЕ: включая данную функцию на существуещем сайте будет закрыт доступ к сайту для всех пользователей, кроме персонала." ga_tracking_code: "Google analytics (ga.js) tracking code код, например: UA-12345678-9; смотрите http://google.com/analytics" @@ -808,6 +830,7 @@ ru: email_token_valid_hours: "Ссылка на восстановление пароля / активацию аккаунта будет действовать в течении (n) часов." email_token_grace_period_hours: "Ссылка на восстановление пароля / активацию аккаунта будет действовать в течении (n) часов." enable_badges: "Включить систему наград" + enable_whispers: "Разрешить пользователям отсылать внутренние сообщения персоналу (такие сообщения будут только им и видны)" allow_index_in_robots_txt: "Разрешить поисковикам индексировать сайт в robots.txt" email_domains_blacklist: "Список почтовых доменов, с которых запрещена регистрация учетных записей. Пример: mailinator.com trashmail.net" email_domains_whitelist: "Список почтовых доменов, с которых ДОЛЖНА производиться регистрация учетных записей.\nВНИМАНИЕ: Пользователям других почтовых доменов регистрация будет недоступна. " @@ -821,6 +844,8 @@ ru: invite_passthrough_hours: "Как долго пользователь может воспользоваться ключем приглашения для входа на сайт, указывается в часах" invite_only: "Публичная регистрация отключена. Регистрация только по приглашениям. Новые пользователи могут получить приглашения для регистрации от существующих пользователей или от редакции сайта." login_required: "Требовать авторизации для доступа к сайту, анонимный доступ запретить." + min_username_length: "Минимально допустимая длина псевдонима, символов." + max_username_length: "Максимально допустимая длина псевдонима, символов." reserved_usernames: "Псевдонимы, запрещенные для регистрации" min_password_length: "Минимальная длина пароля" block_common_passwords: "Не позволять использовать пароли из списка 10 000 самых частоиспользуемых паролей." @@ -845,6 +870,7 @@ ru: github_client_secret: "Клиентский секрет для идентификации с Github, зарегистрированный на https://github.com/settings/applications" allow_restore: "Позволить импорт, который может заменить ВСЕ данные сайта. Оставьте выключенным, если не планируете восстанавливать резервную копию" maximum_backups: "Максимальное количество резервных копий к сохранению. Более старые резервные копии будут автоматически удалены." + backup_frequency: "Как часто создавать резервные копии форума, в днях" enable_s3_backups: "Загружать резервные копии на S3 по завершению. Убедитесь, что настройки S3 заполнены." s3_backup_bucket: "Адрес папки удаленного сервера для резервных копий. ВНИМАНИЕ: Убедитесь, что место назначения защищено от посторонних." active_user_rate_limit_secs: "Как часто мы обновляем поле 'last_seen_at', в секундах" @@ -973,6 +999,7 @@ ru: username_change_period: "Количество дней после регистрации, когда пользователь сможет изменить свой псевдоним (0 - запретить изменение псевдонима)." email_editable: "Позволять пользователям изменять свой адрес электронной почты после регистрации." logout_redirect: "Страница на которую будет перенаправлен пользователь после выхода, т.е. перейдет по ссылке (http://somesite.com/logout)" + allow_uploaded_avatars: "Разрешить пользователям загружать свои собственные картинки профиля." allow_animated_thumbnails: "Генерировать анимированные миниатюры gif-картинок." default_avatars: "URL для аватара, который будет использован по умолчанию для новых пользователей, пока они не изменят его." automatically_download_gravatars: "Скачивать аватарку Gravatar пользователя во время создания учетной записи или изменения e-mail." @@ -980,6 +1007,7 @@ ru: digest_min_excerpt_length: "Минимальная длина (в символах) вытяжки из сообщения в письме - сводке новостей." suppress_digest_email_after_days: "Не рассылать новости для пользователей, которые не заходили на сайт в течении (n) дней." disable_digest_emails: "Отключить рассылку новостей для всех пользователей." + detect_custom_avatars: "Проверять ли, что пользователи загрузили свои собственные картинки профиля (аватарки)." max_daily_gravatar_crawls: "Максимальное количество загрузок аватаорок с Gravatar за один день" public_user_custom_fields: "Список разрешенных дополнительных полей пользователей, которые могут быть отображены публично." staff_user_custom_fields: "Список разрешенных дополнительных полей пользователей, которые могут быть отображены для модераторов." @@ -1190,6 +1218,13 @@ ru: Это персональное приглашение от зарегистрированного пользователя, которому мы доверяем, поэтому вам не понадобиться авторизоваться на сайте. invite_password_instructions: subject_template: "Задайте пароль для вашей учетной записи на сайте %{site_name}" + text_body_template: | + Спасибо, что приняли приглашение на сайт "%{site_name}" и добро пожаловать! + + Нажмите на следующую ссылку, чтобы установить свой пароль: + %{base_url}/users/password-reset/%{email_token} + + Если срок действия этой ссылки истек, нажмите на кнопку "Войти", а затем "Я забыл свой пароль" и введите ваш e-mail. test_mailer: subject_template: "[%{site_name}] Проверка доставки писем" text_body_template: | @@ -1545,6 +1580,10 @@ ru: download_remote_images_disabled: subject_template: "Загрузка копий изображений выключена" text_body_template: "Настройка `download_remote_images_to_local` была отключена, т.к. диск заполнился до отменки, указанной в настройке `download_remote_images_threshold`." + unsubscribe_link: | + Отписаться от подобных писем можно в ваших [настройках профиля](%{user_preferences_url}). + + Чтобы отключить письма с уведомлениями по этой конкретной теме, [нажмите сюда](%{unsubscribe_url}). subject_re: "Re:" subject_pm: "[PM]" user_notifications: @@ -1703,6 +1742,7 @@ ru: (Если вам требуется помощь [администрации](%{base_url}/about), вы можете задать свой вопрос, просто ответив на данное письмо.) signup: + subject_template: "[%{site_name}] Активируйте свою учетную запись" text_body_template: | Добро пожаловать на сайт %{site_name}! diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index 28d96fba60..f7b517db82 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -126,6 +126,7 @@ sv: num_posts: "Inlägg:" num_participants: "Deltagare" read_full_topic: "Läs hela ämnet" + private_message_abbrev: "Meddelande" rss_description: latest: "Aktuella ämnen" hot: "Heta ämnen" @@ -268,7 +269,7 @@ sv: title: "ledare" change_failed_explanation: "Du försökte degradera %{user_name} till '%{new_trust_level}'. Användarens förtroendenivå är redan '%{current_trust_level}'. %{user_name} kommer behålla '%{current_trust_level}'. Om du vill degradera användaren, lås förtroendenivån först" rate_limiter: - slow_down: "Du har utfört den här handlingen för många gånger, var vänligen och försök igen senare." + slow_down: "Du har utfört den här handlingen för många gånger, försök igen senare." too_many_requests: "Du gör det där för ofta. Var god vänta %{time_left} innan du försöker igen." hours: one: "1 timme" diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index b47160534c..e667c501b0 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -957,7 +957,6 @@ tr_TR: enable_cdn_js_debugging: "/logs 'ların asli hataları tüm js içeriklerine crossorigin izinleri ekleyerek göstermesine izin ver." show_create_topics_notice: "Eğer sitede herkese açık konu sayısı 5'den az ise, adminden yeni konular oluşturmasını isteyen bir uyarı mesajı göster. " delete_drafts_older_than_n_days: (n) günden eski taslakları sil. - show_logout_in_header: "Başlık çubuğundaki kullanıcı açılır listesinde oturumu kapatı göster" vacuum_db_days: "Geçiş sonra DB alanı geri kazanmak için TAM VAKUM ANALİZİ'ni çalıştırın (devre dışı bırakmak için 0 girin)" prevent_anons_from_downloading_files: "Anonim kullanıcıların eklenti indirebilmesini önle. DİKKAT: Bu ayar, eklenti olarak gönderilen resim-dışı site içeriklerinin de çalışmasını engelleyebilir." slug_generation_method: "Slug üretim yöntemi seçin. 'kodlanmış' seçeneği yüzde kodlamalı metin oluşturur. 'hiçbiri' seçeneği slug'ı devre dışı bırakır." diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index 0fed9c0499..3e2c12e31b 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -302,8 +302,18 @@ zh_CN: title: "领导" change_failed_explanation: "你尝试将 %{user_name} 降至 '%{new_trust_level}'。然而他们的信任等级已经是 '%{current_trust_level}'。%{user_name} 将仍是 '%{current_trust_level}' —— 如果你想要降级用户,先锁定信任等级" rate_limiter: - slow_down: "你已经重复这一操作太多次了,请一会再重试" + slow_down: "你执行这个操作太多次了,请稍后再试。" too_many_requests: "你的请求过于频繁,请等待 %{time_left} 之后再试。" + by_type: + first_day_replies_per_day: "你回帖的数量已经超出身为新用户当天允许的上限,请等待%{time_left}之后再试。" + first_day_topics_per_day: "你创建新主题的数量已经超出身为新用户当天允许的上限,请等待%{time_left}之后再试。" + create_topic: "你创建新主题太快了,请等待%{time_left}之后再试。" + create_post: "你回帖太快了,请等待%{time_left}之后再试。" + topics_per_day: "你今天新主题的数量已经到达上限,请等待%{time_left}之后再试。" + pms_per_day: "你今天的消息数量已经到达上限,请等待%{time_left}之后再试。" + create_like: "你今天的“赞”数量已经到达上限,请等待%{time_left}之后再试。" + create_bookmark: "你今天的收藏已经到达上限,请等待%{time_left}之后再试。" + edit_post: "你今天编辑帖子的数量已经到达上限,请等待%{time_left}之后再试。" hours: other: "%{count} 小时" minutes: @@ -467,6 +477,10 @@ zh_CN: title: "新用户" xaxis: "天" yaxis: "新用户数" + profile_views: + title: "查看用户资料" + xaxis: "天" + yaxis: "已看的用户资料数量" topics: title: "新发表主题" xaxis: "天" @@ -736,6 +750,7 @@ zh_CN: enable_noscript_support: "通过 no script 标签启用对标准爬虫的支持。" allow_moderators_to_create_categories: "允许版主创建新的分类" cors_origins: "允许跨源请求(CORS)。每个源必须包括 http:// 或 https://。DISCOURSE_ENABLE_CORS 环境变量必须设置为 true 才能启用 CORS 政策。" + use_admin_ip_whitelist: "当目前的IP刚好处于IP段禁止名单(Admin > Logs > Screened Ips)中的时候,只有管理员才可登入。" top_menu: "确定在主页导航条包含哪些条目,以及排列顺序。例如:latest|new|unread|categories|top|read|posted|bookmarks" post_menu: "确定在帖子菜单条包含哪些条目,以及排列顺序。例如:like|edit|flag|delete|share|bookmark|reply" post_menu_hidden_items: "帖子菜单中默认隐藏的按钮,点击省略号后显示。" @@ -755,7 +770,7 @@ zh_CN: email_token_valid_hours: "“忘记密码”/“激活账户” token 有效的小时数(n)。" email_token_grace_period_hours: "“忘记密码”/“激活账户” token 在使用后仍旧有效的小时数(n)。" enable_badges: "启用徽章系统" - enable_whispers: "允许用户发送密语给职员" + enable_whispers: "允许在主题中给职员密频。(experimental)" allow_index_in_robots_txt: "在 robots.txt 中详细指出这个站点允许被网页搜索引擎检索。" email_domains_blacklist: "用管道符“|”分隔的邮箱域名黑名单列表,其中的域名将不能用来注册账户,例如:mailinator.com|trashmail.net" email_domains_whitelist: "用管道符“|”分隔的电子邮箱域名的列表,用户必须使用这些邮箱域名注册。警告:用户使用不包含在这个列表里的邮箱域名,将无法成功注册。" @@ -904,6 +919,7 @@ zh_CN: white_listed_spam_host_domains: "广告主机白名单域名列表。新用户可以任意链接至这些域名。" staff_like_weight: "职员赞时的额外权重。" topic_view_duration_hours: "按照每 IP/用户每 N 小时来记录一次新的主题访问" + user_profile_view_duration_hours: "按照每 IP/用户每 N 小时来记录用户资料访问数" levenshtein_distance_spammer_emails: "当匹配广告邮件时,模糊匹配判断差异的字符数。" max_new_accounts_per_registration_ip: "如果已经有了从这个 IP 创建的(n)个信任等级0的账户(并且没有一个是职员或者是信任等级2以上的用户),不再允许来自该 IP 地址的注册请求。" min_ban_entries_for_roll_up: "当点击折叠按钮时,且至少 (N) 条记录时,将会创建一个子网封禁记录" @@ -988,7 +1004,6 @@ zh_CN: enable_cdn_js_debugging: "为包含的 js 启动跨源访问 /logs 权限以显示合适的错误。" show_create_topics_notice: "如果站点只有少于 5 篇的公开帖子时,显示一条请管理员创建帖子的提示。" delete_drafts_older_than_n_days: 删除超过 n 天得草稿。 - show_logout_in_header: "在顶栏的用户下拉菜单中显示登出" vacuum_db_days: "在数据库迁移后使用完整扫描回收数据库空间(设置 0 为禁用)" prevent_anons_from_downloading_files: "禁止匿名用户下载附件。警告:这将禁止他们访问任何发表在帖子中的非图片资源。" slug_generation_method: "选择一个链接生成方式。“encoded”将生成以百分号编码的链接。“none”将禁用自定义链接,只生成默认链接。" diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index 188a26cd84..3711b486e2 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -61,6 +61,11 @@ zh_TW: other: '%{count} 個錯誤禁止 %{model} 被儲存' embed: load_from_remote: "載入文章時發生了錯誤" + site_settings: + min_username_length_exists: "你不能設比現有用戶名短的「最小用戶名長度」。" + min_username_length_range: "你不能設定「最小」大於「最大」。" + max_username_length_exists: "您不能設置比現有用戶名短的「最大用戶名長度」。" + max_username_length_range: "你不能設定「最大」小於「最小」。" bulk_invite: file_should_be_csv: "上傳的文件應為 csv 或 txt 格式。" backup: @@ -126,6 +131,7 @@ zh_TW: rss_description: latest: "最新討論話題" hot: "熱門討論話題" + posts: " 最近文章" too_late_to_edit: "這文章建立很久了,它已經無法被編輯或刪除" excerpt_image: "圖片" groups: @@ -223,8 +229,9 @@ zh_TW: elder: title: "領先用戶" rate_limiter: - slow_down: "你已經執行這個動作太多次,請稍後再試" too_many_requests: "你的瀏覽速度過於頻繁,請等待 %{time_left} 後再試。" + by_type: + create_post: "你的回覆過快。請等待%{time_left}再重試。" hours: other: "%{count} 小時" minutes: @@ -315,6 +322,7 @@ zh_TW: description: '此帖內容包含對他人的攻擊、侮辱、仇視語言或違反了我們的社群准則。' long_form: '投訴為不當內容' notify_user: + title: '訊息 @{{username}}' description: '此帖包含一些我想與該用戶私下直接交流的內容。不能執行標記操作。' email_title: '你的文章在"%{title}"' email_body: "%{link}\n\n%{message}" @@ -416,18 +424,23 @@ zh_TW: user_to_user_private_messages: title: "用戶對用戶" xaxis: "天" + yaxis: "訊息數量" system_private_messages: title: "系統" xaxis: "天" + yaxis: "訊息數量" moderator_warning_private_messages: title: "板主警告" xaxis: "天" + yaxis: "訊息數量" notify_moderators_private_messages: title: "板主通知" xaxis: "天" + yaxis: "訊息數量" notify_user_private_messages: title: "用戶通知" xaxis: "天" + yaxis: "訊息數量" top_referrers: title: "最高引用" xaxis: "用戶" @@ -540,9 +553,11 @@ zh_TW: allow_user_locale: "允許用戶選擇自己的語言介面" min_post_length: "文章允許的最小文字數" min_first_post_length: "第一篇文章允許的最少文字數" + min_private_message_post_length: "文章允許的最小文字數" max_post_length: "文章允許的最大文字數" min_topic_title_length: "標題允許的最小文字數" max_topic_title_length: "標題允許的最大文字數" + min_private_message_title_length: "標題允許的最小文字數" min_search_term_length: "搜尋條件允許的最小文字數" uncategorized_description: "\"未分類\"的分類的描述,留空則無描述" allow_duplicate_topic_titles: "允許話題有相同,重複的標題" @@ -621,6 +636,7 @@ zh_TW: email_token_valid_hours: "\"忘記密碼\" / \"重啟帳號\" token 有效的小時數 (n)" email_token_grace_period_hours: "\"忘記密碼\" / \"重啟帳號\" 的 token 在使用後仍舊有效的小時數 (n)" enable_badges: "啟用勳章系統" + enable_whispers: "允許用戶與管理團隊密談" allow_index_in_robots_txt: "在 robots.txt 中記錄這個網站允許被搜尋引擎索引的部分" forgot_password_strict: "在找回密碼對話框中不告知用戶帳戶的存在。" log_out_strict: "登出時,登出用戶所有設備上的所有時段" @@ -632,6 +648,8 @@ zh_TW: invite_passthrough_hours: "邀請號碼如已被使用,用戶仍可使用多少小時" invite_only: "已經禁止開放註冊,新的使用者必須取得其他用戶,或是管理員的邀請" login_required: "需要登入才能進入網站,不允許匿名操作" + min_username_length: "最少用戶名長度" + max_username_length: "最大用戶名長度" min_password_length: "最小密碼長度" block_common_passwords: "不允許使用 10,000 個最常用的密碼" enable_sso_provider: "在 /session/sso_provider endpoint 必須設定 sso_secret 以實現 Discourse SSO 提供方協定" @@ -669,6 +687,7 @@ zh_TW: max_bookmarks_per_day: "每個用戶每天最多能建立\"書籤\"的數量" max_edits_per_day: "每個用戶每天最大的\"編輯次數\"的數量" max_topics_per_day: "每個用戶每天最多建立\"討論話題\"的數量" + max_private_messages_per_day: "用戶每天能建立訊息的數量上限" max_invites_per_day: "每個用戶每天最多能邀請用戶的數量。" suggested_topics: "討論話題下的推薦話題數量" limit_suggested_to_category: "目前的話題下只顯示同分類的推薦話題" @@ -950,6 +969,8 @@ zh_TW: reply_by_email: "要回應時,請回覆這封郵件,或在瀏覽器裡開啟 %{base_url}%{url} 網址。" visit_link_to_respond: "要回應時,請在瀏覽器裡開啟 %{base_url}%{url} 網址。" posted_by: "由 %{username} 張貼於 %{post_date}" + user_invited_to_private_message_pm: + subject_template: "[%{site_name}] %{username} 邀請你參與討論 '%{topic_title}'" user_replied: subject_template: "[%{site_name}] %{username} 在 '%{topic_title}' 討論話題回覆了你的文章" text_body_template: | @@ -1060,6 +1081,8 @@ zh_TW: spam_hosts: "此新用戶嘗試發表多篇含有指向相同網域之連結的文章。請見網站設定中的 newuser_spam_host_threshold。" email_log: no_user: "無法找到 ID 為 %{user_id} 的使用者" + anonymous_user: "匿名用戶" + suspended_not_pm: "用戶已被停權,不是訊息" seen_recently: "用戶最近活躍" post_not_found: "無法找到 ID 為 %{post_id} 的文章" notification_already_read: "這封通知 EMail 已被讀取" @@ -1088,6 +1111,20 @@ zh_TW: title: "服務條款" privacy_topic: title: "隱私政策" + badges: + long_descriptions: + nice_post: | + 此徽章授予創建一個能獲取10個讚的回應。幹得不錯! + nice_topic: | + 此徽章授予創建一個能獲取10個讚的話題。幹得不錯! + good_post: | + 此徽章授予創建一個能獲取25個讚的回應。幹得好! + good_topic: | + 此徽章授予創建一個能獲取25個讚的話題。幹得好! + great_post: | + 此徽章授予創建一個能獲取50個讚的又章。勁! + great_topic: | + 此徽章授予創建一個能獲取50個讚的回應。勁! admin_login: success: "已發送郵件" error: "錯誤" diff --git a/config/routes.rb b/config/routes.rb index 187b51d2cd..c65d39004c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -113,7 +113,7 @@ Discourse::Application.routes.draw do resources :impersonate, constraints: AdminConstraint.new - resources :email do + resources :email, constraints: AdminConstraint.new do collection do post "test" get "all" @@ -365,6 +365,7 @@ Discourse::Application.routes.draw do get "excerpt" => "excerpt#show" + resources :post_action_users resources :post_actions do collection do get "users" @@ -534,6 +535,7 @@ Discourse::Application.routes.draw do get "favicon/proxied" => "static#favicon", format: false get "robots.txt" => "robots_txt#index" + get "manifest.json" => "manifest_json#index", as: :manifest Discourse.filters.each do |filter| root to: "list##{filter}", constraints: HomePageConstraint.new("#{filter}"), :as => "list_#{filter}" diff --git a/config/site_settings.yml b/config/site_settings.yml index cff228d7d9..91a7d07d6d 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -656,6 +656,9 @@ security: cors_origins: default: '' type: list + use_admin_ip_whitelist: + default: false + client: true onebox: enable_flash_video_onebox: false @@ -761,7 +764,7 @@ developer: migrate_to_new_scheme: hidden: true default: false - max_tracked_new_unread: + max_new_topics: default: 500 client: true hidden: true @@ -856,6 +859,7 @@ uncategorized: previous_visit_timeout_hours: 1 staff_like_weight: 3 topic_view_duration_hours: 8 + user_profile_view_duration_hours: 8 # Summary mode summary_score_threshold: 15 @@ -978,10 +982,6 @@ uncategorized: default: -1 hidden: true - show_logout_in_header: - default: false - client: true - user_preferences: default_email_digest_frequency: enum: 'DigestEmailSiteSetting' diff --git a/db/migrate/20150914021445_create_user_profile_views.rb b/db/migrate/20150914021445_create_user_profile_views.rb new file mode 100644 index 0000000000..04c085a53b --- /dev/null +++ b/db/migrate/20150914021445_create_user_profile_views.rb @@ -0,0 +1,15 @@ +class CreateUserProfileViews < ActiveRecord::Migration + def change + create_table :user_profile_views do |t| + t.integer :user_profile_id, null: false + t.datetime :viewed_at, null: false + t.inet :ip_address, null: false + t.integer :user_id + end + + add_index :user_profile_views, :user_profile_id + add_index :user_profile_views, :user_id + add_index :user_profile_views, [:viewed_at, :ip_address, :user_profile_id], where: "user_id IS NULL", unique: true, name: 'unique_profile_view_ip' + add_index :user_profile_views, [:viewed_at, :user_id, :user_profile_id], where: "user_id IS NOT NULL", unique: true, name: 'unique_profile_view_user' + end +end diff --git a/db/migrate/20150914034541_add_views_to_user_profile.rb b/db/migrate/20150914034541_add_views_to_user_profile.rb new file mode 100644 index 0000000000..3a13f6c136 --- /dev/null +++ b/db/migrate/20150914034541_add_views_to_user_profile.rb @@ -0,0 +1,5 @@ +class AddViewsToUserProfile < ActiveRecord::Migration + def change + add_column :user_profiles, :views, :integer, default: 0, null: false + end +end diff --git a/db/migrate/20150917071017_add_category_id_to_user_histories.rb b/db/migrate/20150917071017_add_category_id_to_user_histories.rb new file mode 100644 index 0000000000..117edef54a --- /dev/null +++ b/db/migrate/20150917071017_add_category_id_to_user_histories.rb @@ -0,0 +1,6 @@ +class AddCategoryIdToUserHistories < ActiveRecord::Migration + def change + add_column :user_histories, :category_id, :integer + add_index :user_histories, :category_id + end +end diff --git a/db/migrate/20150924022040_add_fancy_title_to_topic.rb b/db/migrate/20150924022040_add_fancy_title_to_topic.rb new file mode 100644 index 0000000000..49863f89f5 --- /dev/null +++ b/db/migrate/20150924022040_add_fancy_title_to_topic.rb @@ -0,0 +1,5 @@ +class AddFancyTitleToTopic < ActiveRecord::Migration + def change + add_column :topics, :fancy_title, :string, limit: 400, null: true + end +end diff --git a/docs/INSTALL-cloud.md b/docs/INSTALL-cloud.md index 5e1e474c5e..96a4a3fdea 100644 --- a/docs/INSTALL-cloud.md +++ b/docs/INSTALL-cloud.md @@ -72,9 +72,9 @@ After completing your edits, press CtrlO then Enter 1 + parsed.address == email && parsed.local != parsed.address && parsed.domain && parsed.domain.split(".").length > 1 + rescue Mail::Field::ParseError + false end def self.downcase(email) diff --git a/lib/freedom_patches/i18n_fallbacks.rb b/lib/freedom_patches/i18n_fallbacks.rb index b8aa691835..859e816c08 100644 --- a/lib/freedom_patches/i18n_fallbacks.rb +++ b/lib/freedom_patches/i18n_fallbacks.rb @@ -1,3 +1,5 @@ +# This should be used until https://github.com/svenfuchs/i18n/pull/326 is merged into the I18n gem. + module I18n module Backend module Fallbacks diff --git a/lib/guardian/category_guardian.rb b/lib/guardian/category_guardian.rb index 63bbd5be79..c24c652b61 100644 --- a/lib/guardian/category_guardian.rb +++ b/lib/guardian/category_guardian.rb @@ -54,8 +54,11 @@ module CategoryGuardian # all allowed category ids def allowed_category_ids - unrestricted = Category.where(read_restricted: false).pluck(:id) - unrestricted.concat(secure_category_ids) + @allowed_category_ids ||= + begin + unrestricted = Category.where(read_restricted: false).pluck(:id) + unrestricted.concat(secure_category_ids) + end end def topic_create_allowed_category_ids diff --git a/lib/html_prettify.rb b/lib/html_prettify.rb new file mode 100644 index 0000000000..6d0b3d065b --- /dev/null +++ b/lib/html_prettify.rb @@ -0,0 +1,407 @@ +# heavily based off +# https://github.com/vmg/redcarpet/blob/master/ext/redcarpet/html_smartypants.c +# and +# https://github.com/jmcnevin/rubypants/blob/master/lib/rubypants/core.rb +# 99% of the code here is by Jeremy McNevin +# +# This Source File is available under BSD/MIT license as well as standard GPL +# + +class HtmlPrettify < String + def self.render(html) + new(html).to_html + end + + # Create a new RubyPants instance with the text in +string+. + # + # Allowed elements in the options array: + # + # 0 :: do nothing + # 1 :: enable all, using only em-dash shortcuts + # 2 :: enable all, using old school en- and em-dash shortcuts (*default*) + # 3 :: enable all, using inverted old school en and em-dash shortcuts + # -1 :: stupefy (translate HTML entities to their ASCII-counterparts) + # + # If you don't like any of these defaults, you can pass symbols to change + # RubyPants' behavior: + # + # :quotes :: quotes + # :backticks :: backtick quotes (``double'' only) + # :allbackticks :: backtick quotes (``double'' and `single') + # :dashes :: dashes + # :oldschool :: old school dashes + # :inverted :: inverted old school dashes + # :ellipses :: ellipses + # :convertquotes :: convert " entities to + # " + # :stupefy :: translate RubyPants HTML entities + # to their ASCII counterparts. + # + # In addition, you can customize the HTML entities that will be injected by + # passing in a hash for the final argument. The defaults for these entities + # are as follows: + # + # :single_left_quote :: + # :double_left_quote :: + # :single_right_quote :: + # :double_right_quote :: + # :em_dash :: + # :en_dash :: + # :ellipsis :: + # :html_quote :: " + # + def initialize(string, options=[2], entities = {}) + super string + + @options = [*options] + @entities = default_entities.update(entities) + end + + # Apply SmartyPants transformations. + def to_html + do_quotes = do_backticks = do_dashes = do_ellipses = nil + + if @options.include?(0) + # Do nothing. + return self + elsif @options.include?(1) + # Do everything, turn all options on. + do_quotes = do_backticks = do_ellipses = true + do_dashes = :normal + elsif @options.include?(2) + # Do everything, turn all options on, use old school dash shorthand. + do_quotes = do_backticks = do_ellipses = true + do_dashes = :oldschool + elsif @options.include?(3) + # Do everything, turn all options on, use inverted old school + # dash shorthand. + do_quotes = do_backticks = do_ellipses = true + do_dashes = :inverted + elsif @options.include?(-1) + do_stupefy = true + else + do_quotes = @options.include?(:quotes) + do_backticks = @options.include?(:backticks) + do_backticks = :both if @options.include?(:allbackticks) + do_dashes = :normal if @options.include?(:dashes) + do_dashes = :oldschool if @options.include?(:oldschool) + do_dashes = :inverted if @options.include?(:inverted) + do_ellipses = @options.include?(:ellipses) + do_stupefy = @options.include?(:stupefy) + end + + # Parse the HTML + tokens = tokenize + + # Keep track of when we're inside
 or  tags.
+    in_pre = false
+
+    # Here is the result stored in.
+    result = ""
+
+    # This is a cheat, used to get some context for one-character
+    # tokens that consist of just a quote char. What we do is remember
+    # the last character of the previous text token, to use as context
+    # to curl single- character quote tokens correctly.
+    prev_token_last_char = nil
+
+    tokens.each do |token|
+      if token.first == :tag
+        result << token[1]
+        if token[1] =~ %r!<(/?)(?:pre|code|kbd|script|math)[\s>]!
+          in_pre = ($1 != "/")  # Opening or closing tag?
+        end
+      else
+        t = token[1]
+
+        # Remember last char of this token before processing.
+        last_char = t[-1].chr
+
+        unless in_pre
+
+          t.gsub!("'", "'")
+
+          t = process_escapes t
+
+          t.gsub!(""", '"')
+
+          if do_dashes
+            t = educate_dashes t            if do_dashes == :normal
+            t = educate_dashes_oldschool t  if do_dashes == :oldschool
+            t = educate_dashes_inverted t   if do_dashes == :inverted
+          end
+
+          t = educate_ellipses t  if do_ellipses
+
+          t = educate_fractions t
+
+          # Note: backticks need to be processed before quotes.
+          if do_backticks
+            t = educate_backticks t
+            t = educate_single_backticks t  if do_backticks == :both
+          end
+
+          if do_quotes
+            if t == "'"
+              # Special case: single-character ' token
+              if prev_token_last_char =~ /\S/
+                t = entity(:single_right_quote)
+              else
+                t = entity(:single_left_quote)
+              end
+            elsif t == '"'
+              # Special case: single-character " token
+              if prev_token_last_char =~ /\S/
+                t = entity(:double_right_quote)
+              else
+                t = entity(:double_left_quote)
+              end
+            else
+              # Normal case:
+              t = educate_quotes t
+            end
+          end
+
+          t = stupefy_entities t  if do_stupefy
+        end
+
+        prev_token_last_char = last_char
+        result << t
+      end
+    end
+
+    # Done
+    result
+  end
+
+  protected
+
+  # Return the string, with after processing the following backslash
+  # escape sequences. This is useful if you want to force a "dumb" quote
+  # or other character to appear.
+  #
+  # Escaped are:
+  #      \\    \"    \'    \.    \-    \`
+  #
+  def process_escapes(str)
+    str = str.gsub('\\\\', '\')
+    str.gsub!('\"',   '"')
+    str.gsub!("\\\'", ''')
+    str.gsub!('\.',   '.')
+    str.gsub!('\-',   '-')
+    str.gsub!('\`',   '`')
+    str
+  end
+
+  # The string, with each instance of "--" translated to an
+  # em-dash HTML entity.
+  #
+  def educate_dashes(str)
+    str.
+      gsub(/--/, entity(:em_dash))
+  end
+
+  # The string, with each instance of "--" translated to an
+  # en-dash HTML entity, and each "---" translated to an
+  # em-dash HTML entity.
+  #
+  def educate_dashes_oldschool(str)
+    str.
+      gsub(/---/, entity(:em_dash)).
+      gsub(/--/,  entity(:en_dash))
+  end
+
+  # Return the string, with each instance of "--" translated
+  # to an em-dash HTML entity, and each "---" translated to
+  # an en-dash HTML entity. Two reasons why: First, unlike the en- and
+  # em-dash syntax supported by +educate_dashes_oldschool+, it's
+  # compatible with existing entries written before SmartyPants 1.1,
+  # back when "--" was only used for em-dashes.  Second,
+  # em-dashes are more common than en-dashes, and so it sort of makes
+  # sense that the shortcut should be shorter to type. (Thanks to
+  # Aaron Swartz for the idea.)
+  #
+  def educate_dashes_inverted(str)
+    str.
+      gsub(/---/, entity(:en_dash)).
+      gsub(/--/,  entity(:em_dash))
+  end
+
+  # Return the string, with each instance of "..." translated
+  # to an ellipsis HTML entity. Also converts the case where there are
+  # spaces between the dots.
+  #
+  def educate_ellipses(str)
+    str.
+      gsub('...',   entity(:ellipsis)).
+      gsub('. . .', entity(:ellipsis))
+  end
+
+  # Return the string, with "``backticks''"-style single quotes
+  # translated into HTML curly quote entities.
+  #
+  def educate_backticks(str)
+    str.
+      gsub("``", entity(:double_left_quote)).
+      gsub("''", entity(:double_right_quote))
+  end
+
+  # Return the string, with "`backticks'"-style single quotes
+  # translated into HTML curly quote entities.
+  #
+  def educate_single_backticks(str)
+    str.
+      gsub("`", entity(:single_left_quote)).
+      gsub("'", entity(:single_right_quote))
+  end
+
+  def educate_fractions(str)
+    str.gsub(/(\s+|^)(1\/4|1\/2|3\/4)([,.;\s]|$)/) do
+      frac =
+        if $2 == "1/2".freeze
+          entity(:frac12)
+        elsif $2 == "1/4".freeze
+          entity(:frac14)
+        elsif $2 == "3/4".freeze
+          entity(:frac34)
+        end
+      "#{$1}#{frac}#{$3}"
+    end
+  end
+
+  # Return the string, with "educated" curly quote HTML entities.
+  #
+  def educate_quotes(str)
+    punct_class = '[!"#\$\%\'()*+,\-.\/:;<=>?\@\[\\\\\]\^_`{|}~]'
+
+    # normalize html
+    str = str.dup
+    # Special case if the very first character is a quote followed by
+    # punctuation at a non-word-break. Close the quotes by brute
+    # force:
+    str.gsub!(/^'(?=#{punct_class}\B)/,
+              entity(:single_right_quote))
+    str.gsub!(/^"(?=#{punct_class}\B)/,
+              entity(:double_right_quote))
+
+    # Special case for double sets of quotes, e.g.:
+    #   

He said, "'Quoted' words in a larger quote."

+ str.gsub!(/"'(?=\w)/, + "#{entity(:double_left_quote)}#{entity(:single_left_quote)}") + str.gsub!(/'"(?=\w)/, + "#{entity(:single_left_quote)}#{entity(:double_left_quote)}") + + # Special case for decade abbreviations (the '80s): + str.gsub!(/'(?=\d\ds)/, + entity(:single_right_quote)) + + close_class = %![^\ \t\r\n\\[\{\(\-]! + dec_dashes = "#{entity(:en_dash)}|#{entity(:em_dash)}" + + # Get most opening single quotes: + str.gsub!(/(\s| |=|--|&[mn]dash;|#{dec_dashes}|ȁ[34];)'(?=\w)/, + '\1' + entity(:single_left_quote)) + + # Single closing quotes: + str.gsub!(/(#{close_class})'/, + '\1' + entity(:single_right_quote)) + str.gsub!(/'(\s|s\b|$)/, + entity(:single_right_quote) + '\1') + + # Any remaining single quotes should be opening ones: + str.gsub!(/'/, + entity(:single_left_quote)) + + # Get most opening double quotes: + str.gsub!(/(\s| |=|--|&[mn]dash;|#{dec_dashes}|ȁ[34];)"(?=\w)/, + '\1' + entity(:double_left_quote)) + + # Double closing quotes: + str.gsub!(/(#{close_class})"/, + '\1' + entity(:double_right_quote)) + str.gsub!(/"(\s|s\b|$)/, + entity(:double_right_quote) + '\1') + + # Any remaining quotes should be opening ones: + str.gsub!(/"/, + entity(:double_left_quote)) + + str + end + + # Return the string, with each RubyPants HTML entity translated to + # its ASCII counterpart. + # + # Note: This is not reversible (but exactly the same as in SmartyPants) + # + def stupefy_entities(str) + new_str = str.dup + + { + :en_dash => '-', + :em_dash => '--', + :single_left_quote => "'", + :single_right_quote => "'", + :double_left_quote => '"', + :double_right_quote => '"', + :ellipsis => '...' + }.each do |k,v| + new_str.gsub!(/#{entity(k)}/, v) + end + + new_str + end + + # Return an array of the tokens comprising the string. Each token is + # either a tag (possibly with nested, tags contained therein, such + # as , or a run of text between + # tags. Each element of the array is a two-element array; the first + # is either :tag or :text; the second is the actual value. + # + # Based on the _tokenize() subroutine from Brad Choate's + # MTRegex plugin. + # + # This is actually the easier variant using tag_soup, as used by + # Chad Miller in the Python port of SmartyPants. + # + def tokenize + tag_soup = /([^<]*)(<[^>]*>)/ + + tokens = [] + + prev_end = 0 + + scan(tag_soup) do + tokens << [:text, $1] if $1 != "" + tokens << [:tag, $2] + prev_end = $~.end(0) + end + + if prev_end < size + tokens << [:text, self[prev_end..-1]] + end + + tokens + end + + def default_entities + { + single_left_quote: "‘", + double_left_quote: "“", + single_right_quote: "’", + double_right_quote: "”", + em_dash: "—", + en_dash: "–", + ellipsis: "…", + html_quote: """, + frac12: "½", + frac14: "¼", + frac34: "¾", + } + end + + def entity(key) + @entities[key] + end + +end diff --git a/lib/onebox/engine/discourse_local_onebox.rb b/lib/onebox/engine/discourse_local_onebox.rb index db4075b43e..06d5449468 100644 --- a/lib/onebox/engine/discourse_local_onebox.rb +++ b/lib/onebox/engine/discourse_local_onebox.rb @@ -33,13 +33,14 @@ module Onebox def to_html uri = URI::parse(@url) route = Rails.application.routes.recognize_path(uri.path) - + url = @url.sub(/[&?]source_topic_id=(\d+)/, "") + source_topic_id = $1.to_i # Figure out what kind of onebox to show based on the URL case route[:controller] when 'topics' - linked = "#{@url}" + linked = "#{url}" if route[:post_number].present? && route[:post_number].to_i > 1 # Post Link post = Post.find_by(topic_id: route[:topic_id], post_number: route[:post_number].to_i) @@ -56,7 +57,9 @@ module Onebox excerpt.gsub!("[/quote]", "[quote]") quote = "[quote=\"#{post.user.username}, topic:#{topic.id}, slug:#{slug}, post:#{post.post_number}\"]#{excerpt}[/quote]" - cooked = PrettyText.cook(quote) + args = {} + args[:topic_id] = source_topic_id if source_topic_id > 0 + cooked = PrettyText.cook(quote, args) return cooked else @@ -77,7 +80,7 @@ module Onebox end quote = post.excerpt(SiteSetting.post_onebox_maxlength) - args = { original_url: @url, + args = { original_url: url, title: topic.title, avatar: PrettyText.avatar_img(topic.user.avatar_template, 'tiny'), posts_count: topic.posts_count, diff --git a/lib/oneboxer.rb b/lib/oneboxer.rb index 50aa8bc7b0..5557b8e750 100644 --- a/lib/oneboxer.rb +++ b/lib/oneboxer.rb @@ -76,12 +76,30 @@ module Oneboxer doc end - def self.apply(string_or_doc) + def self.append_source_topic_id(url, topic_id) + # hack urls to create proper expansions + if url =~ Regexp.new("^#{Discourse.base_url.gsub(".","\\.")}.*$", true) + uri = URI.parse(url) rescue nil + if uri && uri.path + route = Rails.application.routes.recognize_path(uri.path) rescue nil + if route && route[:controller] == 'topics' + url += (url =~ /\?/ ? "&" : "?") + "source_topic_id=#{topic_id}" + end + end + end + url + end + + def self.apply(string_or_doc, args=nil) doc = string_or_doc doc = Nokogiri::HTML::fragment(doc) if doc.is_a?(String) changed = false Oneboxer.each_onebox_link(doc) do |url, element| + + if args && args[:topic_id] + url = append_source_topic_id(url, args[:topic_id]) + end onebox, _preview = yield(url,element) if onebox parsed_onebox = Nokogiri::HTML::fragment(onebox) diff --git a/lib/plugin/auth_provider.rb b/lib/plugin/auth_provider.rb index 367f9a6516..8db104f3ef 100644 --- a/lib/plugin/auth_provider.rb +++ b/lib/plugin/auth_provider.rb @@ -1,9 +1,25 @@ class Plugin::AuthProvider - attr_accessor :glyph, :background_color, :title, - :message, :frame_width, :frame_height, :authenticator + + def self.auth_attributes + [:glyph, :background_color, :title, :message, :frame_width, :frame_height, :authenticator, + :title_setting, :enabled_setting] + end + + attr_accessor(*auth_attributes) def name authenticator.name end + def to_json + result = {name: name} + result['titleOverride'] = title if title + result['titleSetting'] = title_setting if title_setting + result['enabledSetting'] = enabled_setting if enabled_setting + result['messageOverride'] = message if message + result['frameWidth'] = frame_width if frame_width + result['frameHeight'] = frame_height if frame_height + result.to_json + end + end diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index d51adc4f13..0b0493b6e2 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -218,13 +218,8 @@ class Plugin::Instance js = javascripts.join("\n") auth_providers.each do |auth| - overrides = "" - overrides = ", titleOverride: '#{auth.title}'" if auth.title - overrides << ", messageOverride: '#{auth.message}'" if auth.message - overrides << ", frameWidth: '#{auth.frame_width}'" if auth.frame_width - overrides << ", frameHeight: '#{auth.frame_height}'" if auth.frame_height - js << "Discourse.LoginMethod.register(Discourse.LoginMethod.create({name: '#{auth.name}'#{overrides}}));\n" + js << "Discourse.LoginMethod.register(Discourse.LoginMethod.create(#{auth.to_json}));\n" if auth.glyph css << ".btn-social.#{auth.name}:before{ content: '#{auth.glyph}'; }\n" @@ -305,7 +300,8 @@ class Plugin::Instance def auth_provider(opts) provider = Plugin::AuthProvider.new - [:glyph, :background_color, :title, :message, :frame_width, :frame_height, :authenticator].each do |sym| + + Plugin::AuthProvider.auth_attributes.each do |sym| provider.send "#{sym}=", opts.delete(sym) end auth_providers << provider diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 21b07963f5..22ace0dad3 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -176,7 +176,7 @@ class PostCreator cooking_options = post.cooking_options || {} cooking_options[:topic_id] = post.topic_id - post.cooked ||= post.cook(post.raw, cooking_options) + post.cooked ||= post.cook(post.raw, cooking_options.symbolize_keys) post.sort_order = post.post_number post.last_version_at ||= Time.now end diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index 1b43959482..0881296e8b 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -39,6 +39,18 @@ module PrettyText username = username.downcase User.exec_sql('SELECT 1 FROM users WHERE username_lower = ?', username).values.length == 1 end + + def get_topic_info(topic_id) + return unless Fixnum === topic_id + # TODO this only handles public topics, secured one do not get this + topic = Topic.find_by(id: topic_id) + if topic && Guardian.new.can_see?(topic) + { + title: topic.title, + href: topic.url + } + end + end end @mutex = Mutex.new @@ -184,6 +196,7 @@ module PrettyText context.eval('opts["mentionLookup"] = function(u){return helpers.is_username_valid(u);}') context.eval('opts["lookupAvatar"] = function(p){return Discourse.Utilities.avatarImg({size: "tiny", avatarTemplate: helpers.avatar_template(p)});}') + context.eval('opts["getTopicInfo"] = function(i){return helpers.get_topic_info(i)};') baked = context.eval('Discourse.Markdown.markdownConverter(opts).makeHtml(raw)') end diff --git a/lib/rate_limiter.rb b/lib/rate_limiter.rb index e8b51f0f49..df678960d7 100644 --- a/lib/rate_limiter.rb +++ b/lib/rate_limiter.rb @@ -27,9 +27,14 @@ class RateLimiter $redis.delete_prefixed(RateLimiter.key_prefix) end - def initialize(user, key, max, secs) + def build_key(type) + "#{RateLimiter.key_prefix}:#{@user && @user.id}:#{type}" + end + + def initialize(user, type, max, secs) @user = user - @key = "#{RateLimiter.key_prefix}:#{@user && @user.id}:#{key}" + @type = type + @key = build_key(type) @max = max @secs = secs end @@ -53,7 +58,7 @@ class RateLimiter # let's ensure we expire this key at some point, otherwise we have leaks $redis.expire(@key, @secs * 2) else - raise LimitExceeded.new(seconds_to_wait) + raise RateLimiter::LimitExceeded.new(seconds_to_wait, @type) end end diff --git a/lib/rate_limiter/limit_exceeded.rb b/lib/rate_limiter/limit_exceeded.rb index 2b85b467b0..ad7a000577 100644 --- a/lib/rate_limiter/limit_exceeded.rb +++ b/lib/rate_limiter/limit_exceeded.rb @@ -2,9 +2,30 @@ class RateLimiter # A rate limit has been exceeded. class LimitExceeded < StandardError - attr_accessor :available_in - def initialize(available_in) + + def initialize(available_in, type=nil) @available_in = available_in + @type = type + end + + def description + + time_left = "" + if @available_in < 1.minute.to_i + time_left = I18n.t("rate_limiter.seconds", count: @available_in) + elsif @available_in < 1.hour.to_i + time_left = I18n.t("rate_limiter.minutes", count: (@available_in / 1.minute.to_i)) + else + time_left = I18n.t("rate_limiter.hours", count: (@available_in / 1.hour.to_i)) + end + + if @type.present? + type_key = @type.gsub(/-/, '_') + msg = I18n.t("rate_limiter.by_type.#{type_key}", time_left: time_left, default: "") + return msg if msg.present? + end + + I18n.t("rate_limiter.too_many_requests", time_left: time_left) end end diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index 90be6860fc..437cc51193 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -373,6 +373,7 @@ module SiteSettingExtension def clear_cache! SiteText.text_for_cache.clear Rails.cache.delete(SiteSettingExtension.client_settings_cache_key) + Site.clear_anon_cache! end def diff_hash(new_hash, old) diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake index a6fc5d09ff..b220d322d4 100644 --- a/lib/tasks/assets.rake +++ b/lib/tasks/assets.rake @@ -162,7 +162,7 @@ task 'assets:precompile' => 'assets:precompile:before' do STDERR.puts "Compressing: #{file}" # We can specify some files to never minify - unless to_skip.include?(info['logical_path']) + unless (ENV["DONT_MINIFY"] == "1") || to_skip.include?(info['logical_path']) FileUtils.mv(path, _path) compress(_file,file) end diff --git a/lib/tasks/posts.rake b/lib/tasks/posts.rake index 5f661e5500..37fb02bfaf 100644 --- a/lib/tasks/posts.rake +++ b/lib/tasks/posts.rake @@ -8,6 +8,24 @@ task 'posts:refresh_oneboxes' => :environment do ENV['RAILS_DB'] ? rebake_posts(invalidate_oneboxes: true) : rebake_posts_all_sites(invalidate_oneboxes: true) end +desc 'Rebake all posts with a quote using a letter_avatar' +task 'posts:fix_letter_avatars' => :environment do + return unless SiteSetting.external_system_avatars_enabled + + search = Post.where("user_id <> -1") + .where("raw LIKE '%/letter\_avatar/%' OR cooked LIKE '%/letter\_avatar/%'") + + rebaked = 0 + total = search.count + + search.order(updated_at: :asc).find_each do |post| + rebake_post(post) + print_status(rebaked += 1, total) + end + + puts "", "#{rebaked} posts done!", "" +end + def rebake_posts_all_sites(opts = {}) RailsMultisite::ConnectionManagement.each_connection do |db| rebake_posts(opts) @@ -33,7 +51,7 @@ def rebake_posts(opts = {}) puts "", "#{rebaked} posts done!", "-" * 50 end -def rebake_post(post, opts) +def rebake_post(post, opts = {}) post.rebake!(opts) rescue => e puts "", "Failed to rebake (topic_id: #{post.topic_id}, post_id: #{post.id})", e, e.backtrace.join("\n") diff --git a/lib/topic_creator.rb b/lib/topic_creator.rb index cff51e7c89..d8a7bb96df 100644 --- a/lib/topic_creator.rb +++ b/lib/topic_creator.rb @@ -150,7 +150,7 @@ class TopicCreator def add_users(topic, usernames) return unless usernames - User.where(username: usernames.split(',')).each do |user| + User.where(username: usernames.split(',').flatten).each do |user| check_can_send_permission!(topic, user) @added_users << user topic.topic_allowed_users.build(user_id: user.id) diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 73d47fd4ad..434fdbb0ee 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -46,6 +46,7 @@ class TopicQuery options.assert_valid_keys(VALID_OPTIONS) @options = options.dup @user = user + @guardian = Guardian.new(@user) end def joined_topic_user(list=nil) @@ -359,7 +360,7 @@ class TopicQuery when 'unlisted' result = result.where('NOT topics.visible') when 'deleted' - guardian = Guardian.new(@user) + guardian = @guardian if guardian.is_staff? result = result.where('topics.deleted_at IS NOT NULL') require_deleted_clause = false @@ -391,7 +392,7 @@ class TopicQuery result = result.where('topics.posts_count <= ?', options[:max_posts]) if options[:max_posts].present? result = result.where('topics.posts_count >= ?', options[:min_posts]) if options[:min_posts].present? - Guardian.new(@user).filter_allowed_categories(result) + @guardian.filter_allowed_categories(result) end def remove_muted_categories(list, user, opts=nil) diff --git a/lib/topic_view.rb b/lib/topic_view.rb index 24a49d24cf..2a9e70cd96 100644 --- a/lib/topic_view.rb +++ b/lib/topic_view.rb @@ -186,7 +186,8 @@ class TopicView result[g[0]] = g[1] end end - result + + @group_names = result end # Find the sort order for a post in the topic diff --git a/lib/version.rb b/lib/version.rb index f1e2c578f4..908352cc31 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -5,7 +5,7 @@ module Discourse MAJOR = 1 MINOR = 5 TINY = 0 - PRE = 'beta1' + PRE = 'beta2' STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/plugins/poll/config/locales/client.ar.yml b/plugins/poll/config/locales/client.ar.yml index 4c3a9ec5e4..40918ade4f 100644 --- a/plugins/poll/config/locales/client.ar.yml +++ b/plugins/poll/config/locales/client.ar.yml @@ -25,9 +25,27 @@ ar: average_rating: "متوسط التصنيف: %{average} " multiple: help: - at_least_min_options: "من المفترض أن تختار على الأقل %{count} خيارات ." - up_to_max_options: "يجب عليك إختيار على الأكثر %{count} خيارات ." - x_options: "يجب عليك إختيار %{count} خيارات ." + at_least_min_options: + zero: "لا يجب عليك اختيار أي خيار." + one: "يجب عليك أن تختار خيار واحد على الأقل." + two: "يجب عليك أن تختار خياران على الأقل." + few: "يجب عليك أن تختار %{count} بعض الخيارات على الأقل." + many: "يجب عليك أن تختار %{count} عدة خيارات على الأقل." + other: "يجب عليك الاختيار على الأقل." + up_to_max_options: + zero: "يمكنك عدم إختيار أي من الخيارات." + one: "يمكنك إختيار مايصل إلى خيار واحد ." + two: "يمكنك إختيار مايصل إلى خياران." + few: "يمكنك إختيار مايصل إلى %{count} خيار ." + many: "يمكنك إختيار مايصل إلى %{count} خيارات ." + other: "يمكنك إختيار مايصل إلى %{count} خيار ." + x_options: + zero: "لا يجب عليك إختيار أي خيار." + one: "يجب عليك إختيار خيارواحدخياران فقط." + few: "يجب عليك إختيار %{count} بعض الخيارات." + many: "يجب عليك إختيار %{count} عدة خيارات." + other: "يجب عليك إختيار %{count} خيارات أخرى." between_min_and_max_options: "يجب عليك إختيار بين %{min} و %{max} خيارات ." cast-votes: title: "إدراج تصويتك ." diff --git a/plugins/poll/config/locales/client.da.yml b/plugins/poll/config/locales/client.da.yml index 7f31c2d0f9..4747966e3a 100644 --- a/plugins/poll/config/locales/client.da.yml +++ b/plugins/poll/config/locales/client.da.yml @@ -17,9 +17,15 @@ da: average_rating: "Gennemsnitlig rating: %{average}." multiple: help: - at_least_min_options: "Du skal mindst vælge %{count} muligheder." - up_to_max_options: "Du kan vælge op til %{count} muligheder." - x_options: "Du skal vælge %{count} muligheder." + at_least_min_options: + one: "Du skal mindst vælge 1 mulighed." + other: "Du skal mindst vælge %{count} muligheder." + up_to_max_options: + one: "Du kan vælge op til 1 mulighed." + other: "Du kan vælge op til %{count} muligheder." + x_options: + one: "Du skal vælge 1 mulighed." + other: "Du skal vælge %{count} muligheder." between_min_and_max_options: "Du kan vælge mellem %{min} og %{max} muligheder." cast-votes: title: "Afgiv dine stemmer" @@ -31,7 +37,7 @@ da: title: "Tilbage til dine stemmer" label: "Skjul resultat" open: - title: "Åbn afstemning" + title: "Åbn afstemningen" label: "Åbn" confirm: "Er du sikker på, at du vil åbne denne afstemning?" close: diff --git a/plugins/poll/config/locales/client.de.yml b/plugins/poll/config/locales/client.de.yml index 7833ece385..9b8ef70d00 100644 --- a/plugins/poll/config/locales/client.de.yml +++ b/plugins/poll/config/locales/client.de.yml @@ -17,9 +17,15 @@ de: average_rating: "Durchschnittliche Bewertung: %{average}" multiple: help: - at_least_min_options: "Du musst mindestens %{count} Optionen auswählen." - up_to_max_options: "Du kannst bis zu %{count} Optionen auswählen." - x_options: "Du musst %{count} Optionen auswählen." + at_least_min_options: + one: "Du musst mindestens eine Option auswählen." + other: "Du musst mindestens %{count} Optionen auswählen." + up_to_max_options: + one: "Du kannst genau eine Option auswählen." + other: "Du kannst bis zu %{count} Optionen auswählen." + x_options: + one: "Du musst eine Option auswählen." + other: "Du musst %{count} Optionen auswählen." between_min_and_max_options: "Du kannst zwischen %{min} und %{max} Optionen auswählen." cast-votes: title: "Gib deine Stimmen ab" diff --git a/plugins/poll/config/locales/client.en.yml b/plugins/poll/config/locales/client.en.yml index 586a8dfaf5..59b508c885 100644 --- a/plugins/poll/config/locales/client.en.yml +++ b/plugins/poll/config/locales/client.en.yml @@ -29,9 +29,15 @@ en: multiple: help: - at_least_min_options: "You must choose at least %{count} options." - up_to_max_options: "You may choose up to %{count} options." - x_options: "You must choose %{count} options." + at_least_min_options: + one: "You must choose at least 1 option." + other: "You must choose at least %{count} options." + up_to_max_options: + one: "You may choose up to 1 option." + other: "You may choose up to %{count} options." + x_options: + one: "You must choose 1 option." + other: "You must choose %{count} options." between_min_and_max_options: "You may choose between %{min} and %{max} options." cast-votes: diff --git a/plugins/poll/config/locales/client.fi.yml b/plugins/poll/config/locales/client.fi.yml index 1b31427f79..e686f8e6a6 100644 --- a/plugins/poll/config/locales/client.fi.yml +++ b/plugins/poll/config/locales/client.fi.yml @@ -17,9 +17,15 @@ fi: average_rating: "Keskivertoarvio: %{average}." multiple: help: - at_least_min_options: "Sinun täytyy valita vähintään %{count} vaihtoehtoa." - up_to_max_options: "Voit valita enintään %{count} vaihtoehtoa." - x_options: "Sinun täytyy valita %{count} vaihtoehtoa." + at_least_min_options: + one: "Sinun täytyy valita vähintään yksi vaihtoehto." + other: "Sinun täytyy valita vähintään %{count} vaihtoehtoa." + up_to_max_options: + one: "Voit valita enintään yhden vaihtoehdon." + other: "Voit valita enintään %{count} vaihtoehtoa." + x_options: + one: "Sinun täytyy valita yksi vaihtoehto." + other: "Sinun täytyy valita %{count} vaihtoehtoa." between_min_and_max_options: "Voit valita %{min}-%{max}%{average}." multiple: help: - at_least_min_options: "Deve escolher pelo menos %{count} opções." - up_to_max_options: "Pode escolher até %{count} opções." - x_options: "Deve escolher %{count} opções." + at_least_min_options: + one: "Deve escolher pelo menos 1 opção." + other: "Deve escolher pelo menos %{count} opções." + up_to_max_options: + one: "Pode escolher até 1 opção." + other: "Pode escolher até %{count} opções." + x_options: + one: "Deve escolher 1 opção." + other: "Deve escolher %{count} opções." between_min_and_max_options: "Pode escolher entre %{min} e %{max} opções." cast-votes: title: "Votar" diff --git a/plugins/poll/config/locales/client.ru.yml b/plugins/poll/config/locales/client.ru.yml index 8f73a0a443..8dd85117e6 100644 --- a/plugins/poll/config/locales/client.ru.yml +++ b/plugins/poll/config/locales/client.ru.yml @@ -8,13 +8,35 @@ ru: js: poll: - average_rating: "Примерный рейтинг: %{average}." + voters: + one: "голос" + few: "голоса" + many: "голосов" + other: "голосов" + total_votes: + one: "голос" + few: "голоса" + many: "голосов" + other: "голосов" + average_rating: "Средний рейтинг: %{average}." multiple: help: - at_least_min_options: "Вы должны выбрать как минимум %{count} ответов." - up_to_max_options: "Вы можете выбрать не более %{count} вариантов ответов." - x_options: "Вы должны выбрать %{count} варианта ответа." - between_min_and_max_options: "Вы можете выбрать от %{min} до %{max} ответов." + at_least_min_options: + one: "Необходимо выбрать хотя бы 1 ответ." + few: "Необходимо выбрать хотя бы %{count} ответа." + many: "Необходимо выбрать хотя бы %{count} ответов." + other: "Необходимо выбрать хотя бы %{count} ответов." + up_to_max_options: + one: "Можно выбрать только 1 ответ." + few: "Можно выбрать до %{count} ответов." + many: "Можно выбрать до %{count} ответов." + other: "Можно выбрать до %{count} ответов." + x_options: + one: "Необходимо выбрать 1 ответ." + few: "Необходимо выбрать %{count} ответа." + many: "Необходимо выбрать %{count} ответов." + other: "Необходимо выбрать %{count} ответов." + between_min_and_max_options: "Можно выбрать от %{min} до %{max} ответов." cast-votes: title: "Проголосуйте" label: "Голосовать!" @@ -22,15 +44,15 @@ ru: title: "Показать результаты" label: "Показать результаты" hide-results: - title: "Вернуться к голосованию" + title: "Вернуться к опросу" label: "Скрыть результаты" open: - title: "Открыть голосование" + title: "Открыть опрос" label: "Открыть" - confirm: "Вы уверены, что хотите открыть это голосование?" + confirm: "Вы уверены, что хотите открыть этот опрос?" close: - title: "Закрыть голосование" + title: "Закрыть опрос" label: "Закрыть" - confirm: "Вы уверены, что хотите закрыть это голосование?" - error_while_toggling_status: "Произошла ошибка смены статуса голосования." - error_while_casting_votes: "Произошла ошибка в голосовании." + confirm: "Вы уверены, что хотите закрыть этот опрос?" + error_while_toggling_status: "Произошла ошибка при смене статуса опроса." + error_while_casting_votes: "Произошла ошибка во время обработки вашего голоса." diff --git a/plugins/poll/config/locales/client.zh_CN.yml b/plugins/poll/config/locales/client.zh_CN.yml index cd3958a6a3..7a7744543d 100644 --- a/plugins/poll/config/locales/client.zh_CN.yml +++ b/plugins/poll/config/locales/client.zh_CN.yml @@ -15,9 +15,12 @@ zh_CN: average_rating: "平均排名:%{average}。" multiple: help: - at_least_min_options: "你至少要选择 %{count} 个选项。" - up_to_max_options: "你最多可以选择 %{count} 个选项。" - x_options: "你必须选择 %{count} 个选项。" + at_least_min_options: + other: "你必须选择至少 %{count} 个选项。" + up_to_max_options: + other: "你可以选择最多 %{count} 个选项。" + x_options: + other: "你必须选择 %{count} 个选项。" between_min_and_max_options: "你可以选择 %{min}%{max} 个选项。" cast-votes: title: "投你的票" diff --git a/plugins/poll/config/locales/server.ar.yml b/plugins/poll/config/locales/server.ar.yml index 7fcef197a3..b9299d1b51 100644 --- a/plugins/poll/config/locales/server.ar.yml +++ b/plugins/poll/config/locales/server.ar.yml @@ -14,8 +14,20 @@ ar: multiple_polls_with_same_name: "هناك عدة استطلاعات رأي متشابهة بالاسم : %{name}. استخدم خاصية 'name' لتحدي استطلاعك الفريد." default_poll_must_have_at_least_2_options: "استطلاع الرأي يجب أن يكون على الأقل 2 من الخيارات." named_poll_must_have_at_least_2_options: "استطلاع الرأي المسمى %{name} يجب أن يكون على الأقل 2 من الخيارات." - default_poll_must_have_less_options: "استطلاع الرأي يجب أن يكون أقل من %{max} الخيارات." - named_poll_must_have_less_options: "استطلاع الرأي المسمى %{name} يجب أقل من %{max} الخيارات." + default_poll_must_have_less_options: + zero: "التصويت لا يجب أن يكون %{count} بدون خيارات." + one: "التصويت يجب أن يحتوي على %{count} خيار واحد على الأقل." + two: "التصويت يجب أن يحتوي على %{count} خياران على الأقل." + few: "التصويت يجب أن يحتوي على %{count} بعض الخيارات على الأقل." + many: "التصويت يجب أن يحتوي على %{count} عدة خيارات على الأقل." + other: "التصويت يجب أن يحتوي على %{count} خيارت أخرى." + named_poll_must_have_less_options: + zero: "التصويت المسمى %{name} لا يجب أن يكون بدون خيارات." + one: "التصويت المسمى %{name} يجب أن يحتوي على خيار واحد على الأقل." + two: "التصويت المسمى %{name} يجب أن يحتوي على خياران على الأقل." + few: "التصويت المسمى %{name} يجب أن يحتوي على %{count} بعض الخيارات على الأقل." + many: "التصويت المسمى %{name} يجب أن يحتوي على %{count} عدة خيارات على الأقل." + other: "التصويت المسمى %{name} يجب أن يحتوي على %{count} خيارات أخرى." default_poll_must_have_different_options: "استطلاع الرأي يجب أن يكون مختلف الخيارات." named_poll_must_have_different_options: "استطلاع الرأي المسمى %{name} يجب أن يكون مختلف الخيارات." default_poll_with_multiple_choices_has_invalid_parameters: "معلمات استطلاع الرأي ذات الخيار المتعدد غير صالحة." diff --git a/plugins/poll/config/locales/server.da.yml b/plugins/poll/config/locales/server.da.yml index ba7e89bf41..1e32f4ef13 100644 --- a/plugins/poll/config/locales/server.da.yml +++ b/plugins/poll/config/locales/server.da.yml @@ -14,8 +14,12 @@ da: multiple_polls_with_same_name: "Der er flere afstemninger med samme navn %{name}. Brug attributten 'name' for at identificere dine afstemninger." default_poll_must_have_at_least_2_options: "Afstemningen skal mindst have 2 muligheder." named_poll_must_have_at_least_2_options: "Afstemningen %{name} skal mindst have 2 valgmuligheder." - default_poll_must_have_less_options: "Afstemningen skal have mindre end %{max} muligheder." - named_poll_must_have_less_options: "Afstemningen %{name} skal have mindre end %{max} valgmuligheder." + default_poll_must_have_less_options: + one: "Afstemningen må ikke have nogen muligheder." + other: "Afstemningen skal have mindre end %{max} muligheder." + named_poll_must_have_less_options: + one: "Afstemningen %{name} må ikke have nogen valgmuligheder." + other: "Afstemningen %{name} skal have mindre end %{max} valgmuligheder." default_poll_must_have_different_options: "Afstemningen skal have forskellige muligheder." named_poll_must_have_different_options: "Afstemningen %{name} skal have forskellige valgmuligheder." default_poll_with_multiple_choices_has_invalid_parameters: "Afstemning med flere valgmuligheder har ugyldige parametre." diff --git a/plugins/poll/config/locales/server.de.yml b/plugins/poll/config/locales/server.de.yml index 6a7916a897..ade4a865a7 100644 --- a/plugins/poll/config/locales/server.de.yml +++ b/plugins/poll/config/locales/server.de.yml @@ -14,8 +14,12 @@ de: multiple_polls_with_same_name: "Es gibt mehre Umfragen mit dem selben Namen: %{name}. Benutze das Attribute 'name', um deine Umfragen eindeutig identifizierbar zu machen." default_poll_must_have_at_least_2_options: "Umfragen müssen mindestens 2 Optionen haben." named_poll_must_have_at_least_2_options: "Die Umfrage mit dem Namen %{name} muss mindestens 2 Optionen haben." - default_poll_must_have_less_options: "Die Umfrage muss weniger als %{max} Optionen haben." - named_poll_must_have_less_options: "Die Umfrage mit dem Namen %{name} muss weniger als %{max} Optionen haben." + default_poll_must_have_less_options: + one: "Die Umfrage muss weniger als eine Option haben." + other: "Die Umfrage muss weniger als %{count} Optionen haben." + named_poll_must_have_less_options: + one: "Die Umfrage mit dem Namen %{name} muss weniger als eine Option haben." + other: "Die Umfrage mit dem Namen %{name} muss weniger als %{count} Optionen haben." default_poll_must_have_different_options: "Die Umfrage muss unterschiedliche Optionen haben." named_poll_must_have_different_options: "Die Umfrage mit dem Namen %{name} muss unterschiedliche Optionen haben." default_poll_with_multiple_choices_has_invalid_parameters: "Die Mehrfachauswahl-Umfrage hat ungültige Parameter." diff --git a/plugins/poll/config/locales/server.en.yml b/plugins/poll/config/locales/server.en.yml index dd1e4a5da9..9417c9ba08 100644 --- a/plugins/poll/config/locales/server.en.yml +++ b/plugins/poll/config/locales/server.en.yml @@ -26,8 +26,12 @@ en: default_poll_must_have_at_least_2_options: "Poll must have at least 2 options." named_poll_must_have_at_least_2_options: "Poll named %{name} must have at least 2 options." - default_poll_must_have_less_options: "Poll must have less than %{max} options." - named_poll_must_have_less_options: "Poll named %{name} must have less than %{max} options." + default_poll_must_have_less_options: + one: "Poll must have less than 1 option." + other: "Poll must have less than %{count} options." + named_poll_must_have_less_options: + one: "Poll named %{name} must have less than 1 option." + other: "Poll named %{name} must have less than %{count} options." default_poll_must_have_different_options: "Poll must have different options." named_poll_must_have_different_options: "Poll named %{name} must have different options." diff --git a/plugins/poll/config/locales/server.fi.yml b/plugins/poll/config/locales/server.fi.yml index c70cc428f5..7abf82d557 100644 --- a/plugins/poll/config/locales/server.fi.yml +++ b/plugins/poll/config/locales/server.fi.yml @@ -14,8 +14,12 @@ fi: multiple_polls_with_same_name: "Useamman kyselyn nimi on %{name}. Anna kaikille eri nimet 'name'-määreellä." default_poll_must_have_at_least_2_options: "Äänestyksessä pitää olla vähintään 2 vaihtoehtoa." named_poll_must_have_at_least_2_options: "Äänestyksessä nimeltä %{name} pitää olla vähintään 2 vaihtoehtoa." - default_poll_must_have_less_options: "Äänestyksessä pitää olla vähemmän, kuin %{max} vaihtoehtoa." - named_poll_must_have_less_options: "Äänestyksen nimeltä %{name} pitää sisältää vähemmän, kuin %{max} vaihtoehtoa." + default_poll_must_have_less_options: + one: "Äänestyksessä pitää olla vähemmän, kuin yksi vaihtoehto." + other: "Äänestyksessä pitää olla vähemmän, kuin %{count} vaihtoehtoa." + named_poll_must_have_less_options: + one: "Äänestyksessä %{name} pitää olla vähemmän kuin yksi vaihtoehto." + other: "Äänestyksessä %{name} pitää olla vähemmän kuin %{count} vaihtoehtoa." default_poll_must_have_different_options: "Äänestysvaihtoehtojen on erottava toisistaan." named_poll_must_have_different_options: "Äänestyksen nimeltä %{name} vaihtoehtojen on erottava toisistaan." default_poll_with_multiple_choices_has_invalid_parameters: "Kysely, jossa on useita vaihtoehtoja sisältää epäkelpoja parametreja." diff --git a/plugins/poll/config/locales/server.pt.yml b/plugins/poll/config/locales/server.pt.yml index ed455c1b50..24704a455b 100644 --- a/plugins/poll/config/locales/server.pt.yml +++ b/plugins/poll/config/locales/server.pt.yml @@ -14,8 +14,12 @@ pt: multiple_polls_with_same_name: "Há múltiplas votações com o mesmo nome: %{name}. Utilize o atributo 'nome' para identificar as suas votações de maneira única." default_poll_must_have_at_least_2_options: "Votação deve ter pelo menos 2 opções." named_poll_must_have_at_least_2_options: "Votação com o nome %{name} deve ter pelo menos 2 opções." - default_poll_must_have_less_options: "Votação deve ter menos que %{max} opções." - named_poll_must_have_less_options: "Votação com o nome %{name} deve ter menos que %{max} opções." + default_poll_must_have_less_options: + one: "Votação deve ter menos que 1 opção." + other: "Votação deve ter menos que %{count} opções." + named_poll_must_have_less_options: + one: "Votação com o nome %{name} deve ter menos que 1 opção." + other: "Votação com o nome %{name} deve ter menos que %{count} opções." default_poll_must_have_different_options: "Votação deve ter opções diferentes." named_poll_must_have_different_options: "Votação com o nome %{name} deve ter opções diferentes." default_poll_with_multiple_choices_has_invalid_parameters: "Votação com escolha múltipla tem parâmetros inválidos." diff --git a/plugins/poll/config/locales/server.ru.yml b/plugins/poll/config/locales/server.ru.yml index 8f1059c64e..82fa09cd6a 100644 --- a/plugins/poll/config/locales/server.ru.yml +++ b/plugins/poll/config/locales/server.ru.yml @@ -7,27 +7,35 @@ ru: site_settings: - poll_enabled: "Разрешить содание опросов?" - poll_maximum_options: "Максимальное колличество доступных вариантов в голосовании." + poll_enabled: "Разрешить форумчанам создавать опросы?" + poll_maximum_options: "Максимальное колличество вариантов ответов в одном опросе." poll: - multiple_polls_without_name: "Обнаружено несколько голосований без названия. Искользуйте 'name' для уникализации вашего голосования." - multiple_polls_with_same_name: "Голосование с таким названием: %{name} уже существует. Используйте 'name' для уникализации вашего голосования." - default_poll_must_have_at_least_2_options: "В голосовании должно быть хотябы 2 варианта ответа." - named_poll_must_have_at_least_2_options: "В голосовании %{name} должно быть хотябы 2 варианта ответа." - default_poll_must_have_less_options: "Голосование может содержать максимум %{max} ответов." - named_poll_must_have_less_options: "Голосование %{name} может содержать максимум %{max} ответов." - default_poll_must_have_different_options: "Ответы в голосовании должны быть разными." - named_poll_must_have_different_options: "Ответы в голосовании %{name} должны быть разными." - default_poll_with_multiple_choices_has_invalid_parameters: "Опрос c мультивыбором имеет неверные параметры." - named_poll_with_multiple_choices_has_invalid_parameters: "Опрос с мультивыбором %{name} имеет неверные параметры." - requires_at_least_1_valid_option: "Вы должны выбрать как минимум 1 правильный ответ." - cannot_change_polls_after_5_minutes: "Вы не можете удалить или переименовать опрос в течении первых 5 минут." - op_cannot_edit_options_after_5_minutes: "Вы не можете добавлять и удалять ответы голосования по истечению 5 минут после создания. Свяжитесь с модератором если вам необходимо отредактировать ответы в данном голосовании." - staff_cannot_add_or_remove_options_after_5_minutes: "Вы не можете добавлять и удалять ответы голосования по истечению 5 минут после создания. Вы можете закрыть данную тему и создать новую. " - no_polls_associated_with_this_post: "Нет голосований прикрепленных к данному посту" - no_poll_with_this_name: "Нет голосований %{name} прикрепленных к данному посту." + multiple_polls_without_name: "Обнаружено несколько опросов в одном сообщении. Чтобы система могла их различать между собой, используйте атрибут name' для каждого опроса. Например: [poll name=1]" + multiple_polls_with_same_name: "Обнаружено несколько опросов с одним и тем же названием: %{name}. Чтобы система могла различать опросы между собой, придумайте разные значения для атрибута name' - для каждого опроса своё." + default_poll_must_have_at_least_2_options: "В опросе должно быть хотябы 2 варианта ответа." + named_poll_must_have_at_least_2_options: "В опросе с названием \"%{name}\" должно быть хотябы 2 варианта ответа." + default_poll_must_have_less_options: + one: "В опросе может быть не более 1-го варианта ответа." + few: "В опросе может быть не более %{count} вариантов ответов." + many: "В опросе может быть не более %{count} вариантов ответов." + other: "В опросе может быть не более %{count} вариантов ответов." + named_poll_must_have_less_options: + one: "В опросе под названием \"%{name}\" должно быть не более 1-го варианта ответов." + few: "В опросе под названием \"%{name}\" должно быть не более %{max} вариантов ответов." + many: "В опросе под названием \"%{name}\" должно быть не более %{max} вариантов ответов." + other: "В опросе под названием \"%{name}\" должно быть не более %{max} вариантов ответов." + default_poll_must_have_different_options: "В опросе не должно быть одинаковых вариантов ответов." + named_poll_must_have_different_options: "В опросе %{name} не должно быть одинаковых ответов." + default_poll_with_multiple_choices_has_invalid_parameters: "Опрос с множественным выбором вариантов ответов содержит неправильные параметры." + named_poll_with_multiple_choices_has_invalid_parameters: "Опрос под названием %{name} с множественным выбором вариантов ответов содержит неправильные параметры." + requires_at_least_1_valid_option: "Выберите хотя бы 1 вариант ответа." + cannot_change_polls_after_5_minutes: "Запрещается добавлять, удалять или переименовывать опросы через 5 минут после их создания." + op_cannot_edit_options_after_5_minutes: "Запрещается добавлять или удалять варианты ответов в опросах по истечении 5 минут после их создания. Если необходимо внести изменения в ответы, пожалуйста, свяжитесь с модератором." + staff_cannot_add_or_remove_options_after_5_minutes: "Запрещается добавлять или удалять варианты ответов в опросах по истечении 5 минут после их создания. Если нужно изменить набор ответов, следует закрыть данную тему и создать новую." + no_polls_associated_with_this_post: "В данном сообщении нет опросов." + no_poll_with_this_name: "В данном сообщении нет опроса под названием %{name}." post_is_deleted: "Невозможно совершить действие над удаленным сообщением." - topic_must_be_open_to_vote: "Тема должна быть открыта для голосования." - poll_must_be_open_to_vote: "Опрос должен быть открыт для голосования." - topic_must_be_open_to_toggle_status: "Тема должна быть открыта для изменения статуса." - only_staff_or_op_can_toggle_status: "Только модераторы или автор поста может поменять статус голосования." + topic_must_be_open_to_vote: "Для возможности голосования в опросе, тема должна быть открыта." + poll_must_be_open_to_vote: "Опрос должен быть открыт, чтобы в нем можно было голосовать." + topic_must_be_open_to_toggle_status: "Чтобы изменить статус, тема должна быть открыта." + only_staff_or_op_can_toggle_status: "Только модераторы или сам автор могут поменять статус опроса." diff --git a/plugins/poll/config/locales/server.zh_CN.yml b/plugins/poll/config/locales/server.zh_CN.yml index af100e3010..07cbaae4f1 100644 --- a/plugins/poll/config/locales/server.zh_CN.yml +++ b/plugins/poll/config/locales/server.zh_CN.yml @@ -14,8 +14,10 @@ zh_CN: multiple_polls_with_same_name: "有多个投票的名字相同:%{name}。使用“name”属性唯一标识投票。" default_poll_must_have_at_least_2_options: "投票至少要有 2 个选项。" named_poll_must_have_at_least_2_options: "投票“%{name}”必须要有 2 个选项。" - default_poll_must_have_less_options: "投票选项必须少于 %{max} 个。" - named_poll_must_have_less_options: "%{name}投票的选项必须少于 %{max} 个。" + default_poll_must_have_less_options: + other: "投票选项必须少于%{count}个。" + named_poll_must_have_less_options: + other: "投票“%{name}”的选项必须少于%{count}个。" default_poll_must_have_different_options: "投票必须有不同的选项。" named_poll_must_have_different_options: "%{name}投票的选项必须有不同的选项。" default_poll_with_multiple_choices_has_invalid_parameters: "多选投票有无效选项。" diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb index ca9299474d..306a6c9b13 100644 --- a/plugins/poll/plugin.rb +++ b/plugins/poll/plugin.rb @@ -257,8 +257,8 @@ after_initialize do # maximum # of options if poll["options"].size > SiteSetting.poll_maximum_options poll["name"] == DEFAULT_POLL_NAME ? - self.errors.add(:base, I18n.t("poll.default_poll_must_have_less_options", max: SiteSetting.poll_maximum_options)) : - self.errors.add(:base, I18n.t("poll.named_poll_must_have_less_options", name: ERB::Util.html_escape(poll["name"]), max: SiteSetting.poll_maximum_options)) + self.errors.add(:base, I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options)) : + self.errors.add(:base, I18n.t("poll.named_poll_must_have_less_options", name: poll["name"], count: SiteSetting.poll_maximum_options)) return end @@ -293,8 +293,10 @@ after_initialize do # are the polls different? if polls.keys != previous_polls.keys || current_options != previous_options + has_votes = previous_polls.keys.map { |p| previous_polls[p]["voters"].to_i }.sum > 0 + # outside of the 5-minute edit window? - if post.created_at < 5.minutes.ago + if post.created_at < 5.minutes.ago && has_votes # cannot add/remove/rename polls if polls.keys.sort != previous_polls.keys.sort post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes")) @@ -305,7 +307,7 @@ after_initialize do if User.staff.pluck(:id).include?(post.last_editor_id) # staff can only edit options polls.each_key do |poll_name| - if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size + if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size && previous_polls[poll_name]["voters"].to_i > 0 post.errors.add(:base, I18n.t("poll.staff_cannot_add_or_remove_options_after_5_minutes")) return end diff --git a/plugins/poll/spec/controllers/posts_controller_spec.rb b/plugins/poll/spec/controllers/posts_controller_spec.rb index a47d7def26..944225b60c 100644 --- a/plugins/poll/spec/controllers/posts_controller_spec.rb +++ b/plugins/poll/spec/controllers/posts_controller_spec.rb @@ -50,7 +50,7 @@ describe PostsController do expect(response).not_to be_success json = ::JSON.parse(response.body) - expect(json["errors"][0]).to eq(I18n.t("poll.default_poll_must_have_less_options", max: SiteSetting.poll_maximum_options)) + expect(json["errors"][0]).to eq(I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options)) end it "should have valid parameters" do @@ -126,26 +126,30 @@ describe PostsController do end end - it "OP cannot change the options" do - xhr :put, :update, { id: post_id, post: { raw: new_option } } - expect(response).not_to be_success - json = ::JSON.parse(response.body) - expect(json["errors"][0]).to eq(I18n.t("poll.op_cannot_edit_options_after_5_minutes")) - end + describe "with no vote" do - it "staff can change the options" do - log_in_user(Fabricate(:moderator)) - xhr :put, :update, { id: post_id, post: { raw: new_option } } - expect(response).to be_success - json = ::JSON.parse(response.body) - expect(json["post"]["polls"]["poll"]["options"][1]["html"]).to eq("C") - end + it "OP can change the options" do + xhr :put, :update, { id: post_id, post: { raw: new_option } } + expect(response).to be_success + json = ::JSON.parse(response.body) + expect(json["post"]["polls"]["poll"]["options"][1]["html"]).to eq("C") + end + + it "staff can change the options" do + log_in_user(Fabricate(:moderator)) + xhr :put, :update, { id: post_id, post: { raw: new_option } } + expect(response).to be_success + json = ::JSON.parse(response.body) + expect(json["post"]["polls"]["poll"]["options"][1]["html"]).to eq("C") + end + + it "support changes on the post" do + xhr :put, :update, { id: post_id, post: { raw: updated } } + expect(response).to be_success + json = ::JSON.parse(response.body) + expect(json["post"]["cooked"]).to match("before") + end - it "support changes on the post" do - xhr :put, :update, { id: post_id, post: { raw: updated } } - expect(response).to be_success - json = ::JSON.parse(response.body) - expect(json["post"]["cooked"]).to match("before") end describe "with at least one vote" do @@ -154,6 +158,21 @@ describe PostsController do DiscoursePoll::Poll.vote(post_id, "poll", ["5c24fc1df56d764b550ceae1b9319125"], user.id) end + it "OP cannot change the options" do + xhr :put, :update, { id: post_id, post: { raw: new_option } } + expect(response).not_to be_success + json = ::JSON.parse(response.body) + expect(json["errors"][0]).to eq(I18n.t("poll.op_cannot_edit_options_after_5_minutes")) + end + + it "staff can change the options" do + log_in_user(Fabricate(:moderator)) + xhr :put, :update, { id: post_id, post: { raw: new_option } } + expect(response).to be_success + json = ::JSON.parse(response.body) + expect(json["post"]["polls"]["poll"]["options"][1]["html"]).to eq("C") + end + it "support changes on the post" do xhr :put, :update, { id: post_id, post: { raw: updated } } expect(response).to be_success diff --git a/public/403.zh_CN.html b/public/403.zh_CN.html index 90823fbe79..15657d1f7f 100644 --- a/public/403.zh_CN.html +++ b/public/403.zh_CN.html @@ -1,6 +1,6 @@ -操作行为禁止(403) +操作行为禁止 (403)