From 82c21868f38d6ac5115ef2d0e3cae6e406cb719c Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 21 Jun 2013 14:06:20 -0400 Subject: [PATCH] Moved JSHint into Qunit suite. It's much harder to forget about now! --- .jshintrc | 63 - .travis.yml | 2 +- Gemfile | 2 - Gemfile.lock | 6 - Guardfile | 8 - .../admin_email_preview_digest_controller.js | 2 +- .../admin_users_list_controller.js | 2 +- .../javascripts/admin/models/flagged_post.js | 2 +- .../javascripts/admin/models/github_commit.js | 2 +- app/assets/javascripts/admin/models/group.js | 8 +- .../admin/routes/admin_email_index_route.js | 2 +- .../admin_email_preview_digest_route.js | 2 +- .../routes/admin_site_content_edit_route.js | 2 +- app/assets/javascripts/discourse.js | 10 +- .../discourse/components/bbcode.js | 29 +- .../discourse/components/debounce.js | 4 +- .../discourse/components/formatter.js | 6 +- .../discourse/components/key_value_store.js | 2 +- .../discourse/components/lightbox.js | 2 +- .../discourse/components/onebox.js | 2 +- .../discourse/components/probes.js | 4 +- .../discourse/components/search.js | 4 +- .../controllers/edit_category_controller.js | 2 +- .../flag_action_type_controller.js | 2 +- .../discourse/controllers/flag_controller.js | 4 +- .../discourse/controllers/login_controller.js | 4 +- .../controllers/search_controller.js | 2 +- .../discourse/helpers/application_helpers.js | 4 +- .../javascripts/discourse/mixins/scrolling.js | 2 +- .../discourse/models/category_list.js | 6 +- .../discourse/models/post_action_type.js | 2 +- .../discourse/models/selectable_array.js | 9 +- .../javascripts/discourse/models/topic.js | 12 +- .../discourse/models/topic_list.js | 2 +- .../discourse/routes/application_routes.js | 2 +- .../discourse/routes/discourse_location.js | 5 +- .../discourse/routes/list_category_route.js | 2 +- .../discourse/routes/topic_route.js | 4 +- .../discourse/views/auto_close_form_view.js | 2 +- .../views/buttons/clear_pin_button.js | 2 +- .../discourse/views/header_view.js | 2 +- .../discourse/views/list/list_topics_view.js | 2 +- .../discourse/views/popup_input_tip_view.js | 2 +- .../discourse/views/topic_closing_view.js | 2 +- .../views/topic_summary/topic_summary_view.js | 2 +- .../javascripts/discourse/views/topic_view.js | 2 +- .../javascripts/discourse/views/view.js | 2 +- lib/discourse_iife.rb | 2 +- test/javascripts/components/bbcode_test.js | 6 +- .../components/click_track_test.js | 9 +- test/javascripts/components/formatter_test.js | 6 +- test/javascripts/components/markdown_test.js | 7 +- test/javascripts/fixtures/list_fixtures.js | 4 +- test/javascripts/fixtures/topic_fixtures.js | 2 +- test/javascripts/jshint_all.js.erb | 182 + test/javascripts/models/category_test.js | 2 +- test/javascripts/models/composer_test.js | 4 +- test/javascripts/test_helper.js | 6 +- vendor/assets/javascripts/jshint.js | 4023 +++++++++++++++++ 59 files changed, 4320 insertions(+), 181 deletions(-) delete mode 100644 .jshintrc create mode 100644 test/javascripts/jshint_all.js.erb create mode 100644 vendor/assets/javascripts/jshint.js diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 3317474ba..000000000 --- a/.jshintrc +++ /dev/null @@ -1,63 +0,0 @@ -{ - "bitwise":true, - "newcap":true, - "eqeqeq":true, - "immed":false, - "nomen":false, - "onevar":false, - "plusplus":false, - "regexp":false, - "strict":false, - /*"undef":true,*/ - "white":false, - "eqnull":false, - "debug":false, - "es5":false, - "evil":false, - "forin":false, - "laxbreak":false, - "sub":false, - "maxlen":200, - "indent":2, - "maxerr":50, - "passfail":false, - "predef":["Ember", - "jQuery", - "$", - "RSVP", - "Discourse", - "$LAB", - "Em", - "PreloadStore", - "Handlebars", - "I18n", - "bootbox", - "module", - "integration", - "test", - "ok", - "expect", - "equal", - "blank", - "present", - "visit", - "count", - "exists", - "asyncTest", - "find", - "resolvingPromise", - "sinon"], - "browser":true, - "rhino":false, - "devel":true, - "loopfunc":true, - "asi":true, - "boss":true, - "couch":true, - "curly":false, - "noarg":true, - "node":false, - "noempty":false, - "nonew":true, - "lastsemic":false -} diff --git a/.travis.yml b/.travis.yml index 754e69dbb..8e87292af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,6 @@ before_script: - rake db:migrate - export RUBY_GC_MALLOC_LIMIT=50000000 bundler_args: --without development -script: 'rake jshint && rake spec && bundle exec rake qunit:test' +script: 'rake spec && bundle exec rake qunit:test' services: - redis-server diff --git a/Gemfile b/Gemfile index 7786a630a..454b161c2 100644 --- a/Gemfile +++ b/Gemfile @@ -97,9 +97,7 @@ group :test do end group :test, :development do - gem 'jshint_on_rails' gem 'listen', require: false - gem 'guard-jshint-on-rails', require: false gem 'certified', require: false gem 'fabrication', require: false gem 'qunit-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 455b17464..a5a7f415d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -199,9 +199,6 @@ GEM lumberjack (>= 1.0.2) pry (>= 0.9.10) thor (>= 0.14.6) - guard-jshint-on-rails (0.0.2) - guard (>= 1.0.0) - jshint_on_rails (>= 1.0.2) guard-rspec (2.5.4) guard (>= 1.1) rspec (~> 2.11) @@ -226,7 +223,6 @@ GEM image_sorcery (1.1.0) in_threads (1.1.1) journey (1.0.4) - jshint_on_rails (1.0.2) json (1.7.7) jwt (0.1.8) multi_json (>= 1.5) @@ -485,7 +481,6 @@ DEPENDENCIES fast_xs fastimage fog - guard-jshint-on-rails guard-rspec guard-spork handlebars-source (= 1.0.0.rc4) @@ -494,7 +489,6 @@ DEPENDENCIES hiredis image_optim image_sorcery - jshint_on_rails librarian (>= 0.0.25) listen lru_redux diff --git a/Guardfile b/Guardfile index beef7abef..2ca66ee7a 100644 --- a/Guardfile +++ b/Guardfile @@ -3,14 +3,6 @@ require 'terminal-notifier-guard' if RUBY_PLATFORM.include?('darwin') phantom_path = File.expand_path('~/phantomjs/bin/phantomjs') phantom_path = nil unless File.exists?(phantom_path) -# verify that we pass jshint -# see https://github.com/MrOrz/guard-jshint-on-rails -guard 'jshint-on-rails', config_path: 'config/jshint.yml' do - # watch for changes to application javascript files - watch(%r{^app/assets/javascripts/.*\.js$}) - watch(%r{^spec/javascripts/.*\.js$}) -end - unless ENV["USING_AUTOSPEC"] puts "Sam strongly recommends you Run: `bundle exec rake autospec` in favor of guard for specs, set USING_AUTOSPEC in .rvmrc to disable from Guard" diff --git a/app/assets/javascripts/admin/controllers/admin_email_preview_digest_controller.js b/app/assets/javascripts/admin/controllers/admin_email_preview_digest_controller.js index 025f53350..39de14ae9 100644 --- a/app/assets/javascripts/admin/controllers/admin_email_preview_digest_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_email_preview_digest_controller.js @@ -15,7 +15,7 @@ Discourse.AdminEmailPreviewDigestController = Discourse.ObjectController.extend( Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(function (email) { model.setProperties(email.getProperties('html_content', 'text_content')); controller.set('loading', false); - }) + }); } }); diff --git a/app/assets/javascripts/admin/controllers/admin_users_list_controller.js b/app/assets/javascripts/admin/controllers/admin_users_list_controller.js index 946be4955..8864c427e 100644 --- a/app/assets/javascripts/admin/controllers/admin_users_list_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_users_list_controller.js @@ -94,7 +94,7 @@ Discourse.AdminUsersListController = Ember.ArrayController.extend(Discourse.Pres Discourse.AdminUser.findAll(this.get('query'), this.get('username')).then(function (result) { adminUsersListController.set('content', result); adminUsersListController.set('loading', false); - }) + }); }, diff --git a/app/assets/javascripts/admin/models/flagged_post.js b/app/assets/javascripts/admin/models/flagged_post.js index a93d74a32..3facc7685 100644 --- a/app/assets/javascripts/admin/models/flagged_post.js +++ b/app/assets/javascripts/admin/models/flagged_post.js @@ -14,7 +14,7 @@ Discourse.FlaggedPost = Discourse.Post.extend({ .map(function(v,k){ return Em.String.i18n("admin.flags.summary.action_type_" + k, {count: v.length}); }) - .join(",") + .join(","); }.property(), flaggers: function() { diff --git a/app/assets/javascripts/admin/models/github_commit.js b/app/assets/javascripts/admin/models/github_commit.js index b807fd6ae..ff7ce6c62 100644 --- a/app/assets/javascripts/admin/models/github_commit.js +++ b/app/assets/javascripts/admin/models/github_commit.js @@ -20,7 +20,7 @@ Discourse.GithubCommit = Discourse.Model.extend({ }.property("sha"), timeAgo: function() { - return moment(this.get('commit.committer.date')).relativeAge({format: 'medium', leaveAgo: true}) + return moment(this.get('commit.committer.date')).relativeAge({format: 'medium', leaveAgo: true}); }.property("commit.committer.date") }); diff --git a/app/assets/javascripts/admin/models/group.js b/app/assets/javascripts/admin/models/group.js index a67929e77..64e96dfd0 100644 --- a/app/assets/javascripts/admin/models/group.js +++ b/app/assets/javascripts/admin/models/group.js @@ -14,12 +14,12 @@ Discourse.Group = Discourse.Model.extend({ if(id && !this.get('loaded')) { var group = this; Discourse.ajax('/admin/groups/' + this.get('id') + '/users').then(function(payload){ - var users = Em.A() + var users = Em.A(); _.each(payload,function(user){ users.addObject(Discourse.User.create(user)); }); - group.set('users', users) - group.set('loaded', true) + group.set('users', users); + group.set('loaded', true); }); } }, @@ -30,7 +30,7 @@ Discourse.Group = Discourse.Model.extend({ if(users) { usernames = _.map(users, function(user){ return user.get('username'); - }).join(',') + }).join(','); } return usernames; }.property('users'), diff --git a/app/assets/javascripts/admin/routes/admin_email_index_route.js b/app/assets/javascripts/admin/routes/admin_email_index_route.js index d6876b08f..24d8cf5e2 100644 --- a/app/assets/javascripts/admin/routes/admin_email_index_route.js +++ b/app/assets/javascripts/admin/routes/admin_email_index_route.js @@ -11,7 +11,7 @@ Discourse.AdminEmailIndexRoute = Discourse.Route.extend({ setupController: function(controller) { Discourse.EmailSettings.find().then(function (model) { controller.set('model', model); - }) + }); }, renderTemplate: function() { diff --git a/app/assets/javascripts/admin/routes/admin_email_preview_digest_route.js b/app/assets/javascripts/admin/routes/admin_email_preview_digest_route.js index 378844177..5dafb8aad 100644 --- a/app/assets/javascripts/admin/routes/admin_email_preview_digest_route.js +++ b/app/assets/javascripts/admin/routes/admin_email_preview_digest_route.js @@ -9,7 +9,7 @@ var oneWeekAgo = function() { return moment().subtract('days',7).format('YYYY-MM-DD'); -} +}; Discourse.AdminEmailPreviewDigestRoute = Discourse.Route.extend(Discourse.ModelReady, { diff --git a/app/assets/javascripts/admin/routes/admin_site_content_edit_route.js b/app/assets/javascripts/admin/routes/admin_site_content_edit_route.js index f62667af9..57362d567 100644 --- a/app/assets/javascripts/admin/routes/admin_site_content_edit_route.js +++ b/app/assets/javascripts/admin/routes/admin_site_content_edit_route.js @@ -36,7 +36,7 @@ Discourse.AdminSiteContentEditRoute = Discourse.Route.extend({ Discourse.SiteContent.find(Em.get(model, 'content_type')).then(function (sc) { controller.set('content', sc); controller.set('loaded', true); - }) + }); } diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js index 4cb81bb99..997ffcb5a 100644 --- a/app/assets/javascripts/discourse.js +++ b/app/assets/javascripts/discourse.js @@ -199,7 +199,7 @@ Discourse = Ember.Application.createWithMixins({ // Reloading will refresh unbound properties Discourse.KeyValueStore.abandonLocal(); window.location.reload(); - }) + }); }, authenticationComplete: function(options) { @@ -255,7 +255,7 @@ Discourse = Ember.Application.createWithMixins({ if (fixture) { return Ember.Deferred.promise(function(promise) { promise.resolve(fixture); - }) + }); } return Ember.Deferred.promise(function (promise) { @@ -263,7 +263,7 @@ Discourse = Ember.Application.createWithMixins({ args.success = function(xhr) { Ember.run(promise, promise.resolve, xhr); if (oldSuccess) oldSuccess(xhr); - } + }; var oldError = args.error; args.error = function(xhr) { @@ -273,7 +273,7 @@ Discourse = Ember.Application.createWithMixins({ promise.reject(xhr); if (oldError) oldError(xhr); - } + }; // We default to JSON on GET. If we don't, sometimes if the server doesn't return the proper header // it will not be parsed as an object. @@ -307,7 +307,7 @@ Discourse = Ember.Application.createWithMixins({ bus.subscribe("/categories", function(data){ var site = Discourse.Site.instance(); _.each(data.categories,function(c){ - site.updateCategory(c) + site.updateCategory(c); }); }); diff --git a/app/assets/javascripts/discourse/components/bbcode.js b/app/assets/javascripts/discourse/components/bbcode.js index 0a150759c..ac39819c7 100644 --- a/app/assets/javascripts/discourse/components/bbcode.js +++ b/app/assets/javascripts/discourse/components/bbcode.js @@ -158,7 +158,7 @@ Discourse.BBCode = { extractQuotes: function(text) { var result = {text: "" + text, replacements: []}; - var replacements = [] + var replacements = []; var matches; while (matches = Discourse.BBCode.QUOTE_REGEXP.exec(result.text)) { @@ -178,7 +178,7 @@ Discourse.BBCode = { input = input.replace(r.key, val); }); return input; - } + }; return(result); }, @@ -192,21 +192,24 @@ Discourse.BBCode = { **/ formatQuote: function(text, opts) { var args, matches, params, paramsSplit, paramsString, templateName, username; + + var splitter = function(p,i) { + if (i > 0) { + var assignment = p.split(':'); + if (assignment[0] && assignment[1]) { + return params.push({ + key: assignment[0], + value: assignment[1].trim() + }); + } + } + }; + while (matches = this.QUOTE_REGEXP.exec(text)) { paramsString = matches[1].replace(/\"/g, ''); paramsSplit = paramsString.split(/\, */); params = []; - _.each(paramsSplit,function(p,i) { - if (i > 0) { - var assignment = p.split(':'); - if (assignment[0] && assignment[1]) { - return params.push({ - key: assignment[0], - value: assignment[1].trim() - }); - } - } - }); + _.each(paramsSplit, splitter); username = paramsSplit[0]; // remove leading
s diff --git a/app/assets/javascripts/discourse/components/debounce.js b/app/assets/javascripts/discourse/components/debounce.js index d1b069d56..a4ec88001 100644 --- a/app/assets/javascripts/discourse/components/debounce.js +++ b/app/assets/javascripts/discourse/components/debounce.js @@ -59,12 +59,12 @@ Discourse.debouncePromise = function(func, wait) { timeout = Em.run.later(function () { timeout = null; func.apply(context, args).then(function (y) { - promise.resolve(y) + promise.resolve(y); }); }, wait); } return promise; - } + }; }; diff --git a/app/assets/javascripts/discourse/components/formatter.js b/app/assets/javascripts/discourse/components/formatter.js index 7f2f396f7..98d413aa7 100644 --- a/app/assets/javascripts/discourse/components/formatter.js +++ b/app/assets/javascripts/discourse/components/formatter.js @@ -1,3 +1,5 @@ +/*jshint onecase:true */ + Discourse.Formatter = (function(){ var updateRelativeAge, autoUpdatingRelativeAge, relativeAge, relativeAgeTiny, @@ -15,7 +17,7 @@ Discourse.Formatter = (function(){ return str.replace(/\w\S*/g, function(txt){ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); - } + }; longDate = function(dt) { if (!dt) return; @@ -105,7 +107,7 @@ Discourse.Formatter = (function(){ var t = function(key, opts){ return Ember.String.i18n("dates.medium" + (leaveAgo?"_with_ago":"") + "." + key, opts); - } + }; switch(true){ case(distanceInMinutes >= 1 && distanceInMinutes <= 56): diff --git a/app/assets/javascripts/discourse/components/key_value_store.js b/app/assets/javascripts/discourse/components/key_value_store.js index fe68395a7..f0bcfbb63 100644 --- a/app/assets/javascripts/discourse/components/key_value_store.js +++ b/app/assets/javascripts/discourse/components/key_value_store.js @@ -47,5 +47,5 @@ Discourse.KeyValueStore = { } return localStorage[this.context + key]; } -} +}; diff --git a/app/assets/javascripts/discourse/components/lightbox.js b/app/assets/javascripts/discourse/components/lightbox.js index ec129e5fb..fb128c53d 100644 --- a/app/assets/javascripts/discourse/components/lightbox.js +++ b/app/assets/javascripts/discourse/components/lightbox.js @@ -17,4 +17,4 @@ Discourse.Lightbox = { }); }); } -} +}; diff --git a/app/assets/javascripts/discourse/components/onebox.js b/app/assets/javascripts/discourse/components/onebox.js index a4ac43c9f..bb282ce15 100644 --- a/app/assets/javascripts/discourse/components/onebox.js +++ b/app/assets/javascripts/discourse/components/onebox.js @@ -58,7 +58,7 @@ Discourse.Onebox = { var loadingFinished = function() { $elem.removeClass('loading-onebox'); $elem.data('onebox-loaded'); - } + }; var onebox = this; promise.then(function(html) { diff --git a/app/assets/javascripts/discourse/components/probes.js b/app/assets/javascripts/discourse/components/probes.js index ac25627b1..ba055adae 100644 --- a/app/assets/javascripts/discourse/components/probes.js +++ b/app/assets/javascripts/discourse/components/probes.js @@ -110,8 +110,8 @@ } return r; - } - } + }; + }; clear(); diff --git a/app/assets/javascripts/discourse/components/search.js b/app/assets/javascripts/discourse/components/search.js index c9acec6bc..a64f90d07 100644 --- a/app/assets/javascripts/discourse/components/search.js +++ b/app/assets/javascripts/discourse/components/search.js @@ -21,7 +21,7 @@ Discourse.Search = { if (!opts) opts = {}; // Only include the data we have - var data = { term: term } + var data = { term: term }; if (opts.typeFilter) data.type_filter = opts.typeFilter; if (opts.searchContext) { @@ -34,5 +34,5 @@ Discourse.Search = { return Discourse.ajax('/search', { data: data }); } -} +}; diff --git a/app/assets/javascripts/discourse/controllers/edit_category_controller.js b/app/assets/javascripts/discourse/controllers/edit_category_controller.js index 7dd2fbcac..f83862bcd 100644 --- a/app/assets/javascripts/discourse/controllers/edit_category_controller.js +++ b/app/assets/javascripts/discourse/controllers/edit_category_controller.js @@ -96,7 +96,7 @@ Discourse.EditCategoryController = Discourse.ObjectController.extend(Discourse.M }.property(), showCategoryTopic: function() { - this.send('closeModal') + this.send('closeModal'); Discourse.URL.routeTo(this.get('topic_url')); return false; }, diff --git a/app/assets/javascripts/discourse/controllers/flag_action_type_controller.js b/app/assets/javascripts/discourse/controllers/flag_action_type_controller.js index ca97cd3f1..039c0394e 100644 --- a/app/assets/javascripts/discourse/controllers/flag_action_type_controller.js +++ b/app/assets/javascripts/discourse/controllers/flag_action_type_controller.js @@ -27,7 +27,7 @@ Discourse.FlagActionTypeController = Discourse.ObjectController.extend({ showDescription: Em.computed.not('showMessageInput'), customMessageLengthClasses: function() { - return (this.get('message.length') < Discourse.SiteSettings.min_private_message_post_length) ? "too-short" : "ok" + return (this.get('message.length') < Discourse.SiteSettings.min_private_message_post_length) ? "too-short" : "ok"; }.property('message.length'), customMessageLength: function() { diff --git a/app/assets/javascripts/discourse/controllers/flag_controller.js b/app/assets/javascripts/discourse/controllers/flag_controller.js index ab8615dc4..01d30633e 100644 --- a/app/assets/javascripts/discourse/controllers/flag_controller.js +++ b/app/assets/javascripts/discourse/controllers/flag_controller.js @@ -43,14 +43,14 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc }.property('selected.is_custom_flag'), takeAction: function() { - this.createFlag({takeAction: true}) + this.createFlag({takeAction: true}); this.set('hidden', true); }, createFlag: function(opts) { var flagController = this; var postAction = this.get('actionByName.' + this.get('selected.name_key')); - var params = this.get('selected.is_custom_flag') ? {message: this.get('message')} : {} + var params = this.get('selected.is_custom_flag') ? {message: this.get('message')} : {}; if (opts) params = $.extend(params, opts); diff --git a/app/assets/javascripts/discourse/controllers/login_controller.js b/app/assets/javascripts/discourse/controllers/login_controller.js index a84cb70f5..5a01cf278 100644 --- a/app/assets/javascripts/discourse/controllers/login_controller.js +++ b/app/assets/javascripts/discourse/controllers/login_controller.js @@ -71,7 +71,7 @@ Discourse.LoginController = Discourse.Controller.extend(Discourse.ModalFunctiona // Failed to login loginController.flash(Em.String.i18n('login.error'), 'error'); loginController.set('loggingIn', false); - }) + }); return false; }, @@ -153,7 +153,7 @@ Discourse.LoginController = Discourse.Controller.extend(Discourse.ModalFunctiona accountUsername: options.username, accountName: options.name, authOptions: Em.Object.create(options) - }) + }); this.send('showCreateAccount'); } diff --git a/app/assets/javascripts/discourse/controllers/search_controller.js b/app/assets/javascripts/discourse/controllers/search_controller.js index bfaefd20f..215622ff1 100644 --- a/app/assets/javascripts/discourse/controllers/search_controller.js +++ b/app/assets/javascripts/discourse/controllers/search_controller.js @@ -38,7 +38,7 @@ Discourse.SearchController = Em.ArrayController.extend(Discourse.Presence, { var index = 0; results = _(['topic', 'category', 'user']) .map(function(n){ - return _(results).where({type: n}).first() + return _(results).where({type: n}).first(); }) .compact() .each(function(list){ diff --git a/app/assets/javascripts/discourse/helpers/application_helpers.js b/app/assets/javascripts/discourse/helpers/application_helpers.js index 501542af7..c1e2e1ad8 100644 --- a/app/assets/javascripts/discourse/helpers/application_helpers.js +++ b/app/assets/javascripts/discourse/helpers/application_helpers.js @@ -223,7 +223,7 @@ Handlebars.registerHelper('editDate', function(property, options) { **/ Ember.Handlebars.registerHelper('percentile', function(property, options) { var percentile = Ember.Handlebars.get(this, property, options); - return Math.round((1.0 - percentile) * 100) + return Math.round((1.0 - percentile) * 100); }); /** @@ -236,7 +236,7 @@ Ember.Handlebars.registerHelper('float', function(property, options) { var x = Ember.Handlebars.get(this, property, options); if (!x) return "0"; if (Math.round(x) === x) return x; - return x.toFixed(3) + return x.toFixed(3); }); /** diff --git a/app/assets/javascripts/discourse/mixins/scrolling.js b/app/assets/javascripts/discourse/mixins/scrolling.js index 8d3ed112d..875d71297 100644 --- a/app/assets/javascripts/discourse/mixins/scrolling.js +++ b/app/assets/javascripts/discourse/mixins/scrolling.js @@ -66,4 +66,4 @@ Discourse.ScrollingDOMMethods = { $(document).unbind('touchmove.discourse'); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/app/assets/javascripts/discourse/models/category_list.js b/app/assets/javascripts/discourse/models/category_list.js index ac7a00330..bd3367b20 100644 --- a/app/assets/javascripts/discourse/models/category_list.js +++ b/app/assets/javascripts/discourse/models/category_list.js @@ -49,10 +49,10 @@ Discourse.CategoryList.reopenClass({ var finder = null; if (filter === 'categories') { finder = PreloadStore.getAndRemove("categories_list", function() { - return Discourse.ajax("/categories.json") + return Discourse.ajax("/categories.json"); }); } else { - finder = Discourse.ajax("/" + filter + ".json") + finder = Discourse.ajax("/" + filter + ".json"); } return finder.then(function(result) { @@ -63,7 +63,7 @@ Discourse.CategoryList.reopenClass({ categories: route.categoriesFrom(result), draft_key: result.category_list.draft_key, draft_sequence: result.category_list.draft_sequence - }) + }); return categoryList; }); } diff --git a/app/assets/javascripts/discourse/models/post_action_type.js b/app/assets/javascripts/discourse/models/post_action_type.js index 20b7cb42e..c3583967b 100644 --- a/app/assets/javascripts/discourse/models/post_action_type.js +++ b/app/assets/javascripts/discourse/models/post_action_type.js @@ -10,4 +10,4 @@ Discourse.PostActionType = Discourse.Model.extend({}); Discourse.PostActionType.reopenClass({ MAX_MESSAGE_LENGTH: 500 -}) +}); diff --git a/app/assets/javascripts/discourse/models/selectable_array.js b/app/assets/javascripts/discourse/models/selectable_array.js index 07eca704b..43577bcbb 100644 --- a/app/assets/javascripts/discourse/models/selectable_array.js +++ b/app/assets/javascripts/discourse/models/selectable_array.js @@ -1,24 +1,28 @@ // this allows you to track the selected item in an array, ghetto for now Discourse.SelectableArray = Em.ArrayProxy.extend({ + init: function() { this.content = []; this._super(); }, + selectIndex: function(index){ this.select(this[index]); }, + select: function(selected){ _.each(this.content,function(item){ if(item === selected){ - Em.set(item, "active", true) + Em.set(item, "active", true); } else { if (item.get("active")) { - Em.set(item, "active", false) + Em.set(item, "active", false); } } }); this.set("active", selected); }, + removeObject: function(object) { if(object === this.get("active")){ this.set("active", null); @@ -27,4 +31,5 @@ Discourse.SelectableArray = Em.ArrayProxy.extend({ this._super(object); } + }); diff --git a/app/assets/javascripts/discourse/models/topic.js b/app/assets/javascripts/discourse/models/topic.js index 2bd9289c2..68a09a32c 100644 --- a/app/assets/javascripts/discourse/models/topic.js +++ b/app/assets/javascripts/discourse/models/topic.js @@ -69,7 +69,7 @@ Discourse.Topic = Discourse.Model.extend({ // The last post in the topic lastPost: function() { - var posts = this.get('posts') + var posts = this.get('posts'); return posts[posts.length-1]; }, @@ -287,7 +287,7 @@ Discourse.Topic = Discourse.Model.extend({ topic.set('allowed_users', Em.A(result.allowed_users)); topic.set('loaded', true); - } + }; var errorLoadingTopic = function(result) { @@ -295,22 +295,22 @@ Discourse.Topic = Discourse.Model.extend({ // If the result was 404 the post is not found if (result.status === 404) { - topic.set('errorTitle', Em.String.i18n('topic.not_found.title')) + topic.set('errorTitle', Em.String.i18n('topic.not_found.title')); topic.set('message', Em.String.i18n('topic.not_found.description')); return; } // If the result is 403 it means invalid access if (result.status === 403) { - topic.set('errorTitle', Em.String.i18n('topic.invalid_access.title')) + topic.set('errorTitle', Em.String.i18n('topic.invalid_access.title')); topic.set('message', Em.String.i18n('topic.invalid_access.description')); return; } // Otherwise supply a generic error message - topic.set('errorTitle', Em.String.i18n('topic.server_error.title')) + topic.set('errorTitle', Em.String.i18n('topic.server_error.title')); topic.set('message', Em.String.i18n('topic.server_error.description')); - } + }; // Finally, call our find method return Discourse.Topic.find(this.get('id'), { diff --git a/app/assets/javascripts/discourse/models/topic_list.js b/app/assets/javascripts/discourse/models/topic_list.js index 179f21864..808b6a8b4 100644 --- a/app/assets/javascripts/discourse/models/topic_list.js +++ b/app/assets/javascripts/discourse/models/topic_list.js @@ -148,7 +148,7 @@ Discourse.TopicList.reopenClass({ var url = Discourse.getURL("/") + filter + ".json"; if (excludeCategory) { url += "?exclude_category=" + excludeCategory; } return Discourse.ajax(url); - } + }; return PreloadStore.getAndRemove("topic_list", finder).then(function(result) { var topicList = Discourse.TopicList.create({ diff --git a/app/assets/javascripts/discourse/routes/application_routes.js b/app/assets/javascripts/discourse/routes/application_routes.js index 97a4345b5..d28b5d429 100644 --- a/app/assets/javascripts/discourse/routes/application_routes.js +++ b/app/assets/javascripts/discourse/routes/application_routes.js @@ -30,7 +30,7 @@ Discourse.Route.buildRoutes(function() { }); // the homepage is the first item of the 'top_menu' site setting - var settings = Discourse.SiteSettings || PreloadStore.get('siteSettings') + var settings = Discourse.SiteSettings || PreloadStore.get('siteSettings'); var homepage = settings.top_menu.split("|")[0].split(",")[0]; this.route(homepage, { path: '/' }); diff --git a/app/assets/javascripts/discourse/routes/discourse_location.js b/app/assets/javascripts/discourse/routes/discourse_location.js index 26fbd0b6f..6c29043b7 100644 --- a/app/assets/javascripts/discourse/routes/discourse_location.js +++ b/app/assets/javascripts/discourse/routes/discourse_location.js @@ -18,8 +18,9 @@ var popstateReady = false; Ember.DiscourseLocation = Ember.Object.extend({ init: function() { set(this, 'location', get(this, 'location') || window.location); - if ( $.inArray('state', $.event.props) < 0 ) - jQuery.event.props.push('state') + if ( $.inArray('state', $.event.props) < 0 ) { + jQuery.event.props.push('state'); + } this.initState(); }, diff --git a/app/assets/javascripts/discourse/routes/list_category_route.js b/app/assets/javascripts/discourse/routes/list_category_route.js index 886c5498a..631a47242 100644 --- a/app/assets/javascripts/discourse/routes/list_category_route.js +++ b/app/assets/javascripts/discourse/routes/list_category_route.js @@ -16,7 +16,7 @@ Discourse.ListCategoryRoute = Discourse.FilteredListRoute.extend({ var uncategorized = Discourse.Category.uncategorizedInstance(); if (slug === uncategorized.get('slug')) return uncategorized; - var category = categories.findProperty('slug', Em.get(params, 'slug')) + var category = categories.findProperty('slug', Em.get(params, 'slug')); // In case the slug didn't work, try to find it by id instead. if (!category) { diff --git a/app/assets/javascripts/discourse/routes/topic_route.js b/app/assets/javascripts/discourse/routes/topic_route.js index 1fca0097b..517c06243 100644 --- a/app/assets/javascripts/discourse/routes/topic_route.js +++ b/app/assets/javascripts/discourse/routes/topic_route.js @@ -34,7 +34,7 @@ Discourse.TopicRoute = Discourse.Route.extend({ }, showPrivateInvite: function() { - Discourse.Route.showModal(this, 'invitePrivate', this.modelFor('topic')) + Discourse.Route.showModal(this, 'invitePrivate', this.modelFor('topic')); this.controllerFor('invitePrivate').setProperties({ email: null, error: false, @@ -46,7 +46,7 @@ Discourse.TopicRoute = Discourse.Route.extend({ showHistory: function(post) { Discourse.Route.showModal(this, 'history', post); this.controllerFor('history').refresh(); - this.controllerFor('modal').set('modalClass', 'history-modal') + this.controllerFor('modal').set('modalClass', 'history-modal'); }, mergeTopic: function() { diff --git a/app/assets/javascripts/discourse/views/auto_close_form_view.js b/app/assets/javascripts/discourse/views/auto_close_form_view.js index 465186c76..9c367122b 100644 --- a/app/assets/javascripts/discourse/views/auto_close_form_view.js +++ b/app/assets/javascripts/discourse/views/auto_close_form_view.js @@ -15,7 +15,7 @@ Discourse.AutoCloseFormView = Ember.View.extend({ autoCloseChanged: function() { if( this.get('autoCloseDays') && this.get('autoCloseDays').length > 0 ) { - this.set('autoCloseDays', this.get('autoCloseDays').replace(/[^\d]/g, '') ) + this.set('autoCloseDays', this.get('autoCloseDays').replace(/[^\d]/g, '') ); } }.observes('autoCloseDays') }); diff --git a/app/assets/javascripts/discourse/views/buttons/clear_pin_button.js b/app/assets/javascripts/discourse/views/buttons/clear_pin_button.js index a70c8f8c4..f17aae170 100644 --- a/app/assets/javascripts/discourse/views/buttons/clear_pin_button.js +++ b/app/assets/javascripts/discourse/views/buttons/clear_pin_button.js @@ -14,7 +14,7 @@ Discourse.ClearPinButton = Discourse.ButtonView.extend({ // Hide the button if it becomes unpinned unpinned: function() { // When not logged in don't show the button - if (!Discourse.User.current()) return 'hidden' + if (!Discourse.User.current()) return 'hidden'; return this.get('controller.pinned') ? null : 'hidden'; }.property('controller.pinned'), diff --git a/app/assets/javascripts/discourse/views/header_view.js b/app/assets/javascripts/discourse/views/header_view.js index 83d780043..ce349dd9a 100644 --- a/app/assets/javascripts/discourse/views/header_view.js +++ b/app/assets/javascripts/discourse/views/header_view.js @@ -104,7 +104,7 @@ Discourse.HeaderView = Discourse.View.extend({ if(logo && logo.length > 1) { result += ""; } else { - result += "" + result += ""; } } result += ""; diff --git a/app/assets/javascripts/discourse/views/list/list_topics_view.js b/app/assets/javascripts/discourse/views/list/list_topics_view.js index 1e2a623ef..58dde1c94 100644 --- a/app/assets/javascripts/discourse/views/list/list_topics_view.js +++ b/app/assets/javascripts/discourse/views/list/list_topics_view.js @@ -66,7 +66,7 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, { if (!hasMoreResults) { listTopicsView.get('eyeline').flushRest(); } - }) + }); }, // Remember where we were scrolled to diff --git a/app/assets/javascripts/discourse/views/popup_input_tip_view.js b/app/assets/javascripts/discourse/views/popup_input_tip_view.js index 29b969c8b..a1a7ad1c2 100644 --- a/app/assets/javascripts/discourse/views/popup_input_tip_view.js +++ b/app/assets/javascripts/discourse/views/popup_input_tip_view.js @@ -31,7 +31,7 @@ Discourse.PopupInputTipView = Discourse.View.extend({ bounce: function() { if( this.get('shownAt') ) { - var $elem = this.$() + var $elem = this.$(); if( !this.animateAttribute ) { this.animateAttribute = $elem.css('left') === 'auto' ? 'right' : 'left'; } diff --git a/app/assets/javascripts/discourse/views/topic_closing_view.js b/app/assets/javascripts/discourse/views/topic_closing_view.js index cf396464d..c3248235b 100644 --- a/app/assets/javascripts/discourse/views/topic_closing_view.js +++ b/app/assets/javascripts/discourse/views/topic_closing_view.js @@ -54,7 +54,7 @@ Discourse.TopicClosingView = Discourse.View.extend({ willDestroyElement: function() { if( this.delayedRerender ) { - Em.run.cancel(this.delayedRerender) + Em.run.cancel(this.delayedRerender); } } }); diff --git a/app/assets/javascripts/discourse/views/topic_summary/topic_summary_view.js b/app/assets/javascripts/discourse/views/topic_summary/topic_summary_view.js index 694ce5d60..68c202919 100644 --- a/app/assets/javascripts/discourse/views/topic_summary/topic_summary_view.js +++ b/app/assets/javascripts/discourse/views/topic_summary/topic_summary_view.js @@ -46,7 +46,7 @@ Discourse.TopicSummaryView = Discourse.ContainerView.extend({ templateName: 'topic_summary/info', topic: this.get('topic'), summaryView: this - }) + }); this.trigger('appendSummaryInformation', this); }, diff --git a/app/assets/javascripts/discourse/views/topic_view.js b/app/assets/javascripts/discourse/views/topic_view.js index 223df8eb4..f0167e87c 100644 --- a/app/assets/javascripts/discourse/views/topic_view.js +++ b/app/assets/javascripts/discourse/views/topic_view.js @@ -106,7 +106,7 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { this.resetExamineDockCache(); // this happens after route exit, stuff could have trickled in - this.set('controller.controllers.header.showExtraInfo', false) + this.set('controller.controllers.header.showExtraInfo', false); }, didInsertElement: function(e) { diff --git a/app/assets/javascripts/discourse/views/view.js b/app/assets/javascripts/discourse/views/view.js index 66f11f7e3..90d484535 100644 --- a/app/assets/javascripts/discourse/views/view.js +++ b/app/assets/javascripts/discourse/views/view.js @@ -28,4 +28,4 @@ Discourse.View.reopenClass({ }); } -}) +}); diff --git a/lib/discourse_iife.rb b/lib/discourse_iife.rb index c79bff4ee..c6a1488d6 100644 --- a/lib/discourse_iife.rb +++ b/lib/discourse_iife.rb @@ -20,7 +20,7 @@ class DiscourseIIFE < Sprockets::Processor return data if path =~ /\.shbrs/ return data if path =~ /\.hbrs/ - "(function () {\n\nvar $ = window.jQuery;\n\n#{data}\n\n})(this);" + "(function () {\n\nvar $ = window.jQuery;\n// IIFE Wrapped Content Begins:\n\n#{data}\n\n// IIFE Wrapped Content Ends\n\n })(this);" end end \ No newline at end of file diff --git a/test/javascripts/components/bbcode_test.js b/test/javascripts/components/bbcode_test.js index 20b9ed117..5963e81b3 100644 --- a/test/javascripts/components/bbcode_test.js +++ b/test/javascripts/components/bbcode_test.js @@ -1,10 +1,10 @@ /*global md5:true */ - module("Discourse.BBCode"); var format = function(input, expected, text) { + // testing 1 2 3 equal(Discourse.BBCode.format(input, {lookupAvatar: false}), expected, text); -} +}; test('basic bbcode', function() { format("[b]strong[/b]", "strong", "bolds text"); @@ -50,7 +50,7 @@ test("quotes", function() { var formatQuote = function(val, expected, text) { equal(Discourse.BBCode.buildQuoteBBCode(post, val), expected, text); - } + }; formatQuote(undefined, "", "empty string for undefined content"); formatQuote(null, "", "empty string for null content"); diff --git a/test/javascripts/components/click_track_test.js b/test/javascripts/components/click_track_test.js index 6cab96c7d..84a69d3f8 100644 --- a/test/javascripts/components/click_track_test.js +++ b/test/javascripts/components/click_track_test.js @@ -37,9 +37,10 @@ module("Discourse.ClickTrack", { var track = Discourse.ClickTrack.trackClick; +// test var generateClickEventOn = function(selector) { return $.Event("click", { currentTarget: $(selector)[0] }); -} +}; test("does not track clicks on lightboxes", function() { var clickEvent = generateClickEventOn('.lightbox'); @@ -57,11 +58,11 @@ test("it calls preventDefault when clicking on an a", function() { }); test("does not track clicks on back buttons", function() { - ok(track(generateClickEventOn('.back'))) + ok(track(generateClickEventOn('.back'))); }); test("does not track clicks on quote buttons", function() { - ok(track(generateClickEventOn('.quote-other-topic'))) + ok(track(generateClickEventOn('.quote-other-topic'))); }); test("removes the href and put it as a data attribute", function() { @@ -99,7 +100,7 @@ test("updates badge counts correctly", function() { }); var trackRightClick = function() { - var clickEvent = generateClickEventOn('a') + var clickEvent = generateClickEventOn('a'); clickEvent.which = 3; return track(clickEvent); }; diff --git a/test/javascripts/components/formatter_test.js b/test/javascripts/components/formatter_test.js index 85b1b137e..a2910cbd3 100644 --- a/test/javascripts/components/formatter_test.js +++ b/test/javascripts/components/formatter_test.js @@ -27,15 +27,15 @@ test("formating medium length dates", function() { format = "medium"; var strip = function(html){ return $(html).text(); - } + }; var shortDate = function(days){ return moment().subtract('days', days).format('D MMM'); - } + }; var shortDateYear = function(days){ return moment().subtract('days', days).format('D MMM, YYYY'); - } + }; leaveAgo = true; equal(strip(formatMins(1.4)), "1 min ago"); diff --git a/test/javascripts/components/markdown_test.js b/test/javascripts/components/markdown_test.js index 97b491758..83d94cb6c 100644 --- a/test/javascripts/components/markdown_test.js +++ b/test/javascripts/components/markdown_test.js @@ -12,11 +12,10 @@ var cooked = function(input, expected, text) { var cookedOptions = function(input, opts, expected, text) { equal(Discourse.Markdown.cook(input, opts), expected, text); -} +}; test("basic cooking", function() { cooked("hello", "

hello

", "surrounds text with paragraphs"); - }); test("Line Breaks", function() { @@ -29,10 +28,10 @@ test("Line Breaks", function() { cookedOptions(input, {traditional_markdown_linebreaks: true}, traditionalOutput, - "It supports traditional markdown via an option") + "It supports traditional markdown via an option"); Discourse.SiteSettings.traditional_markdown_linebreaks = true; - cooked(input, traditionalOutput, "It supports traditional markdown via a Site Setting") + cooked(input, traditionalOutput, "It supports traditional markdown via a Site Setting"); }); diff --git a/test/javascripts/fixtures/list_fixtures.js b/test/javascripts/fixtures/list_fixtures.js index 7d6f83b67..ed258d097 100644 --- a/test/javascripts/fixtures/list_fixtures.js +++ b/test/javascripts/fixtures/list_fixtures.js @@ -1,3 +1,3 @@ /*jshint maxlen:10000000 */ -Discourse.URL_FIXTURES["/latest.json"] = {"categories":[{"id":1,"name":"bug","color":"ae3a27","text_color":"FFFFFF","slug":"bug","topic_count":338,"description":"> Bug reports on Discourse. Please be sure to search prior to submitting bugs. Please include repro steps, and report only 1 bug per topic.","topic_url":"/t/category-definition-for-bug/2","hotness":5.0,"secure":false},{"id":2,"name":"feature","color":"0E76BD","text_color":"FFFFFF","slug":"feature","topic_count":354,"description":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","topic_url":"/t/category-definition-for-feature/11","hotness":5.0,"secure":false},{"id":3,"name":"meta","color":"aaa","text_color":"FFFFFF","slug":"meta","topic_count":59,"description":"Discussion about meta.discourse.org itself, the organization of this forum about Discourse, how it works, and how we can improve this site.","topic_url":"/t/category-definition-for-meta/24","hotness":5.0,"secure":false},{"id":12,"name":"discourse hub","color":"b2c79f","text_color":"FFFFFF","slug":"discourse-hub","topic_count":4,"description":"Topics about current or future Discourse Hub functionality at discourse.org including nickname registration, global user pages, and the site directory.","topic_url":"/t/category-definition-for-discourse-hub/3038","hotness":5.0,"secure":false},{"id":7,"name":"dev","color":"000","text_color":"FFFFFF","slug":"dev","topic_count":157,"description":"This category is for topics related to hacking on Discourse: submitting pull requests, configuring development environments, coding conventions, and so forth.","topic_url":"/t/category-definition-for-dev/1026","hotness":5.0,"secure":false},{"id":9,"name":"ux","color":"5F497A","text_color":"FFFFFF","slug":"ux","topic_count":88,"description":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","topic_url":"/t/category-definition-for-ux/2628","hotness":5.0,"secure":false},{"id":5,"name":"extensibility ","color":"FE8432","text_color":"FFFFFF","slug":"extensibility","topic_count":26,"description":"Topics about extending the functionality of Discourse with plugins, themes, add-ons, or other mechanisms for extensibility. ","topic_url":"/t/category-definition-for-extensibility/28","hotness":5.0,"secure":false},{"id":6,"name":"support","color":"b99","text_color":"FFFFFF","slug":"support","topic_count":276,"description":"Support on configuring, using, and installing Discourse. Not for software development related topics, but for admins and end users configuring and using Discourse.","topic_url":"/t/category-definition-for-support/389","hotness":5.0,"secure":false},{"id":4,"name":"faq","color":"33b","text_color":"FFFFFF","slug":"faq","topic_count":38,"description":"Topics that come up very often when discussing Discourse will eventually be classified into this Frequently Asked Questions category. Should only be added to popular topics.","topic_url":"/t/category-definition-for-faq/25","hotness":5.0,"secure":false}],"users":[{"id":1,"username":"sam","avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon"},{"id":19,"username":"eviltrout","avatar_template":"https://www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon"},{"id":14,"username":"clay","avatar_template":"https://www.gravatar.com/avatar/e371bbd32ba2e9b27842e60ef5952d47.png?s={size}&r=pg&d=identicon"},{"id":32,"username":"codinghorror","avatar_template":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon"},{"id":5483,"username":"briangillespie","avatar_template":"https://www.gravatar.com/avatar/3baf9989e97ccf45aff1cf61fb730931.png?s={size}&r=pg&d=identicon"},{"id":4757,"username":"cerberus","avatar_template":"https://www.gravatar.com/avatar/cf99a7295aafa43c75ce25668b24df29.png?s={size}&r=pg&d=identicon"},{"id":247,"username":"chrishunt","avatar_template":"https://www.gravatar.com/avatar/4fafaca2401263fd03b62ff37a157a35.png?s={size}&r=pg&d=identicon"},{"id":406,"username":"RGJ","avatar_template":"https://www.gravatar.com/avatar/55c791f0242e5167536c65496046eef5.png?s={size}&r=pg&d=identicon"},{"id":38,"username":"frandallfarmer","avatar_template":"https://www.gravatar.com/avatar/6c38e00d92cd9bd3ada3392b15015553.png?s={size}&r=pg&d=identicon"},{"id":5425,"username":"mattengi","avatar_template":"https://www.gravatar.com/avatar/b66610f025dda14e96453bc5a0124685.png?s={size}&r=pg&d=identicon"},{"id":562,"username":"nightpool","avatar_template":"https://www.gravatar.com/avatar/d73164d2180b4cf6099526e42e33a7fd.png?s={size}&r=pg&d=identicon"},{"id":4939,"username":"stevebaer","avatar_template":"https://www.gravatar.com/avatar/7a42855912a58f4c3c5d0ce82e33905f.png?s={size}&r=pg&d=identicon"},{"id":761,"username":"marcoceppi","avatar_template":"https://www.gravatar.com/avatar/4ddc8924e79bcec03256821af65fca91.png?s={size}&r=pg&d=identicon"},{"id":1681,"username":"juanformoso","avatar_template":"https://www.gravatar.com/avatar/4dfc8f56817006ef21327d5ff19ce04f.png?s={size}&r=pg&d=identicon"},{"id":4612,"username":"Iszi","avatar_template":"https://www.gravatar.com/avatar/8f8571493d71202986f2a6ab0dbd7c23.png?s={size}&r=pg&d=identicon"},{"id":1995,"username":"zogstrip","avatar_template":"https://www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon"},{"id":1219,"username":"Gweebz","avatar_template":"https://www.gravatar.com/avatar/62e7d47ca49b942923f36eb90c1a31bd.png?s={size}&r=pg&d=identicon"},{"id":388,"username":"Drew","avatar_template":"https://www.gravatar.com/avatar/12b37360428acef73ff2c29cd3f272ef.png?s={size}&r=pg&d=identicon"},{"id":1566,"username":"hamburglar","avatar_template":"https://www.gravatar.com/avatar/57b39f59fa025f64e173ba6dffb8f2f7.png?s={size}&r=pg&d=identicon"},{"id":2,"username":"Neil","avatar_template":"https://www.gravatar.com/avatar/42776c4982dff1fa45ee8248532f8ad0.png?s={size}&r=pg&d=identicon"},{"id":5382,"username":"Charlie","avatar_template":"https://www.gravatar.com/avatar/4ae31ca93cd8a41fb44dace9d9d65166.png?s={size}&r=pg&d=identicon"},{"id":5372,"username":"computerdruid","avatar_template":"https://www.gravatar.com/avatar/34c3b45c075a6d85555beb674892d0d8.png?s={size}&r=pg&d=identicon"},{"id":1263,"username":"ChrisB","avatar_template":"https://www.gravatar.com/avatar/1d75aba12b3961894f93959b5b013de0.png?s={size}&r=pg&d=identicon"},{"id":5438,"username":"hpesoj","avatar_template":"https://www.gravatar.com/avatar/c6f01bdb4443d3686d8a11d8899c0100.png?s={size}&r=pg&d=identicon"},{"id":2770,"username":"awesomerobot","avatar_template":"https://www.gravatar.com/avatar/9744a2573a43bb6d76deef82b7424023.png?s={size}&r=pg&d=identicon"},{"id":5174,"username":"Maria_Sergeeva","avatar_template":"https://www.gravatar.com/avatar/3e8ede783ef16c8234c03473a5b8780f.png?s={size}&r=pg&d=identicon"},{"id":2291,"username":"PabloC","avatar_template":"https://www.gravatar.com/avatar/82c793022ec1bce6ea7573bc27b2340b.png?s={size}&r=pg&d=identicon"},{"id":3657,"username":"steelmaiden","avatar_template":"https://www.gravatar.com/avatar/ee057e3db79dbbf327ee1e2d3af3320d.png?s={size}&r=pg&d=identicon"},{"id":5502,"username":"ankursethi108","avatar_template":"https://www.gravatar.com/avatar/da27d97199c2e567ff848bf70984d405.png?s={size}&r=pg&d=identicon"},{"id":5271,"username":"royguo","avatar_template":"https://www.gravatar.com/avatar/7e795755fe8a817981c3a81620faf359.png?s={size}&r=pg&d=identicon"},{"id":2063,"username":"amoiseev","avatar_template":"https://www.gravatar.com/avatar/56c19d0f5d2640d4ddf730e25acf4364.png?s={size}&r=pg&d=identicon"},{"id":3987,"username":"Sander78","avatar_template":"https://www.gravatar.com/avatar/e7069beb46df22270a41afc7b277fe50.png?s={size}&r=pg&d=identicon"},{"id":3,"username":"supermathie","avatar_template":"https://www.gravatar.com/avatar/44ae1b2d44d48aed3d432129a5703942.png?s={size}&r=pg&d=identicon"},{"id":4220,"username":"kirantpatil","avatar_template":"https://www.gravatar.com/avatar/99d5ef2d7a453192340ca2c8adc21aa9.png?s={size}&r=pg&d=identicon"},{"id":471,"username":"BhaelOchon","avatar_template":"https://www.gravatar.com/avatar/413ef976f0d2ca993005c9aee4769254.png?s={size}&r=pg&d=identicon"},{"id":2316,"username":"pakl","avatar_template":"https://www.gravatar.com/avatar/42910619ef3d550e37f7150caa0d94ff.png?s={size}&r=pg&d=identicon"},{"id":5293,"username":"nick12377","avatar_template":"https://www.gravatar.com/avatar/de9a910f6c145938e047f77a524df50b.png?s={size}&r=pg&d=identicon"},{"id":1374,"username":"naggie","avatar_template":"https://www.gravatar.com/avatar/43675ac6f4ef94f40b5e44add2a984c1.png?s={size}&r=pg&d=identicon"}],"topic_list":{"can_create_topic":false,"more_topics_url":"/latest.json?page=1","draft":null,"draft_key":"new_topic","draft_sequence":null,"topics":[{"id":1,"fancy_title":"Welcome to meta.discourse.org","slug":"welcome-to-meta-discourse-org","posts_count":5,"reply_count":5,"highest_post_number":23,"image_url":null,"created_at":"2013-01-31T23:52:28-05:00","last_posted_at":"2013-02-07T16:50:41-05:00","bumped":true,"bumped_at":"2013-02-07T11:57:34-05:00","unseen":false,"title":"Welcome to meta.discourse.org","pinned":true,"excerpt":"Welcome to meta, the official site for discussing the next-gen open source Discourse forum software. You'll find topics on features, bugs, hosting, development, and general support here. \n\nDiscourse is early beta softwar…","visible":true,"closed":true,"archived":false,"views":9279,"like_count":84,"has_best_of":false,"archetype":"regular","category_id":null,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":14},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":7418,"fancy_title":"Auto-suggest topics shows Private topics","slug":"auto-suggest-topics-shows-private-topics","posts_count":2,"reply_count":1,"highest_post_number":2,"image_url":null,"created_at":"2013-06-12T12:08:11-04:00","last_posted_at":"2013-06-12T12:32:08-04:00","bumped":true,"bumped_at":"2013-06-12T12:32:08-04:00","unseen":false,"title":"Auto-suggest topics shows Private topics","pinned":false,"visible":true,"closed":false,"archived":false,"views":7,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":5483},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":7392,"fancy_title":"It there user’s birthday in user profile?","slug":"it-there-users-birthday-in-user-profile","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-06-11T19:02:05-04:00","last_posted_at":"2013-06-12T12:23:06-04:00","bumped":true,"bumped_at":"2013-06-12T12:23:06-04:00","unseen":false,"title":"It there user's birthday in user profile?","pinned":false,"visible":true,"closed":false,"archived":false,"views":42,"like_count":2,"has_best_of":false,"archetype":"regular","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":4757},{"extras":null,"description":"Most Posts","user_id":247},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":406}]},{"id":7420,"fancy_title":"Federated login for Microsoft and Apple","slug":"federated-login-for-microsoft-and-apple","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-12T12:19:23-04:00","last_posted_at":"2013-06-12T12:19:24-04:00","bumped":true,"bumped_at":"2013-06-12T12:19:24-04:00","unseen":false,"title":"Federated login for Microsoft and Apple","pinned":false,"visible":true,"closed":false,"archived":false,"views":4,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5483}]},{"id":3102,"fancy_title":"Please visit our Discourse Forum! (Directory)","slug":"please-visit-our-discourse-forum-directory","posts_count":36,"reply_count":16,"highest_post_number":36,"image_url":null,"created_at":"2013-02-14T14:30:38-05:00","last_posted_at":"2013-06-12T12:18:41-04:00","bumped":true,"bumped_at":"2013-06-12T12:18:41-04:00","unseen":false,"title":"Please visit our Discourse Forum! (Directory)","pinned":false,"visible":true,"closed":false,"archived":false,"views":2458,"like_count":32,"has_best_of":false,"archetype":"regular","category_id":3,"posters":[{"extras":null,"description":"Original Poster","user_id":38},{"extras":null,"description":"Most Posts","user_id":5425},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":562},{"extras":"latest","description":"Most Recent Poster","user_id":4939}]},{"id":7419,"fancy_title":"Non-authenticated users see all topics in mobile view","slug":"non-authenticated-users-see-all-topics-in-mobile-view","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-12T12:11:13-04:00","last_posted_at":"2013-06-12T12:11:14-04:00","bumped":true,"bumped_at":"2013-06-12T12:11:14-04:00","unseen":false,"title":"Non-authenticated users see all topics in mobile view","pinned":false,"visible":true,"closed":false,"archived":false,"views":4,"like_count":1,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5483}]},{"id":7417,"fancy_title":"Oneboxing “non-traditional” URLs","slug":"oneboxing-non-traditional-urls","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-12T11:42:19-04:00","last_posted_at":"2013-06-12T11:42:20-04:00","bumped":true,"bumped_at":"2013-06-12T11:42:20-04:00","unseen":false,"title":"Oneboxing \"non-traditional\" URLs","pinned":false,"visible":true,"closed":false,"archived":false,"views":13,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":761}]},{"id":7411,"fancy_title":"Unable to send digests","slug":"unable-to-send-digests","posts_count":12,"reply_count":7,"highest_post_number":12,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1078/030dd27562e8cc64.png","created_at":"2013-06-12T09:25:21-04:00","last_posted_at":"2013-06-12T11:21:10-04:00","bumped":true,"bumped_at":"2013-06-12T11:34:35-04:00","unseen":false,"title":"Unable to send digests","pinned":false,"visible":true,"closed":false,"archived":false,"views":26,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":1681},{"extras":null,"description":"Most Posts","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":1}]},{"id":6130,"fancy_title":"Make it easier to close large images","slug":"make-it-easier-to-close-large-images","posts_count":10,"reply_count":6,"highest_post_number":10,"image_url":null,"created_at":"2013-04-24T11:35:04-04:00","last_posted_at":"2013-06-12T11:26:21-04:00","bumped":true,"bumped_at":"2013-06-12T11:26:21-04:00","unseen":false,"title":"Make it easier to close large images","pinned":false,"visible":true,"closed":false,"archived":false,"views":124,"like_count":6,"has_best_of":false,"archetype":"regular","category_id":null,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":4612},{"extras":null,"description":"Most Posts","user_id":1995},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1219}]},{"id":424,"fancy_title":"What are the ‘consequences’ of changing your name?","slug":"what-are-the-consequences-of-changing-your-name","posts_count":34,"reply_count":31,"highest_post_number":34,"image_url":null,"created_at":"2013-02-05T17:37:52-05:00","last_posted_at":"2013-06-12T10:56:54-04:00","bumped":true,"bumped_at":"2013-06-12T10:56:54-04:00","unseen":false,"title":"What are the 'consequences' of changing your name?","pinned":false,"visible":true,"closed":false,"archived":false,"views":953,"like_count":41,"has_best_of":false,"archetype":"regular","category_id":12,"posters":[{"extras":null,"description":"Original Poster","user_id":388},{"extras":null,"description":"Most Posts","user_id":1566},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":2},{"extras":"latest","description":"Most Recent Poster","user_id":5382}]},{"id":7410,"fancy_title":"Twitter oneboxes are bust","slug":"twitter-oneboxes-are-bust","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2013-06-12T07:19:42-04:00","last_posted_at":"2013-06-12T10:50:31-04:00","bumped":true,"bumped_at":"2013-06-12T10:50:31-04:00","unseen":false,"title":"Twitter oneboxes are bust","pinned":false,"visible":true,"closed":false,"archived":false,"views":31,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":5372}]},{"id":7403,"fancy_title":"Deploy Disource at local machine with juju, lxc","slug":"deploy-disource-at-local-machine-with-juju-lxc","posts_count":7,"reply_count":4,"highest_post_number":7,"image_url":"http://cdn.discourse.org/assets/emoji/rage.png","created_at":"2013-06-11T23:23:09-04:00","last_posted_at":"2013-06-12T10:37:03-04:00","bumped":true,"bumped_at":"2013-06-12T10:37:03-04:00","unseen":false,"title":"Deploy Disource at local machine with juju, lxc","pinned":false,"visible":true,"closed":false,"archived":false,"views":32,"like_count":3,"has_best_of":false,"archetype":"regular","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":5425},{"extras":null,"description":"Most Posts","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":761}]},{"id":7362,"fancy_title":"Should post date/times be moved to remove ambiguity between user join dates or activity?","slug":"should-post-date-times-be-moved-to-remove-ambiguity-between-user-join-dates-or-activity","posts_count":23,"reply_count":18,"highest_post_number":23,"image_url":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s=40&r=pg&d=identicon","created_at":"2013-06-10T18:52:34-04:00","last_posted_at":"2013-06-12T10:35:29-04:00","bumped":true,"bumped_at":"2013-06-12T10:35:29-04:00","unseen":false,"title":"Should post date/times be moved to remove ambiguity between user join dates or activity?","pinned":false,"visible":true,"closed":false,"archived":false,"views":114,"like_count":9,"has_best_of":false,"archetype":"regular","category_id":9,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":1263},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":5438},{"extras":null,"description":"Frequent Poster","user_id":2770}]},{"id":7412,"fancy_title":"To group posts by a user","slug":"to-group-posts-by-a-user","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-06-12T09:35:57-04:00","last_posted_at":"2013-06-12T10:06:38-04:00","bumped":true,"bumped_at":"2013-06-12T10:14:51-04:00","unseen":false,"title":"To group posts by a user","pinned":false,"visible":true,"closed":false,"archived":false,"views":16,"like_count":3,"has_best_of":false,"archetype":"regular","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5174},{"extras":null,"description":"Most Posts","user_id":1}]},{"id":5689,"fancy_title":"Wordpress plugin to include latest topics as a sidebar?","slug":"wordpress-plugin-to-include-latest-topics-as-a-sidebar","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":null,"created_at":"2013-04-07T16:08:21-04:00","last_posted_at":"2013-06-12T09:58:21-04:00","bumped":true,"bumped_at":"2013-06-12T09:58:21-04:00","unseen":false,"title":"Wordpress plugin to include latest topics as a sidebar?","pinned":false,"visible":true,"closed":false,"archived":false,"views":139,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":5,"posters":[{"extras":null,"description":"Original Poster","user_id":2291},{"extras":null,"description":"Most Posts","user_id":3657},{"extras":"latest","description":"Most Recent Poster","user_id":5502}]},{"id":7401,"fancy_title":"Shall we add category names in top_menu?","slug":"shall-we-add-category-names-in-top-menu","posts_count":7,"reply_count":4,"highest_post_number":7,"image_url":null,"created_at":"2013-06-11T22:47:34-04:00","last_posted_at":"2013-06-12T06:55:03-04:00","bumped":true,"bumped_at":"2013-06-12T06:55:03-04:00","unseen":false,"title":"Shall we add category names in top_menu?","pinned":false,"visible":true,"closed":false,"archived":false,"views":35,"like_count":1,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5271},{"extras":null,"description":"Most Posts","user_id":1}]},{"id":7393,"fancy_title":"Cannot click on image immediately after uploading","slug":"cannot-click-on-image-immediately-after-uploading","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-11T19:05:32-04:00","last_posted_at":"2013-06-12T06:15:02-04:00","bumped":true,"bumped_at":"2013-06-12T06:15:02-04:00","unseen":false,"title":"Cannot click on image immediately after uploading","pinned":false,"visible":true,"closed":false,"archived":false,"views":24,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5438},{"extras":null,"description":"Most Posts","user_id":1995}]},{"id":7391,"fancy_title":"Upload image button doesn’t work after uploading an image","slug":"upload-image-button-doesnt-work-after-uploading-an-image","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-11T19:01:56-04:00","last_posted_at":"2013-06-12T06:14:01-04:00","bumped":true,"bumped_at":"2013-06-12T06:14:01-04:00","unseen":false,"title":"Upload image button doesn't work after uploading an image","pinned":false,"visible":true,"closed":false,"archived":false,"views":22,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5438},{"extras":null,"description":"Most Posts","user_id":1995}]},{"id":7409,"fancy_title":"New Relic installation","slug":"new-relic-installation","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-06-12T05:11:58-04:00","last_posted_at":"2013-06-12T05:20:30-04:00","bumped":true,"bumped_at":"2013-06-12T05:20:30-04:00","unseen":false,"title":"New Relic installation","pinned":false,"visible":true,"closed":false,"archived":false,"views":22,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":2063},{"extras":null,"description":"Most Posts","user_id":3987}]},{"id":7405,"fancy_title":"How to add “/faq” link in top menu?","slug":"how-to-add-faq-link-in-top-menu","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-12T01:28:32-04:00","last_posted_at":"2013-06-12T01:28:32-04:00","bumped":true,"bumped_at":"2013-06-12T03:12:30-04:00","unseen":false,"title":"How to add \"/faq\" link in top menu?","pinned":false,"visible":true,"closed":false,"archived":false,"views":31,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5271}]},{"id":7408,"fancy_title":"What are the options for hammering a bad account?","slug":"what-are-the-options-for-hammering-a-bad-account","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1077/43066a4fc47d1d70.png","created_at":"2013-06-12T02:33:53-04:00","last_posted_at":"2013-06-12T02:33:53-04:00","bumped":false,"bumped_at":"2013-06-12T02:33:53-04:00","unseen":false,"title":"What are the options for hammering a bad account?","pinned":false,"visible":true,"closed":false,"archived":false,"views":26,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":null,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":3}]},{"id":7378,"fancy_title":"Discourse Release Announcements for each tagged version release","slug":"discourse-release-announcements-for-each-tagged-version-release","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-06-11T10:31:03-04:00","last_posted_at":"2013-06-12T02:18:33-04:00","bumped":true,"bumped_at":"2013-06-12T02:18:33-04:00","unseen":false,"title":"Discourse Release Announcements for each tagged version release","pinned":false,"visible":true,"closed":false,"archived":false,"views":79,"like_count":7,"has_best_of":false,"archetype":"regular","category_id":4,"posters":[{"extras":null,"description":"Original Poster","user_id":4220},{"extras":null,"description":"Most Posts","user_id":471},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":761}]},{"id":7406,"fancy_title":"[missing {{user}} value] in user’s home page","slug":"missing-user-value-in-users-home-page","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1074/09013263c0ffc523.png","created_at":"2013-06-12T01:32:04-04:00","last_posted_at":"2013-06-12T01:47:03-04:00","bumped":true,"bumped_at":"2013-06-12T01:47:03-04:00","unseen":false,"title":"[missing {{user}} value] in user's home page","pinned":false,"visible":true,"closed":false,"archived":false,"views":17,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5271},{"extras":null,"description":"Most Posts","user_id":1}]},{"id":7400,"fancy_title":"Mockups/Ideas for extended profiles","slug":"mockups-ideas-for-extended-profiles","posts_count":2,"reply_count":1,"highest_post_number":2,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1061/8a6ac964893019ea.png","created_at":"2013-06-11T22:14:03-04:00","last_posted_at":"2013-06-12T01:01:26-04:00","bumped":true,"bumped_at":"2013-06-12T01:01:26-04:00","unseen":false,"title":"Mockups/Ideas for extended profiles","pinned":false,"visible":true,"closed":false,"archived":false,"views":34,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":2316},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":7146,"fancy_title":"Getting rid of sugar.js","slug":"getting-rid-of-sugar-js","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2013-06-04T03:22:49-04:00","last_posted_at":"2013-06-11T23:50:46-04:00","bumped":true,"bumped_at":"2013-06-11T23:50:46-04:00","unseen":false,"title":"Getting rid of sugar.js","pinned":false,"visible":true,"closed":false,"archived":false,"views":133,"like_count":8,"has_best_of":false,"archetype":"regular","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":2}]},{"id":7397,"fancy_title":"Incorrect string in new posts counter tooltip","slug":"incorrect-string-in-new-posts-counter-tooltip","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":"http://i.imgur.com/7557h94.png","created_at":"2013-06-11T21:15:40-04:00","last_posted_at":"2013-06-11T22:25:04-04:00","bumped":true,"bumped_at":"2013-06-11T22:11:00-04:00","unseen":false,"title":"Incorrect string in new posts counter tooltip","pinned":false,"visible":true,"closed":false,"archived":true,"views":19,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":5425},{"extras":"latest","description":"Most Recent Poster, Most Posts","user_id":1}]},{"id":7390,"fancy_title":"“Topic_count” instead of actual count","slug":"topic-count-instead-of-actual-count","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1060/6496b3226de5168d.png","created_at":"2013-06-11T18:58:20-04:00","last_posted_at":"2013-06-11T21:48:17-04:00","bumped":true,"bumped_at":"2013-06-11T19:48:20-04:00","unseen":false,"title":"\"Topic_count\" instead of actual count","pinned":false,"visible":true,"closed":false,"archived":true,"views":28,"like_count":3,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":5293},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":7389,"fancy_title":"I’m trying to making linux container(lxc) vagrant box image","slug":"im-trying-to-making-linux-container-lxc-vagrant-box-image","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"http://cdn.discourse.org/assets/emoji/wink.png","created_at":"2013-06-11T18:56:31-04:00","last_posted_at":"2013-06-11T18:56:31-04:00","bumped":true,"bumped_at":"2013-06-11T19:06:13-04:00","unseen":false,"title":"I'm trying to making linux container(lxc) vagrant box image","pinned":false,"visible":true,"closed":false,"archived":false,"views":25,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5425}]},{"id":7386,"fancy_title":"Mail settings shows password in clear text","slug":"mail-settings-shows-password-in-clear-text","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2013-06-11T17:19:41-04:00","last_posted_at":"2013-06-11T19:05:33-04:00","bumped":true,"bumped_at":"2013-06-11T19:05:33-04:00","unseen":false,"title":"Mail settings shows password in clear text","pinned":false,"visible":true,"closed":false,"archived":false,"views":45,"like_count":5,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":1681},{"extras":"latest","description":"Most Recent Poster","user_id":247}]},{"id":7175,"fancy_title":"Relative time something was posted","slug":"relative-time-something-was-posted","posts_count":105,"reply_count":90,"highest_post_number":105,"image_url":"http://meta.discourse.org/uploads/meta_discourse/1007/0f5263b3ee0a0f45.png","created_at":"2013-06-04T19:05:09-04:00","last_posted_at":"2013-06-11T19:01:41-04:00","bumped":true,"bumped_at":"2013-06-11T19:01:41-04:00","unseen":false,"title":"Relative time something was posted","pinned":false,"visible":true,"closed":false,"archived":false,"views":433,"like_count":94,"has_best_of":true,"archetype":"regular","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":1374},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":406},{"extras":null,"description":"Frequent Poster","user_id":5438},{"extras":"latest","description":"Most Recent Poster","user_id":1263}]}]}} -Discourse.URL_FIXTURES["/categories.json"] = {"featured_users":[{"id":32,"username":"codinghorror","avatar_template":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon"},{"id":1,"username":"sam","avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon"},{"id":1995,"username":"zogstrip","avatar_template":"https://www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon"},{"id":2,"username":"Neil","avatar_template":"https://www.gravatar.com/avatar/42776c4982dff1fa45ee8248532f8ad0.png?s={size}&r=pg&d=identicon"},{"id":19,"username":"eviltrout","avatar_template":"https://www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon"},{"id":2600,"username":"NinjaFoodstuff","avatar_template":"https://www.gravatar.com/avatar/3709f34da1ff5433b41bc56df94dd453.png?s={size}&r=pg&d=identicon"},{"id":406,"username":"RGJ","avatar_template":"https://www.gravatar.com/avatar/55c791f0242e5167536c65496046eef5.png?s={size}&r=pg&d=identicon"},{"id":1353,"username":"sparr","avatar_template":"https://www.gravatar.com/avatar/7906663b1197829751673465948e0b05.png?s={size}&r=pg&d=identicon"},{"id":8,"username":"geek","avatar_template":"https://www.gravatar.com/avatar/b0b1ce3a4e0a77abd157ec0309b72922.png?s={size}&r=pg&d=identicon"},{"id":4,"username":"stienman","avatar_template":"https://www.gravatar.com/avatar/281486f2a20201375414760dd347951d.png?s={size}&r=pg&d=identicon"},{"id":1274,"username":"binaryphile","avatar_template":"https://www.gravatar.com/avatar/82564a08aebae1de68bea8e3df61ef93.png?s={size}&r=pg&d=identicon"},{"id":1263,"username":"ChrisB","avatar_template":"https://www.gravatar.com/avatar/1d75aba12b3961894f93959b5b013de0.png?s={size}&r=pg&d=identicon"},{"id":38,"username":"frandallfarmer","avatar_template":"https://www.gravatar.com/avatar/6c38e00d92cd9bd3ada3392b15015553.png?s={size}&r=pg&d=identicon"},{"id":2839,"username":"baus","avatar_template":"https://www.gravatar.com/avatar/57d4030570f672f515a7385cc74c8cfe.png?s={size}&r=pg&d=identicon"},{"id":2664,"username":"Odd_Bloke","avatar_template":"https://www.gravatar.com/avatar/af881deb1a7ef0a1f568e18cd967c0d3.png?s={size}&r=pg&d=identicon"},{"id":714,"username":"jcolebrand","avatar_template":"https://www.gravatar.com/avatar/c4cca9bfec5d5e77f625d9fbe8e37a41.png?s={size}&r=pg&d=identicon"},{"id":810,"username":"ChrisHanel","avatar_template":"https://www.gravatar.com/avatar/467863a322e1a3ce557bfd17f4677600.png?s={size}&r=pg&d=identicon"},{"id":2291,"username":"PabloC","avatar_template":"https://www.gravatar.com/avatar/82c793022ec1bce6ea7573bc27b2340b.png?s={size}&r=pg&d=identicon"},{"id":1674,"username":"colin","avatar_template":"https://www.gravatar.com/avatar/4cfb483116a822652d698dce303ec842.png?s={size}&r=pg&d=identicon"},{"id":2128,"username":"ultimape","avatar_template":"https://www.gravatar.com/avatar/6fe82efded2ee5e218e0452644a07e2e.png?s={size}&r=pg&d=identicon"},{"id":811,"username":"jpeg","avatar_template":"https://www.gravatar.com/avatar/4a214d4a12b7223b61ec36c7aa224c97.png?s={size}&r=pg&d=identicon"},{"id":5468,"username":"pixelBender67","avatar_template":"https://www.gravatar.com/avatar/4f4cc88cc2ebd747240c7bc53af99261.png?s={size}&r=pg&d=identicon"},{"id":2471,"username":"robconery","avatar_template":"https://www.gravatar.com/avatar/31b18bc48108bc410884022764dbeec6.png?s={size}&r=pg&d=identicon"},{"id":4217,"username":"mshappe","avatar_template":"https://www.gravatar.com/avatar/36ffc752906110cd16e1746d5c95516f.png?s={size}&r=pg&d=identicon"},{"id":5460,"username":"ned","avatar_template":"https://www.gravatar.com/avatar/bc5e09a5ce0a85bf02a3fceb9b0bfaf4.png?s={size}&r=pg&d=identicon"},{"id":2399,"username":"passionate","avatar_template":"https://www.gravatar.com/avatar/bcb2185dd24051bce727b29230a2c171.png?s={size}&r=pg&d=identicon"},{"id":4263,"username":"mcwumbly","avatar_template":"https://www.gravatar.com/avatar/e217128117fe24525c7af5ebc5e45745.png?s={size}&r=pg&d=identicon"},{"id":2465,"username":"finid","avatar_template":"https://www.gravatar.com/avatar/989a7705a77732d888ddaff8b440fc3d.png?s={size}&r=pg&d=identicon"},{"id":4939,"username":"stevebaer","avatar_template":"https://www.gravatar.com/avatar/7a42855912a58f4c3c5d0ce82e33905f.png?s={size}&r=pg&d=identicon"},{"id":3507,"username":"mozCallahad","avatar_template":"https://www.gravatar.com/avatar/a2746e0bf42c3245bfd80dea9b3efb32.png?s={size}&r=pg&d=identicon"},{"id":704,"username":"AstonJ","avatar_template":"https://www.gravatar.com/avatar/03af361cc843bc56e95cb6c406d06f80.png?s={size}&r=pg&d=identicon"},{"id":461,"username":"kuba","avatar_template":"https://www.gravatar.com/avatar/1835cb6a5f35bd4089e416a99af90f5f.png?s={size}&r=pg&d=identicon"},{"id":1566,"username":"hamburglar","avatar_template":"https://www.gravatar.com/avatar/57b39f59fa025f64e173ba6dffb8f2f7.png?s={size}&r=pg&d=identicon"}],"category_list":{"can_create_category":false,"can_create_topic":false,"draft":null,"draft_key":"new_topic","draft_sequence":null,"categories":[{"id":1,"name":"bug","color":"ae3a27","text_color":"FFFFFF","slug":"bug","topic_count":361,"topics_week":24,"topics_month":93,"topics_year":357,"description":"Bug reports on Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","description_excerpt":"Bug reports on Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","featured_user_ids":[32,1,1995,2,19],"topics":[{"id":7288,"fancy_title":"Digest mail ignores secure groups","slug":"digest-mail-ignores-secure-groups","posts_count":7,"reply_count":3,"highest_post_number":7,"image_url":"http://cdn.discourse.org/assets/emoji/smile.png","created_at":"2013-06-08T08:54:12-04:00","last_posted_at":"2013-06-08T13:00:38-04:00","bumped":true,"bumped_at":"2013-06-08T13:00:38-04:00","unseen":false,"title":"Digest mail ignores secure groups","pinned":true,"excerpt":"People receiving the digest mail can easily read posts not meant for them. That's because the digest mail ignores the secure groups a member has access to or not. \n\nQuite a problem as I unfortunately found out. [smile]","visible":true,"closed":false,"archived":false},{"id":7554,"fancy_title":"Loading (never stops)","slug":"loading-never-stops","posts_count":14,"reply_count":10,"highest_post_number":14,"image_url":null,"created_at":"2013-06-17T04:10:19-04:00","last_posted_at":"2013-06-20T11:25:38-04:00","bumped":true,"bumped_at":"2013-06-20T11:25:38-04:00","unseen":false,"title":"Loading (never stops)","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7655,"fancy_title":"Are invisible topics broken again?","slug":"are-invisible-topics-broken-again","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"https://s3.amazonaws.com/lechat-im-share/77734fb7713f41c1c642d106f04848c29297c12c3563ec4b5b7863a1ad83c605/clip.png","created_at":"2013-06-20T10:14:16-04:00","last_posted_at":"2013-06-20T10:14:19-04:00","bumped":false,"bumped_at":"2013-06-20T10:14:16-04:00","unseen":false,"title":"Are invisible topics broken again?","pinned":false,"visible":false,"closed":false,"archived":false},{"id":7654,"fancy_title":"Grant admin failed","slug":"grant-admin-failed","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1167/6693820be81fb347.png","created_at":"2013-06-20T10:01:27-04:00","last_posted_at":"2013-06-20T10:01:27-04:00","bumped":false,"bumped_at":"2013-06-20T10:01:27-04:00","unseen":false,"title":"Grant admin failed","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7652,"fancy_title":"Onebox with Chinese failed!","slug":"onebox-with-chinese-failed","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1166/59abf864c9513bf1.png","created_at":"2013-06-20T09:21:32-04:00","last_posted_at":"2013-06-20T09:21:33-04:00","bumped":true,"bumped_at":"2013-06-20T09:21:33-04:00","unseen":false,"title":"Onebox with Chinese failed!","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7613,"fancy_title":"Site Customization not working","slug":"site-customization-not-working","posts_count":9,"reply_count":6,"highest_post_number":9,"image_url":null,"created_at":"2013-06-18T17:47:54-04:00","last_posted_at":"2013-06-20T07:36:24-04:00","bumped":true,"bumped_at":"2013-06-20T07:36:24-04:00","unseen":false,"title":"Site Customization not working","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":6,"name":"support","color":"b99","text_color":"FFFFFF","slug":"support","topic_count":300,"topics_week":22,"topics_month":90,"topics_year":299,"description":"Support on configuring, using, and installing Discourse. Not for software development related topics, but for admins and end users configuring and using Discourse.","description_excerpt":"Support on configuring, using, and installing Discourse. Not for software development related topics, but for admins and end users configuring and using Discourse.","featured_user_ids":[32,1,2,2600,406],"topics":[{"id":7645,"fancy_title":"Why does my logo come out like this?","slug":"why-does-my-logo-come-out-like-this","posts_count":8,"reply_count":3,"highest_post_number":8,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1163/9bdb455a1fdef5bd.png","created_at":"2013-06-20T00:07:35-04:00","last_posted_at":"2013-06-20T13:03:44-04:00","bumped":true,"bumped_at":"2013-06-20T13:03:44-04:00","unseen":false,"title":"Why does my logo come out like this?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7630,"fancy_title":"Google account login redirect to 127.0.0.1:3000","slug":"google-account-login-redirect-to-127-0-0-1-3000","posts_count":13,"reply_count":11,"highest_post_number":13,"image_url":"http://meta.discourse.org/assets/favicons/stackexchange-40d494822da7175d54657ac327a01ab4.png","created_at":"2013-06-19T09:04:16-04:00","last_posted_at":"2013-06-20T10:37:01-04:00","bumped":true,"bumped_at":"2013-06-20T10:37:01-04:00","unseen":false,"title":"Google account login redirect to 127.0.0.1:3000","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7642,"fancy_title":"500 error on posts","slug":"500-error-on-posts","posts_count":4,"reply_count":2,"highest_post_number":4,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1161/f8e0a36aceeab57b.png","created_at":"2013-06-19T18:32:41-04:00","last_posted_at":"2013-06-19T19:26:18-04:00","bumped":true,"bumped_at":"2013-06-19T19:34:02-04:00","unseen":false,"title":"500 error on posts","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7409,"fancy_title":"New Relic installation","slug":"new-relic-installation","posts_count":11,"reply_count":9,"highest_post_number":11,"image_url":null,"created_at":"2013-06-12T05:11:58-04:00","last_posted_at":"2013-06-19T18:56:43-04:00","bumped":true,"bumped_at":"2013-06-19T18:56:43-04:00","unseen":false,"title":"New Relic installation","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7604,"fancy_title":"Airbrake’s license has changed, not MIT anymore","slug":"airbrakes-license-has-changed-not-mit-anymore","posts_count":9,"reply_count":6,"highest_post_number":9,"image_url":null,"created_at":"2013-06-18T15:58:13-04:00","last_posted_at":"2013-06-19T11:37:39-04:00","bumped":true,"bumped_at":"2013-06-19T11:37:39-04:00","unseen":false,"title":"Airbrake's license has changed, not MIT anymore","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7583,"fancy_title":"What’s the best way to add global Javascript code?","slug":"whats-the-best-way-to-add-global-javascript-code","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-17T21:45:31-04:00","last_posted_at":"2013-06-17T21:45:31-04:00","bumped":true,"bumped_at":"2013-06-19T08:58:05-04:00","unseen":false,"title":"What's the best way to add global Javascript code?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":2,"name":"feature","color":"0E76BD","text_color":"FFFFFF","slug":"feature","topic_count":379,"topics_week":21,"topics_month":68,"topics_year":378,"description":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","description_excerpt":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","featured_user_ids":[32,1,1353,8,4],"topics":[{"id":7581,"fancy_title":"Category preferences","slug":"category-preferences","posts_count":6,"reply_count":2,"highest_post_number":6,"image_url":null,"created_at":"2013-06-17T19:40:35-04:00","last_posted_at":"2013-06-20T10:25:15-04:00","bumped":true,"bumped_at":"2013-06-20T10:25:15-04:00","unseen":false,"title":"Category preferences","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7651,"fancy_title":"Easier way to stop watching or tracking topic","slug":"easier-way-to-stop-watching-or-tracking-topic","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-20T09:20:48-04:00","last_posted_at":"2013-06-20T09:20:49-04:00","bumped":true,"bumped_at":"2013-06-20T09:20:49-04:00","unseen":false,"title":"Easier way to stop watching or tracking topic","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7450,"fancy_title":"We need an “Archive” Flag Notification button","slug":"we-need-an-archive-flag-notification-button","posts_count":9,"reply_count":7,"highest_post_number":9,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1098/fd9f66b93330aeea.png","created_at":"2013-06-13T12:31:05-04:00","last_posted_at":"2013-06-19T23:55:25-04:00","bumped":true,"bumped_at":"2013-06-19T23:55:25-04:00","unseen":false,"title":"We need an \"Archive\" Flag Notification button","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4415,"fancy_title":"Scaling Discourse","slug":"scaling-discourse","posts_count":8,"reply_count":6,"highest_post_number":8,"image_url":null,"created_at":"2013-03-04T10:17:09-05:00","last_posted_at":"2013-06-19T23:53:21-04:00","bumped":true,"bumped_at":"2013-06-19T23:53:21-04:00","unseen":false,"title":"Scaling Discourse","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7631,"fancy_title":"New topics since last visit are the same even after logout and exit","slug":"new-topics-since-last-visit-are-the-same-even-after-logout-and-exit","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-19T10:12:41-04:00","last_posted_at":"2013-06-19T19:34:15-04:00","bumped":true,"bumped_at":"2013-06-19T19:34:15-04:00","unseen":false,"title":"New topics since last visit are the same even after logout and exit","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7640,"fancy_title":"Confirmation on about to delete first post in the topic and consiquently entire topic","slug":"confirmation-on-about-to-delete-first-post-in-the-topic-and-consiquently-entire-topic","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-19T16:30:07-04:00","last_posted_at":"2013-06-19T19:10:54-04:00","bumped":true,"bumped_at":"2013-06-19T19:10:54-04:00","unseen":false,"title":"Confirmation on about to delete first post in the topic and consiquently entire topic","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":null,"name":"uncategorized","color":"AB9364","text_color":"FFFFFF","slug":"uncategorized","topic_count":173,"topics_week":6,"topics_month":24,"topics_year":173,"description":null,"description_excerpt":null,"is_uncategorized":true,"featured_user_ids":[],"topics":[{"id":7644,"fancy_title":"Is forum migration really worth it?","slug":"is-forum-migration-really-worth-it","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":"https://www.gravatar.com/avatar/eff1b72d56a97459a27161ccf7f20c89.png?s=40&r=pg&d=identicon","created_at":"2013-06-20T00:07:04-04:00","last_posted_at":"2013-06-20T12:30:16-04:00","bumped":true,"bumped_at":"2013-06-20T12:30:16-04:00","unseen":false,"title":"Is forum migration really worth it?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4533,"fancy_title":"Did you reply wrong topic in discourse forum?","slug":"did-you-reply-wrong-topic-in-discourse-forum","posts_count":11,"reply_count":8,"highest_post_number":11,"image_url":null,"created_at":"2013-03-06T20:41:25-05:00","last_posted_at":"2013-06-19T12:56:54-04:00","bumped":true,"bumped_at":"2013-06-19T12:56:54-04:00","unseen":false,"title":"Did you reply wrong topic in discourse forum?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7594,"fancy_title":"How to add links to the title header","slug":"how-to-add-links-to-the-title-header","posts_count":6,"reply_count":5,"highest_post_number":6,"image_url":null,"created_at":"2013-06-18T11:37:53-04:00","last_posted_at":"2013-06-18T17:12:26-04:00","bumped":true,"bumped_at":"2013-06-18T17:12:26-04:00","unseen":false,"title":"How to add links to the title header","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7602,"fancy_title":"SMTP Errors on latest","slug":"smtp-errors-on-latest","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2013-06-18T15:30:04-04:00","last_posted_at":"2013-06-18T16:09:45-04:00","bumped":true,"bumped_at":"2013-06-18T16:09:17-04:00","unseen":false,"title":"SMTP Errors on latest","pinned":false,"visible":true,"closed":false,"archived":true},{"id":7600,"fancy_title":"Any example or tutorial of using doorkeeper with discourse","slug":"any-example-or-tutorial-of-using-doorkeeper-with-discourse","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-18T14:23:44-04:00","last_posted_at":"2013-06-18T14:23:44-04:00","bumped":false,"bumped_at":"2013-06-18T14:23:44-04:00","unseen":false,"title":"Any example or tutorial of using doorkeeper with discourse","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7558,"fancy_title":"How can i get discourse to use for my community?","slug":"how-can-i-get-discourse-to-use-for-my-community","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-06-17T08:30:56-04:00","last_posted_at":"2013-06-17T11:05:31-04:00","bumped":true,"bumped_at":"2013-06-17T11:05:31-04:00","unseen":false,"title":"How can i get discourse to use for my community?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":7,"name":"dev","color":"000","text_color":"FFFFFF","slug":"dev","topic_count":162,"topics_week":4,"topics_month":31,"topics_year":162,"description":"This category is for topics related to hacking on Discourse: submitting pull requests, configuring development environments, coding conventions, and so forth.","description_excerpt":"This category is for topics related to hacking on Discourse: submitting pull requests, configuring development environments, coding conventions, and so forth.","featured_user_ids":[1,32,19,1274,1995],"topics":[{"id":3823,"fancy_title":"So, you want to help out with Discourse","slug":"so-you-want-to-help-out-with-discourse","posts_count":19,"reply_count":17,"highest_post_number":32,"image_url":null,"created_at":"2013-02-23T00:46:11-05:00","last_posted_at":"2013-06-05T21:09:56-04:00","bumped":true,"bumped_at":"2013-06-05T21:09:56-04:00","unseen":false,"title":"So, you want to help out with Discourse","pinned":true,"excerpt":"People are wondering, how it is they can help out with Discourse. \n\nWe have seen some chattering both here and on Github. \n\nI wanted to create a topic @eviltrout , @codinghorror and myself can keep up to date with clear…","visible":true,"closed":false,"archived":false},{"id":7638,"fancy_title":"Styling Discourse","slug":"styling-discourse","posts_count":8,"reply_count":5,"highest_post_number":8,"image_url":null,"created_at":"2013-06-19T15:23:36-04:00","last_posted_at":"2013-06-20T13:03:52-04:00","bumped":true,"bumped_at":"2013-06-20T13:03:52-04:00","unseen":false,"title":"Styling Discourse","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4403,"fancy_title":"Comrades let’s join our efforts on ukrainian and russian translations","slug":"comrades-lets-join-our-efforts-on-ukrainian-and-russian-translations","posts_count":38,"reply_count":18,"highest_post_number":38,"image_url":null,"created_at":"2013-03-04T08:04:53-05:00","last_posted_at":"2013-06-20T03:36:05-04:00","bumped":true,"bumped_at":"2013-06-20T03:36:05-04:00","unseen":false,"title":"Comrades let's join our efforts on ukrainian and russian translations","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6013,"fancy_title":"Image optimisation (work in progress)","slug":"image-optimisation-work-in-progress","posts_count":14,"reply_count":11,"highest_post_number":14,"image_url":null,"created_at":"2013-04-18T19:55:04-04:00","last_posted_at":"2013-06-20T02:33:06-04:00","bumped":true,"bumped_at":"2013-06-20T02:33:06-04:00","unseen":false,"title":"Image optimisation (work in progress)","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6669,"fancy_title":"Integrating Discourse with current user database?","slug":"integrating-discourse-with-current-user-database","posts_count":11,"reply_count":4,"highest_post_number":11,"image_url":null,"created_at":"2013-05-16T14:55:47-04:00","last_posted_at":"2013-06-19T22:04:47-04:00","bumped":true,"bumped_at":"2013-06-19T22:04:47-04:00","unseen":false,"title":"Integrating Discourse with current user database?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":3943,"fancy_title":"Give me those authentication hooks! :D","slug":"give-me-those-authentication-hooks-d","posts_count":5,"reply_count":3,"highest_post_number":5,"image_url":null,"created_at":"2013-02-24T02:18:13-05:00","last_posted_at":"2013-06-19T18:58:45-04:00","bumped":true,"bumped_at":"2013-06-19T18:58:45-04:00","unseen":false,"title":"Give me those authentication hooks! :D","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":9,"name":"ux","color":"5F497A","text_color":"FFFFFF","slug":"ux","topic_count":92,"topics_week":4,"topics_month":15,"topics_year":92,"description":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","description_excerpt":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","featured_user_ids":[32,1,406,1263,38],"topics":[{"id":7362,"fancy_title":"Should post date/times be moved to remove ambiguity between user join dates or activity?","slug":"should-post-date-times-be-moved-to-remove-ambiguity-between-user-join-dates-or-activity","posts_count":49,"reply_count":38,"highest_post_number":49,"image_url":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s=40&r=pg&d=identicon","created_at":"2013-06-10T18:52:34-04:00","last_posted_at":"2013-06-20T11:53:13-04:00","bumped":true,"bumped_at":"2013-06-20T11:53:13-04:00","unseen":false,"title":"Should post date/times be moved to remove ambiguity between user join dates or activity?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7614,"fancy_title":"“Your topic is similar to…” Always comes up","slug":"your-topic-is-similar-to-always-comes-up","posts_count":4,"reply_count":3,"highest_post_number":4,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1146/9dea86f9a04e59c8.png","created_at":"2013-06-18T19:27:03-04:00","last_posted_at":"2013-06-19T13:36:04-04:00","bumped":true,"bumped_at":"2013-06-19T13:36:04-04:00","unseen":false,"title":"\"Your topic is similar to...\" Always comes up","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7493,"fancy_title":"Activity reports on admin page","slug":"activity-reports-on-admin-page","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"https://s3.amazonaws.com/lechat-im-share/aa059a2e2227e1772ce49cae3611363e2e209bea8139d5adf93df0031e8bed75/clip.png","created_at":"2013-06-14T22:29:00-04:00","last_posted_at":"2013-06-17T11:16:11-04:00","bumped":true,"bumped_at":"2013-06-17T11:16:11-04:00","unseen":false,"title":"Activity reports on admin page","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7479,"fancy_title":"Expand topic details button not always necessary","slug":"expand-topic-details-button-not-always-necessary","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-06-14T10:54:00-04:00","last_posted_at":"2013-06-15T01:53:18-04:00","bumped":true,"bumped_at":"2013-06-15T01:53:18-04:00","unseen":false,"title":"Expand topic details button not always necessary","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7449,"fancy_title":"Categories view constantly resorting","slug":"categories-view-constantly-resorting","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-06-13T12:27:55-04:00","last_posted_at":"2013-06-14T05:37:27-04:00","bumped":true,"bumped_at":"2013-06-14T05:37:27-04:00","unseen":false,"title":"Categories view constantly resorting","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7400,"fancy_title":"Adding “developer” labels to the post avatars","slug":"adding-developer-labels-to-the-post-avatars","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1061/8a6ac964893019ea.png","created_at":"2013-06-11T22:14:03-04:00","last_posted_at":"2013-06-13T04:57:29-04:00","bumped":true,"bumped_at":"2013-06-13T04:57:29-04:00","unseen":false,"title":"Adding \"developer\" labels to the post avatars","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":10,"name":"howto","color":"76923C","text_color":"FFFFFF","slug":"howto","topic_count":38,"topics_week":3,"topics_month":11,"topics_year":38,"description":"Tutorial topics that describe how to set up, configure, or install Discourse using a specific platform or environment.","description_excerpt":"Tutorial topics that describe how to set up, configure, or install Discourse using a specific platform or environment.","featured_user_ids":[1,2839,2600,32,2664],"topics":[{"id":7461,"fancy_title":"Wordpress to Discourse importer!","slug":"wordpress-to-discourse-importer","posts_count":6,"reply_count":3,"highest_post_number":6,"image_url":null,"created_at":"2013-06-13T17:58:43-04:00","last_posted_at":"2013-06-19T23:17:47-04:00","bumped":true,"bumped_at":"2013-06-19T23:17:47-04:00","unseen":false,"title":"Wordpress to Discourse importer!","pinned":false,"visible":true,"closed":false,"archived":false},{"id":3395,"fancy_title":"Installing Discourse on Ubuntu 12.10 and Digital Ocean","slug":"installing-discourse-on-ubuntu-12-10-and-digital-ocean","posts_count":112,"reply_count":82,"highest_post_number":112,"image_url":null,"created_at":"2013-02-19T00:48:11-05:00","last_posted_at":"2013-06-18T19:28:40-04:00","bumped":true,"bumped_at":"2013-06-18T19:28:40-04:00","unseen":false,"title":"Installing Discourse on Ubuntu 12.10 and Digital Ocean","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7582,"fancy_title":"Twitter login with Passenger + Varnish - quick lessons learned","slug":"twitter-login-with-passenger-varnish-quick-lessons-learned","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"http://cdn.discourse.org/assets/emoji/smile.png","created_at":"2013-06-17T19:46:31-04:00","last_posted_at":"2013-06-17T20:11:08-04:00","bumped":true,"bumped_at":"2013-06-17T20:11:08-04:00","unseen":false,"title":"Twitter login with Passenger + Varnish - quick lessons learned","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7531,"fancy_title":"Building a new community, why you need a blog as a front page?","slug":"building-a-new-community-why-you-need-a-blog-as-a-front-page","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":null,"created_at":"2013-06-16T11:05:10-04:00","last_posted_at":"2013-06-17T13:21:54-04:00","bumped":true,"bumped_at":"2013-06-17T13:21:54-04:00","unseen":false,"title":"Building a new community, why you need a blog as a front page?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2349,"fancy_title":"Installing Discourse on Debian","slug":"installing-discourse-on-debian","posts_count":94,"reply_count":69,"highest_post_number":94,"image_url":null,"created_at":"2013-02-08T02:43:23-05:00","last_posted_at":"2013-06-17T11:52:06-04:00","bumped":true,"bumped_at":"2013-06-17T11:52:06-04:00","unseen":false,"title":"Installing Discourse on Debian","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6089,"fancy_title":"Import posts from Facebook group into Discourse","slug":"import-posts-from-facebook-group-into-discourse","posts_count":15,"reply_count":9,"highest_post_number":15,"image_url":"http://meta.discourse.org/uploads/meta_discourse/864/a39dd8d9b5a7768a.png","created_at":"2013-04-22T17:27:54-04:00","last_posted_at":"2013-06-17T05:40:11-04:00","bumped":true,"bumped_at":"2013-06-17T05:40:11-04:00","unseen":false,"title":"Import posts from Facebook group into Discourse","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":5,"name":"extensibility ","color":"FE8432","text_color":"FFFFFF","slug":"extensibility","topic_count":28,"topics_week":2,"topics_month":3,"topics_year":28,"description":"Topics about extending the functionality of Discourse with plugins, themes, add-ons, or other mechanisms for extensibility. ","description_excerpt":"Topics about extending the functionality of Discourse with plugins, themes, add-ons, or other mechanisms for extensibility.","featured_user_ids":[1,32,714,810,8],"topics":[{"id":7534,"fancy_title":"Has anybody created themes for Discourse?","slug":"has-anybody-created-themes-for-discourse","posts_count":15,"reply_count":13,"highest_post_number":15,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1114/a1a6f1260ebcb974.png","created_at":"2013-06-16T12:19:26-04:00","last_posted_at":"2013-06-17T14:47:00-04:00","bumped":true,"bumped_at":"2013-06-17T14:53:17-04:00","unseen":false,"title":"Has anybody created themes for Discourse?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7464,"fancy_title":"CSS documentation?","slug":"css-documentation","posts_count":2,"reply_count":1,"highest_post_number":2,"image_url":null,"created_at":"2013-06-13T18:25:19-04:00","last_posted_at":"2013-06-14T05:05:14-04:00","bumped":true,"bumped_at":"2013-06-14T05:05:14-04:00","unseen":false,"title":"CSS documentation?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5689,"fancy_title":"Wordpress plugin to include latest topics as a sidebar?","slug":"wordpress-plugin-to-include-latest-topics-as-a-sidebar","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":null,"created_at":"2013-04-07T16:08:21-04:00","last_posted_at":"2013-06-12T14:10:44-04:00","bumped":true,"bumped_at":"2013-06-12T14:10:44-04:00","unseen":false,"title":"Wordpress plugin to include latest topics as a sidebar?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":531,"fancy_title":"Discourse and Wordpress Integration","slug":"discourse-and-wordpress-integration","posts_count":57,"reply_count":50,"highest_post_number":59,"image_url":null,"created_at":"2013-02-05T18:56:37-05:00","last_posted_at":"2013-06-10T17:35:34-04:00","bumped":true,"bumped_at":"2013-06-10T17:35:34-04:00","unseen":false,"title":"Discourse and Wordpress Integration","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7213,"fancy_title":"Discourse as a Job Portal","slug":"discourse-as-a-job-portal","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":null,"created_at":"2013-06-06T04:43:59-04:00","last_posted_at":"2013-06-07T14:23:22-04:00","bumped":true,"bumped_at":"2013-06-07T14:23:22-04:00","unseen":false,"title":"Discourse as a Job Portal","pinned":false,"visible":true,"closed":false,"archived":false},{"id":31,"fancy_title":"What is the most awesome plugin for Discourse, that does not yet exist?","slug":"what-is-the-most-awesome-plugin-for-discourse-that-does-not-yet-exist","posts_count":153,"reply_count":105,"highest_post_number":154,"image_url":null,"created_at":"2013-02-03T06:43:18-05:00","last_posted_at":"2013-06-03T08:41:19-04:00","bumped":true,"bumped_at":"2013-06-03T08:41:19-04:00","unseen":false,"title":"What is the most awesome plugin for Discourse, that does not yet exist?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":8,"name":"hosting","color":"25AAE1","text_color":"FFFFFF","slug":"hosting","topic_count":41,"topics_week":1,"topics_month":9,"topics_year":41,"description":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_excerpt":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","featured_user_ids":[1,32,2291,1674,2839],"topics":[{"id":7595,"fancy_title":"Installing Discourse on AppFog","slug":"installing-discourse-on-appfog","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-18T11:43:50-04:00","last_posted_at":"2013-06-18T11:43:51-04:00","bumped":true,"bumped_at":"2013-06-18T11:43:51-04:00","unseen":false,"title":"Installing Discourse on AppFog","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4864,"fancy_title":"Discourse behind Varnish?","slug":"discourse-behind-varnish","posts_count":7,"reply_count":4,"highest_post_number":7,"image_url":null,"created_at":"2013-03-18T07:48:24-04:00","last_posted_at":"2013-06-16T12:12:47-04:00","bumped":true,"bumped_at":"2013-06-16T12:12:47-04:00","unseen":false,"title":"Discourse behind Varnish?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6783,"fancy_title":"Installation instructions for Ubuntu","slug":"installation-instructions-for-ubuntu","posts_count":33,"reply_count":26,"highest_post_number":33,"image_url":null,"created_at":"2013-05-22T00:53:38-04:00","last_posted_at":"2013-06-15T18:17:55-04:00","bumped":true,"bumped_at":"2013-06-15T18:17:55-04:00","unseen":false,"title":"Installation instructions for Ubuntu","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6779,"fancy_title":"Storing Images in the cloud","slug":"storing-images-in-the-cloud","posts_count":14,"reply_count":8,"highest_post_number":14,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/926/05bfcce4f39b1d06.png","created_at":"2013-05-21T19:56:32-04:00","last_posted_at":"2013-06-11T16:48:43-04:00","bumped":true,"bumped_at":"2013-06-11T16:48:43-04:00","unseen":false,"title":"Storing Images in the cloud","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7284,"fancy_title":"Meta.discourse.org stats?","slug":"meta-discourse-org-stats","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-08T03:16:26-04:00","last_posted_at":"2013-06-08T13:58:53-04:00","bumped":true,"bumped_at":"2013-06-08T13:58:53-04:00","unseen":false,"title":"Meta.discourse.org stats?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6992,"fancy_title":"Has anyone used webfaction for discourse?","slug":"has-anyone-used-webfaction-for-discourse","posts_count":7,"reply_count":6,"highest_post_number":10,"image_url":null,"created_at":"2013-05-29T11:41:50-04:00","last_posted_at":"2013-06-01T11:28:29-04:00","bumped":true,"bumped_at":"2013-06-01T11:28:29-04:00","unseen":false,"title":"Has anyone used webfaction for discourse?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":3,"name":"meta","color":"aaa","text_color":"FFFFFF","slug":"meta","topic_count":61,"topics_week":1,"topics_month":5,"topics_year":61,"description":"Discussion about meta.discourse.org itself, the organization of this forum about Discourse, how it works, and how we can improve this site.","description_excerpt":"Discussion about meta.discourse.org itself, the organization of this forum about Discourse, how it works, and how we can improve this site.","featured_user_ids":[32,1,38,2128,811],"topics":[{"id":5249,"fancy_title":"What is “Meta”?","slug":"what-is-meta","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2013-03-25T18:00:52-04:00","last_posted_at":"2013-03-25T18:00:56-04:00","bumped":false,"bumped_at":"2013-03-25T18:00:52-04:00","unseen":false,"title":"What is \"Meta\"?","pinned":true,"excerpt":"What is "Meta"?\n\nMeta means discussion of the discussion itself instead of the actual topic of the discussion. For example, discussions about... \n\n\nThe style of discussion.\nThe participants in the discussion.\nThe setting…","visible":true,"closed":false,"archived":false},{"id":1421,"fancy_title":"How do I send people invites?","slug":"how-do-i-send-people-invites","posts_count":13,"reply_count":10,"highest_post_number":13,"image_url":null,"created_at":"2013-02-06T11:28:45-05:00","last_posted_at":"2013-06-20T05:34:30-04:00","bumped":true,"bumped_at":"2013-06-20T05:34:30-04:00","unseen":false,"title":"How do I send people invites?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":3098,"fancy_title":"Ember and SEO challenges regarding discourse.org","slug":"ember-and-seo-challenges-regarding-discourse-org","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2013-02-14T13:40:31-05:00","last_posted_at":"2013-06-20T00:30:18-04:00","bumped":true,"bumped_at":"2013-06-20T00:30:18-04:00","unseen":false,"title":"Ember and SEO challenges regarding discourse.org","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7618,"fancy_title":"New posts, read posts, unread posts - I like this","slug":"new-posts-read-posts-unread-posts-i-like-this","posts_count":4,"reply_count":3,"highest_post_number":4,"image_url":"http://cdn.discourse.org/assets/emoji/wink2.png","created_at":"2013-06-18T21:21:00-04:00","last_posted_at":"2013-06-19T23:10:46-04:00","bumped":true,"bumped_at":"2013-06-19T23:10:46-04:00","unseen":false,"title":"New posts, read posts, unread posts - I like this","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6633,"fancy_title":"Discourse.org website is not available in Turkey","slug":"discourse-org-website-is-not-available-in-turkey","posts_count":18,"reply_count":10,"highest_post_number":18,"image_url":null,"created_at":"2013-05-15T05:40:32-04:00","last_posted_at":"2013-06-16T20:37:29-04:00","bumped":true,"bumped_at":"2013-06-16T20:37:29-04:00","unseen":false,"title":"Discourse.org website is not available in Turkey","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7431,"fancy_title":"Embedded images are not loading for me","slug":"embedded-images-are-not-loading-for-me","posts_count":2,"reply_count":1,"highest_post_number":2,"image_url":null,"created_at":"2013-06-12T23:37:29-04:00","last_posted_at":"2013-06-12T23:41:12-04:00","bumped":true,"bumped_at":"2013-06-12T23:41:12-04:00","unseen":false,"title":"Embedded images are not loading for me","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":13,"name":"blog","color":"ED207B","text_color":"FFFFFF","slug":"blog","topic_count":7,"topics_week":1,"topics_month":2,"topics_year":7,"description":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_excerpt":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","featured_user_ids":[32,1,5468,2471,4217],"topics":[{"id":7478,"fancy_title":"Discourse on Ubuntu: Video Walkthrough","slug":"discourse-on-ubuntu-video-walkthrough","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-14T10:47:53-04:00","last_posted_at":"2013-06-17T10:55:31-04:00","bumped":true,"bumped_at":"2013-06-17T10:55:31-04:00","unseen":false,"title":"Discourse on Ubuntu: Video Walkthrough","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5751,"fancy_title":"Discourse as Your First Rails App","slug":"discourse-as-your-first-rails-app","posts_count":43,"reply_count":31,"highest_post_number":48,"image_url":null,"created_at":"2013-04-09T19:08:33-04:00","last_posted_at":"2013-06-11T19:00:14-04:00","bumped":true,"bumped_at":"2013-06-13T05:22:30-04:00","unseen":false,"title":"Discourse as Your First Rails App","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5575,"fancy_title":"Our First Partner: How-To Geek","slug":"our-first-partner-how-to-geek","posts_count":22,"reply_count":16,"highest_post_number":23,"image_url":null,"created_at":"2013-04-03T18:42:46-04:00","last_posted_at":"2013-06-07T19:27:46-04:00","bumped":true,"bumped_at":"2013-06-07T19:27:46-04:00","unseen":false,"title":"Our First Partner: How-To Geek","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5898,"fancy_title":"The Discourse Servers","slug":"the-discourse-servers","posts_count":29,"reply_count":21,"highest_post_number":29,"image_url":null,"created_at":"2013-04-15T15:19:09-04:00","last_posted_at":"2013-05-30T15:44:01-04:00","bumped":true,"bumped_at":"2013-05-30T15:44:01-04:00","unseen":false,"title":"The Discourse Servers","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6744,"fancy_title":"Your Online Clubhouse","slug":"your-online-clubhouse","posts_count":7,"reply_count":4,"highest_post_number":7,"image_url":null,"created_at":"2013-05-20T16:26:44-04:00","last_posted_at":"2013-05-23T12:07:47-04:00","bumped":true,"bumped_at":"2013-05-23T12:07:47-04:00","unseen":false,"title":"Your Online Clubhouse","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5366,"fancy_title":"Forums, As Seen On TV","slug":"forums-as-seen-on-tv","posts_count":7,"reply_count":5,"highest_post_number":7,"image_url":"http://blog.discourse.org/wp-uploads/2013/03/30-rock-forums-1.jpg","created_at":"2013-03-28T16:53:41-04:00","last_posted_at":"2013-04-19T16:20:08-04:00","bumped":true,"bumped_at":"2013-04-19T16:20:08-04:00","unseen":false,"title":"Forums, As Seen On TV","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":14,"name":"marketplace","color":"8C6238","text_color":"FFFFFF","slug":"marketplace","topic_count":4,"topics_week":0,"topics_month":3,"topics_year":4,"description":"About commercial Discourse related stuff: jobs or paid gigs, plugins, themes, hosting, etc.","description_excerpt":"About commercial Discourse related stuff: jobs or paid gigs, plugins, themes, hosting, etc.","featured_user_ids":[1263,406,5460,32,2399],"topics":[{"id":7305,"fancy_title":"I would like to hire someone to install discourse on my site","slug":"i-would-like-to-hire-someone-to-install-discourse-on-my-site","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-08T18:32:07-04:00","last_posted_at":"2013-06-09T00:27:21-04:00","bumped":true,"bumped_at":"2013-06-09T00:27:21-04:00","unseen":false,"title":"I would like to hire someone to install discourse on my site","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7221,"fancy_title":"I want discourse for small business","slug":"i-want-discourse-for-small-business","posts_count":8,"reply_count":5,"highest_post_number":8,"image_url":null,"created_at":"2013-06-06T09:05:43-04:00","last_posted_at":"2013-06-06T19:52:47-04:00","bumped":true,"bumped_at":"2013-06-06T19:52:47-04:00","unseen":false,"title":"I want discourse for small business","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7054,"fancy_title":"Contract available: Web developer (RoR, JS, Node.JS, Discourse)","slug":"contract-available-web-developer-ror-js-node-js-discourse","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-05-31T19:30:52-04:00","last_posted_at":"2013-05-31T19:30:52-04:00","bumped":false,"bumped_at":"2013-05-31T19:30:52-04:00","unseen":false,"title":"Contract available: Web developer (RoR, JS, Node.JS, Discourse)","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5409,"fancy_title":"Looking for a Discourse specialist !!","slug":"looking-for-a-discourse-specialist","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-03-30T02:15:56-04:00","last_posted_at":"2013-03-30T02:15:56-04:00","bumped":false,"bumped_at":"2013-03-30T02:15:56-04:00","unseen":false,"title":"Looking for a Discourse specialist !!","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":4,"name":"faq","color":"33b","text_color":"FFFFFF","slug":"faq","topic_count":38,"topics_week":0,"topics_month":1,"topics_year":38,"description":"Topics that come up very often when discussing Discourse will eventually be classified into this Frequently Asked Questions category. Should only be added to popular topics.","description_excerpt":"Topics that come up very often when discussing Discourse will eventually be classified into this Frequently Asked Questions category. Should only be added to popular topics.","featured_user_ids":[32,1,4,4263,2465],"topics":[{"id":6724,"fancy_title":"Where is this development roadmap?","slug":"where-is-this-development-roadmap","posts_count":12,"reply_count":8,"highest_post_number":12,"image_url":null,"created_at":"2013-05-19T15:15:19-04:00","last_posted_at":"2013-06-17T11:03:07-04:00","bumped":true,"bumped_at":"2013-06-17T11:03:07-04:00","unseen":false,"title":"Where is this development roadmap?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2408,"fancy_title":"Private Discourse Forum","slug":"private-discourse-forum","posts_count":14,"reply_count":7,"highest_post_number":14,"image_url":null,"created_at":"2013-02-08T08:25:22-05:00","last_posted_at":"2013-06-13T04:49:27-04:00","bumped":true,"bumped_at":"2013-06-13T04:49:27-04:00","unseen":false,"title":"Private Discourse Forum","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7378,"fancy_title":"Discourse Release Announcements for each tagged version release","slug":"discourse-release-announcements-for-each-tagged-version-release","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-06-11T10:31:03-04:00","last_posted_at":"2013-06-12T02:18:33-04:00","bumped":true,"bumped_at":"2013-06-12T02:18:33-04:00","unseen":false,"title":"Discourse Release Announcements for each tagged version release","pinned":false,"visible":true,"closed":false,"archived":false},{"id":3059,"fancy_title":"Two different unread counts","slug":"two-different-unread-counts","posts_count":8,"reply_count":6,"highest_post_number":8,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/266/screen_shot_20130213_at_11_32_07_pm.png","created_at":"2013-02-14T02:37:50-05:00","last_posted_at":"2013-03-24T15:40:45-04:00","bumped":true,"bumped_at":"2013-06-04T18:20:22-04:00","unseen":false,"title":"Two different unread counts","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2724,"fancy_title":"What about the spam problem?","slug":"what-about-the-spam-problem","posts_count":8,"reply_count":3,"highest_post_number":8,"image_url":null,"created_at":"2013-02-11T00:58:15-05:00","last_posted_at":"2013-05-29T19:04:48-04:00","bumped":true,"bumped_at":"2013-05-29T19:04:48-04:00","unseen":false,"title":"What about the spam problem?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4546,"fancy_title":"What is a onebox?","slug":"what-is-a-onebox","posts_count":11,"reply_count":7,"highest_post_number":11,"image_url":"http://meta.discourse.org/users/stienman/avatar/40?__ws=meta.discourse.org","created_at":"2013-03-07T07:56:00-05:00","last_posted_at":"2013-05-22T16:34:11-04:00","bumped":true,"bumped_at":"2013-05-22T16:34:11-04:00","unseen":false,"title":"What is a onebox?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":11,"name":"login","color":"edb400","text_color":"FFFFFF","slug":"login","topic_count":15,"topics_week":0,"topics_month":1,"topics_year":15,"description":"Topics about logging in to Discourse, using any standard third party provider (Twitter, Facebook, Google), traditional username and password, or with a custom plugin.","description_excerpt":"Topics about logging in to Discourse, using any standard third party provider (Twitter, Facebook, Google), traditional username and password, or with a custom plugin.","featured_user_ids":[32,2,1,4939,3507],"topics":[{"id":6869,"fancy_title":"Force use of an authentication provider","slug":"force-use-of-an-authentication-provider","posts_count":21,"reply_count":16,"highest_post_number":21,"image_url":null,"created_at":"2013-05-24T15:26:41-04:00","last_posted_at":"2013-06-15T16:58:09-04:00","bumped":true,"bumped_at":"2013-06-15T16:58:09-04:00","unseen":false,"title":"Force use of an authentication provider","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5800,"fancy_title":"UTF-8 to webalized char set transliteration for Facebook login","slug":"utf-8-to-webalized-char-set-transliteration-for-facebook-login","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-04-11T13:58:56-04:00","last_posted_at":"2013-04-11T13:58:56-04:00","bumped":false,"bumped_at":"2013-04-11T13:58:56-04:00","unseen":false,"title":"UTF-8 to webalized char set transliteration for Facebook login","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4011,"fancy_title":"I can’t login using my password","slug":"i-can-t-login-using-my-password","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2013-02-25T06:47:42-05:00","last_posted_at":"2013-03-13T19:36:48-04:00","bumped":true,"bumped_at":"2013-03-13T19:36:44-04:00","unseen":false,"title":"I can't login using my password","pinned":false,"visible":true,"closed":false,"archived":true},{"id":4738,"fancy_title":"Login support for browser password managers","slug":"login-support-for-browser-password-managers","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-03-13T17:55:29-04:00","last_posted_at":"2013-03-13T19:11:56-04:00","bumped":true,"bumped_at":"2013-03-13T19:11:56-04:00","unseen":false,"title":"Login support for browser password managers","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2257,"fancy_title":"Alternative (non-ajax) way to login, so that browser password manager can save the login credentials","slug":"alternative-non-ajax-way-to-login-so-that-browser-password-manager-can-save-the-login-credentials","posts_count":9,"reply_count":4,"highest_post_number":9,"image_url":null,"created_at":"2013-02-07T17:05:37-05:00","last_posted_at":"2013-03-13T19:06:28-04:00","bumped":true,"bumped_at":"2013-03-13T19:06:23-04:00","unseen":false,"title":"Alternative (non-ajax) way to login, so that browser password manager can save the login credentials","pinned":false,"visible":true,"closed":false,"archived":true},{"id":4473,"fancy_title":"Log in doesn’t work correctly when behind a proxy","slug":"log-in-doesn-t-work-correctly-when-behind-a-proxy","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-03-05T10:38:08-05:00","last_posted_at":"2013-03-05T10:38:08-05:00","bumped":false,"bumped_at":"2013-03-05T10:38:08-05:00","unseen":false,"title":"Log in doesn't work correctly when behind a proxy","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":12,"name":"discourse hub","color":"b2c79f","text_color":"FFFFFF","slug":"discourse-hub","topic_count":4,"topics_week":0,"topics_month":0,"topics_year":4,"description":"Topics about current or future Discourse Hub functionality at discourse.org including nickname registration, global user pages, and the site directory.","description_excerpt":"Topics about current or future Discourse Hub functionality at discourse.org including nickname registration, global user pages, and the site directory.","featured_user_ids":[32,2,704,461,1566],"topics":[{"id":6547,"fancy_title":"Where to get discourse_org_access_key?","slug":"where-to-get-discourse-org-access-key","posts_count":6,"reply_count":1,"highest_post_number":6,"image_url":null,"created_at":"2013-05-10T22:06:08-04:00","last_posted_at":"2013-06-18T11:49:18-04:00","bumped":true,"bumped_at":"2013-06-18T11:49:18-04:00","unseen":false,"title":"Where to get discourse_org_access_key?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":424,"fancy_title":"What are the ‘consequences’ of changing your name?","slug":"what-are-the-consequences-of-changing-your-name","posts_count":33,"reply_count":32,"highest_post_number":33,"image_url":null,"created_at":"2013-02-05T17:37:52-05:00","last_posted_at":"2013-05-17T11:28:00-04:00","bumped":true,"bumped_at":"2013-06-12T13:22:10-04:00","unseen":false,"title":"What are the 'consequences' of changing your name?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2544,"fancy_title":"Discourse central hub questions","slug":"discourse-central-hub-questions","posts_count":49,"reply_count":41,"highest_post_number":49,"image_url":null,"created_at":"2013-02-09T04:28:21-05:00","last_posted_at":"2013-05-28T12:15:25-04:00","bumped":true,"bumped_at":"2013-05-28T12:15:25-04:00","unseen":false,"title":"Discourse central hub questions","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5664,"fancy_title":"Discourse Hub nickname uniqueness not working?","slug":"discourse-hub-nickname-uniqueness-not-working","posts_count":22,"reply_count":16,"highest_post_number":23,"image_url":null,"created_at":"2013-04-06T03:40:11-04:00","last_posted_at":"2013-04-09T10:56:46-04:00","bumped":true,"bumped_at":"2013-04-09T10:56:46-04:00","unseen":false,"title":"Discourse Hub nickname uniqueness not working?","pinned":false,"visible":true,"closed":false,"archived":false}]}]}} \ No newline at end of file +Discourse.URL_FIXTURES["/latest.json"] = {"categories":[{"id":1,"name":"bug","color":"ae3a27","text_color":"FFFFFF","slug":"bug","topic_count":338,"description":"> Bug reports on Discourse. Please be sure to search prior to submitting bugs. Please include repro steps, and report only 1 bug per topic.","topic_url":"/t/category-definition-for-bug/2","hotness":5.0,"secure":false},{"id":2,"name":"feature","color":"0E76BD","text_color":"FFFFFF","slug":"feature","topic_count":354,"description":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","topic_url":"/t/category-definition-for-feature/11","hotness":5.0,"secure":false},{"id":3,"name":"meta","color":"aaa","text_color":"FFFFFF","slug":"meta","topic_count":59,"description":"Discussion about meta.discourse.org itself, the organization of this forum about Discourse, how it works, and how we can improve this site.","topic_url":"/t/category-definition-for-meta/24","hotness":5.0,"secure":false},{"id":12,"name":"discourse hub","color":"b2c79f","text_color":"FFFFFF","slug":"discourse-hub","topic_count":4,"description":"Topics about current or future Discourse Hub functionality at discourse.org including nickname registration, global user pages, and the site directory.","topic_url":"/t/category-definition-for-discourse-hub/3038","hotness":5.0,"secure":false},{"id":7,"name":"dev","color":"000","text_color":"FFFFFF","slug":"dev","topic_count":157,"description":"This category is for topics related to hacking on Discourse: submitting pull requests, configuring development environments, coding conventions, and so forth.","topic_url":"/t/category-definition-for-dev/1026","hotness":5.0,"secure":false},{"id":9,"name":"ux","color":"5F497A","text_color":"FFFFFF","slug":"ux","topic_count":88,"description":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","topic_url":"/t/category-definition-for-ux/2628","hotness":5.0,"secure":false},{"id":5,"name":"extensibility ","color":"FE8432","text_color":"FFFFFF","slug":"extensibility","topic_count":26,"description":"Topics about extending the functionality of Discourse with plugins, themes, add-ons, or other mechanisms for extensibility. ","topic_url":"/t/category-definition-for-extensibility/28","hotness":5.0,"secure":false},{"id":6,"name":"support","color":"b99","text_color":"FFFFFF","slug":"support","topic_count":276,"description":"Support on configuring, using, and installing Discourse. Not for software development related topics, but for admins and end users configuring and using Discourse.","topic_url":"/t/category-definition-for-support/389","hotness":5.0,"secure":false},{"id":4,"name":"faq","color":"33b","text_color":"FFFFFF","slug":"faq","topic_count":38,"description":"Topics that come up very often when discussing Discourse will eventually be classified into this Frequently Asked Questions category. Should only be added to popular topics.","topic_url":"/t/category-definition-for-faq/25","hotness":5.0,"secure":false}],"users":[{"id":1,"username":"sam","avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon"},{"id":19,"username":"eviltrout","avatar_template":"https://www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon"},{"id":14,"username":"clay","avatar_template":"https://www.gravatar.com/avatar/e371bbd32ba2e9b27842e60ef5952d47.png?s={size}&r=pg&d=identicon"},{"id":32,"username":"codinghorror","avatar_template":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon"},{"id":5483,"username":"briangillespie","avatar_template":"https://www.gravatar.com/avatar/3baf9989e97ccf45aff1cf61fb730931.png?s={size}&r=pg&d=identicon"},{"id":4757,"username":"cerberus","avatar_template":"https://www.gravatar.com/avatar/cf99a7295aafa43c75ce25668b24df29.png?s={size}&r=pg&d=identicon"},{"id":247,"username":"chrishunt","avatar_template":"https://www.gravatar.com/avatar/4fafaca2401263fd03b62ff37a157a35.png?s={size}&r=pg&d=identicon"},{"id":406,"username":"RGJ","avatar_template":"https://www.gravatar.com/avatar/55c791f0242e5167536c65496046eef5.png?s={size}&r=pg&d=identicon"},{"id":38,"username":"frandallfarmer","avatar_template":"https://www.gravatar.com/avatar/6c38e00d92cd9bd3ada3392b15015553.png?s={size}&r=pg&d=identicon"},{"id":5425,"username":"mattengi","avatar_template":"https://www.gravatar.com/avatar/b66610f025dda14e96453bc5a0124685.png?s={size}&r=pg&d=identicon"},{"id":562,"username":"nightpool","avatar_template":"https://www.gravatar.com/avatar/d73164d2180b4cf6099526e42e33a7fd.png?s={size}&r=pg&d=identicon"},{"id":4939,"username":"stevebaer","avatar_template":"https://www.gravatar.com/avatar/7a42855912a58f4c3c5d0ce82e33905f.png?s={size}&r=pg&d=identicon"},{"id":761,"username":"marcoceppi","avatar_template":"https://www.gravatar.com/avatar/4ddc8924e79bcec03256821af65fca91.png?s={size}&r=pg&d=identicon"},{"id":1681,"username":"juanformoso","avatar_template":"https://www.gravatar.com/avatar/4dfc8f56817006ef21327d5ff19ce04f.png?s={size}&r=pg&d=identicon"},{"id":4612,"username":"Iszi","avatar_template":"https://www.gravatar.com/avatar/8f8571493d71202986f2a6ab0dbd7c23.png?s={size}&r=pg&d=identicon"},{"id":1995,"username":"zogstrip","avatar_template":"https://www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon"},{"id":1219,"username":"Gweebz","avatar_template":"https://www.gravatar.com/avatar/62e7d47ca49b942923f36eb90c1a31bd.png?s={size}&r=pg&d=identicon"},{"id":388,"username":"Drew","avatar_template":"https://www.gravatar.com/avatar/12b37360428acef73ff2c29cd3f272ef.png?s={size}&r=pg&d=identicon"},{"id":1566,"username":"hamburglar","avatar_template":"https://www.gravatar.com/avatar/57b39f59fa025f64e173ba6dffb8f2f7.png?s={size}&r=pg&d=identicon"},{"id":2,"username":"Neil","avatar_template":"https://www.gravatar.com/avatar/42776c4982dff1fa45ee8248532f8ad0.png?s={size}&r=pg&d=identicon"},{"id":5382,"username":"Charlie","avatar_template":"https://www.gravatar.com/avatar/4ae31ca93cd8a41fb44dace9d9d65166.png?s={size}&r=pg&d=identicon"},{"id":5372,"username":"computerdruid","avatar_template":"https://www.gravatar.com/avatar/34c3b45c075a6d85555beb674892d0d8.png?s={size}&r=pg&d=identicon"},{"id":1263,"username":"ChrisB","avatar_template":"https://www.gravatar.com/avatar/1d75aba12b3961894f93959b5b013de0.png?s={size}&r=pg&d=identicon"},{"id":5438,"username":"hpesoj","avatar_template":"https://www.gravatar.com/avatar/c6f01bdb4443d3686d8a11d8899c0100.png?s={size}&r=pg&d=identicon"},{"id":2770,"username":"awesomerobot","avatar_template":"https://www.gravatar.com/avatar/9744a2573a43bb6d76deef82b7424023.png?s={size}&r=pg&d=identicon"},{"id":5174,"username":"Maria_Sergeeva","avatar_template":"https://www.gravatar.com/avatar/3e8ede783ef16c8234c03473a5b8780f.png?s={size}&r=pg&d=identicon"},{"id":2291,"username":"PabloC","avatar_template":"https://www.gravatar.com/avatar/82c793022ec1bce6ea7573bc27b2340b.png?s={size}&r=pg&d=identicon"},{"id":3657,"username":"steelmaiden","avatar_template":"https://www.gravatar.com/avatar/ee057e3db79dbbf327ee1e2d3af3320d.png?s={size}&r=pg&d=identicon"},{"id":5502,"username":"ankursethi108","avatar_template":"https://www.gravatar.com/avatar/da27d97199c2e567ff848bf70984d405.png?s={size}&r=pg&d=identicon"},{"id":5271,"username":"royguo","avatar_template":"https://www.gravatar.com/avatar/7e795755fe8a817981c3a81620faf359.png?s={size}&r=pg&d=identicon"},{"id":2063,"username":"amoiseev","avatar_template":"https://www.gravatar.com/avatar/56c19d0f5d2640d4ddf730e25acf4364.png?s={size}&r=pg&d=identicon"},{"id":3987,"username":"Sander78","avatar_template":"https://www.gravatar.com/avatar/e7069beb46df22270a41afc7b277fe50.png?s={size}&r=pg&d=identicon"},{"id":3,"username":"supermathie","avatar_template":"https://www.gravatar.com/avatar/44ae1b2d44d48aed3d432129a5703942.png?s={size}&r=pg&d=identicon"},{"id":4220,"username":"kirantpatil","avatar_template":"https://www.gravatar.com/avatar/99d5ef2d7a453192340ca2c8adc21aa9.png?s={size}&r=pg&d=identicon"},{"id":471,"username":"BhaelOchon","avatar_template":"https://www.gravatar.com/avatar/413ef976f0d2ca993005c9aee4769254.png?s={size}&r=pg&d=identicon"},{"id":2316,"username":"pakl","avatar_template":"https://www.gravatar.com/avatar/42910619ef3d550e37f7150caa0d94ff.png?s={size}&r=pg&d=identicon"},{"id":5293,"username":"nick12377","avatar_template":"https://www.gravatar.com/avatar/de9a910f6c145938e047f77a524df50b.png?s={size}&r=pg&d=identicon"},{"id":1374,"username":"naggie","avatar_template":"https://www.gravatar.com/avatar/43675ac6f4ef94f40b5e44add2a984c1.png?s={size}&r=pg&d=identicon"}],"topic_list":{"can_create_topic":false,"more_topics_url":"/latest.json?page=1","draft":null,"draft_key":"new_topic","draft_sequence":null,"topics":[{"id":1,"fancy_title":"Welcome to meta.discourse.org","slug":"welcome-to-meta-discourse-org","posts_count":5,"reply_count":5,"highest_post_number":23,"image_url":null,"created_at":"2013-01-31T23:52:28-05:00","last_posted_at":"2013-02-07T16:50:41-05:00","bumped":true,"bumped_at":"2013-02-07T11:57:34-05:00","unseen":false,"title":"Welcome to meta.discourse.org","pinned":true,"excerpt":"Welcome to meta, the official site for discussing the next-gen open source Discourse forum software. You'll find topics on features, bugs, hosting, development, and general support here. \n\nDiscourse is early beta softwar…","visible":true,"closed":true,"archived":false,"views":9279,"like_count":84,"has_best_of":false,"archetype":"regular","category_id":null,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":14},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":7418,"fancy_title":"Auto-suggest topics shows Private topics","slug":"auto-suggest-topics-shows-private-topics","posts_count":2,"reply_count":1,"highest_post_number":2,"image_url":null,"created_at":"2013-06-12T12:08:11-04:00","last_posted_at":"2013-06-12T12:32:08-04:00","bumped":true,"bumped_at":"2013-06-12T12:32:08-04:00","unseen":false,"title":"Auto-suggest topics shows Private topics","pinned":false,"visible":true,"closed":false,"archived":false,"views":7,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":5483},{"extras":"latest","description":"Most Recent Poster","user_id":32}]},{"id":7392,"fancy_title":"It there user’s birthday in user profile?","slug":"it-there-users-birthday-in-user-profile","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-06-11T19:02:05-04:00","last_posted_at":"2013-06-12T12:23:06-04:00","bumped":true,"bumped_at":"2013-06-12T12:23:06-04:00","unseen":false,"title":"It there user's birthday in user profile?","pinned":false,"visible":true,"closed":false,"archived":false,"views":42,"like_count":2,"has_best_of":false,"archetype":"regular","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":4757},{"extras":null,"description":"Most Posts","user_id":247},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":406}]},{"id":7420,"fancy_title":"Federated login for Microsoft and Apple","slug":"federated-login-for-microsoft-and-apple","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-12T12:19:23-04:00","last_posted_at":"2013-06-12T12:19:24-04:00","bumped":true,"bumped_at":"2013-06-12T12:19:24-04:00","unseen":false,"title":"Federated login for Microsoft and Apple","pinned":false,"visible":true,"closed":false,"archived":false,"views":4,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5483}]},{"id":3102,"fancy_title":"Please visit our Discourse Forum! (Directory)","slug":"please-visit-our-discourse-forum-directory","posts_count":36,"reply_count":16,"highest_post_number":36,"image_url":null,"created_at":"2013-02-14T14:30:38-05:00","last_posted_at":"2013-06-12T12:18:41-04:00","bumped":true,"bumped_at":"2013-06-12T12:18:41-04:00","unseen":false,"title":"Please visit our Discourse Forum! (Directory)","pinned":false,"visible":true,"closed":false,"archived":false,"views":2458,"like_count":32,"has_best_of":false,"archetype":"regular","category_id":3,"posters":[{"extras":null,"description":"Original Poster","user_id":38},{"extras":null,"description":"Most Posts","user_id":5425},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":562},{"extras":"latest","description":"Most Recent Poster","user_id":4939}]},{"id":7419,"fancy_title":"Non-authenticated users see all topics in mobile view","slug":"non-authenticated-users-see-all-topics-in-mobile-view","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-12T12:11:13-04:00","last_posted_at":"2013-06-12T12:11:14-04:00","bumped":true,"bumped_at":"2013-06-12T12:11:14-04:00","unseen":false,"title":"Non-authenticated users see all topics in mobile view","pinned":false,"visible":true,"closed":false,"archived":false,"views":4,"like_count":1,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5483}]},{"id":7417,"fancy_title":"Oneboxing “non-traditional” URLs","slug":"oneboxing-non-traditional-urls","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-12T11:42:19-04:00","last_posted_at":"2013-06-12T11:42:20-04:00","bumped":true,"bumped_at":"2013-06-12T11:42:20-04:00","unseen":false,"title":"Oneboxing \"non-traditional\" URLs","pinned":false,"visible":true,"closed":false,"archived":false,"views":13,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":761}]},{"id":7411,"fancy_title":"Unable to send digests","slug":"unable-to-send-digests","posts_count":12,"reply_count":7,"highest_post_number":12,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1078/030dd27562e8cc64.png","created_at":"2013-06-12T09:25:21-04:00","last_posted_at":"2013-06-12T11:21:10-04:00","bumped":true,"bumped_at":"2013-06-12T11:34:35-04:00","unseen":false,"title":"Unable to send digests","pinned":false,"visible":true,"closed":false,"archived":false,"views":26,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":1681},{"extras":null,"description":"Most Posts","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":1}]},{"id":6130,"fancy_title":"Make it easier to close large images","slug":"make-it-easier-to-close-large-images","posts_count":10,"reply_count":6,"highest_post_number":10,"image_url":null,"created_at":"2013-04-24T11:35:04-04:00","last_posted_at":"2013-06-12T11:26:21-04:00","bumped":true,"bumped_at":"2013-06-12T11:26:21-04:00","unseen":false,"title":"Make it easier to close large images","pinned":false,"visible":true,"closed":false,"archived":false,"views":124,"like_count":6,"has_best_of":false,"archetype":"regular","category_id":null,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":4612},{"extras":null,"description":"Most Posts","user_id":1995},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1219}]},{"id":424,"fancy_title":"What are the ‘consequences’ of changing your name?","slug":"what-are-the-consequences-of-changing-your-name","posts_count":34,"reply_count":31,"highest_post_number":34,"image_url":null,"created_at":"2013-02-05T17:37:52-05:00","last_posted_at":"2013-06-12T10:56:54-04:00","bumped":true,"bumped_at":"2013-06-12T10:56:54-04:00","unseen":false,"title":"What are the 'consequences' of changing your name?","pinned":false,"visible":true,"closed":false,"archived":false,"views":953,"like_count":41,"has_best_of":false,"archetype":"regular","category_id":12,"posters":[{"extras":null,"description":"Original Poster","user_id":388},{"extras":null,"description":"Most Posts","user_id":1566},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":2},{"extras":"latest","description":"Most Recent Poster","user_id":5382}]},{"id":7410,"fancy_title":"Twitter oneboxes are bust","slug":"twitter-oneboxes-are-bust","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2013-06-12T07:19:42-04:00","last_posted_at":"2013-06-12T10:50:31-04:00","bumped":true,"bumped_at":"2013-06-12T10:50:31-04:00","unseen":false,"title":"Twitter oneboxes are bust","pinned":false,"visible":true,"closed":false,"archived":false,"views":31,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":5372}]},{"id":7403,"fancy_title":"Deploy Disource at local machine with juju, lxc","slug":"deploy-disource-at-local-machine-with-juju-lxc","posts_count":7,"reply_count":4,"highest_post_number":7,"image_url":"http://cdn.discourse.org/assets/emoji/rage.png","created_at":"2013-06-11T23:23:09-04:00","last_posted_at":"2013-06-12T10:37:03-04:00","bumped":true,"bumped_at":"2013-06-12T10:37:03-04:00","unseen":false,"title":"Deploy Disource at local machine with juju, lxc","pinned":false,"visible":true,"closed":false,"archived":false,"views":32,"like_count":3,"has_best_of":false,"archetype":"regular","category_id":7,"posters":[{"extras":null,"description":"Original Poster","user_id":5425},{"extras":null,"description":"Most Posts","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":761}]},{"id":7362,"fancy_title":"Should post date/times be moved to remove ambiguity between user join dates or activity?","slug":"should-post-date-times-be-moved-to-remove-ambiguity-between-user-join-dates-or-activity","posts_count":23,"reply_count":18,"highest_post_number":23,"image_url":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s=40&r=pg&d=identicon","created_at":"2013-06-10T18:52:34-04:00","last_posted_at":"2013-06-12T10:35:29-04:00","bumped":true,"bumped_at":"2013-06-12T10:35:29-04:00","unseen":false,"title":"Should post date/times be moved to remove ambiguity between user join dates or activity?","pinned":false,"visible":true,"closed":false,"archived":false,"views":114,"like_count":9,"has_best_of":false,"archetype":"regular","category_id":9,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":1263},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":null,"description":"Frequent Poster","user_id":5438},{"extras":null,"description":"Frequent Poster","user_id":2770}]},{"id":7412,"fancy_title":"To group posts by a user","slug":"to-group-posts-by-a-user","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-06-12T09:35:57-04:00","last_posted_at":"2013-06-12T10:06:38-04:00","bumped":true,"bumped_at":"2013-06-12T10:14:51-04:00","unseen":false,"title":"To group posts by a user","pinned":false,"visible":true,"closed":false,"archived":false,"views":16,"like_count":3,"has_best_of":false,"archetype":"regular","category_id":2,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5174},{"extras":null,"description":"Most Posts","user_id":1}]},{"id":5689,"fancy_title":"Wordpress plugin to include latest topics as a sidebar?","slug":"wordpress-plugin-to-include-latest-topics-as-a-sidebar","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":null,"created_at":"2013-04-07T16:08:21-04:00","last_posted_at":"2013-06-12T09:58:21-04:00","bumped":true,"bumped_at":"2013-06-12T09:58:21-04:00","unseen":false,"title":"Wordpress plugin to include latest topics as a sidebar?","pinned":false,"visible":true,"closed":false,"archived":false,"views":139,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":5,"posters":[{"extras":null,"description":"Original Poster","user_id":2291},{"extras":null,"description":"Most Posts","user_id":3657},{"extras":"latest","description":"Most Recent Poster","user_id":5502}]},{"id":7401,"fancy_title":"Shall we add category names in top_menu?","slug":"shall-we-add-category-names-in-top-menu","posts_count":7,"reply_count":4,"highest_post_number":7,"image_url":null,"created_at":"2013-06-11T22:47:34-04:00","last_posted_at":"2013-06-12T06:55:03-04:00","bumped":true,"bumped_at":"2013-06-12T06:55:03-04:00","unseen":false,"title":"Shall we add category names in top_menu?","pinned":false,"visible":true,"closed":false,"archived":false,"views":35,"like_count":1,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5271},{"extras":null,"description":"Most Posts","user_id":1}]},{"id":7393,"fancy_title":"Cannot click on image immediately after uploading","slug":"cannot-click-on-image-immediately-after-uploading","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-11T19:05:32-04:00","last_posted_at":"2013-06-12T06:15:02-04:00","bumped":true,"bumped_at":"2013-06-12T06:15:02-04:00","unseen":false,"title":"Cannot click on image immediately after uploading","pinned":false,"visible":true,"closed":false,"archived":false,"views":24,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5438},{"extras":null,"description":"Most Posts","user_id":1995}]},{"id":7391,"fancy_title":"Upload image button doesn’t work after uploading an image","slug":"upload-image-button-doesnt-work-after-uploading-an-image","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-11T19:01:56-04:00","last_posted_at":"2013-06-12T06:14:01-04:00","bumped":true,"bumped_at":"2013-06-12T06:14:01-04:00","unseen":false,"title":"Upload image button doesn't work after uploading an image","pinned":false,"visible":true,"closed":false,"archived":false,"views":22,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5438},{"extras":null,"description":"Most Posts","user_id":1995}]},{"id":7409,"fancy_title":"New Relic installation","slug":"new-relic-installation","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-06-12T05:11:58-04:00","last_posted_at":"2013-06-12T05:20:30-04:00","bumped":true,"bumped_at":"2013-06-12T05:20:30-04:00","unseen":false,"title":"New Relic installation","pinned":false,"visible":true,"closed":false,"archived":false,"views":22,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":2063},{"extras":null,"description":"Most Posts","user_id":3987}]},{"id":7405,"fancy_title":"How to add “/faq” link in top menu?","slug":"how-to-add-faq-link-in-top-menu","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-12T01:28:32-04:00","last_posted_at":"2013-06-12T01:28:32-04:00","bumped":true,"bumped_at":"2013-06-12T03:12:30-04:00","unseen":false,"title":"How to add \"/faq\" link in top menu?","pinned":false,"visible":true,"closed":false,"archived":false,"views":31,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":6,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5271}]},{"id":7408,"fancy_title":"What are the options for hammering a bad account?","slug":"what-are-the-options-for-hammering-a-bad-account","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1077/43066a4fc47d1d70.png","created_at":"2013-06-12T02:33:53-04:00","last_posted_at":"2013-06-12T02:33:53-04:00","bumped":false,"bumped_at":"2013-06-12T02:33:53-04:00","unseen":false,"title":"What are the options for hammering a bad account?","pinned":false,"visible":true,"closed":false,"archived":false,"views":26,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":null,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":3}]},{"id":7378,"fancy_title":"Discourse Release Announcements for each tagged version release","slug":"discourse-release-announcements-for-each-tagged-version-release","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-06-11T10:31:03-04:00","last_posted_at":"2013-06-12T02:18:33-04:00","bumped":true,"bumped_at":"2013-06-12T02:18:33-04:00","unseen":false,"title":"Discourse Release Announcements for each tagged version release","pinned":false,"visible":true,"closed":false,"archived":false,"views":79,"like_count":7,"has_best_of":false,"archetype":"regular","category_id":4,"posters":[{"extras":null,"description":"Original Poster","user_id":4220},{"extras":null,"description":"Most Posts","user_id":471},{"extras":null,"description":"Frequent Poster","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":1},{"extras":"latest","description":"Most Recent Poster","user_id":761}]},{"id":7406,"fancy_title":"[missing {{user}} value] in user’s home page","slug":"missing-user-value-in-users-home-page","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1074/09013263c0ffc523.png","created_at":"2013-06-12T01:32:04-04:00","last_posted_at":"2013-06-12T01:47:03-04:00","bumped":true,"bumped_at":"2013-06-12T01:47:03-04:00","unseen":false,"title":"[missing {{user}} value] in user's home page","pinned":false,"visible":true,"closed":false,"archived":false,"views":17,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5271},{"extras":null,"description":"Most Posts","user_id":1}]},{"id":7400,"fancy_title":"Mockups/Ideas for extended profiles","slug":"mockups-ideas-for-extended-profiles","posts_count":2,"reply_count":1,"highest_post_number":2,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1061/8a6ac964893019ea.png","created_at":"2013-06-11T22:14:03-04:00","last_posted_at":"2013-06-12T01:01:26-04:00","bumped":true,"bumped_at":"2013-06-12T01:01:26-04:00","unseen":false,"title":"Mockups/Ideas for extended profiles","pinned":false,"visible":true,"closed":false,"archived":false,"views":34,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":2316},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":7146,"fancy_title":"Getting rid of sugar.js","slug":"getting-rid-of-sugar-js","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2013-06-04T03:22:49-04:00","last_posted_at":"2013-06-11T23:50:46-04:00","bumped":true,"bumped_at":"2013-06-11T23:50:46-04:00","unseen":false,"title":"Getting rid of sugar.js","pinned":false,"visible":true,"closed":false,"archived":false,"views":133,"like_count":8,"has_best_of":false,"archetype":"regular","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":1},{"extras":null,"description":"Most Posts","user_id":19},{"extras":null,"description":"Frequent Poster","user_id":2}]},{"id":7397,"fancy_title":"Incorrect string in new posts counter tooltip","slug":"incorrect-string-in-new-posts-counter-tooltip","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":"http://i.imgur.com/7557h94.png","created_at":"2013-06-11T21:15:40-04:00","last_posted_at":"2013-06-11T22:25:04-04:00","bumped":true,"bumped_at":"2013-06-11T22:11:00-04:00","unseen":false,"title":"Incorrect string in new posts counter tooltip","pinned":false,"visible":true,"closed":false,"archived":true,"views":19,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":5425},{"extras":"latest","description":"Most Recent Poster, Most Posts","user_id":1}]},{"id":7390,"fancy_title":"“Topic_count” instead of actual count","slug":"topic-count-instead-of-actual-count","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1060/6496b3226de5168d.png","created_at":"2013-06-11T18:58:20-04:00","last_posted_at":"2013-06-11T21:48:17-04:00","bumped":true,"bumped_at":"2013-06-11T19:48:20-04:00","unseen":false,"title":"\"Topic_count\" instead of actual count","pinned":false,"visible":true,"closed":false,"archived":true,"views":28,"like_count":3,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":5293},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":7389,"fancy_title":"I’m trying to making linux container(lxc) vagrant box image","slug":"im-trying-to-making-linux-container-lxc-vagrant-box-image","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"http://cdn.discourse.org/assets/emoji/wink.png","created_at":"2013-06-11T18:56:31-04:00","last_posted_at":"2013-06-11T18:56:31-04:00","bumped":true,"bumped_at":"2013-06-11T19:06:13-04:00","unseen":false,"title":"I'm trying to making linux container(lxc) vagrant box image","pinned":false,"visible":true,"closed":false,"archived":false,"views":25,"like_count":0,"has_best_of":false,"archetype":"regular","category_id":7,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":5425}]},{"id":7386,"fancy_title":"Mail settings shows password in clear text","slug":"mail-settings-shows-password-in-clear-text","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2013-06-11T17:19:41-04:00","last_posted_at":"2013-06-11T19:05:33-04:00","bumped":true,"bumped_at":"2013-06-11T19:05:33-04:00","unseen":false,"title":"Mail settings shows password in clear text","pinned":false,"visible":true,"closed":false,"archived":false,"views":45,"like_count":5,"has_best_of":false,"archetype":"regular","category_id":1,"posters":[{"extras":null,"description":"Original Poster","user_id":1681},{"extras":"latest","description":"Most Recent Poster","user_id":247}]},{"id":7175,"fancy_title":"Relative time something was posted","slug":"relative-time-something-was-posted","posts_count":105,"reply_count":90,"highest_post_number":105,"image_url":"http://meta.discourse.org/uploads/meta_discourse/1007/0f5263b3ee0a0f45.png","created_at":"2013-06-04T19:05:09-04:00","last_posted_at":"2013-06-11T19:01:41-04:00","bumped":true,"bumped_at":"2013-06-11T19:01:41-04:00","unseen":false,"title":"Relative time something was posted","pinned":false,"visible":true,"closed":false,"archived":false,"views":433,"like_count":94,"has_best_of":true,"archetype":"regular","category_id":9,"posters":[{"extras":null,"description":"Original Poster","user_id":1374},{"extras":null,"description":"Most Posts","user_id":32},{"extras":null,"description":"Frequent Poster","user_id":406},{"extras":null,"description":"Frequent Poster","user_id":5438},{"extras":"latest","description":"Most Recent Poster","user_id":1263}]}]}}; +Discourse.URL_FIXTURES["/categories.json"] = {"featured_users":[{"id":32,"username":"codinghorror","avatar_template":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon"},{"id":1,"username":"sam","avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon"},{"id":1995,"username":"zogstrip","avatar_template":"https://www.gravatar.com/avatar/b7797beb47cfb7aa0fe60d09604aaa09.png?s={size}&r=pg&d=identicon"},{"id":2,"username":"Neil","avatar_template":"https://www.gravatar.com/avatar/42776c4982dff1fa45ee8248532f8ad0.png?s={size}&r=pg&d=identicon"},{"id":19,"username":"eviltrout","avatar_template":"https://www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon"},{"id":2600,"username":"NinjaFoodstuff","avatar_template":"https://www.gravatar.com/avatar/3709f34da1ff5433b41bc56df94dd453.png?s={size}&r=pg&d=identicon"},{"id":406,"username":"RGJ","avatar_template":"https://www.gravatar.com/avatar/55c791f0242e5167536c65496046eef5.png?s={size}&r=pg&d=identicon"},{"id":1353,"username":"sparr","avatar_template":"https://www.gravatar.com/avatar/7906663b1197829751673465948e0b05.png?s={size}&r=pg&d=identicon"},{"id":8,"username":"geek","avatar_template":"https://www.gravatar.com/avatar/b0b1ce3a4e0a77abd157ec0309b72922.png?s={size}&r=pg&d=identicon"},{"id":4,"username":"stienman","avatar_template":"https://www.gravatar.com/avatar/281486f2a20201375414760dd347951d.png?s={size}&r=pg&d=identicon"},{"id":1274,"username":"binaryphile","avatar_template":"https://www.gravatar.com/avatar/82564a08aebae1de68bea8e3df61ef93.png?s={size}&r=pg&d=identicon"},{"id":1263,"username":"ChrisB","avatar_template":"https://www.gravatar.com/avatar/1d75aba12b3961894f93959b5b013de0.png?s={size}&r=pg&d=identicon"},{"id":38,"username":"frandallfarmer","avatar_template":"https://www.gravatar.com/avatar/6c38e00d92cd9bd3ada3392b15015553.png?s={size}&r=pg&d=identicon"},{"id":2839,"username":"baus","avatar_template":"https://www.gravatar.com/avatar/57d4030570f672f515a7385cc74c8cfe.png?s={size}&r=pg&d=identicon"},{"id":2664,"username":"Odd_Bloke","avatar_template":"https://www.gravatar.com/avatar/af881deb1a7ef0a1f568e18cd967c0d3.png?s={size}&r=pg&d=identicon"},{"id":714,"username":"jcolebrand","avatar_template":"https://www.gravatar.com/avatar/c4cca9bfec5d5e77f625d9fbe8e37a41.png?s={size}&r=pg&d=identicon"},{"id":810,"username":"ChrisHanel","avatar_template":"https://www.gravatar.com/avatar/467863a322e1a3ce557bfd17f4677600.png?s={size}&r=pg&d=identicon"},{"id":2291,"username":"PabloC","avatar_template":"https://www.gravatar.com/avatar/82c793022ec1bce6ea7573bc27b2340b.png?s={size}&r=pg&d=identicon"},{"id":1674,"username":"colin","avatar_template":"https://www.gravatar.com/avatar/4cfb483116a822652d698dce303ec842.png?s={size}&r=pg&d=identicon"},{"id":2128,"username":"ultimape","avatar_template":"https://www.gravatar.com/avatar/6fe82efded2ee5e218e0452644a07e2e.png?s={size}&r=pg&d=identicon"},{"id":811,"username":"jpeg","avatar_template":"https://www.gravatar.com/avatar/4a214d4a12b7223b61ec36c7aa224c97.png?s={size}&r=pg&d=identicon"},{"id":5468,"username":"pixelBender67","avatar_template":"https://www.gravatar.com/avatar/4f4cc88cc2ebd747240c7bc53af99261.png?s={size}&r=pg&d=identicon"},{"id":2471,"username":"robconery","avatar_template":"https://www.gravatar.com/avatar/31b18bc48108bc410884022764dbeec6.png?s={size}&r=pg&d=identicon"},{"id":4217,"username":"mshappe","avatar_template":"https://www.gravatar.com/avatar/36ffc752906110cd16e1746d5c95516f.png?s={size}&r=pg&d=identicon"},{"id":5460,"username":"ned","avatar_template":"https://www.gravatar.com/avatar/bc5e09a5ce0a85bf02a3fceb9b0bfaf4.png?s={size}&r=pg&d=identicon"},{"id":2399,"username":"passionate","avatar_template":"https://www.gravatar.com/avatar/bcb2185dd24051bce727b29230a2c171.png?s={size}&r=pg&d=identicon"},{"id":4263,"username":"mcwumbly","avatar_template":"https://www.gravatar.com/avatar/e217128117fe24525c7af5ebc5e45745.png?s={size}&r=pg&d=identicon"},{"id":2465,"username":"finid","avatar_template":"https://www.gravatar.com/avatar/989a7705a77732d888ddaff8b440fc3d.png?s={size}&r=pg&d=identicon"},{"id":4939,"username":"stevebaer","avatar_template":"https://www.gravatar.com/avatar/7a42855912a58f4c3c5d0ce82e33905f.png?s={size}&r=pg&d=identicon"},{"id":3507,"username":"mozCallahad","avatar_template":"https://www.gravatar.com/avatar/a2746e0bf42c3245bfd80dea9b3efb32.png?s={size}&r=pg&d=identicon"},{"id":704,"username":"AstonJ","avatar_template":"https://www.gravatar.com/avatar/03af361cc843bc56e95cb6c406d06f80.png?s={size}&r=pg&d=identicon"},{"id":461,"username":"kuba","avatar_template":"https://www.gravatar.com/avatar/1835cb6a5f35bd4089e416a99af90f5f.png?s={size}&r=pg&d=identicon"},{"id":1566,"username":"hamburglar","avatar_template":"https://www.gravatar.com/avatar/57b39f59fa025f64e173ba6dffb8f2f7.png?s={size}&r=pg&d=identicon"}],"category_list":{"can_create_category":false,"can_create_topic":false,"draft":null,"draft_key":"new_topic","draft_sequence":null,"categories":[{"id":1,"name":"bug","color":"ae3a27","text_color":"FFFFFF","slug":"bug","topic_count":361,"topics_week":24,"topics_month":93,"topics_year":357,"description":"Bug reports on Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","description_excerpt":"Bug reports on Discourse. Do be sure to search prior to submitting bugs. Include repro steps, and only describe one bug per topic please.","featured_user_ids":[32,1,1995,2,19],"topics":[{"id":7288,"fancy_title":"Digest mail ignores secure groups","slug":"digest-mail-ignores-secure-groups","posts_count":7,"reply_count":3,"highest_post_number":7,"image_url":"http://cdn.discourse.org/assets/emoji/smile.png","created_at":"2013-06-08T08:54:12-04:00","last_posted_at":"2013-06-08T13:00:38-04:00","bumped":true,"bumped_at":"2013-06-08T13:00:38-04:00","unseen":false,"title":"Digest mail ignores secure groups","pinned":true,"excerpt":"People receiving the digest mail can easily read posts not meant for them. That's because the digest mail ignores the secure groups a member has access to or not. \n\nQuite a problem as I unfortunately found out. [smile]","visible":true,"closed":false,"archived":false},{"id":7554,"fancy_title":"Loading (never stops)","slug":"loading-never-stops","posts_count":14,"reply_count":10,"highest_post_number":14,"image_url":null,"created_at":"2013-06-17T04:10:19-04:00","last_posted_at":"2013-06-20T11:25:38-04:00","bumped":true,"bumped_at":"2013-06-20T11:25:38-04:00","unseen":false,"title":"Loading (never stops)","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7655,"fancy_title":"Are invisible topics broken again?","slug":"are-invisible-topics-broken-again","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"https://s3.amazonaws.com/lechat-im-share/77734fb7713f41c1c642d106f04848c29297c12c3563ec4b5b7863a1ad83c605/clip.png","created_at":"2013-06-20T10:14:16-04:00","last_posted_at":"2013-06-20T10:14:19-04:00","bumped":false,"bumped_at":"2013-06-20T10:14:16-04:00","unseen":false,"title":"Are invisible topics broken again?","pinned":false,"visible":false,"closed":false,"archived":false},{"id":7654,"fancy_title":"Grant admin failed","slug":"grant-admin-failed","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1167/6693820be81fb347.png","created_at":"2013-06-20T10:01:27-04:00","last_posted_at":"2013-06-20T10:01:27-04:00","bumped":false,"bumped_at":"2013-06-20T10:01:27-04:00","unseen":false,"title":"Grant admin failed","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7652,"fancy_title":"Onebox with Chinese failed!","slug":"onebox-with-chinese-failed","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1166/59abf864c9513bf1.png","created_at":"2013-06-20T09:21:32-04:00","last_posted_at":"2013-06-20T09:21:33-04:00","bumped":true,"bumped_at":"2013-06-20T09:21:33-04:00","unseen":false,"title":"Onebox with Chinese failed!","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7613,"fancy_title":"Site Customization not working","slug":"site-customization-not-working","posts_count":9,"reply_count":6,"highest_post_number":9,"image_url":null,"created_at":"2013-06-18T17:47:54-04:00","last_posted_at":"2013-06-20T07:36:24-04:00","bumped":true,"bumped_at":"2013-06-20T07:36:24-04:00","unseen":false,"title":"Site Customization not working","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":6,"name":"support","color":"b99","text_color":"FFFFFF","slug":"support","topic_count":300,"topics_week":22,"topics_month":90,"topics_year":299,"description":"Support on configuring, using, and installing Discourse. Not for software development related topics, but for admins and end users configuring and using Discourse.","description_excerpt":"Support on configuring, using, and installing Discourse. Not for software development related topics, but for admins and end users configuring and using Discourse.","featured_user_ids":[32,1,2,2600,406],"topics":[{"id":7645,"fancy_title":"Why does my logo come out like this?","slug":"why-does-my-logo-come-out-like-this","posts_count":8,"reply_count":3,"highest_post_number":8,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1163/9bdb455a1fdef5bd.png","created_at":"2013-06-20T00:07:35-04:00","last_posted_at":"2013-06-20T13:03:44-04:00","bumped":true,"bumped_at":"2013-06-20T13:03:44-04:00","unseen":false,"title":"Why does my logo come out like this?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7630,"fancy_title":"Google account login redirect to 127.0.0.1:3000","slug":"google-account-login-redirect-to-127-0-0-1-3000","posts_count":13,"reply_count":11,"highest_post_number":13,"image_url":"http://meta.discourse.org/assets/favicons/stackexchange-40d494822da7175d54657ac327a01ab4.png","created_at":"2013-06-19T09:04:16-04:00","last_posted_at":"2013-06-20T10:37:01-04:00","bumped":true,"bumped_at":"2013-06-20T10:37:01-04:00","unseen":false,"title":"Google account login redirect to 127.0.0.1:3000","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7642,"fancy_title":"500 error on posts","slug":"500-error-on-posts","posts_count":4,"reply_count":2,"highest_post_number":4,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1161/f8e0a36aceeab57b.png","created_at":"2013-06-19T18:32:41-04:00","last_posted_at":"2013-06-19T19:26:18-04:00","bumped":true,"bumped_at":"2013-06-19T19:34:02-04:00","unseen":false,"title":"500 error on posts","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7409,"fancy_title":"New Relic installation","slug":"new-relic-installation","posts_count":11,"reply_count":9,"highest_post_number":11,"image_url":null,"created_at":"2013-06-12T05:11:58-04:00","last_posted_at":"2013-06-19T18:56:43-04:00","bumped":true,"bumped_at":"2013-06-19T18:56:43-04:00","unseen":false,"title":"New Relic installation","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7604,"fancy_title":"Airbrake’s license has changed, not MIT anymore","slug":"airbrakes-license-has-changed-not-mit-anymore","posts_count":9,"reply_count":6,"highest_post_number":9,"image_url":null,"created_at":"2013-06-18T15:58:13-04:00","last_posted_at":"2013-06-19T11:37:39-04:00","bumped":true,"bumped_at":"2013-06-19T11:37:39-04:00","unseen":false,"title":"Airbrake's license has changed, not MIT anymore","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7583,"fancy_title":"What’s the best way to add global Javascript code?","slug":"whats-the-best-way-to-add-global-javascript-code","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-17T21:45:31-04:00","last_posted_at":"2013-06-17T21:45:31-04:00","bumped":true,"bumped_at":"2013-06-19T08:58:05-04:00","unseen":false,"title":"What's the best way to add global Javascript code?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":2,"name":"feature","color":"0E76BD","text_color":"FFFFFF","slug":"feature","topic_count":379,"topics_week":21,"topics_month":68,"topics_year":378,"description":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","description_excerpt":"Discussion about features or potential features of Discourse: how they work, why they work, etc.","featured_user_ids":[32,1,1353,8,4],"topics":[{"id":7581,"fancy_title":"Category preferences","slug":"category-preferences","posts_count":6,"reply_count":2,"highest_post_number":6,"image_url":null,"created_at":"2013-06-17T19:40:35-04:00","last_posted_at":"2013-06-20T10:25:15-04:00","bumped":true,"bumped_at":"2013-06-20T10:25:15-04:00","unseen":false,"title":"Category preferences","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7651,"fancy_title":"Easier way to stop watching or tracking topic","slug":"easier-way-to-stop-watching-or-tracking-topic","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-20T09:20:48-04:00","last_posted_at":"2013-06-20T09:20:49-04:00","bumped":true,"bumped_at":"2013-06-20T09:20:49-04:00","unseen":false,"title":"Easier way to stop watching or tracking topic","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7450,"fancy_title":"We need an “Archive” Flag Notification button","slug":"we-need-an-archive-flag-notification-button","posts_count":9,"reply_count":7,"highest_post_number":9,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1098/fd9f66b93330aeea.png","created_at":"2013-06-13T12:31:05-04:00","last_posted_at":"2013-06-19T23:55:25-04:00","bumped":true,"bumped_at":"2013-06-19T23:55:25-04:00","unseen":false,"title":"We need an \"Archive\" Flag Notification button","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4415,"fancy_title":"Scaling Discourse","slug":"scaling-discourse","posts_count":8,"reply_count":6,"highest_post_number":8,"image_url":null,"created_at":"2013-03-04T10:17:09-05:00","last_posted_at":"2013-06-19T23:53:21-04:00","bumped":true,"bumped_at":"2013-06-19T23:53:21-04:00","unseen":false,"title":"Scaling Discourse","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7631,"fancy_title":"New topics since last visit are the same even after logout and exit","slug":"new-topics-since-last-visit-are-the-same-even-after-logout-and-exit","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-19T10:12:41-04:00","last_posted_at":"2013-06-19T19:34:15-04:00","bumped":true,"bumped_at":"2013-06-19T19:34:15-04:00","unseen":false,"title":"New topics since last visit are the same even after logout and exit","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7640,"fancy_title":"Confirmation on about to delete first post in the topic and consiquently entire topic","slug":"confirmation-on-about-to-delete-first-post-in-the-topic-and-consiquently-entire-topic","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-19T16:30:07-04:00","last_posted_at":"2013-06-19T19:10:54-04:00","bumped":true,"bumped_at":"2013-06-19T19:10:54-04:00","unseen":false,"title":"Confirmation on about to delete first post in the topic and consiquently entire topic","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":null,"name":"uncategorized","color":"AB9364","text_color":"FFFFFF","slug":"uncategorized","topic_count":173,"topics_week":6,"topics_month":24,"topics_year":173,"description":null,"description_excerpt":null,"is_uncategorized":true,"featured_user_ids":[],"topics":[{"id":7644,"fancy_title":"Is forum migration really worth it?","slug":"is-forum-migration-really-worth-it","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":"https://www.gravatar.com/avatar/eff1b72d56a97459a27161ccf7f20c89.png?s=40&r=pg&d=identicon","created_at":"2013-06-20T00:07:04-04:00","last_posted_at":"2013-06-20T12:30:16-04:00","bumped":true,"bumped_at":"2013-06-20T12:30:16-04:00","unseen":false,"title":"Is forum migration really worth it?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4533,"fancy_title":"Did you reply wrong topic in discourse forum?","slug":"did-you-reply-wrong-topic-in-discourse-forum","posts_count":11,"reply_count":8,"highest_post_number":11,"image_url":null,"created_at":"2013-03-06T20:41:25-05:00","last_posted_at":"2013-06-19T12:56:54-04:00","bumped":true,"bumped_at":"2013-06-19T12:56:54-04:00","unseen":false,"title":"Did you reply wrong topic in discourse forum?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7594,"fancy_title":"How to add links to the title header","slug":"how-to-add-links-to-the-title-header","posts_count":6,"reply_count":5,"highest_post_number":6,"image_url":null,"created_at":"2013-06-18T11:37:53-04:00","last_posted_at":"2013-06-18T17:12:26-04:00","bumped":true,"bumped_at":"2013-06-18T17:12:26-04:00","unseen":false,"title":"How to add links to the title header","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7602,"fancy_title":"SMTP Errors on latest","slug":"smtp-errors-on-latest","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2013-06-18T15:30:04-04:00","last_posted_at":"2013-06-18T16:09:45-04:00","bumped":true,"bumped_at":"2013-06-18T16:09:17-04:00","unseen":false,"title":"SMTP Errors on latest","pinned":false,"visible":true,"closed":false,"archived":true},{"id":7600,"fancy_title":"Any example or tutorial of using doorkeeper with discourse","slug":"any-example-or-tutorial-of-using-doorkeeper-with-discourse","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-18T14:23:44-04:00","last_posted_at":"2013-06-18T14:23:44-04:00","bumped":false,"bumped_at":"2013-06-18T14:23:44-04:00","unseen":false,"title":"Any example or tutorial of using doorkeeper with discourse","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7558,"fancy_title":"How can i get discourse to use for my community?","slug":"how-can-i-get-discourse-to-use-for-my-community","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-06-17T08:30:56-04:00","last_posted_at":"2013-06-17T11:05:31-04:00","bumped":true,"bumped_at":"2013-06-17T11:05:31-04:00","unseen":false,"title":"How can i get discourse to use for my community?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":7,"name":"dev","color":"000","text_color":"FFFFFF","slug":"dev","topic_count":162,"topics_week":4,"topics_month":31,"topics_year":162,"description":"This category is for topics related to hacking on Discourse: submitting pull requests, configuring development environments, coding conventions, and so forth.","description_excerpt":"This category is for topics related to hacking on Discourse: submitting pull requests, configuring development environments, coding conventions, and so forth.","featured_user_ids":[1,32,19,1274,1995],"topics":[{"id":3823,"fancy_title":"So, you want to help out with Discourse","slug":"so-you-want-to-help-out-with-discourse","posts_count":19,"reply_count":17,"highest_post_number":32,"image_url":null,"created_at":"2013-02-23T00:46:11-05:00","last_posted_at":"2013-06-05T21:09:56-04:00","bumped":true,"bumped_at":"2013-06-05T21:09:56-04:00","unseen":false,"title":"So, you want to help out with Discourse","pinned":true,"excerpt":"People are wondering, how it is they can help out with Discourse. \n\nWe have seen some chattering both here and on Github. \n\nI wanted to create a topic @eviltrout , @codinghorror and myself can keep up to date with clear…","visible":true,"closed":false,"archived":false},{"id":7638,"fancy_title":"Styling Discourse","slug":"styling-discourse","posts_count":8,"reply_count":5,"highest_post_number":8,"image_url":null,"created_at":"2013-06-19T15:23:36-04:00","last_posted_at":"2013-06-20T13:03:52-04:00","bumped":true,"bumped_at":"2013-06-20T13:03:52-04:00","unseen":false,"title":"Styling Discourse","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4403,"fancy_title":"Comrades let’s join our efforts on ukrainian and russian translations","slug":"comrades-lets-join-our-efforts-on-ukrainian-and-russian-translations","posts_count":38,"reply_count":18,"highest_post_number":38,"image_url":null,"created_at":"2013-03-04T08:04:53-05:00","last_posted_at":"2013-06-20T03:36:05-04:00","bumped":true,"bumped_at":"2013-06-20T03:36:05-04:00","unseen":false,"title":"Comrades let's join our efforts on ukrainian and russian translations","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6013,"fancy_title":"Image optimisation (work in progress)","slug":"image-optimisation-work-in-progress","posts_count":14,"reply_count":11,"highest_post_number":14,"image_url":null,"created_at":"2013-04-18T19:55:04-04:00","last_posted_at":"2013-06-20T02:33:06-04:00","bumped":true,"bumped_at":"2013-06-20T02:33:06-04:00","unseen":false,"title":"Image optimisation (work in progress)","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6669,"fancy_title":"Integrating Discourse with current user database?","slug":"integrating-discourse-with-current-user-database","posts_count":11,"reply_count":4,"highest_post_number":11,"image_url":null,"created_at":"2013-05-16T14:55:47-04:00","last_posted_at":"2013-06-19T22:04:47-04:00","bumped":true,"bumped_at":"2013-06-19T22:04:47-04:00","unseen":false,"title":"Integrating Discourse with current user database?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":3943,"fancy_title":"Give me those authentication hooks! :D","slug":"give-me-those-authentication-hooks-d","posts_count":5,"reply_count":3,"highest_post_number":5,"image_url":null,"created_at":"2013-02-24T02:18:13-05:00","last_posted_at":"2013-06-19T18:58:45-04:00","bumped":true,"bumped_at":"2013-06-19T18:58:45-04:00","unseen":false,"title":"Give me those authentication hooks! :D","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":9,"name":"ux","color":"5F497A","text_color":"FFFFFF","slug":"ux","topic_count":92,"topics_week":4,"topics_month":15,"topics_year":92,"description":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","description_excerpt":"Discussion about the user interface of Discourse, how features are presented to the user in the client, including language and UI elements.","featured_user_ids":[32,1,406,1263,38],"topics":[{"id":7362,"fancy_title":"Should post date/times be moved to remove ambiguity between user join dates or activity?","slug":"should-post-date-times-be-moved-to-remove-ambiguity-between-user-join-dates-or-activity","posts_count":49,"reply_count":38,"highest_post_number":49,"image_url":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s=40&r=pg&d=identicon","created_at":"2013-06-10T18:52:34-04:00","last_posted_at":"2013-06-20T11:53:13-04:00","bumped":true,"bumped_at":"2013-06-20T11:53:13-04:00","unseen":false,"title":"Should post date/times be moved to remove ambiguity between user join dates or activity?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7614,"fancy_title":"“Your topic is similar to…” Always comes up","slug":"your-topic-is-similar-to-always-comes-up","posts_count":4,"reply_count":3,"highest_post_number":4,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1146/9dea86f9a04e59c8.png","created_at":"2013-06-18T19:27:03-04:00","last_posted_at":"2013-06-19T13:36:04-04:00","bumped":true,"bumped_at":"2013-06-19T13:36:04-04:00","unseen":false,"title":"\"Your topic is similar to...\" Always comes up","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7493,"fancy_title":"Activity reports on admin page","slug":"activity-reports-on-admin-page","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"https://s3.amazonaws.com/lechat-im-share/aa059a2e2227e1772ce49cae3611363e2e209bea8139d5adf93df0031e8bed75/clip.png","created_at":"2013-06-14T22:29:00-04:00","last_posted_at":"2013-06-17T11:16:11-04:00","bumped":true,"bumped_at":"2013-06-17T11:16:11-04:00","unseen":false,"title":"Activity reports on admin page","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7479,"fancy_title":"Expand topic details button not always necessary","slug":"expand-topic-details-button-not-always-necessary","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-06-14T10:54:00-04:00","last_posted_at":"2013-06-15T01:53:18-04:00","bumped":true,"bumped_at":"2013-06-15T01:53:18-04:00","unseen":false,"title":"Expand topic details button not always necessary","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7449,"fancy_title":"Categories view constantly resorting","slug":"categories-view-constantly-resorting","posts_count":3,"reply_count":2,"highest_post_number":3,"image_url":null,"created_at":"2013-06-13T12:27:55-04:00","last_posted_at":"2013-06-14T05:37:27-04:00","bumped":true,"bumped_at":"2013-06-14T05:37:27-04:00","unseen":false,"title":"Categories view constantly resorting","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7400,"fancy_title":"Adding “developer” labels to the post avatars","slug":"adding-developer-labels-to-the-post-avatars","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1061/8a6ac964893019ea.png","created_at":"2013-06-11T22:14:03-04:00","last_posted_at":"2013-06-13T04:57:29-04:00","bumped":true,"bumped_at":"2013-06-13T04:57:29-04:00","unseen":false,"title":"Adding \"developer\" labels to the post avatars","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":10,"name":"howto","color":"76923C","text_color":"FFFFFF","slug":"howto","topic_count":38,"topics_week":3,"topics_month":11,"topics_year":38,"description":"Tutorial topics that describe how to set up, configure, or install Discourse using a specific platform or environment.","description_excerpt":"Tutorial topics that describe how to set up, configure, or install Discourse using a specific platform or environment.","featured_user_ids":[1,2839,2600,32,2664],"topics":[{"id":7461,"fancy_title":"Wordpress to Discourse importer!","slug":"wordpress-to-discourse-importer","posts_count":6,"reply_count":3,"highest_post_number":6,"image_url":null,"created_at":"2013-06-13T17:58:43-04:00","last_posted_at":"2013-06-19T23:17:47-04:00","bumped":true,"bumped_at":"2013-06-19T23:17:47-04:00","unseen":false,"title":"Wordpress to Discourse importer!","pinned":false,"visible":true,"closed":false,"archived":false},{"id":3395,"fancy_title":"Installing Discourse on Ubuntu 12.10 and Digital Ocean","slug":"installing-discourse-on-ubuntu-12-10-and-digital-ocean","posts_count":112,"reply_count":82,"highest_post_number":112,"image_url":null,"created_at":"2013-02-19T00:48:11-05:00","last_posted_at":"2013-06-18T19:28:40-04:00","bumped":true,"bumped_at":"2013-06-18T19:28:40-04:00","unseen":false,"title":"Installing Discourse on Ubuntu 12.10 and Digital Ocean","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7582,"fancy_title":"Twitter login with Passenger + Varnish - quick lessons learned","slug":"twitter-login-with-passenger-varnish-quick-lessons-learned","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"http://cdn.discourse.org/assets/emoji/smile.png","created_at":"2013-06-17T19:46:31-04:00","last_posted_at":"2013-06-17T20:11:08-04:00","bumped":true,"bumped_at":"2013-06-17T20:11:08-04:00","unseen":false,"title":"Twitter login with Passenger + Varnish - quick lessons learned","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7531,"fancy_title":"Building a new community, why you need a blog as a front page?","slug":"building-a-new-community-why-you-need-a-blog-as-a-front-page","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":null,"created_at":"2013-06-16T11:05:10-04:00","last_posted_at":"2013-06-17T13:21:54-04:00","bumped":true,"bumped_at":"2013-06-17T13:21:54-04:00","unseen":false,"title":"Building a new community, why you need a blog as a front page?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2349,"fancy_title":"Installing Discourse on Debian","slug":"installing-discourse-on-debian","posts_count":94,"reply_count":69,"highest_post_number":94,"image_url":null,"created_at":"2013-02-08T02:43:23-05:00","last_posted_at":"2013-06-17T11:52:06-04:00","bumped":true,"bumped_at":"2013-06-17T11:52:06-04:00","unseen":false,"title":"Installing Discourse on Debian","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6089,"fancy_title":"Import posts from Facebook group into Discourse","slug":"import-posts-from-facebook-group-into-discourse","posts_count":15,"reply_count":9,"highest_post_number":15,"image_url":"http://meta.discourse.org/uploads/meta_discourse/864/a39dd8d9b5a7768a.png","created_at":"2013-04-22T17:27:54-04:00","last_posted_at":"2013-06-17T05:40:11-04:00","bumped":true,"bumped_at":"2013-06-17T05:40:11-04:00","unseen":false,"title":"Import posts from Facebook group into Discourse","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":5,"name":"extensibility ","color":"FE8432","text_color":"FFFFFF","slug":"extensibility","topic_count":28,"topics_week":2,"topics_month":3,"topics_year":28,"description":"Topics about extending the functionality of Discourse with plugins, themes, add-ons, or other mechanisms for extensibility. ","description_excerpt":"Topics about extending the functionality of Discourse with plugins, themes, add-ons, or other mechanisms for extensibility.","featured_user_ids":[1,32,714,810,8],"topics":[{"id":7534,"fancy_title":"Has anybody created themes for Discourse?","slug":"has-anybody-created-themes-for-discourse","posts_count":15,"reply_count":13,"highest_post_number":15,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/1114/a1a6f1260ebcb974.png","created_at":"2013-06-16T12:19:26-04:00","last_posted_at":"2013-06-17T14:47:00-04:00","bumped":true,"bumped_at":"2013-06-17T14:53:17-04:00","unseen":false,"title":"Has anybody created themes for Discourse?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7464,"fancy_title":"CSS documentation?","slug":"css-documentation","posts_count":2,"reply_count":1,"highest_post_number":2,"image_url":null,"created_at":"2013-06-13T18:25:19-04:00","last_posted_at":"2013-06-14T05:05:14-04:00","bumped":true,"bumped_at":"2013-06-14T05:05:14-04:00","unseen":false,"title":"CSS documentation?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5689,"fancy_title":"Wordpress plugin to include latest topics as a sidebar?","slug":"wordpress-plugin-to-include-latest-topics-as-a-sidebar","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":null,"created_at":"2013-04-07T16:08:21-04:00","last_posted_at":"2013-06-12T14:10:44-04:00","bumped":true,"bumped_at":"2013-06-12T14:10:44-04:00","unseen":false,"title":"Wordpress plugin to include latest topics as a sidebar?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":531,"fancy_title":"Discourse and Wordpress Integration","slug":"discourse-and-wordpress-integration","posts_count":57,"reply_count":50,"highest_post_number":59,"image_url":null,"created_at":"2013-02-05T18:56:37-05:00","last_posted_at":"2013-06-10T17:35:34-04:00","bumped":true,"bumped_at":"2013-06-10T17:35:34-04:00","unseen":false,"title":"Discourse and Wordpress Integration","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7213,"fancy_title":"Discourse as a Job Portal","slug":"discourse-as-a-job-portal","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":null,"created_at":"2013-06-06T04:43:59-04:00","last_posted_at":"2013-06-07T14:23:22-04:00","bumped":true,"bumped_at":"2013-06-07T14:23:22-04:00","unseen":false,"title":"Discourse as a Job Portal","pinned":false,"visible":true,"closed":false,"archived":false},{"id":31,"fancy_title":"What is the most awesome plugin for Discourse, that does not yet exist?","slug":"what-is-the-most-awesome-plugin-for-discourse-that-does-not-yet-exist","posts_count":153,"reply_count":105,"highest_post_number":154,"image_url":null,"created_at":"2013-02-03T06:43:18-05:00","last_posted_at":"2013-06-03T08:41:19-04:00","bumped":true,"bumped_at":"2013-06-03T08:41:19-04:00","unseen":false,"title":"What is the most awesome plugin for Discourse, that does not yet exist?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":8,"name":"hosting","color":"25AAE1","text_color":"FFFFFF","slug":"hosting","topic_count":41,"topics_week":1,"topics_month":9,"topics_year":41,"description":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","description_excerpt":"Topics about hosting Discourse, either on your own servers, in the cloud, or with specific hosting services.","featured_user_ids":[1,32,2291,1674,2839],"topics":[{"id":7595,"fancy_title":"Installing Discourse on AppFog","slug":"installing-discourse-on-appfog","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-06-18T11:43:50-04:00","last_posted_at":"2013-06-18T11:43:51-04:00","bumped":true,"bumped_at":"2013-06-18T11:43:51-04:00","unseen":false,"title":"Installing Discourse on AppFog","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4864,"fancy_title":"Discourse behind Varnish?","slug":"discourse-behind-varnish","posts_count":7,"reply_count":4,"highest_post_number":7,"image_url":null,"created_at":"2013-03-18T07:48:24-04:00","last_posted_at":"2013-06-16T12:12:47-04:00","bumped":true,"bumped_at":"2013-06-16T12:12:47-04:00","unseen":false,"title":"Discourse behind Varnish?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6783,"fancy_title":"Installation instructions for Ubuntu","slug":"installation-instructions-for-ubuntu","posts_count":33,"reply_count":26,"highest_post_number":33,"image_url":null,"created_at":"2013-05-22T00:53:38-04:00","last_posted_at":"2013-06-15T18:17:55-04:00","bumped":true,"bumped_at":"2013-06-15T18:17:55-04:00","unseen":false,"title":"Installation instructions for Ubuntu","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6779,"fancy_title":"Storing Images in the cloud","slug":"storing-images-in-the-cloud","posts_count":14,"reply_count":8,"highest_post_number":14,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/926/05bfcce4f39b1d06.png","created_at":"2013-05-21T19:56:32-04:00","last_posted_at":"2013-06-11T16:48:43-04:00","bumped":true,"bumped_at":"2013-06-11T16:48:43-04:00","unseen":false,"title":"Storing Images in the cloud","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7284,"fancy_title":"Meta.discourse.org stats?","slug":"meta-discourse-org-stats","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-08T03:16:26-04:00","last_posted_at":"2013-06-08T13:58:53-04:00","bumped":true,"bumped_at":"2013-06-08T13:58:53-04:00","unseen":false,"title":"Meta.discourse.org stats?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6992,"fancy_title":"Has anyone used webfaction for discourse?","slug":"has-anyone-used-webfaction-for-discourse","posts_count":7,"reply_count":6,"highest_post_number":10,"image_url":null,"created_at":"2013-05-29T11:41:50-04:00","last_posted_at":"2013-06-01T11:28:29-04:00","bumped":true,"bumped_at":"2013-06-01T11:28:29-04:00","unseen":false,"title":"Has anyone used webfaction for discourse?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":3,"name":"meta","color":"aaa","text_color":"FFFFFF","slug":"meta","topic_count":61,"topics_week":1,"topics_month":5,"topics_year":61,"description":"Discussion about meta.discourse.org itself, the organization of this forum about Discourse, how it works, and how we can improve this site.","description_excerpt":"Discussion about meta.discourse.org itself, the organization of this forum about Discourse, how it works, and how we can improve this site.","featured_user_ids":[32,1,38,2128,811],"topics":[{"id":5249,"fancy_title":"What is “Meta”?","slug":"what-is-meta","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2013-03-25T18:00:52-04:00","last_posted_at":"2013-03-25T18:00:56-04:00","bumped":false,"bumped_at":"2013-03-25T18:00:52-04:00","unseen":false,"title":"What is \"Meta\"?","pinned":true,"excerpt":"What is "Meta"?\n\nMeta means discussion of the discussion itself instead of the actual topic of the discussion. For example, discussions about... \n\n\nThe style of discussion.\nThe participants in the discussion.\nThe setting…","visible":true,"closed":false,"archived":false},{"id":1421,"fancy_title":"How do I send people invites?","slug":"how-do-i-send-people-invites","posts_count":13,"reply_count":10,"highest_post_number":13,"image_url":null,"created_at":"2013-02-06T11:28:45-05:00","last_posted_at":"2013-06-20T05:34:30-04:00","bumped":true,"bumped_at":"2013-06-20T05:34:30-04:00","unseen":false,"title":"How do I send people invites?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":3098,"fancy_title":"Ember and SEO challenges regarding discourse.org","slug":"ember-and-seo-challenges-regarding-discourse-org","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2013-02-14T13:40:31-05:00","last_posted_at":"2013-06-20T00:30:18-04:00","bumped":true,"bumped_at":"2013-06-20T00:30:18-04:00","unseen":false,"title":"Ember and SEO challenges regarding discourse.org","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7618,"fancy_title":"New posts, read posts, unread posts - I like this","slug":"new-posts-read-posts-unread-posts-i-like-this","posts_count":4,"reply_count":3,"highest_post_number":4,"image_url":"http://cdn.discourse.org/assets/emoji/wink2.png","created_at":"2013-06-18T21:21:00-04:00","last_posted_at":"2013-06-19T23:10:46-04:00","bumped":true,"bumped_at":"2013-06-19T23:10:46-04:00","unseen":false,"title":"New posts, read posts, unread posts - I like this","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6633,"fancy_title":"Discourse.org website is not available in Turkey","slug":"discourse-org-website-is-not-available-in-turkey","posts_count":18,"reply_count":10,"highest_post_number":18,"image_url":null,"created_at":"2013-05-15T05:40:32-04:00","last_posted_at":"2013-06-16T20:37:29-04:00","bumped":true,"bumped_at":"2013-06-16T20:37:29-04:00","unseen":false,"title":"Discourse.org website is not available in Turkey","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7431,"fancy_title":"Embedded images are not loading for me","slug":"embedded-images-are-not-loading-for-me","posts_count":2,"reply_count":1,"highest_post_number":2,"image_url":null,"created_at":"2013-06-12T23:37:29-04:00","last_posted_at":"2013-06-12T23:41:12-04:00","bumped":true,"bumped_at":"2013-06-12T23:41:12-04:00","unseen":false,"title":"Embedded images are not loading for me","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":13,"name":"blog","color":"ED207B","text_color":"FFFFFF","slug":"blog","topic_count":7,"topics_week":1,"topics_month":2,"topics_year":7,"description":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","description_excerpt":"Discussion topics generated from the official Discourse Blog. These topics are linked from the bottom of each blog entry where the blog comments would normally be.","featured_user_ids":[32,1,5468,2471,4217],"topics":[{"id":7478,"fancy_title":"Discourse on Ubuntu: Video Walkthrough","slug":"discourse-on-ubuntu-video-walkthrough","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-14T10:47:53-04:00","last_posted_at":"2013-06-17T10:55:31-04:00","bumped":true,"bumped_at":"2013-06-17T10:55:31-04:00","unseen":false,"title":"Discourse on Ubuntu: Video Walkthrough","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5751,"fancy_title":"Discourse as Your First Rails App","slug":"discourse-as-your-first-rails-app","posts_count":43,"reply_count":31,"highest_post_number":48,"image_url":null,"created_at":"2013-04-09T19:08:33-04:00","last_posted_at":"2013-06-11T19:00:14-04:00","bumped":true,"bumped_at":"2013-06-13T05:22:30-04:00","unseen":false,"title":"Discourse as Your First Rails App","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5575,"fancy_title":"Our First Partner: How-To Geek","slug":"our-first-partner-how-to-geek","posts_count":22,"reply_count":16,"highest_post_number":23,"image_url":null,"created_at":"2013-04-03T18:42:46-04:00","last_posted_at":"2013-06-07T19:27:46-04:00","bumped":true,"bumped_at":"2013-06-07T19:27:46-04:00","unseen":false,"title":"Our First Partner: How-To Geek","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5898,"fancy_title":"The Discourse Servers","slug":"the-discourse-servers","posts_count":29,"reply_count":21,"highest_post_number":29,"image_url":null,"created_at":"2013-04-15T15:19:09-04:00","last_posted_at":"2013-05-30T15:44:01-04:00","bumped":true,"bumped_at":"2013-05-30T15:44:01-04:00","unseen":false,"title":"The Discourse Servers","pinned":false,"visible":true,"closed":false,"archived":false},{"id":6744,"fancy_title":"Your Online Clubhouse","slug":"your-online-clubhouse","posts_count":7,"reply_count":4,"highest_post_number":7,"image_url":null,"created_at":"2013-05-20T16:26:44-04:00","last_posted_at":"2013-05-23T12:07:47-04:00","bumped":true,"bumped_at":"2013-05-23T12:07:47-04:00","unseen":false,"title":"Your Online Clubhouse","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5366,"fancy_title":"Forums, As Seen On TV","slug":"forums-as-seen-on-tv","posts_count":7,"reply_count":5,"highest_post_number":7,"image_url":"http://blog.discourse.org/wp-uploads/2013/03/30-rock-forums-1.jpg","created_at":"2013-03-28T16:53:41-04:00","last_posted_at":"2013-04-19T16:20:08-04:00","bumped":true,"bumped_at":"2013-04-19T16:20:08-04:00","unseen":false,"title":"Forums, As Seen On TV","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":14,"name":"marketplace","color":"8C6238","text_color":"FFFFFF","slug":"marketplace","topic_count":4,"topics_week":0,"topics_month":3,"topics_year":4,"description":"About commercial Discourse related stuff: jobs or paid gigs, plugins, themes, hosting, etc.","description_excerpt":"About commercial Discourse related stuff: jobs or paid gigs, plugins, themes, hosting, etc.","featured_user_ids":[1263,406,5460,32,2399],"topics":[{"id":7305,"fancy_title":"I would like to hire someone to install discourse on my site","slug":"i-would-like-to-hire-someone-to-install-discourse-on-my-site","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2013-06-08T18:32:07-04:00","last_posted_at":"2013-06-09T00:27:21-04:00","bumped":true,"bumped_at":"2013-06-09T00:27:21-04:00","unseen":false,"title":"I would like to hire someone to install discourse on my site","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7221,"fancy_title":"I want discourse for small business","slug":"i-want-discourse-for-small-business","posts_count":8,"reply_count":5,"highest_post_number":8,"image_url":null,"created_at":"2013-06-06T09:05:43-04:00","last_posted_at":"2013-06-06T19:52:47-04:00","bumped":true,"bumped_at":"2013-06-06T19:52:47-04:00","unseen":false,"title":"I want discourse for small business","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7054,"fancy_title":"Contract available: Web developer (RoR, JS, Node.JS, Discourse)","slug":"contract-available-web-developer-ror-js-node-js-discourse","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-05-31T19:30:52-04:00","last_posted_at":"2013-05-31T19:30:52-04:00","bumped":false,"bumped_at":"2013-05-31T19:30:52-04:00","unseen":false,"title":"Contract available: Web developer (RoR, JS, Node.JS, Discourse)","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5409,"fancy_title":"Looking for a Discourse specialist !!","slug":"looking-for-a-discourse-specialist","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-03-30T02:15:56-04:00","last_posted_at":"2013-03-30T02:15:56-04:00","bumped":false,"bumped_at":"2013-03-30T02:15:56-04:00","unseen":false,"title":"Looking for a Discourse specialist !!","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":4,"name":"faq","color":"33b","text_color":"FFFFFF","slug":"faq","topic_count":38,"topics_week":0,"topics_month":1,"topics_year":38,"description":"Topics that come up very often when discussing Discourse will eventually be classified into this Frequently Asked Questions category. Should only be added to popular topics.","description_excerpt":"Topics that come up very often when discussing Discourse will eventually be classified into this Frequently Asked Questions category. Should only be added to popular topics.","featured_user_ids":[32,1,4,4263,2465],"topics":[{"id":6724,"fancy_title":"Where is this development roadmap?","slug":"where-is-this-development-roadmap","posts_count":12,"reply_count":8,"highest_post_number":12,"image_url":null,"created_at":"2013-05-19T15:15:19-04:00","last_posted_at":"2013-06-17T11:03:07-04:00","bumped":true,"bumped_at":"2013-06-17T11:03:07-04:00","unseen":false,"title":"Where is this development roadmap?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2408,"fancy_title":"Private Discourse Forum","slug":"private-discourse-forum","posts_count":14,"reply_count":7,"highest_post_number":14,"image_url":null,"created_at":"2013-02-08T08:25:22-05:00","last_posted_at":"2013-06-13T04:49:27-04:00","bumped":true,"bumped_at":"2013-06-13T04:49:27-04:00","unseen":false,"title":"Private Discourse Forum","pinned":false,"visible":true,"closed":false,"archived":false},{"id":7378,"fancy_title":"Discourse Release Announcements for each tagged version release","slug":"discourse-release-announcements-for-each-tagged-version-release","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-06-11T10:31:03-04:00","last_posted_at":"2013-06-12T02:18:33-04:00","bumped":true,"bumped_at":"2013-06-12T02:18:33-04:00","unseen":false,"title":"Discourse Release Announcements for each tagged version release","pinned":false,"visible":true,"closed":false,"archived":false},{"id":3059,"fancy_title":"Two different unread counts","slug":"two-different-unread-counts","posts_count":8,"reply_count":6,"highest_post_number":8,"image_url":"http://cdn.discourse.org/uploads/meta_discourse/266/screen_shot_20130213_at_11_32_07_pm.png","created_at":"2013-02-14T02:37:50-05:00","last_posted_at":"2013-03-24T15:40:45-04:00","bumped":true,"bumped_at":"2013-06-04T18:20:22-04:00","unseen":false,"title":"Two different unread counts","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2724,"fancy_title":"What about the spam problem?","slug":"what-about-the-spam-problem","posts_count":8,"reply_count":3,"highest_post_number":8,"image_url":null,"created_at":"2013-02-11T00:58:15-05:00","last_posted_at":"2013-05-29T19:04:48-04:00","bumped":true,"bumped_at":"2013-05-29T19:04:48-04:00","unseen":false,"title":"What about the spam problem?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4546,"fancy_title":"What is a onebox?","slug":"what-is-a-onebox","posts_count":11,"reply_count":7,"highest_post_number":11,"image_url":"http://meta.discourse.org/users/stienman/avatar/40?__ws=meta.discourse.org","created_at":"2013-03-07T07:56:00-05:00","last_posted_at":"2013-05-22T16:34:11-04:00","bumped":true,"bumped_at":"2013-05-22T16:34:11-04:00","unseen":false,"title":"What is a onebox?","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":11,"name":"login","color":"edb400","text_color":"FFFFFF","slug":"login","topic_count":15,"topics_week":0,"topics_month":1,"topics_year":15,"description":"Topics about logging in to Discourse, using any standard third party provider (Twitter, Facebook, Google), traditional username and password, or with a custom plugin.","description_excerpt":"Topics about logging in to Discourse, using any standard third party provider (Twitter, Facebook, Google), traditional username and password, or with a custom plugin.","featured_user_ids":[32,2,1,4939,3507],"topics":[{"id":6869,"fancy_title":"Force use of an authentication provider","slug":"force-use-of-an-authentication-provider","posts_count":21,"reply_count":16,"highest_post_number":21,"image_url":null,"created_at":"2013-05-24T15:26:41-04:00","last_posted_at":"2013-06-15T16:58:09-04:00","bumped":true,"bumped_at":"2013-06-15T16:58:09-04:00","unseen":false,"title":"Force use of an authentication provider","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5800,"fancy_title":"UTF-8 to webalized char set transliteration for Facebook login","slug":"utf-8-to-webalized-char-set-transliteration-for-facebook-login","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-04-11T13:58:56-04:00","last_posted_at":"2013-04-11T13:58:56-04:00","bumped":false,"bumped_at":"2013-04-11T13:58:56-04:00","unseen":false,"title":"UTF-8 to webalized char set transliteration for Facebook login","pinned":false,"visible":true,"closed":false,"archived":false},{"id":4011,"fancy_title":"I can’t login using my password","slug":"i-can-t-login-using-my-password","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2013-02-25T06:47:42-05:00","last_posted_at":"2013-03-13T19:36:48-04:00","bumped":true,"bumped_at":"2013-03-13T19:36:44-04:00","unseen":false,"title":"I can't login using my password","pinned":false,"visible":true,"closed":false,"archived":true},{"id":4738,"fancy_title":"Login support for browser password managers","slug":"login-support-for-browser-password-managers","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2013-03-13T17:55:29-04:00","last_posted_at":"2013-03-13T19:11:56-04:00","bumped":true,"bumped_at":"2013-03-13T19:11:56-04:00","unseen":false,"title":"Login support for browser password managers","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2257,"fancy_title":"Alternative (non-ajax) way to login, so that browser password manager can save the login credentials","slug":"alternative-non-ajax-way-to-login-so-that-browser-password-manager-can-save-the-login-credentials","posts_count":9,"reply_count":4,"highest_post_number":9,"image_url":null,"created_at":"2013-02-07T17:05:37-05:00","last_posted_at":"2013-03-13T19:06:28-04:00","bumped":true,"bumped_at":"2013-03-13T19:06:23-04:00","unseen":false,"title":"Alternative (non-ajax) way to login, so that browser password manager can save the login credentials","pinned":false,"visible":true,"closed":false,"archived":true},{"id":4473,"fancy_title":"Log in doesn’t work correctly when behind a proxy","slug":"log-in-doesn-t-work-correctly-when-behind-a-proxy","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2013-03-05T10:38:08-05:00","last_posted_at":"2013-03-05T10:38:08-05:00","bumped":false,"bumped_at":"2013-03-05T10:38:08-05:00","unseen":false,"title":"Log in doesn't work correctly when behind a proxy","pinned":false,"visible":true,"closed":false,"archived":false}]},{"id":12,"name":"discourse hub","color":"b2c79f","text_color":"FFFFFF","slug":"discourse-hub","topic_count":4,"topics_week":0,"topics_month":0,"topics_year":4,"description":"Topics about current or future Discourse Hub functionality at discourse.org including nickname registration, global user pages, and the site directory.","description_excerpt":"Topics about current or future Discourse Hub functionality at discourse.org including nickname registration, global user pages, and the site directory.","featured_user_ids":[32,2,704,461,1566],"topics":[{"id":6547,"fancy_title":"Where to get discourse_org_access_key?","slug":"where-to-get-discourse-org-access-key","posts_count":6,"reply_count":1,"highest_post_number":6,"image_url":null,"created_at":"2013-05-10T22:06:08-04:00","last_posted_at":"2013-06-18T11:49:18-04:00","bumped":true,"bumped_at":"2013-06-18T11:49:18-04:00","unseen":false,"title":"Where to get discourse_org_access_key?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":424,"fancy_title":"What are the ‘consequences’ of changing your name?","slug":"what-are-the-consequences-of-changing-your-name","posts_count":33,"reply_count":32,"highest_post_number":33,"image_url":null,"created_at":"2013-02-05T17:37:52-05:00","last_posted_at":"2013-05-17T11:28:00-04:00","bumped":true,"bumped_at":"2013-06-12T13:22:10-04:00","unseen":false,"title":"What are the 'consequences' of changing your name?","pinned":false,"visible":true,"closed":false,"archived":false},{"id":2544,"fancy_title":"Discourse central hub questions","slug":"discourse-central-hub-questions","posts_count":49,"reply_count":41,"highest_post_number":49,"image_url":null,"created_at":"2013-02-09T04:28:21-05:00","last_posted_at":"2013-05-28T12:15:25-04:00","bumped":true,"bumped_at":"2013-05-28T12:15:25-04:00","unseen":false,"title":"Discourse central hub questions","pinned":false,"visible":true,"closed":false,"archived":false},{"id":5664,"fancy_title":"Discourse Hub nickname uniqueness not working?","slug":"discourse-hub-nickname-uniqueness-not-working","posts_count":22,"reply_count":16,"highest_post_number":23,"image_url":null,"created_at":"2013-04-06T03:40:11-04:00","last_posted_at":"2013-04-09T10:56:46-04:00","bumped":true,"bumped_at":"2013-04-09T10:56:46-04:00","unseen":false,"title":"Discourse Hub nickname uniqueness not working?","pinned":false,"visible":true,"closed":false,"archived":false}]}]}}; \ No newline at end of file diff --git a/test/javascripts/fixtures/topic_fixtures.js b/test/javascripts/fixtures/topic_fixtures.js index 5b65b74d3..bc962ca7d 100644 --- a/test/javascripts/fixtures/topic_fixtures.js +++ b/test/javascripts/fixtures/topic_fixtures.js @@ -1,2 +1,2 @@ /*jshint maxlen:10000000 */ -Discourse.URL_FIXTURES["/t/280/1.json"] = {"id":280,"title":"Internationalization / localization","fancy_title":"Internationalization / localization","posts_count":93,"created_at":"2013-02-05T16:29:00-05:00","views":2225,"reply_count":64,"last_posted_at":"2013-06-18T14:22:35-04:00","visible":true,"closed":false,"archived":false,"moderator_posts_count":0,"has_best_of":true,"archetype":"regular","slug":"internationalization-localization","auto_close_at":null,"draft":null,"draft_key":"topic_280","draft_sequence":null,"post_action_visibility":[2,5],"voted_in_topic":false,"categoryName":"feature","posts":[{"id":398,"post_number":1,"post_type":1,"created_at":"2013-02-05T16:29:00-05:00","updated_at":"2013-02-05T16:29:00-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":21,"incoming_link_count":0,"reads":321,"score":97.25,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Uwe Keim","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://meta.discourse.org/t/language-mirrors/2378/2","internal":true,"reflection":true,"title":"Language mirrors","clicks":29},{"url":"http://meta.discourse.org/t/roadplan-for-discourse/2939/5","internal":true,"reflection":true,"title":"Roadplan for Discourse","clicks":18},{"url":"http://meta.discourse.org/t/internationalization-i18n-provided-for-discourse-/2073/2","internal":true,"reflection":true,"title":"Internationalization I18n provided for discourse ?","clicks":17},{"url":"http://meta.discourse.org/t/solving-xda-developer-style-forums/4368/4","internal":true,"reflection":true,"title":"Solving XDA-Developer style forums","clicks":4},{"url":"http://meta.discourse.org/t/bookmark-last-read-sometimes-doesn-t-go-to-the-end-of-a-topic/4825/9","internal":true,"reflection":true,"title":"Bookmark/last read sometimes doesn't go to the end of a topic","clicks":3},{"url":"http://meta.discourse.org/t/translation-workflow/6102","internal":true,"reflection":true,"title":"Translation workflow","clicks":3},{"url":"http://meta.discourse.org/t/comrades-let-s-join-our-efforts-on-ukrainian-and-russian-translations/4403/5","internal":true,"reflection":true,"title":"Comrades let's join our efforts on ukrainian and russian translations","clicks":3},{"url":"http://meta.discourse.org/t/how-to-change-language/6900/2","internal":true,"reflection":true,"title":"How to change language?","clicks":3},{"url":"http://meta.discourse.org/t/suggestion-translation-on-admin-panel/6923/5","internal":true,"reflection":true,"title":"Suggestion: Translation on admin panel","clicks":1},{"url":"http://meta.discourse.org/t/when-will-discourse-provide-an-local-language-configuration/7068/2","internal":true,"reflection":true,"title":"When will discourse provide an local language configuration","clicks":1},{"url":"http://meta.discourse.org/t/jump-to-last-post-does-not-work-in-best-of-mode/7626","internal":true,"reflection":true,"title":"Jump to last post does not work in \"Best of\" mode","clicks":0},{"url":"http://meta.discourse.org/t/missing-user-value-in-chinese-localized-page/7406/6","internal":true,"reflection":true,"title":"[missing {{user}} value] in Chinese localized page","clicks":0},{"url":"http://meta.discourse.org/t/i-like-discourse-want-join-discourse-translation-team/6679/3","internal":true,"reflection":true,"title":"I like discourse,want join discourse translation team","clicks":0},{"url":"http://meta.discourse.org/t/suggestion-translation-on-admin-panel/6923/6","internal":true,"reflection":true,"title":"Suggestion: Translation on admin panel","clicks":0}],"cooked":"

Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?

","read":false,"username":"uwe_keim","name":"Uwe Keim","actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/53a82f701ae492808834e621de2586eb.png?s={size}&r=pg&d=identicon","user_id":255,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":1},{"id":419,"post_number":2,"post_type":1,"created_at":"2013-02-05T16:32:47-05:00","updated_at":"2013-02-06T05:15:27-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":25,"incoming_link_count":10,"reads":318,"score":234.85,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Tim Stone","version":2,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales/en.yml","internal":false,"reflection":false,"clicks":70}],"cooked":"

The application strings are externalized, so localization should be entirely possible with enough translation effort.

","read":false,"username":"tms","name":"Tim Stone","actions_summary":[{"id":2,"count":3,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/3981cd271c302f5cba628c6b6d2b32ee.png?s={size}&r=pg&d=identicon","user_id":9,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":2},{"id":1060,"post_number":3,"post_type":1,"created_at":"2013-02-05T21:26:24-05:00","updated_at":"2013-06-18T22:58:28-04:00","reply_count":3,"reply_to_post_number":null,"quote_count":0,"avg_time":35,"incoming_link_count":4,"reads":312,"score":144.15,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Jeff Atwood","version":3,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales","internal":false,"reflection":false,"clicks":1}],"cooked":"

Yep, all strings are going through a lookup table.*

\n\n

https://github.com/discourse/discourse/blob/master/config/locales

\n\n

So you could replace that lookup table with the \"de\" one to get German.

\n\n

* we didn't get all the strings into the lookup table for launch, but the vast, vast majority of them are and the ones that are not, we will fix as we go!

","read":false,"username":"codinghorror","name":"Jeff Atwood","actions_summary":[{"id":2,"count":3,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","user_id":32,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":3},{"id":3623,"post_number":4,"post_type":1,"created_at":"2013-02-07T07:55:33-05:00","updated_at":"2013-02-07T07:55:33-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":21,"incoming_link_count":10,"reads":269,"score":239.85,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Shade","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://meta.discourse.org/t/hi-support-chinese/4393/6","internal":true,"reflection":true,"title":"Hi, support Chinese?","clicks":9}],"cooked":"

Is it a coincidence that the strings file is 1337 lines long? :D

","read":false,"username":"shade","name":"Shade","actions_summary":[{"id":2,"count":7,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/02c3f1806f6962f56168c7bd9f8924b8.png?s={size}&r=pg&d=identicon","user_id":1808,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":4},{"id":3651,"post_number":5,"post_type":1,"created_at":"2013-02-07T09:02:07-05:00","updated_at":"2013-02-07T09:05:42-05:00","reply_count":2,"reply_to_post_number":3,"quote_count":1,"avg_time":24,"incoming_link_count":6,"reads":263,"score":153.8,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders","internal":false,"reflection":false,"clicks":45},{"url":"/users/codinghorror","internal":true,"reflection":false,"clicks":5}],"cooked":"

\n\n

The problem I see here is that this file is likely two grow and change massively over the next couple months, and tracking these changes in order to keep a localized file up to date is going to be a bitch.

\n\n

I wonder where there is a tool that can compare two yml structures and point out which nodes are missing? That would help keep track of new strings.

\n\n

Re keeping track of changed strings, @codinghorror I found this very interesting: http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders if plain English placeholders were used, any change in strings would lead to a new node in the yml file, making keeping the translation up to date easier. Maybe worth thinking about in the future.

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":5},{"id":3654,"post_number":6,"post_type":1,"created_at":"2013-02-07T09:05:39-05:00","updated_at":"2013-02-07T09:05:39-05:00","reply_count":1,"reply_to_post_number":5,"quote_count":0,"avg_time":19,"incoming_link_count":1,"reads":237,"score":73.35,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Sam Saffron","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"https://github.com/SlexAxton/messageformat.js","internal":false,"reflection":false,"clicks":34},{"url":"https://github.com/SlexAxton","internal":false,"reflection":false,"clicks":7}],"cooked":"

Yes, I really like the concept of fuzzy matching for localization, perhaps you can chase up alex sexton he was meaning to upload a localization tool for this kind of stuff.

\n\n

Also, I am a big fan of ICU message format, but it is not the \"Rails way (tm)\".

","read":false,"username":"sam","name":"Sam Saffron","reply_to_user":{"username":"pekka","name":"Pekka Gaiser"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","user_id":1,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":6},{"id":3655,"post_number":7,"post_type":1,"created_at":"2013-02-07T09:08:17-05:00","updated_at":"2013-02-07T09:12:02-05:00","reply_count":1,"reply_to_post_number":6,"quote_count":1,"avg_time":17,"incoming_link_count":0,"reads":239,"score":68.65,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://translate.wordpress.org/projects/bbpress/dev","internal":false,"reflection":false,"clicks":13}],"cooked":"

Looks interesting, I'll take a peek.

\n\n

As said on dev, the best tool I can see in terms of giving translators a proper interface and quality control would be something like GlotPress. It's based on the PO messages format (is that somehow related to ICU?) but looks pretty great.

\n\n

\n\n

I'm not familiar with the term in this context, you mean keeping the English version in the code base (instead of a generic code like message_error_nametooshort ?)

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":7},{"id":3658,"post_number":8,"post_type":1,"created_at":"2013-02-07T09:12:22-05:00","updated_at":"2013-02-07T09:12:22-05:00","reply_count":1,"reply_to_post_number":7,"quote_count":0,"avg_time":12,"incoming_link_count":0,"reads":216,"score":63.8,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Sam Saffron","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://meta.discourse.org/t/what-i-love-about-wordpress-plugins/5697","internal":true,"reflection":true,"title":"What I love about WordPress plugins","clicks":7}],"cooked":"

ICU Message format is basically Gettext on steroids, Gettext has been around for so many years and actually works pretty well, being super prevalent in Linux.

\n\n

Trouble is you need a fuzzy matcher for translators if you are going to store stuff like mf.compile( 'This is a message.' ) in source, one letter change and all your translators need to validate it.

","read":false,"username":"sam","name":"Sam Saffron","reply_to_user":{"username":"pekka","name":"Pekka Gaiser"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","user_id":1,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":8},{"id":3660,"post_number":9,"post_type":1,"created_at":"2013-02-07T09:14:12-05:00","updated_at":"2013-02-07T09:18:09-05:00","reply_count":1,"reply_to_post_number":8,"quote_count":1,"avg_time":11,"incoming_link_count":0,"reads":216,"score":63.75,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

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

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":9},{"id":3667,"post_number":10,"post_type":1,"created_at":"2013-02-07T09:25:16-05:00","updated_at":"2013-02-07T09:25:16-05:00","reply_count":1,"reply_to_post_number":9,"quote_count":0,"avg_time":8,"incoming_link_count":0,"reads":206,"score":61.6,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Tim Stone","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

Hmm...You could theoretically also build something into the development process that would monitor changes to the English locale file and make a translator-friendly list of changes between versions.

","read":false,"username":"tms","name":"Tim Stone","reply_to_user":{"username":"pekka","name":"Pekka Gaiser"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/3981cd271c302f5cba628c6b6d2b32ee.png?s={size}&r=pg&d=identicon","user_id":9,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":10},{"id":3673,"post_number":11,"post_type":1,"created_at":"2013-02-07T09:30:21-05:00","updated_at":"2013-02-07T09:30:21-05:00","reply_count":1,"reply_to_post_number":10,"quote_count":0,"avg_time":8,"incoming_link_count":0,"reads":202,"score":60.8,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Sam Saffron","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

Yeah, totally, also we could build tools for dev that make introducing string less annoying and make it possible to garbage collect old unused strings, I hate trudging through that file.

","read":false,"username":"sam","name":"Sam Saffron","reply_to_user":{"username":"tms","name":"Tim Stone"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","user_id":1,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":11},{"id":3675,"post_number":12,"post_type":1,"created_at":"2013-02-07T09:33:38-05:00","updated_at":"2013-02-07T09:34:39-05:00","reply_count":1,"reply_to_post_number":11,"quote_count":1,"avg_time":9,"incoming_link_count":1,"reads":203,"score":66.05,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

As said, I'd look into whether WP's tools can't be reused for this with some tweaking. They seem to be able to scan a code base for new strings, and make them available automatically to translators.

\n\n

They're PHP based which isn't ideal, but it looks like they've done a crapload of work to take the hassle out of translations.

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":12},{"id":3690,"post_number":13,"post_type":1,"created_at":"2013-02-07T10:05:35-05:00","updated_at":"2013-02-07T10:05:35-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":8,"incoming_link_count":9,"reads":206,"score":116.6,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Valts","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

This site looks so nice with all the little tweaks like \"10 minutes ago\" instead of simply time, etc - I wonder if there will also be support for proper pluralization in other languages? That's a pretty hard task though, I don't think I've ever seen a website that has done that. But it would be awesome.

","read":false,"username":"Vilx","name":"Valts","actions_summary":[{"id":2,"count":2,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/7bd2e50770e937761cfc3811a332bccc.png?s={size}&r=pg&d=identicon","user_id":1216,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":13},{"id":3925,"post_number":14,"post_type":1,"created_at":"2013-02-07T14:37:06-05:00","updated_at":"2013-02-07T14:37:06-05:00","reply_count":1,"reply_to_post_number":12,"quote_count":1,"avg_time":9,"incoming_link_count":0,"reads":194,"score":74.25,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Eric Kidd","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://www.localeapp.com/","internal":false,"reflection":false,"clicks":44}],"cooked":"

\n\n

I've had pretty decent luck using Localeapp to localize Rails applications:

\n\n

http://www.localeapp.com/

\n\n

The developer workflow took me about an hour to really get used to, and there were a few minor glitches. But the non-technical translators had very few problems. One limitation: It insists on rewriting all those yaml files full of strings.

\n\n

Anyway, it's worth a look, and it's free for open source, if I recall correctly. Certainly easier than doing a whole bunch of toolsmithing from scratch.

","read":false,"username":"emk","name":"Eric Kidd","actions_summary":[{"id":2,"count":2,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/528ca205857ff8f648359dcd3e74c84a.png?s={size}&r=pg&d=identicon","user_id":1860,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":14},{"id":3938,"post_number":15,"post_type":1,"created_at":"2013-02-07T14:52:13-05:00","updated_at":"2013-02-07T14:52:13-05:00","reply_count":1,"reply_to_post_number":14,"quote_count":1,"avg_time":8,"incoming_link_count":0,"reads":184,"score":57.2,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

Ohhh. Looking sexy. droool

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":15},{"id":3982,"post_number":16,"post_type":1,"created_at":"2013-02-07T15:52:22-05:00","updated_at":"2013-02-07T15:52:22-05:00","reply_count":1,"reply_to_post_number":15,"quote_count":1,"avg_time":8,"incoming_link_count":0,"reads":180,"score":56.4,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Eric Kidd","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

Yeah, it's pretty. :-) But there were still some rough edges as of a few months ago.

\n\n

Whether or not those rough edges are a deal-breaker will probably depends on whether or not localization is already a source of acute pain. If you're already hurting, Localeapp is a pretty useful tool, especially when it comes to enlisting non-technical translators.

\n\n

But it does require changing how you work with text, and adding one new tool to the mix. So for projects that just don't want to know about non-English languages, it's not yet seamless the way Unicode is these days.

\n\n

(Sweet forum software, by the way. I was just testing out Egyptian hieroglyphics on the test server, because they're well off the Basic Multilingual Plane, and tend to flush Unicode bugs. Everything worked flawlessly.)

","read":false,"username":"emk","name":"Eric Kidd","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/528ca205857ff8f648359dcd3e74c84a.png?s={size}&r=pg&d=identicon","user_id":1860,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":16},{"id":3989,"post_number":17,"post_type":1,"created_at":"2013-02-07T16:04:15-05:00","updated_at":"2013-02-07T16:04:15-05:00","reply_count":2,"reply_to_post_number":16,"quote_count":1,"avg_time":8,"incoming_link_count":0,"reads":181,"score":61.6,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

Interesting, thanks for the insight. I don't think localization is seriously on their table right now, there's likely to be many other things on the table before it... but it will become an issue sooner or later.

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":17},{"id":3996,"post_number":18,"post_type":1,"created_at":"2013-02-07T16:12:06-05:00","updated_at":"2013-02-07T16:12:06-05:00","reply_count":2,"reply_to_post_number":17,"quote_count":0,"avg_time":9,"incoming_link_count":0,"reads":186,"score":107.65,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Sam Saffron","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

I had an idea ... what if in dev mode, you could right-click on a page and get access to all the translations on the page, make your edits and have it refreshed live.

\n\n

I think it would be awesome, very doable technically.

","read":false,"username":"sam","name":"Sam Saffron","reply_to_user":{"username":"pekka","name":"Pekka Gaiser"},"actions_summary":[{"id":2,"count":4,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","user_id":1,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":18},{"id":4009,"post_number":19,"post_type":1,"created_at":"2013-02-07T16:18:47-05:00","updated_at":"2013-02-07T16:22:10-05:00","reply_count":0,"reply_to_post_number":18,"quote_count":0,"avg_time":9,"incoming_link_count":0,"reads":176,"score":50.65,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

That would be fricking cool. There'd still be some leftovers (like error messages that normally never show up, etc.) but you could corral those up on a specific page.

\n\n

It could have a dropdown giving you all the languages that you have a .yml for in the locale directory, and write the changes into the one selected. I'm sure people would love it.

","read":false,"username":"pekka","name":"Pekka Gaiser","reply_to_user":{"username":"sam","name":"Sam Saffron"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":19},{"id":4012,"post_number":20,"post_type":1,"created_at":"2013-02-07T16:22:46-05:00","updated_at":"2013-02-07T16:22:46-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":11,"incoming_link_count":1,"reads":176,"score":55.75,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Marco Ceppi","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"https://translations.launchpad.net/","internal":false,"reflection":false,"clicks":7}],"cooked":"

If you use gettext format you could leverage Launchpad translations and the community behind it.

","read":false,"username":"marcoceppi","name":"Marco Ceppi","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/4ddc8924e79bcec03256821af65fca91.png?s={size}&r=pg&d=identicon","user_id":761,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":20},{"id":4025,"post_number":21,"post_type":1,"created_at":"2013-02-07T16:36:14-05:00","updated_at":"2013-02-07T16:36:14-05:00","reply_count":0,"reply_to_post_number":18,"quote_count":2,"avg_time":19,"incoming_link_count":1,"reads":184,"score":102.75,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Eric Kidd","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://www.localeapp.com/projects/1537/translations?utf8=%E2%9C%93&search=source_code","internal":false,"reflection":false,"clicks":16}],"cooked":"

\n\n

One of the modes in LocaleApp is actually pretty great for this sort of work. When I'm coding, I just type something like like the following:

\n\n
          link_to t('.source_code'), langforums_github_url\n
\n\n

Then I reload the page, and I see a bunch of empty boxes on this page here (this link is real):

\n\n

\n\n

Then I fill in the two languages that I allegedly speak, and everything shows up automatically on the next reload. I can sync the translations back down into my local yaml file with one command, or ask for it to happen automagically.

\n\n

So it's definitely a little more work than just typing \"Source code langforums.org\" directly into the view source. But if you need to localize, it's really pretty reasonable. And the interface for translators is quite easy to use.

\n\n

As of a few months ago, LocaleApp was far-and-away the best option for Rails localization. But as you can see, it takes a couple of extra steps.

\n\n

\n\n

I'm generally in favor of localization, especially for big open source projects. I think most of the groundwork has already been laid, actually. And if you give translators halfway-decent tools, it's easy to find volunteers.

","read":false,"username":"emk","name":"Eric Kidd","actions_summary":[{"id":2,"count":2,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/528ca205857ff8f648359dcd3e74c84a.png?s={size}&r=pg&d=identicon","user_id":1860,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":21}],"at_bottom":false,"highest_post_number":94,"pinned":false,"filtered_posts_count":93,"created_by":{"id":255,"username":"uwe_keim","avatar_template":"https://www.gravatar.com/avatar/53a82f701ae492808834e621de2586eb.png?s={size}&r=pg&d=identicon"},"last_poster":{"id":5564,"username":"Sjors","avatar_template":"https://www.gravatar.com/avatar/2fb09bd6501779802459a171d3f8fbd9.png?s={size}&r=pg&d=identicon"},"allowed_groups":[],"links":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales/en.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"70","user_id":9},{"url":"http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"45","user_id":7},{"url":"http://www.localeapp.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"44","user_id":1860},{"url":"https://github.com/SlexAxton/messageformat.js","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"34","user_id":1},{"url":"http://meta.discourse.org/t/language-mirrors/2378/2","title":"Language mirrors","fancy_title":null,"internal":true,"reflection":true,"clicks":"29","user_id":32},{"url":"https://github.com/berk/tr8n","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"18","user_id":1},{"url":"http://meta.discourse.org/t/roadplan-for-discourse/2939/5","title":"Roadplan for Discourse","fancy_title":null,"internal":true,"reflection":true,"clicks":"18","user_id":32},{"url":"http://meta.discourse.org/t/internationalization-i18n-provided-for-discourse-/2073/2","title":"Internationalization I18n provided for discourse ?","fancy_title":null,"internal":true,"reflection":true,"clicks":"17","user_id":114},{"url":"http://www.localeapp.com/projects/1537/translations?utf8=%E2%9C%93&search=source_code","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"16","user_id":1860},{"url":"https://www.transifex.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"14","user_id":1979},{"url":"https://translations.launchpad.net/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"14","user_id":761},{"url":"http://translate.wordpress.org/projects/bbpress/dev","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"13","user_id":7},{"url":"http://weblate.org","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"11","user_id":2316},{"url":"http://meta.discourse.org/t/hi-support-chinese/4393/6","title":"Hi, support Chinese?","fancy_title":null,"internal":true,"reflection":true,"clicks":"9","user_id":2014},{"url":"https://github.com/discourse/discourse/pull/493","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"7","user_id":2753},{"url":"http://meta.discourse.org/t/what-i-love-about-wordpress-plugins/5697","title":"What I love about WordPress plugins","fancy_title":null,"internal":true,"reflection":true,"clicks":"7","user_id":1},{"url":"https://github.com/SlexAxton","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"7","user_id":1},{"url":"https://github.com/gururea/discourse/tree/master/config/locales","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"6","user_id":3190},{"url":"http://www.getlocalization.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"5","user_id":22},{"url":"/users/codinghorror","title":null,"fancy_title":null,"internal":true,"reflection":false,"clicks":"5","user_id":7},{"url":"http://tr8n.github.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"5","user_id":212},{"url":"https://github.com/dacap/discourse/tree/spanish","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"4","user_id":1275},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.nl.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"4","user_id":461},{"url":"http://meta.discourse.org/t/solving-xda-developer-style-forums/4368/4","title":"Solving XDA-Developer style forums","fancy_title":null,"internal":true,"reflection":true,"clicks":"4","user_id":639},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.en.yml#L691","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"4","user_id":461},{"url":"http://translate.sourceforge.net/wiki/virtaal/index","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"3","user_id":1979},{"url":"https://github.com/discourse/discourse/commit/c5761eae8afe37e20cec0d0f9d14b85b6e585bda","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"3","user_id":212},{"url":"http://meta.discourse.org/t/comrades-let-s-join-our-efforts-on-ukrainian-and-russian-translations/4403/5","title":"Comrades let's join our efforts on ukrainian and russian translations","fancy_title":null,"internal":true,"reflection":true,"clicks":"3","user_id":3417},{"url":"http://meta.discourse.org/t/bookmark-last-read-sometimes-doesn-t-go-to-the-end-of-a-topic/4825/9","title":"Bookmark/last read sometimes doesn't go to the end of a topic","fancy_title":null,"internal":true,"reflection":true,"clicks":"3","user_id":3681},{"url":"http://meta.discourse.org/t/translation-workflow/6102","title":"Translation workflow","fancy_title":null,"internal":true,"reflection":true,"clicks":"3","user_id":4702},{"url":"http://meta.discourse.org/t/failed-to-create-new-category-when-set-the-default-locale-to-zh-cn/4703","title":"Failed to create new category when set the default_locale to zh_CN","fancy_title":null,"internal":true,"reflection":false,"clicks":"3","user_id":2995},{"url":"http://meta.discourse.org/t/how-to-change-language/6900/2","title":"How to change language?","fancy_title":null,"internal":true,"reflection":true,"clicks":"3","user_id":1995},{"url":"http://en.lichess.org/@/Hellball","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":1979},{"url":"http://www.youtube.com/watch?v=MqqdzJ98q7s","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":212},{"url":"https://github.com/alxndr/discourse/blob/i18n-chinese/config/locales/server.zh.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":212},{"url":"http://pootle.locamotion.org/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":3190},{"url":"http://www.madanalogy.com/2012/06/rails-i18n-translations-in-yaml.html","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":3190},{"url":"https://poeditor.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":1979},{"url":"http://www.slideshare.net/HeatherRivers/linguistic-potluck-crowdsourcing-localization-with-rails","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":212},{"url":"http://blog.discourse.org/2013/04/discourse-as-your-first-rails-app/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":1995},{"url":"http://meta.discourse.org/t/suggestion-translation-on-admin-panel/6923/5","title":"Suggestion: Translation on admin panel","fancy_title":null,"internal":true,"reflection":true,"clicks":"1","user_id":1},{"url":"http://meta.discourse.org/t/translation-workflow/6102/6","title":"Translation workflow","fancy_title":null,"internal":true,"reflection":false,"clicks":"1","user_id":1995},{"url":"http://meta.discourse.org/t/when-will-discourse-provide-an-local-language-configuration/7068/2","title":"When will discourse provide an local language configuration","fancy_title":null,"internal":true,"reflection":true,"clicks":"1","user_id":3987},{"url":"https://github.com/discourse/discourse/blob/master/config/locales","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"1","user_id":32},{"url":"http://guides.rubyonrails.org/i18n.html#the-public-i18n-api","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"0","user_id":1895},{"url":"https://github.com/discourse/discourse/blob/master/app/assets/javascripts/locales/date_locales.js","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"0","user_id":461},{"url":"http://en.wikipedia.org/wiki/T%E2%80%93V_distinction","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"0","user_id":3620},{"url":"http://sugarjs.com/dates#date_locales","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"0","user_id":461},{"url":"http://meta.discourse.org/t/suggestion-translation-on-admin-panel/6923/6","title":"Suggestion: Translation on admin panel","fancy_title":null,"internal":true,"reflection":true,"clicks":"0","user_id":1},{"url":"http://meta.discourse.org/t/i-like-discourse-want-join-discourse-translation-team/6679/3","title":"I like discourse,want join discourse translation team","fancy_title":null,"internal":true,"reflection":true,"clicks":"0","user_id":1995},{"url":"http://meta.discourse.org/t/missing-user-value-in-chinese-localized-page/7406/6","title":"[missing {{user}} value] in Chinese localized page","fancy_title":null,"internal":true,"reflection":true,"clicks":"0","user_id":1},{"url":"http://meta.discourse.org/t/jump-to-last-post-does-not-work-in-best-of-mode/7626","title":"Jump to last post does not work in \"Best of\" mode","fancy_title":null,"internal":true,"reflection":true,"clicks":"0","user_id":5174}],"participants":[{"id":1,"username":"sam","avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","post_count":11},{"id":212,"username":"alxndr","avatar_template":"https://www.gravatar.com/avatar/51c9cfe3d5ebd64a79983aa3117f4aed.png?s={size}&r=pg&d=identicon","post_count":11},{"id":7,"username":"pekka","avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","post_count":7},{"id":461,"username":"kuba","avatar_template":"https://www.gravatar.com/avatar/1835cb6a5f35bd4089e416a99af90f5f.png?s={size}&r=pg&d=identicon","post_count":7},{"id":2995,"username":"tattoo","avatar_template":"https://www.gravatar.com/avatar/645454e097898e3f0d9a54c699995678.png?s={size}&r=pg&d=identicon","post_count":6},{"id":2540,"username":"jgourdon","avatar_template":"https://www.gravatar.com/avatar/3f0ee7e17ec820c458958ed7b0e8538b.png?s={size}&r=pg&d=identicon","post_count":5},{"id":1860,"username":"emk","avatar_template":"https://www.gravatar.com/avatar/528ca205857ff8f648359dcd3e74c84a.png?s={size}&r=pg&d=identicon","post_count":4},{"id":1275,"username":"dacap","avatar_template":"https://www.gravatar.com/avatar/ec0ebc7c17f649d03ee78d4eba56ef73.png?s={size}&r=pg&d=identicon","post_count":4},{"id":3704,"username":"mojzis","avatar_template":"https://www.gravatar.com/avatar/90847dfc44c19a250e9000bcfc7d1507.png?s={size}&r=pg&d=identicon","post_count":3},{"id":3190,"username":"gururea","avatar_template":"https://www.gravatar.com/avatar/5ffb222c9c1bd2d99d9267c1557ca984.png?s={size}&r=pg&d=identicon","post_count":3},{"id":19,"username":"eviltrout","avatar_template":"https://www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","post_count":3},{"id":1895,"username":"maciek","avatar_template":"https://www.gravatar.com/avatar/e3fe0c49f509994d67045602f49808ee.png?s={size}&r=pg&d=identicon","post_count":3},{"id":32,"username":"codinghorror","avatar_template":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","post_count":2},{"id":3818,"username":"Tudor","avatar_template":"https://www.gravatar.com/avatar/8f367608e1d013beed72a8941bb768ca.png?s={size}&r=pg&d=identicon","post_count":2},{"id":3620,"username":"potthast","avatar_template":"https://www.gravatar.com/avatar/1753724263a5dee3e38790e6ac3d685c.png?s={size}&r=pg&d=identicon","post_count":2},{"id":1979,"username":"Superuser","avatar_template":"https://www.gravatar.com/avatar/a7f1529299c8fb9a263b8e8afcab23da.png?s={size}&r=pg&d=identicon","post_count":2},{"id":22,"username":"splattne","avatar_template":"https://www.gravatar.com/avatar/7847006dbf49f1722b07c8da396f1275.png?s={size}&r=pg&d=identicon","post_count":2},{"id":9,"username":"tms","avatar_template":"https://www.gravatar.com/avatar/3981cd271c302f5cba628c6b6d2b32ee.png?s={size}&r=pg&d=identicon","post_count":2},{"id":761,"username":"marcoceppi","avatar_template":"https://www.gravatar.com/avatar/4ddc8924e79bcec03256821af65fca91.png?s={size}&r=pg&d=identicon","post_count":1},{"id":255,"username":"uwe_keim","avatar_template":"https://www.gravatar.com/avatar/53a82f701ae492808834e621de2586eb.png?s={size}&r=pg&d=identicon","post_count":1},{"id":3403,"username":"Andersos","avatar_template":"https://www.gravatar.com/avatar/097f9f15bb97c2d6b8392f1858a7d8a8.png?s={size}&r=pg&d=identicon","post_count":1},{"id":2753,"username":"mikl","avatar_template":"https://www.gravatar.com/avatar/2c3b9882e6898958b892a218b5493af9.png?s={size}&r=pg&d=identicon","post_count":1},{"id":5052,"username":"vulkanino","avatar_template":"https://www.gravatar.com/avatar/811bf232b634245aebba5323462d885c.png?s={size}&r=pg&d=identicon","post_count":1},{"id":1216,"username":"Vilx","avatar_template":"https://www.gravatar.com/avatar/7bd2e50770e937761cfc3811a332bccc.png?s={size}&r=pg&d=identicon","post_count":1}]} \ No newline at end of file +Discourse.URL_FIXTURES["/t/280/1.json"] = {"id":280,"title":"Internationalization / localization","fancy_title":"Internationalization / localization","posts_count":93,"created_at":"2013-02-05T16:29:00-05:00","views":2225,"reply_count":64,"last_posted_at":"2013-06-18T14:22:35-04:00","visible":true,"closed":false,"archived":false,"moderator_posts_count":0,"has_best_of":true,"archetype":"regular","slug":"internationalization-localization","auto_close_at":null,"draft":null,"draft_key":"topic_280","draft_sequence":null,"post_action_visibility":[2,5],"voted_in_topic":false,"categoryName":"feature","posts":[{"id":398,"post_number":1,"post_type":1,"created_at":"2013-02-05T16:29:00-05:00","updated_at":"2013-02-05T16:29:00-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":21,"incoming_link_count":0,"reads":321,"score":97.25,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Uwe Keim","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://meta.discourse.org/t/language-mirrors/2378/2","internal":true,"reflection":true,"title":"Language mirrors","clicks":29},{"url":"http://meta.discourse.org/t/roadplan-for-discourse/2939/5","internal":true,"reflection":true,"title":"Roadplan for Discourse","clicks":18},{"url":"http://meta.discourse.org/t/internationalization-i18n-provided-for-discourse-/2073/2","internal":true,"reflection":true,"title":"Internationalization I18n provided for discourse ?","clicks":17},{"url":"http://meta.discourse.org/t/solving-xda-developer-style-forums/4368/4","internal":true,"reflection":true,"title":"Solving XDA-Developer style forums","clicks":4},{"url":"http://meta.discourse.org/t/bookmark-last-read-sometimes-doesn-t-go-to-the-end-of-a-topic/4825/9","internal":true,"reflection":true,"title":"Bookmark/last read sometimes doesn't go to the end of a topic","clicks":3},{"url":"http://meta.discourse.org/t/translation-workflow/6102","internal":true,"reflection":true,"title":"Translation workflow","clicks":3},{"url":"http://meta.discourse.org/t/comrades-let-s-join-our-efforts-on-ukrainian-and-russian-translations/4403/5","internal":true,"reflection":true,"title":"Comrades let's join our efforts on ukrainian and russian translations","clicks":3},{"url":"http://meta.discourse.org/t/how-to-change-language/6900/2","internal":true,"reflection":true,"title":"How to change language?","clicks":3},{"url":"http://meta.discourse.org/t/suggestion-translation-on-admin-panel/6923/5","internal":true,"reflection":true,"title":"Suggestion: Translation on admin panel","clicks":1},{"url":"http://meta.discourse.org/t/when-will-discourse-provide-an-local-language-configuration/7068/2","internal":true,"reflection":true,"title":"When will discourse provide an local language configuration","clicks":1},{"url":"http://meta.discourse.org/t/jump-to-last-post-does-not-work-in-best-of-mode/7626","internal":true,"reflection":true,"title":"Jump to last post does not work in \"Best of\" mode","clicks":0},{"url":"http://meta.discourse.org/t/missing-user-value-in-chinese-localized-page/7406/6","internal":true,"reflection":true,"title":"[missing {{user}} value] in Chinese localized page","clicks":0},{"url":"http://meta.discourse.org/t/i-like-discourse-want-join-discourse-translation-team/6679/3","internal":true,"reflection":true,"title":"I like discourse,want join discourse translation team","clicks":0},{"url":"http://meta.discourse.org/t/suggestion-translation-on-admin-panel/6923/6","internal":true,"reflection":true,"title":"Suggestion: Translation on admin panel","clicks":0}],"cooked":"

Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?

","read":false,"username":"uwe_keim","name":"Uwe Keim","actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/53a82f701ae492808834e621de2586eb.png?s={size}&r=pg&d=identicon","user_id":255,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":1},{"id":419,"post_number":2,"post_type":1,"created_at":"2013-02-05T16:32:47-05:00","updated_at":"2013-02-06T05:15:27-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":25,"incoming_link_count":10,"reads":318,"score":234.85,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Tim Stone","version":2,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales/en.yml","internal":false,"reflection":false,"clicks":70}],"cooked":"

The application strings are externalized, so localization should be entirely possible with enough translation effort.

","read":false,"username":"tms","name":"Tim Stone","actions_summary":[{"id":2,"count":3,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/3981cd271c302f5cba628c6b6d2b32ee.png?s={size}&r=pg&d=identicon","user_id":9,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":2},{"id":1060,"post_number":3,"post_type":1,"created_at":"2013-02-05T21:26:24-05:00","updated_at":"2013-06-18T22:58:28-04:00","reply_count":3,"reply_to_post_number":null,"quote_count":0,"avg_time":35,"incoming_link_count":4,"reads":312,"score":144.15,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Jeff Atwood","version":3,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales","internal":false,"reflection":false,"clicks":1}],"cooked":"

Yep, all strings are going through a lookup table.*

\n\n

https://github.com/discourse/discourse/blob/master/config/locales

\n\n

So you could replace that lookup table with the \"de\" one to get German.

\n\n

* we didn't get all the strings into the lookup table for launch, but the vast, vast majority of them are and the ones that are not, we will fix as we go!

","read":false,"username":"codinghorror","name":"Jeff Atwood","actions_summary":[{"id":2,"count":3,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","user_id":32,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":3},{"id":3623,"post_number":4,"post_type":1,"created_at":"2013-02-07T07:55:33-05:00","updated_at":"2013-02-07T07:55:33-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":21,"incoming_link_count":10,"reads":269,"score":239.85,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Shade","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://meta.discourse.org/t/hi-support-chinese/4393/6","internal":true,"reflection":true,"title":"Hi, support Chinese?","clicks":9}],"cooked":"

Is it a coincidence that the strings file is 1337 lines long? :D

","read":false,"username":"shade","name":"Shade","actions_summary":[{"id":2,"count":7,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/02c3f1806f6962f56168c7bd9f8924b8.png?s={size}&r=pg&d=identicon","user_id":1808,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":4},{"id":3651,"post_number":5,"post_type":1,"created_at":"2013-02-07T09:02:07-05:00","updated_at":"2013-02-07T09:05:42-05:00","reply_count":2,"reply_to_post_number":3,"quote_count":1,"avg_time":24,"incoming_link_count":6,"reads":263,"score":153.8,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders","internal":false,"reflection":false,"clicks":45},{"url":"/users/codinghorror","internal":true,"reflection":false,"clicks":5}],"cooked":"

\n\n

The problem I see here is that this file is likely two grow and change massively over the next couple months, and tracking these changes in order to keep a localized file up to date is going to be a bitch.

\n\n

I wonder where there is a tool that can compare two yml structures and point out which nodes are missing? That would help keep track of new strings.

\n\n

Re keeping track of changed strings, @codinghorror I found this very interesting: http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders if plain English placeholders were used, any change in strings would lead to a new node in the yml file, making keeping the translation up to date easier. Maybe worth thinking about in the future.

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":5},{"id":3654,"post_number":6,"post_type":1,"created_at":"2013-02-07T09:05:39-05:00","updated_at":"2013-02-07T09:05:39-05:00","reply_count":1,"reply_to_post_number":5,"quote_count":0,"avg_time":19,"incoming_link_count":1,"reads":237,"score":73.35,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Sam Saffron","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"https://github.com/SlexAxton/messageformat.js","internal":false,"reflection":false,"clicks":34},{"url":"https://github.com/SlexAxton","internal":false,"reflection":false,"clicks":7}],"cooked":"

Yes, I really like the concept of fuzzy matching for localization, perhaps you can chase up alex sexton he was meaning to upload a localization tool for this kind of stuff.

\n\n

Also, I am a big fan of ICU message format, but it is not the \"Rails way (tm)\".

","read":false,"username":"sam","name":"Sam Saffron","reply_to_user":{"username":"pekka","name":"Pekka Gaiser"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","user_id":1,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":6},{"id":3655,"post_number":7,"post_type":1,"created_at":"2013-02-07T09:08:17-05:00","updated_at":"2013-02-07T09:12:02-05:00","reply_count":1,"reply_to_post_number":6,"quote_count":1,"avg_time":17,"incoming_link_count":0,"reads":239,"score":68.65,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://translate.wordpress.org/projects/bbpress/dev","internal":false,"reflection":false,"clicks":13}],"cooked":"

Looks interesting, I'll take a peek.

\n\n

As said on dev, the best tool I can see in terms of giving translators a proper interface and quality control would be something like GlotPress. It's based on the PO messages format (is that somehow related to ICU?) but looks pretty great.

\n\n

\n\n

I'm not familiar with the term in this context, you mean keeping the English version in the code base (instead of a generic code like message_error_nametooshort ?)

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":7},{"id":3658,"post_number":8,"post_type":1,"created_at":"2013-02-07T09:12:22-05:00","updated_at":"2013-02-07T09:12:22-05:00","reply_count":1,"reply_to_post_number":7,"quote_count":0,"avg_time":12,"incoming_link_count":0,"reads":216,"score":63.8,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Sam Saffron","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://meta.discourse.org/t/what-i-love-about-wordpress-plugins/5697","internal":true,"reflection":true,"title":"What I love about WordPress plugins","clicks":7}],"cooked":"

ICU Message format is basically Gettext on steroids, Gettext has been around for so many years and actually works pretty well, being super prevalent in Linux.

\n\n

Trouble is you need a fuzzy matcher for translators if you are going to store stuff like mf.compile( 'This is a message.' ) in source, one letter change and all your translators need to validate it.

","read":false,"username":"sam","name":"Sam Saffron","reply_to_user":{"username":"pekka","name":"Pekka Gaiser"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","user_id":1,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":8},{"id":3660,"post_number":9,"post_type":1,"created_at":"2013-02-07T09:14:12-05:00","updated_at":"2013-02-07T09:18:09-05:00","reply_count":1,"reply_to_post_number":8,"quote_count":1,"avg_time":11,"incoming_link_count":0,"reads":216,"score":63.75,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

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

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":9},{"id":3667,"post_number":10,"post_type":1,"created_at":"2013-02-07T09:25:16-05:00","updated_at":"2013-02-07T09:25:16-05:00","reply_count":1,"reply_to_post_number":9,"quote_count":0,"avg_time":8,"incoming_link_count":0,"reads":206,"score":61.6,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Tim Stone","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

Hmm...You could theoretically also build something into the development process that would monitor changes to the English locale file and make a translator-friendly list of changes between versions.

","read":false,"username":"tms","name":"Tim Stone","reply_to_user":{"username":"pekka","name":"Pekka Gaiser"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/3981cd271c302f5cba628c6b6d2b32ee.png?s={size}&r=pg&d=identicon","user_id":9,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":10},{"id":3673,"post_number":11,"post_type":1,"created_at":"2013-02-07T09:30:21-05:00","updated_at":"2013-02-07T09:30:21-05:00","reply_count":1,"reply_to_post_number":10,"quote_count":0,"avg_time":8,"incoming_link_count":0,"reads":202,"score":60.8,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Sam Saffron","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

Yeah, totally, also we could build tools for dev that make introducing string less annoying and make it possible to garbage collect old unused strings, I hate trudging through that file.

","read":false,"username":"sam","name":"Sam Saffron","reply_to_user":{"username":"tms","name":"Tim Stone"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","user_id":1,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":11},{"id":3675,"post_number":12,"post_type":1,"created_at":"2013-02-07T09:33:38-05:00","updated_at":"2013-02-07T09:34:39-05:00","reply_count":1,"reply_to_post_number":11,"quote_count":1,"avg_time":9,"incoming_link_count":1,"reads":203,"score":66.05,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

As said, I'd look into whether WP's tools can't be reused for this with some tweaking. They seem to be able to scan a code base for new strings, and make them available automatically to translators.

\n\n

They're PHP based which isn't ideal, but it looks like they've done a crapload of work to take the hassle out of translations.

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":12},{"id":3690,"post_number":13,"post_type":1,"created_at":"2013-02-07T10:05:35-05:00","updated_at":"2013-02-07T10:05:35-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":8,"incoming_link_count":9,"reads":206,"score":116.6,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Valts","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

This site looks so nice with all the little tweaks like \"10 minutes ago\" instead of simply time, etc - I wonder if there will also be support for proper pluralization in other languages? That's a pretty hard task though, I don't think I've ever seen a website that has done that. But it would be awesome.

","read":false,"username":"Vilx","name":"Valts","actions_summary":[{"id":2,"count":2,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/7bd2e50770e937761cfc3811a332bccc.png?s={size}&r=pg&d=identicon","user_id":1216,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":13},{"id":3925,"post_number":14,"post_type":1,"created_at":"2013-02-07T14:37:06-05:00","updated_at":"2013-02-07T14:37:06-05:00","reply_count":1,"reply_to_post_number":12,"quote_count":1,"avg_time":9,"incoming_link_count":0,"reads":194,"score":74.25,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Eric Kidd","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://www.localeapp.com/","internal":false,"reflection":false,"clicks":44}],"cooked":"

\n\n

I've had pretty decent luck using Localeapp to localize Rails applications:

\n\n

http://www.localeapp.com/

\n\n

The developer workflow took me about an hour to really get used to, and there were a few minor glitches. But the non-technical translators had very few problems. One limitation: It insists on rewriting all those yaml files full of strings.

\n\n

Anyway, it's worth a look, and it's free for open source, if I recall correctly. Certainly easier than doing a whole bunch of toolsmithing from scratch.

","read":false,"username":"emk","name":"Eric Kidd","actions_summary":[{"id":2,"count":2,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/528ca205857ff8f648359dcd3e74c84a.png?s={size}&r=pg&d=identicon","user_id":1860,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":14},{"id":3938,"post_number":15,"post_type":1,"created_at":"2013-02-07T14:52:13-05:00","updated_at":"2013-02-07T14:52:13-05:00","reply_count":1,"reply_to_post_number":14,"quote_count":1,"avg_time":8,"incoming_link_count":0,"reads":184,"score":57.2,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

Ohhh. Looking sexy. droool

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":15},{"id":3982,"post_number":16,"post_type":1,"created_at":"2013-02-07T15:52:22-05:00","updated_at":"2013-02-07T15:52:22-05:00","reply_count":1,"reply_to_post_number":15,"quote_count":1,"avg_time":8,"incoming_link_count":0,"reads":180,"score":56.4,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Eric Kidd","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

Yeah, it's pretty. :-) But there were still some rough edges as of a few months ago.

\n\n

Whether or not those rough edges are a deal-breaker will probably depends on whether or not localization is already a source of acute pain. If you're already hurting, Localeapp is a pretty useful tool, especially when it comes to enlisting non-technical translators.

\n\n

But it does require changing how you work with text, and adding one new tool to the mix. So for projects that just don't want to know about non-English languages, it's not yet seamless the way Unicode is these days.

\n\n

(Sweet forum software, by the way. I was just testing out Egyptian hieroglyphics on the test server, because they're well off the Basic Multilingual Plane, and tend to flush Unicode bugs. Everything worked flawlessly.)

","read":false,"username":"emk","name":"Eric Kidd","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/528ca205857ff8f648359dcd3e74c84a.png?s={size}&r=pg&d=identicon","user_id":1860,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":16},{"id":3989,"post_number":17,"post_type":1,"created_at":"2013-02-07T16:04:15-05:00","updated_at":"2013-02-07T16:04:15-05:00","reply_count":2,"reply_to_post_number":16,"quote_count":1,"avg_time":8,"incoming_link_count":0,"reads":181,"score":61.6,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

\n\n

Interesting, thanks for the insight. I don't think localization is seriously on their table right now, there's likely to be many other things on the table before it... but it will become an issue sooner or later.

","read":false,"username":"pekka","name":"Pekka Gaiser","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":17},{"id":3996,"post_number":18,"post_type":1,"created_at":"2013-02-07T16:12:06-05:00","updated_at":"2013-02-07T16:12:06-05:00","reply_count":2,"reply_to_post_number":17,"quote_count":0,"avg_time":9,"incoming_link_count":0,"reads":186,"score":107.65,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Sam Saffron","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

I had an idea ... what if in dev mode, you could right-click on a page and get access to all the translations on the page, make your edits and have it refreshed live.

\n\n

I think it would be awesome, very doable technically.

","read":false,"username":"sam","name":"Sam Saffron","reply_to_user":{"username":"pekka","name":"Pekka Gaiser"},"actions_summary":[{"id":2,"count":4,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":true,"avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","user_id":1,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":18},{"id":4009,"post_number":19,"post_type":1,"created_at":"2013-02-07T16:18:47-05:00","updated_at":"2013-02-07T16:22:10-05:00","reply_count":0,"reply_to_post_number":18,"quote_count":0,"avg_time":9,"incoming_link_count":0,"reads":176,"score":50.65,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Pekka Gaiser","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"cooked":"

That would be fricking cool. There'd still be some leftovers (like error messages that normally never show up, etc.) but you could corral those up on a specific page.

\n\n

It could have a dropdown giving you all the languages that you have a .yml for in the locale directory, and write the changes into the one selected. I'm sure people would love it.

","read":false,"username":"pekka","name":"Pekka Gaiser","reply_to_user":{"username":"sam","name":"Sam Saffron"},"actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","user_id":7,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":19},{"id":4012,"post_number":20,"post_type":1,"created_at":"2013-02-07T16:22:46-05:00","updated_at":"2013-02-07T16:22:46-05:00","reply_count":0,"reply_to_post_number":null,"quote_count":0,"avg_time":11,"incoming_link_count":1,"reads":176,"score":55.75,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Marco Ceppi","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"https://translations.launchpad.net/","internal":false,"reflection":false,"clicks":7}],"cooked":"

If you use gettext format you could leverage Launchpad translations and the community behind it.

","read":false,"username":"marcoceppi","name":"Marco Ceppi","actions_summary":[{"id":2,"count":1,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/4ddc8924e79bcec03256821af65fca91.png?s={size}&r=pg&d=identicon","user_id":761,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":2,"index":20},{"id":4025,"post_number":21,"post_type":1,"created_at":"2013-02-07T16:36:14-05:00","updated_at":"2013-02-07T16:36:14-05:00","reply_count":0,"reply_to_post_number":18,"quote_count":2,"avg_time":19,"incoming_link_count":1,"reads":184,"score":102.75,"yours":false,"topic_slug":"internationalization-localization","topic_id":280,"display_username":"Eric Kidd","version":1,"can_edit":null,"can_delete":null,"can_recover":false,"link_counts":[{"url":"http://www.localeapp.com/projects/1537/translations?utf8=%E2%9C%93&search=source_code","internal":false,"reflection":false,"clicks":16}],"cooked":"

\n\n

One of the modes in LocaleApp is actually pretty great for this sort of work. When I'm coding, I just type something like like the following:

\n\n
          link_to t('.source_code'), langforums_github_url\n
\n\n

Then I reload the page, and I see a bunch of empty boxes on this page here (this link is real):

\n\n

\n\n

Then I fill in the two languages that I allegedly speak, and everything shows up automatically on the next reload. I can sync the translations back down into my local yaml file with one command, or ask for it to happen automagically.

\n\n

So it's definitely a little more work than just typing \"Source code langforums.org\" directly into the view source. But if you need to localize, it's really pretty reasonable. And the interface for translators is quite easy to use.

\n\n

As of a few months ago, LocaleApp was far-and-away the best option for Rails localization. But as you can see, it takes a couple of extra steps.

\n\n

\n\n

I'm generally in favor of localization, especially for big open source projects. I think most of the groundwork has already been laid, actually. And if you give translators halfway-decent tools, it's easy to find volunteers.

","read":false,"username":"emk","name":"Eric Kidd","actions_summary":[{"id":2,"count":2,"hidden":false,"can_act":null},{"id":3,"count":0,"hidden":false,"can_act":null},{"id":4,"count":0,"hidden":false,"can_act":null},{"id":5,"count":0,"hidden":true,"can_act":null},{"id":6,"count":0,"hidden":false,"can_act":null},{"id":7,"count":0,"hidden":false,"can_act":null},{"id":8,"count":0,"hidden":false,"can_act":null}],"moderator":false,"staff":false,"avatar_template":"https://www.gravatar.com/avatar/528ca205857ff8f648359dcd3e74c84a.png?s={size}&r=pg&d=identicon","user_id":1860,"hidden":false,"hidden_reason_id":null,"deleted_at":null,"trust_level":1,"index":21}],"at_bottom":false,"highest_post_number":94,"pinned":false,"filtered_posts_count":93,"created_by":{"id":255,"username":"uwe_keim","avatar_template":"https://www.gravatar.com/avatar/53a82f701ae492808834e621de2586eb.png?s={size}&r=pg&d=identicon"},"last_poster":{"id":5564,"username":"Sjors","avatar_template":"https://www.gravatar.com/avatar/2fb09bd6501779802459a171d3f8fbd9.png?s={size}&r=pg&d=identicon"},"allowed_groups":[],"links":[{"url":"https://github.com/discourse/discourse/blob/master/config/locales/en.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"70","user_id":9},{"url":"http://stackoverflow.com/questions/4232922/why-do-people-use-plain-english-as-translation-placeholders","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"45","user_id":7},{"url":"http://www.localeapp.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"44","user_id":1860},{"url":"https://github.com/SlexAxton/messageformat.js","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"34","user_id":1},{"url":"http://meta.discourse.org/t/language-mirrors/2378/2","title":"Language mirrors","fancy_title":null,"internal":true,"reflection":true,"clicks":"29","user_id":32},{"url":"https://github.com/berk/tr8n","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"18","user_id":1},{"url":"http://meta.discourse.org/t/roadplan-for-discourse/2939/5","title":"Roadplan for Discourse","fancy_title":null,"internal":true,"reflection":true,"clicks":"18","user_id":32},{"url":"http://meta.discourse.org/t/internationalization-i18n-provided-for-discourse-/2073/2","title":"Internationalization I18n provided for discourse ?","fancy_title":null,"internal":true,"reflection":true,"clicks":"17","user_id":114},{"url":"http://www.localeapp.com/projects/1537/translations?utf8=%E2%9C%93&search=source_code","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"16","user_id":1860},{"url":"https://www.transifex.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"14","user_id":1979},{"url":"https://translations.launchpad.net/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"14","user_id":761},{"url":"http://translate.wordpress.org/projects/bbpress/dev","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"13","user_id":7},{"url":"http://weblate.org","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"11","user_id":2316},{"url":"http://meta.discourse.org/t/hi-support-chinese/4393/6","title":"Hi, support Chinese?","fancy_title":null,"internal":true,"reflection":true,"clicks":"9","user_id":2014},{"url":"https://github.com/discourse/discourse/pull/493","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"7","user_id":2753},{"url":"http://meta.discourse.org/t/what-i-love-about-wordpress-plugins/5697","title":"What I love about WordPress plugins","fancy_title":null,"internal":true,"reflection":true,"clicks":"7","user_id":1},{"url":"https://github.com/SlexAxton","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"7","user_id":1},{"url":"https://github.com/gururea/discourse/tree/master/config/locales","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"6","user_id":3190},{"url":"http://www.getlocalization.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"5","user_id":22},{"url":"/users/codinghorror","title":null,"fancy_title":null,"internal":true,"reflection":false,"clicks":"5","user_id":7},{"url":"http://tr8n.github.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"5","user_id":212},{"url":"https://github.com/dacap/discourse/tree/spanish","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"4","user_id":1275},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.nl.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"4","user_id":461},{"url":"http://meta.discourse.org/t/solving-xda-developer-style-forums/4368/4","title":"Solving XDA-Developer style forums","fancy_title":null,"internal":true,"reflection":true,"clicks":"4","user_id":639},{"url":"https://github.com/discourse/discourse/blob/master/config/locales/client.en.yml#L691","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"4","user_id":461},{"url":"http://translate.sourceforge.net/wiki/virtaal/index","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"3","user_id":1979},{"url":"https://github.com/discourse/discourse/commit/c5761eae8afe37e20cec0d0f9d14b85b6e585bda","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"3","user_id":212},{"url":"http://meta.discourse.org/t/comrades-let-s-join-our-efforts-on-ukrainian-and-russian-translations/4403/5","title":"Comrades let's join our efforts on ukrainian and russian translations","fancy_title":null,"internal":true,"reflection":true,"clicks":"3","user_id":3417},{"url":"http://meta.discourse.org/t/bookmark-last-read-sometimes-doesn-t-go-to-the-end-of-a-topic/4825/9","title":"Bookmark/last read sometimes doesn't go to the end of a topic","fancy_title":null,"internal":true,"reflection":true,"clicks":"3","user_id":3681},{"url":"http://meta.discourse.org/t/translation-workflow/6102","title":"Translation workflow","fancy_title":null,"internal":true,"reflection":true,"clicks":"3","user_id":4702},{"url":"http://meta.discourse.org/t/failed-to-create-new-category-when-set-the-default-locale-to-zh-cn/4703","title":"Failed to create new category when set the default_locale to zh_CN","fancy_title":null,"internal":true,"reflection":false,"clicks":"3","user_id":2995},{"url":"http://meta.discourse.org/t/how-to-change-language/6900/2","title":"How to change language?","fancy_title":null,"internal":true,"reflection":true,"clicks":"3","user_id":1995},{"url":"http://en.lichess.org/@/Hellball","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":1979},{"url":"http://www.youtube.com/watch?v=MqqdzJ98q7s","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":212},{"url":"https://github.com/alxndr/discourse/blob/i18n-chinese/config/locales/server.zh.yml","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":212},{"url":"http://pootle.locamotion.org/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":3190},{"url":"http://www.madanalogy.com/2012/06/rails-i18n-translations-in-yaml.html","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":3190},{"url":"https://poeditor.com/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":1979},{"url":"http://www.slideshare.net/HeatherRivers/linguistic-potluck-crowdsourcing-localization-with-rails","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":212},{"url":"http://blog.discourse.org/2013/04/discourse-as-your-first-rails-app/","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"2","user_id":1995},{"url":"http://meta.discourse.org/t/suggestion-translation-on-admin-panel/6923/5","title":"Suggestion: Translation on admin panel","fancy_title":null,"internal":true,"reflection":true,"clicks":"1","user_id":1},{"url":"http://meta.discourse.org/t/translation-workflow/6102/6","title":"Translation workflow","fancy_title":null,"internal":true,"reflection":false,"clicks":"1","user_id":1995},{"url":"http://meta.discourse.org/t/when-will-discourse-provide-an-local-language-configuration/7068/2","title":"When will discourse provide an local language configuration","fancy_title":null,"internal":true,"reflection":true,"clicks":"1","user_id":3987},{"url":"https://github.com/discourse/discourse/blob/master/config/locales","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"1","user_id":32},{"url":"http://guides.rubyonrails.org/i18n.html#the-public-i18n-api","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"0","user_id":1895},{"url":"https://github.com/discourse/discourse/blob/master/app/assets/javascripts/locales/date_locales.js","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"0","user_id":461},{"url":"http://en.wikipedia.org/wiki/T%E2%80%93V_distinction","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"0","user_id":3620},{"url":"http://sugarjs.com/dates#date_locales","title":null,"fancy_title":null,"internal":false,"reflection":false,"clicks":"0","user_id":461},{"url":"http://meta.discourse.org/t/suggestion-translation-on-admin-panel/6923/6","title":"Suggestion: Translation on admin panel","fancy_title":null,"internal":true,"reflection":true,"clicks":"0","user_id":1},{"url":"http://meta.discourse.org/t/i-like-discourse-want-join-discourse-translation-team/6679/3","title":"I like discourse,want join discourse translation team","fancy_title":null,"internal":true,"reflection":true,"clicks":"0","user_id":1995},{"url":"http://meta.discourse.org/t/missing-user-value-in-chinese-localized-page/7406/6","title":"[missing {{user}} value] in Chinese localized page","fancy_title":null,"internal":true,"reflection":true,"clicks":"0","user_id":1},{"url":"http://meta.discourse.org/t/jump-to-last-post-does-not-work-in-best-of-mode/7626","title":"Jump to last post does not work in \"Best of\" mode","fancy_title":null,"internal":true,"reflection":true,"clicks":"0","user_id":5174}],"participants":[{"id":1,"username":"sam","avatar_template":"https://www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon","post_count":11},{"id":212,"username":"alxndr","avatar_template":"https://www.gravatar.com/avatar/51c9cfe3d5ebd64a79983aa3117f4aed.png?s={size}&r=pg&d=identicon","post_count":11},{"id":7,"username":"pekka","avatar_template":"https://www.gravatar.com/avatar/100a6c42a31a56e882475725d65537f8.png?s={size}&r=pg&d=identicon","post_count":7},{"id":461,"username":"kuba","avatar_template":"https://www.gravatar.com/avatar/1835cb6a5f35bd4089e416a99af90f5f.png?s={size}&r=pg&d=identicon","post_count":7},{"id":2995,"username":"tattoo","avatar_template":"https://www.gravatar.com/avatar/645454e097898e3f0d9a54c699995678.png?s={size}&r=pg&d=identicon","post_count":6},{"id":2540,"username":"jgourdon","avatar_template":"https://www.gravatar.com/avatar/3f0ee7e17ec820c458958ed7b0e8538b.png?s={size}&r=pg&d=identicon","post_count":5},{"id":1860,"username":"emk","avatar_template":"https://www.gravatar.com/avatar/528ca205857ff8f648359dcd3e74c84a.png?s={size}&r=pg&d=identicon","post_count":4},{"id":1275,"username":"dacap","avatar_template":"https://www.gravatar.com/avatar/ec0ebc7c17f649d03ee78d4eba56ef73.png?s={size}&r=pg&d=identicon","post_count":4},{"id":3704,"username":"mojzis","avatar_template":"https://www.gravatar.com/avatar/90847dfc44c19a250e9000bcfc7d1507.png?s={size}&r=pg&d=identicon","post_count":3},{"id":3190,"username":"gururea","avatar_template":"https://www.gravatar.com/avatar/5ffb222c9c1bd2d99d9267c1557ca984.png?s={size}&r=pg&d=identicon","post_count":3},{"id":19,"username":"eviltrout","avatar_template":"https://www.gravatar.com/avatar/c6e17f2ae2a215e87ff9e878a4e63cd9.png?s={size}&r=pg&d=identicon","post_count":3},{"id":1895,"username":"maciek","avatar_template":"https://www.gravatar.com/avatar/e3fe0c49f509994d67045602f49808ee.png?s={size}&r=pg&d=identicon","post_count":3},{"id":32,"username":"codinghorror","avatar_template":"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff35e15dbe8.png?s={size}&r=pg&d=identicon","post_count":2},{"id":3818,"username":"Tudor","avatar_template":"https://www.gravatar.com/avatar/8f367608e1d013beed72a8941bb768ca.png?s={size}&r=pg&d=identicon","post_count":2},{"id":3620,"username":"potthast","avatar_template":"https://www.gravatar.com/avatar/1753724263a5dee3e38790e6ac3d685c.png?s={size}&r=pg&d=identicon","post_count":2},{"id":1979,"username":"Superuser","avatar_template":"https://www.gravatar.com/avatar/a7f1529299c8fb9a263b8e8afcab23da.png?s={size}&r=pg&d=identicon","post_count":2},{"id":22,"username":"splattne","avatar_template":"https://www.gravatar.com/avatar/7847006dbf49f1722b07c8da396f1275.png?s={size}&r=pg&d=identicon","post_count":2},{"id":9,"username":"tms","avatar_template":"https://www.gravatar.com/avatar/3981cd271c302f5cba628c6b6d2b32ee.png?s={size}&r=pg&d=identicon","post_count":2},{"id":761,"username":"marcoceppi","avatar_template":"https://www.gravatar.com/avatar/4ddc8924e79bcec03256821af65fca91.png?s={size}&r=pg&d=identicon","post_count":1},{"id":255,"username":"uwe_keim","avatar_template":"https://www.gravatar.com/avatar/53a82f701ae492808834e621de2586eb.png?s={size}&r=pg&d=identicon","post_count":1},{"id":3403,"username":"Andersos","avatar_template":"https://www.gravatar.com/avatar/097f9f15bb97c2d6b8392f1858a7d8a8.png?s={size}&r=pg&d=identicon","post_count":1},{"id":2753,"username":"mikl","avatar_template":"https://www.gravatar.com/avatar/2c3b9882e6898958b892a218b5493af9.png?s={size}&r=pg&d=identicon","post_count":1},{"id":5052,"username":"vulkanino","avatar_template":"https://www.gravatar.com/avatar/811bf232b634245aebba5323462d885c.png?s={size}&r=pg&d=identicon","post_count":1},{"id":1216,"username":"Vilx","avatar_template":"https://www.gravatar.com/avatar/7bd2e50770e937761cfc3811a332bccc.png?s={size}&r=pg&d=identicon","post_count":1}]}; \ No newline at end of file diff --git a/test/javascripts/jshint_all.js.erb b/test/javascripts/jshint_all.js.erb new file mode 100644 index 000000000..88910e92a --- /dev/null +++ b/test/javascripts/jshint_all.js.erb @@ -0,0 +1,182 @@ +module("JSHint"); + +var qHint = function(name, sourceFile, options, globals) { + if (sourceFile === undefined || typeof(sourceFile) == "object") { + // jsHintTest('file.js', [options]) + globals = options; + options = sourceFile; + sourceFile = name; + } + + return asyncTest(name, function() { + qHint.sendRequest(sourceFile, function(req) { + start(); + + if (req.status == 200) { + + var text = req.responseText; + + // Remove our generate IIFEs so we get the same line numbers as original + // files + text = text.replace(/^[^]*\/\/ IIFE Wrapped Content Begins:\n\n/m, ""); + text = text.replace(/\n\n\/\/ IIFE Wrapped Content Ends[^]*$/m, ""); + qHint.validateFile(text, options, globals); + } else { + ok(false, "HTTP error " + req.status + + " while fetching " + sourceFile); + } + }); + }); +}; + +qHint.validateFile = function (source, options, globals) { + var i, len, err; + + if (JSHINT(source, options, globals)) { + ok(true); + return; + } + + for (i = 0, len = JSHINT.errors.length; i < len; i++) { + err = JSHINT.errors[i]; + if (!err) { + continue; + } + + ok(false, err.reason + + " on line " + err.line + + ", character " + err.character); + } +}; + +var XMLHttpFactories = [ + function () { return new XMLHttpRequest(); }, + function () { return new ActiveXObject("Msxml2.XMLHTTP"); }, + function () { return new ActiveXObject("Msxml3.XMLHTTP"); }, + function () { return new ActiveXObject("Microsoft.XMLHTTP"); } +]; + +function createXMLHTTPObject() { + for (var i = 0; i < XMLHttpFactories.length; i++) { + try { + return XMLHttpFactories[i](); + } catch (e) {} + } + return false; +} + +// modified version of XHR script by PPK +// http://www.quirksmode.org/js/xmlhttp.html +// attached to qHint to allow substitution / mocking +qHint.sendRequest = function (url, callback) { + var req = createXMLHTTPObject(); + if (!req) { + return; + } + + var method = "GET"; + req.open(method,url + "?" + (new Date().getTime()),true); + req.onreadystatechange = function () { + if (req.readyState != 4) { + return; + } + + callback(req); + }; + + if (req.readyState == 4) { + return; + } + req.send(); +}; + +var jsHintOpts = { + "predef":["Ember", + "jQuery", + "$", + "RSVP", + "Discourse", + "$LAB", + "Em", + "PreloadStore", + "Handlebars", + "I18n", + "bootbox", + "module", + "integration", + "test", + "ok", + "expect", + "equal", + "blank", + "present", + "visit", + "count", + "exists", + "asyncTest", + "find", + "resolvingPromise", + "sinon", + "moment", + "start", + "_", + "console", + "alert"], + "node" : false, + "browser" : true, + "boss" : true, + "curly": false, + "debug": false, + "devel": false, + "eqeqeq": true, + "evil": true, + "forin": false, + "immed": false, + "laxbreak": false, + "newcap": true, + "noarg": true, + "noempty": false, + "nonew": false, + "nomen": false, + "onevar": false, + "plusplus": false, + "regexp": false, + "undef": true, + "sub": true, + "strict": false, + "white": false, + "eqnull": true, + "lastsemic": true +}; + +<% + def jshint(dir, remove, to_ignore) + result = "" + + Dir.glob(dir).each do |f| + filename = f.sub("#{Rails.root}#{remove}", "") + + ok = true + to_ignore.each do |ig| + ok = false if (filename =~ ig) + end + + result << "qHint('#{filename}', '/assets/#{filename}', jsHintOpts);\n" if ok + + end + result + end + +%> + +<%= jshint("#{Rails.root}/test/**/*.js", + "/test/javascripts/", + [/helpers\//]) %> + +<%= jshint("#{Rails.root}/app/assets/javascripts/**/*.js", + "/app/assets/javascripts/", + [/external\//, + /external_development\//, + /external_production\//, + /defer\//, + /locales\//]) %> \ No newline at end of file diff --git a/test/javascripts/models/category_test.js b/test/javascripts/models/category_test.js index 9fd9d3037..68956f22b 100644 --- a/test/javascripts/models/category_test.js +++ b/test/javascripts/models/category_test.js @@ -4,7 +4,7 @@ test('slugFor', function(){ var slugFor = function(args, val, text) { equal(Discourse.Category.slugFor(args), val, text); - } + }; slugFor({slug: 'hello'}, "hello", "It calculates the proper slug for hello"); slugFor({id: 123, slug: ''}, "123-category", "It returns id-category for empty strings"); diff --git a/test/javascripts/models/composer_test.js b/test/javascripts/models/composer_test.js index 17f415a18..b63b66429 100644 --- a/test/javascripts/models/composer_test.js +++ b/test/javascripts/models/composer_test.js @@ -21,7 +21,7 @@ test('missingReplyCharacters', function() { var missingReplyCharacters = function(val, isPM, expected, message) { var composer = Discourse.Composer.create({ reply: val, creatingPrivateMessage: isPM }); equal(composer.get('missingReplyCharacters'), expected, message); - } + }; missingReplyCharacters('hi', false, Discourse.SiteSettings.min_post_length - 2, 'too short public post'); missingReplyCharacters('hi', true, Discourse.SiteSettings.min_private_message_post_length - 2, 'too short private message'); @@ -31,7 +31,7 @@ test('missingTitleCharacters', function() { var missingTitleCharacters = function(val, isPM, expected, message) { var composer = Discourse.Composer.create({ title: val, creatingPrivateMessage: isPM }); equal(composer.get('missingTitleCharacters'), expected, message); - } + }; missingTitleCharacters('hi', false, Discourse.SiteSettings.min_topic_title_length - 2, 'too short post title'); missingTitleCharacters('z', true, Discourse.SiteSettings.min_private_message_title_length - 1, 'too short pm title'); diff --git a/test/javascripts/test_helper.js b/test/javascripts/test_helper.js index e3ad16ba2..98ca5f11c 100644 --- a/test/javascripts/test_helper.js +++ b/test/javascripts/test_helper.js @@ -32,8 +32,9 @@ //= require_tree ../../app/assets/javascripts/defer -//= require sinon-1.7.1.js -//= require sinon-qunit-1.0.0.js +//= require sinon-1.7.1 +//= require sinon-qunit-1.0.0 +//= require jshint //= require helpers/qunit_helpers //= require helpers/assertions @@ -41,6 +42,7 @@ //= require_tree ./fixtures //= require_tree . //= require_self +//= require jshint_all // sinon settings sinon.config = { diff --git a/vendor/assets/javascripts/jshint.js b/vendor/assets/javascripts/jshint.js new file mode 100644 index 000000000..412874710 --- /dev/null +++ b/vendor/assets/javascripts/jshint.js @@ -0,0 +1,4023 @@ +/*! + * JSHint, by JSHint Community. + * + * Licensed under the same slightly modified MIT license that JSLint is. + * It stops evil-doers everywhere. + * + * JSHint is a derivative work of JSLint: + * + * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * JSHint was forked from 2010-12-16 edition of JSLint. + * + */ + +/* + JSHINT is a global function. It takes two parameters. + + var myResult = JSHINT(source, option); + + The first parameter is either a string or an array of strings. If it is a + string, it will be split on '\n' or '\r'. If it is an array of strings, it + is assumed that each string represents one line. The source can be a + JavaScript text or a JSON text. + + The second parameter is an optional object of options which control the + operation of JSHINT. Most of the options are booleans: They are all + optional and have a default value of false. One of the options, predef, + can be an array of names, which will be used to declare global variables, + or an object whose keys are used as global names, with a boolean value + that determines if they are assignable. + + If it checks out, JSHINT returns true. Otherwise, it returns false. + + If false, you can inspect JSHINT.errors to find out the problems. + JSHINT.errors is an array of objects containing these members: + + { + line : The line (relative to 0) at which the lint was found + character : The character (relative to 0) at which the lint was found + reason : The problem + evidence : The text line in which the problem occurred + raw : The raw message before the details were inserted + a : The first detail + b : The second detail + c : The third detail + d : The fourth detail + } + + If a fatal error was found, a null will be the last element of the + JSHINT.errors array. + + You can request a Function Report, which shows all of the functions + and the parameters and vars that they use. This can be used to find + implied global variables and other problems. The report is in HTML and + can be inserted in an HTML . + + var myReport = JSHINT.report(limited); + + If limited is true, then the report will be limited to only errors. + + You can request a data structure which contains JSHint's results. + + var myData = JSHINT.data(); + + It returns a structure with this form: + + { + errors: [ + { + line: NUMBER, + character: NUMBER, + reason: STRING, + evidence: STRING + } + ], + functions: [ + name: STRING, + line: NUMBER, + last: NUMBER, + param: [ + STRING + ], + closure: [ + STRING + ], + var: [ + STRING + ], + exception: [ + STRING + ], + outer: [ + STRING + ], + unused: [ + STRING + ], + global: [ + STRING + ], + label: [ + STRING + ] + ], + globals: [ + STRING + ], + member: { + STRING: NUMBER + }, + unuseds: [ + { + name: STRING, + line: NUMBER + } + ], + implieds: [ + { + name: STRING, + line: NUMBER + } + ], + urls: [ + STRING + ], + json: BOOLEAN + } + + Empty arrays will not be included. + +*/ + +/*jshint + evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true, + undef: true, maxlen: 100 +*/ + +/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)", + "(breakage)", "(context)", "(error)", "(global)", "(identifier)", "(last)", + "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)", + "(statement)", "(verb)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==", + "===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax, + __filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio, + Autocompleter, Assets, Boolean, Builder, Buffer, Browser, COM, CScript, Canvas, + CustomAnimation, Class, Control, Chain, Color, Cookie, Core, DataView, Date, + Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMReady, Drag, + E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event, + Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form, + FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey, + HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement, + HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement, + HTMLDivElement, HTMLDListElement, HTMLFieldSetElement, + HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement, + HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement, + HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement, + HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement, + HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement, + HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement, + HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement, + HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement, + HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement, + HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement, + HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement + Iframe, IframeShim, Image, Int16Array, Int32Array, Int8Array, + Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E, + MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem, MoveAnimation, MooTools, Native, + NEGATIVE_INFINITY, Number, Object, ObjectRange, Option, Options, OverText, PI, + POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype, RangeError, + Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation, + SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion, + ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller, + Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables, + SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template, + Timer, Tips, Type, TypeError, Toggle, Try, unescape, URI, URIError, URL, VBArray, WSH, + WScript, Web, Window, XMLDOM, XMLHttpRequest, XPathEvaluator, XPathException, + XPathExpression, XPathNamespace, XPathNSResolver, XPathResult, "\\", a, + addEventListener, address, alert, apply, applicationCache, arguments, arity, + asi, b, bitwise, block, blur, boolOptions, boss, browser, c, call, callee, + caller, cases, charAt, charCodeAt, character, clearInterval, clearTimeout, + close, closed, closure, comment, condition, confirm, console, constructor, + content, couch, create, css, curly, d, data, datalist, dd, debug, decodeURI, + decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document, + dojo, dijit, dojox, define, edition, else, emit, encodeURI, encodeURIComponent, + entityify, eqeqeq, eqnull, errors, es5, escape, eval, event, evidence, evil, + ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus, + forin, fragment, frames, from, fromCharCode, fud, funct, function, functions, + g, gc, getComputedStyle, getRow, GLOBAL, global, globals, globalstrict, + hasOwnProperty, help, history, i, id, identifier, immed, implieds, include, + indent, indexOf, init, ins, instanceOf, isAlpha, isApplicationRunning, isArray, + isDigit, isFinite, isNaN, iterator, join, jshint, + JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastsemic, laxbreak, + latedef, lbp, led, left, length, line, load, loadClass, localStorage, location, + log, loopfunc, m, match, maxerr, maxlen, member,message, meta, module, moveBy, + moveTo, mootools, name, navigator, new, newcap, noarg, node, noempty, nomen, + nonew, nonstandard, nud, onbeforeunload, onblur, onerror, onevar, onecase, onfocus, + onload, onresize, onunload, open, openDatabase, openURL, opener, opera, outer, param, + parent, parseFloat, parseInt, passfail, plusplus, predef, print, process, prompt, + proto, prototype, prototypejs, push, quit, range, raw, reach, reason, regexp, + readFile, readUrl, regexdash, removeEventListener, replace, report, require, + reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, respond, rhino, right, + runCommand, scroll, screen, scripturl, scrollBy, scrollTo, scrollbar, search, seal, + send, serialize, sessionStorage, setInterval, setTimeout, shift, slice, sort,spawn, + split, stack, status, start, strict, sub, substr, supernew, shadow, supplant, sum, + sync, test, toLowerCase, toString, toUpperCase, toint32, token, top, trailing, type, + typeOf, Uint16Array, Uint32Array, Uint8Array, undef, unused, urls, validthis, value, valueOf, + var, version, WebSocket, white, window, Worker, wsh*/ + +/*global exports: false */ + +// We build the application inside a function so that we produce only a single +// global variable. That function will be invoked immediately, and its return +// value is the JSHINT function itself. + +var JSHINT = (function () { + "use strict"; + + var anonname, // The guessed name for anonymous functions. + +// These are operators that should not be used with the ! operator. + + bang = { + '<' : true, + '<=' : true, + '==' : true, + '===': true, + '!==': true, + '!=' : true, + '>' : true, + '>=' : true, + '+' : true, + '-' : true, + '*' : true, + '/' : true, + '%' : true + }, + + // These are the JSHint boolean options. + boolOptions = { + asi : true, // if automatic semicolon insertion should be tolerated + bitwise : true, // if bitwise operators should not be allowed + boss : true, // if advanced usage of assignments should be allowed + browser : true, // if the standard browser globals should be predefined + couch : true, // if CouchDB globals should be predefined + curly : true, // if curly braces around all blocks should be required + debug : true, // if debugger statements should be allowed + devel : true, // if logging globals should be predefined (console, + // alert, etc.) + dojo : true, // if Dojo Toolkit globals should be predefined + eqeqeq : true, // if === should be required + eqnull : true, // if == null comparisons should be tolerated + es5 : true, // if ES5 syntax should be allowed + evil : true, // if eval should be allowed + expr : true, // if ExpressionStatement should be allowed as Programs + forin : true, // if for in statements must filter + globalstrict: true, // if global "use strict"; should be allowed (also + // enables 'strict') + immed : true, // if immediate invocations must be wrapped in parens + iterator : true, // if the `__iterator__` property should be disallowed + jquery : true, // if jQuery globals should be predefined + lastsemic : true, // if semicolons may be ommitted for the trailing + // statements inside of a one-line blocks. + latedef : true, // if the use before definition should not be tolerated + laxbreak : true, // if line breaks should not be checked + loopfunc : true, // if functions should be allowed to be defined within + // loops + mootools : true, // if MooTools globals should be predefined + newcap : true, // if constructor names must be capitalized + noarg : true, // if arguments.caller and arguments.callee should be + // disallowed + node : true, // if the Node.js environment globals should be + // predefined + noempty : true, // if empty blocks should be disallowed + nonew : true, // if using `new` for side-effects should be disallowed + nonstandard : true, // if non-standard (but widely adopted) globals should + // be predefined + nomen : true, // if names should be checked + onevar : true, // if only one var statement per function should be + // allowed + onecase : true, // if one case switch statements should be allowed + passfail : true, // if the scan should stop on first error + plusplus : true, // if increment/decrement should not be allowed + proto : true, // if the `__proto__` property should be disallowed + prototypejs : true, // if Prototype and Scriptaculous globals should be + // predefined + regexdash : true, // if unescaped last dash (-) inside brackets should be + // tolerated + regexp : true, // if the . should not be allowed in regexp literals + rhino : true, // if the Rhino environment globals should be predefined + undef : true, // if variables should be declared before used + scripturl : true, // if script-targeted URLs should be tolerated + shadow : true, // if variable shadowing should be tolerated + strict : true, // require the "use strict"; pragma + sub : true, // if all forms of subscript notation are tolerated + supernew : true, // if `new function () { ... };` and `new Object;` + // should be tolerated + trailing : true, // if trailing whitespace rules apply + validthis : true, // if 'this' inside a non-constructor function is valid. + // This is a function scoped option only. + white : true, // if strict whitespace rules apply + wsh : true // if the Windows Scripting Host environment globals + // should be predefined + }, + + // browser contains a set of global names which are commonly provided by a + // web browser environment. + browser = { + ArrayBuffer : false, + ArrayBufferView : false, + Audio : false, + addEventListener : false, + applicationCache : false, + blur : false, + clearInterval : false, + clearTimeout : false, + close : false, + closed : false, + DataView : false, + defaultStatus : false, + document : false, + event : false, + FileReader : false, + Float32Array : false, + Float64Array : false, + FormData : false, + focus : false, + frames : false, + getComputedStyle : false, + HTMLElement : false, + HTMLAnchorElement : false, + HTMLBaseElement : false, + HTMLBlockquoteElement : false, + HTMLBodyElement : false, + HTMLBRElement : false, + HTMLButtonElement : false, + HTMLCanvasElement : false, + HTMLDirectoryElement : false, + HTMLDivElement : false, + HTMLDListElement : false, + HTMLFieldSetElement : false, + HTMLFontElement : false, + HTMLFormElement : false, + HTMLFrameElement : false, + HTMLFrameSetElement : false, + HTMLHeadElement : false, + HTMLHeadingElement : false, + HTMLHRElement : false, + HTMLHtmlElement : false, + HTMLIFrameElement : false, + HTMLImageElement : false, + HTMLInputElement : false, + HTMLIsIndexElement : false, + HTMLLabelElement : false, + HTMLLayerElement : false, + HTMLLegendElement : false, + HTMLLIElement : false, + HTMLLinkElement : false, + HTMLMapElement : false, + HTMLMenuElement : false, + HTMLMetaElement : false, + HTMLModElement : false, + HTMLObjectElement : false, + HTMLOListElement : false, + HTMLOptGroupElement : false, + HTMLOptionElement : false, + HTMLParagraphElement : false, + HTMLParamElement : false, + HTMLPreElement : false, + HTMLQuoteElement : false, + HTMLScriptElement : false, + HTMLSelectElement : false, + HTMLStyleElement : false, + HTMLTableCaptionElement : false, + HTMLTableCellElement : false, + HTMLTableColElement : false, + HTMLTableElement : false, + HTMLTableRowElement : false, + HTMLTableSectionElement : false, + HTMLTextAreaElement : false, + HTMLTitleElement : false, + HTMLUListElement : false, + HTMLVideoElement : false, + history : false, + Int16Array : false, + Int32Array : false, + Int8Array : false, + Image : false, + length : false, + localStorage : false, + location : false, + moveBy : false, + moveTo : false, + name : false, + navigator : false, + onbeforeunload : true, + onblur : true, + onerror : true, + onfocus : true, + onload : true, + onresize : true, + onunload : true, + open : false, + openDatabase : false, + opener : false, + Option : false, + parent : false, + print : false, + removeEventListener : false, + resizeBy : false, + resizeTo : false, + screen : false, + scroll : false, + scrollBy : false, + scrollTo : false, + sessionStorage : false, + setInterval : false, + setTimeout : false, + SharedWorker : false, + status : false, + top : false, + Uint16Array : false, + Uint32Array : false, + Uint8Array : false, + WebSocket : false, + window : false, + Worker : false, + XMLHttpRequest : false, + XPathEvaluator : false, + XPathException : false, + XPathExpression : false, + XPathNamespace : false, + XPathNSResolver : false, + XPathResult : false + }, + + couch = { + "require" : false, + respond : false, + getRow : false, + emit : false, + send : false, + start : false, + sum : false, + log : false, + exports : false, + module : false + }, + + devel = { + alert : false, + confirm : false, + console : false, + Debug : false, + opera : false, + prompt : false + }, + + dojo = { + dojo : false, + dijit : false, + dojox : false, + define : false, + "require" : false + }, + + escapes = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '/' : '\\/', + '\\': '\\\\' + }, + + funct, // The current function + + functionicity = [ + 'closure', 'exception', 'global', 'label', + 'outer', 'unused', 'var' + ], + + functions, // All of the functions + + global, // The global scope + implied, // Implied globals + inblock, + indent, + jsonmode, + + jquery = { + '$' : false, + jQuery : false + }, + + lines, + lookahead, + member, + membersOnly, + + mootools = { + '$' : false, + '$$' : false, + Assets : false, + Browser : false, + Chain : false, + Class : false, + Color : false, + Cookie : false, + Core : false, + Document : false, + DomReady : false, + DOMReady : false, + Drag : false, + Element : false, + Elements : false, + Event : false, + Events : false, + Fx : false, + Group : false, + Hash : false, + HtmlTable : false, + Iframe : false, + IframeShim : false, + InputValidator : false, + instanceOf : false, + Keyboard : false, + Locale : false, + Mask : false, + MooTools : false, + Native : false, + Options : false, + OverText : false, + Request : false, + Scroller : false, + Slick : false, + Slider : false, + Sortables : false, + Spinner : false, + Swiff : false, + Tips : false, + Type : false, + typeOf : false, + URI : false, + Window : false + }, + + nexttoken, + + node = { + __filename : false, + __dirname : false, + Buffer : false, + console : false, + exports : false, + GLOBAL : false, + global : false, + module : false, + process : false, + require : false + }, + + noreach, + option, + predefined, // Global variables defined by option + prereg, + prevtoken, + + prototypejs = { + '$' : false, + '$$' : false, + '$A' : false, + '$F' : false, + '$H' : false, + '$R' : false, + '$break' : false, + '$continue' : false, + '$w' : false, + Abstract : false, + Ajax : false, + Class : false, + Enumerable : false, + Element : false, + Event : false, + Field : false, + Form : false, + Hash : false, + Insertion : false, + ObjectRange : false, + PeriodicalExecuter: false, + Position : false, + Prototype : false, + Selector : false, + Template : false, + Toggle : false, + Try : false, + Autocompleter : false, + Builder : false, + Control : false, + Draggable : false, + Draggables : false, + Droppables : false, + Effect : false, + Sortable : false, + SortableObserver : false, + Sound : false, + Scriptaculous : false + }, + + rhino = { + defineClass : false, + deserialize : false, + gc : false, + help : false, + load : false, + loadClass : false, + print : false, + quit : false, + readFile : false, + readUrl : false, + runCommand : false, + seal : false, + serialize : false, + spawn : false, + sync : false, + toint32 : false, + version : false + }, + + scope, // The current scope + src, + stack, + + // standard contains the global names that are provided by the + // ECMAScript standard. + standard = { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + 'eval' : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + JSON : false, + Math : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false + }, + + // widely adopted global names that are not part of ECMAScript standard + nonstandard = { + escape : false, + unescape : false + }, + + standard_member = { + E : true, + LN2 : true, + LN10 : true, + LOG2E : true, + LOG10E : true, + MAX_VALUE : true, + MIN_VALUE : true, + NEGATIVE_INFINITY : true, + PI : true, + POSITIVE_INFINITY : true, + SQRT1_2 : true, + SQRT2 : true + }, + + strict_mode, + syntax = {}, + tab, + token, + urls, + warnings, + + wsh = { + ActiveXObject : true, + Enumerator : true, + GetObject : true, + ScriptEngine : true, + ScriptEngineBuildVersion : true, + ScriptEngineMajorVersion : true, + ScriptEngineMinorVersion : true, + VBArray : true, + WSH : true, + WScript : true + }; + + // Regular expressions. Some of these are stupidly long. + var ax, cx, tx, nx, nxg, lx, ix, jx, ft; + (function () { + /*jshint maxlen:300 */ + + // unsafe comment or string + ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i; + + // unsafe characters that are silently deleted by one or more browsers + cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + + // token + tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/; + + // characters in strings that need escapement + nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + // star slash + lx = /\*\/|\/\*/; + + // identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; + + // javascript url + jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + + // catches /* falls through */ comments + ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/; + }()); + + function F() {} // Used by Object.create + + function is_own(object, name) { + +// The object.hasOwnProperty method fails when the property under consideration +// is named 'hasOwnProperty'. So we have to use this more convoluted form. + + return Object.prototype.hasOwnProperty.call(object, name); + } + +// Provide critical ES5 functions to ES3. + + if (typeof Array.isArray !== 'function') { + Array.isArray = function (o) { + return Object.prototype.toString.apply(o) === '[object Array]'; + }; + } + + if (typeof Object.create !== 'function') { + Object.create = function (o) { + F.prototype = o; + return new F(); + }; + } + + if (typeof Object.keys !== 'function') { + Object.keys = function (o) { + var a = [], k; + for (k in o) { + if (is_own(o, k)) { + a.push(k); + } + } + return a; + }; + } + +// Non standard methods + + if (typeof String.prototype.entityify !== 'function') { + String.prototype.entityify = function () { + return this + .replace(/&/g, '&') + .replace(//g, '>'); + }; + } + + if (typeof String.prototype.isAlpha !== 'function') { + String.prototype.isAlpha = function () { + return (this >= 'a' && this <= 'z\uffff') || + (this >= 'A' && this <= 'Z\uffff'); + }; + } + + if (typeof String.prototype.isDigit !== 'function') { + String.prototype.isDigit = function () { + return (this >= '0' && this <= '9'); + }; + } + + if (typeof String.prototype.supplant !== 'function') { + String.prototype.supplant = function (o) { + return this.replace(/\{([^{}]*)\}/g, function (a, b) { + var r = o[b]; + return typeof r === 'string' || typeof r === 'number' ? r : a; + }); + }; + } + + if (typeof String.prototype.name !== 'function') { + String.prototype.name = function () { + +// If the string looks like an identifier, then we can return it as is. +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can simply slap some quotes around it. +// Otherwise we must also replace the offending characters with safe +// sequences. + + if (ix.test(this)) { + return this; + } + if (nx.test(this)) { + return '"' + this.replace(nxg, function (a) { + var c = escapes[a]; + if (c) { + return c; + } + return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); + }) + '"'; + } + return '"' + this + '"'; + }; + } + + + function combine(t, o) { + var n; + for (n in o) { + if (is_own(o, n)) { + t[n] = o[n]; + } + } + } + + function assume() { + if (option.couch) + combine(predefined, couch); + + if (option.rhino) + combine(predefined, rhino); + + if (option.prototypejs) + combine(predefined, prototypejs); + + if (option.node) + combine(predefined, node); + + if (option.devel) + combine(predefined, devel); + + if (option.dojo) + combine(predefined, dojo); + + if (option.browser) + combine(predefined, browser); + + if (option.nonstandard) + combine(predefined, nonstandard); + + if (option.jquery) + combine(predefined, jquery); + + if (option.mootools) + combine(predefined, mootools); + + if (option.wsh) + combine(predefined, wsh); + + if (option.globalstrict && option.strict !== false) + option.strict = true; + } + + + // Produce an error warning. + function quit(message, line, chr) { + var percentage = Math.floor((line / lines.length) * 100); + + throw { + name: 'JSHintError', + line: line, + character: chr, + message: message + " (" + percentage + "% scanned)." + }; + } + + function warning(m, t, a, b, c, d) { + var ch, l, w; + t = t || nexttoken; + if (t.id === '(end)') { // `~ + t = token; + } + l = t.line || 0; + ch = t.from || 0; + w = { + id: '(error)', + raw: m, + evidence: lines[l - 1] || '', + line: l, + character: ch, + a: a, + b: b, + c: c, + d: d + }; + w.reason = m.supplant(w); + JSHINT.errors.push(w); + if (option.passfail) { + quit('Stopping. ', l, ch); + } + warnings += 1; + if (warnings >= option.maxerr) { + quit("Too many errors.", l, ch); + } + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning(m, { + line: l, + from: ch + }, a, b, c, d); + } + + function error(m, t, a, b, c, d) { + var w = warning(m, t, a, b, c, d); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error(m, { + line: l, + from: ch + }, a, b, c, d); + } + + + +// lexical analysis and token construction + + var lex = (function lex() { + var character, from, line, s; + +// Private lex methods + + function nextLine() { + var at, + tw; // trailing whitespace check + + if (line >= lines.length) + return false; + + character = 1; + s = lines[line]; + line += 1; + at = s.search(/ \t/); + + if (at >= 0) + warningAt("Mixed spaces and tabs.", line, at + 1); + + s = s.replace(/\t/g, tab); + at = s.search(cx); + + if (at >= 0) + warningAt("Unsafe character.", line, at); + + if (option.maxlen && option.maxlen < s.length) + warningAt("Line too long.", line, s.length); + + // Check for trailing whitespaces + tw = s.search(/\s+$/); + if (option.trailing && ~tw && !~s.search(/^\s+$/)) + warningAt("Trailing whitespace.", line, tw); + + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var i, t; + if (type === '(color)' || type === '(range)') { + t = {type: type}; + } else if (type === '(punctuator)' || + (type === '(identifier)' && is_own(syntax, value))) { + t = syntax[value] || syntax['(error)']; + } else { + t = syntax[type]; + } + t = Object.create(t); + if (type === '(string)' || type === '(range)') { + if (!option.scripturl && jx.test(value)) { + warningAt("Script URL.", line, from); + } + } + if (type === '(identifier)') { + t.identifier = true; + if (value === '__proto__' && !option.proto) { + warningAt("The '{a}' property is deprecated.", + line, from, value); + } else if (value === '__iterator__' && !option.iterator) { + warningAt("'{a}' is only available in JavaScript 1.7.", + line, from, value); + } else if (option.nomen && (value.charAt(0) === '_' || + value.charAt(value.length - 1) === '_')) { + if (!option.node || token.id == '.' || + (value != '__dirname' && value != '__filename')) { + warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", value); + } + } + } + t.value = value; + t.line = line; + t.character = character; + t.from = from; + i = t.id; + if (i !== '(endline)') { + prereg = i && + (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) || + i === 'return'); + } + return t; + } + + // Public lex methods + return { + init: function (source) { + if (typeof source === 'string') { + lines = source + .replace(/\r\n/g, '\n') + .replace(/\r/g, '\n') + .split('\n'); + } else { + lines = source; + } + + // If the first line is a shebang (#!), make it a blank and move on. + // Shebangs are used by Node scripts. + if (lines[0] && lines[0].substr(0, 2) == '#!') + lines[0] = ''; + + line = 0; + nextLine(); + from = 1; + }, + + range: function (begin, end) { + var c, value = ''; + from = character; + if (s.charAt(0) !== begin) { + errorAt("Expected '{a}' and instead saw '{b}'.", + line, character, begin, s.charAt(0)); + } + for (;;) { + s = s.slice(1); + character += 1; + c = s.charAt(0); + switch (c) { + case '': + errorAt("Missing '{a}'.", line, character, c); + break; + case end: + s = s.slice(1); + character += 1; + return it('(range)', value); + case '\\': + warningAt("Unexpected '{a}'.", line, character, c); + } + value += c; + } + + }, + + + // token -- this is called by advance to get the next token + token: function () { + var b, c, captures, d, depth, high, i, l, low, q, t; + + function match(x) { + var r = x.exec(s), r1; + if (r) { + l = r[0].length; + r1 = r[1]; + c = r1.charAt(0); + s = s.substr(l); + from = character + l - r1.length; + character += l; + return r1; + } + } + + function string(x) { + var c, j, r = ''; + + if (jsonmode && x !== '"') { + warningAt("Strings must use doublequote.", + line, character); + } + + function esc(n) { + var i = parseInt(s.substr(j + 1, n), 16); + j += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warningAt("Unnecessary escapement.", line, character); + } + character += n; + c = String.fromCharCode(i); + } + j = 0; + for (;;) { + while (j >= s.length) { + j = 0; + if (!nextLine()) { + errorAt("Unclosed string.", line, from); + } + } + c = s.charAt(j); + if (c === x) { + character += 1; + s = s.substr(j + 1); + return it('(string)', r, x); + } + if (c < ' ') { + if (c === '\n' || c === '\r') { + break; + } + warningAt("Control character in string: {a}.", + line, character + j, s.slice(0, j)); + } else if (c === '\\') { + j += 1; + character += 1; + c = s.charAt(j); + switch (c) { + case '\\': + case '"': + case '/': + break; + case '\'': + if (jsonmode) { + warningAt("Avoid \\'.", line, character); + } + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'u': + esc(4); + break; + case 'v': + if (jsonmode) { + warningAt("Avoid \\v.", line, character); + } + c = '\v'; + break; + case 'x': + if (jsonmode) { + warningAt("Avoid \\x-.", line, character); + } + esc(2); + break; + default: + warningAt("Bad escapement.", line, character); + } + } + r += c; + character += 1; + j += 1; + } + } + + for (;;) { + if (!s) { + return it(nextLine() ? '(endline)' : '(end)', ''); + } + t = match(tx); + if (!t) { + t = ''; + c = ''; + while (s && s < '!') { + s = s.substr(1); + } + if (s) { + errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1)); + } + } else { + + // identifier + + if (c.isAlpha() || c === '_' || c === '$') { + return it('(identifier)', t); + } + + // number + + if (c.isDigit()) { + if (!isFinite(Number(t))) { + warningAt("Bad number '{a}'.", + line, character, t); + } + if (s.substr(0, 1).isAlpha()) { + warningAt("Missing space after '{a}'.", + line, character, t); + } + if (c === '0') { + d = t.substr(1, 1); + if (d.isDigit()) { + if (token.id !== '.') { + warningAt("Don't use extra leading zeros '{a}'.", + line, character, t); + } + } else if (jsonmode && (d === 'x' || d === 'X')) { + warningAt("Avoid 0x-. '{a}'.", + line, character, t); + } + } + if (t.substr(t.length - 1) === '.') { + warningAt( +"A trailing decimal point can be confused with a dot '{a}'.", line, character, t); + } + return it('(number)', t); + } + switch (t) { + + // string + + case '"': + case "'": + return string(t); + + // // comment + + case '//': + if (src) { + warningAt("Unexpected comment.", line, character); + } + s = ''; + token.comment = true; + break; + + // /* comment + + case '/*': + if (src) { + warningAt("Unexpected comment.", line, character); + } + for (;;) { + i = s.search(lx); + if (i >= 0) { + break; + } + if (!nextLine()) { + errorAt("Unclosed comment.", line, character); + } + } + character += i + 2; + if (s.substr(i, 1) === '/') { + errorAt("Nested comment.", line, character); + } + s = s.substr(i + 2); + token.comment = true; + break; + + // /*members /*jshint /*global + + case '/*members': + case '/*member': + case '/*jshint': + case '/*jslint': + case '/*global': + case '*/': + return { + value: t, + type: 'special', + line: line, + character: character, + from: from + }; + + case '': + break; + // / + case '/': + if (token.id === '/=') { + errorAt( +"A regular expression literal can be confused with '/='.", line, from); + } + if (prereg) { + depth = 0; + captures = 0; + l = 0; + for (;;) { + b = true; + c = s.charAt(l); + l += 1; + switch (c) { + case '': + errorAt("Unclosed regular expression.", + line, from); + return; + case '/': + if (depth > 0) { + warningAt("Unescaped '{a}'.", + line, from + l, '/'); + } + c = s.substr(0, l - 1); + q = { + g: true, + i: true, + m: true + }; + while (q[s.charAt(l)] === true) { + q[s.charAt(l)] = false; + l += 1; + } + character += l; + s = s.substr(l); + q = s.charAt(0); + if (q === '/' || q === '*') { + errorAt("Confusing regular expression.", + line, from); + } + return it('(regexp)', c); + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + break; + case '(': + depth += 1; + b = false; + if (s.charAt(l) === '?') { + l += 1; + switch (s.charAt(l)) { + case ':': + case '=': + case '!': + l += 1; + break; + default: + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l)); + } + } else { + captures += 1; + } + break; + case '|': + b = false; + break; + case ')': + if (depth === 0) { + warningAt("Unescaped '{a}'.", + line, from + l, ')'); + } else { + depth -= 1; + } + break; + case ' ': + q = 1; + while (s.charAt(l) === ' ') { + l += 1; + q += 1; + } + if (q > 1) { + warningAt( +"Spaces are hard to count. Use {{a}}.", line, from + l, q); + } + break; + case '[': + c = s.charAt(l); + if (c === '^') { + l += 1; + if (option.regexp) { + warningAt("Insecure '{a}'.", + line, from + l, c); + } else if (s.charAt(l) === ']') { + errorAt("Unescaped '{a}'.", + line, from + l, '^'); + } + } + q = false; + if (c === ']') { + warningAt("Empty class.", line, + from + l - 1); + q = true; + } +klass: do { + c = s.charAt(l); + l += 1; + switch (c) { + case '[': + case '^': + warningAt("Unescaped '{a}'.", + line, from + l, c); + q = true; + break; + case '-': + if (q) { + q = false; + } else { + warningAt("Unescaped '{a}'.", + line, from + l, '-'); + q = true; + } + break; + case ']': + if (!q && !option.regexdash) { + warningAt("Unescaped '{a}'.", + line, from + l - 1, '-'); + } + break klass; + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + q = true; + break; + case '/': + warningAt("Unescaped '{a}'.", + line, from + l - 1, '/'); + q = true; + break; + case '<': + q = true; + break; + default: + q = true; + } + } while (c); + break; + case '.': + if (option.regexp) { + warningAt("Insecure '{a}'.", line, + from + l, c); + } + break; + case ']': + case '?': + case '{': + case '}': + case '+': + case '*': + warningAt("Unescaped '{a}'.", line, + from + l, c); + } + if (b) { + switch (s.charAt(l)) { + case '?': + case '+': + case '*': + l += 1; + if (s.charAt(l) === '?') { + l += 1; + } + break; + case '{': + l += 1; + c = s.charAt(l); + if (c < '0' || c > '9') { + warningAt( +"Expected a number and instead saw '{a}'.", line, from + l, c); + } + l += 1; + low = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + low = +c + (low * 10); + } + high = low; + if (c === ',') { + l += 1; + high = Infinity; + c = s.charAt(l); + if (c >= '0' && c <= '9') { + l += 1; + high = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + high = +c + (high * 10); + } + } + } + if (s.charAt(l) !== '}') { + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c); + } else { + l += 1; + } + if (s.charAt(l) === '?') { + l += 1; + } + if (low > high) { + warningAt( +"'{a}' should not be greater than '{b}'.", line, from + l, low, high); + } + } + } + } + c = s.substr(0, l - 1); + character += l; + s = s.substr(l); + return it('(regexp)', c); + } + return it('(punctuator)', t); + + // punctuator + + case '#': + return it('(punctuator)', t); + default: + return it('(punctuator)', t); + } + } + } + } + }; + }()); + + + function addlabel(t, type) { + + if (t === 'hasOwnProperty') { + warning("'hasOwnProperty' is a really bad name."); + } + +// Define t in the current function in the current scope. + + if (is_own(funct, t) && !funct['(global)']) { + if (funct[t] === true) { + if (option.latedef) + warning("'{a}' was used before it was defined.", nexttoken, t); + } else { + if (!option.shadow) + warning("'{a}' is already defined.", nexttoken, t); + } + } + + funct[t] = type; + if (funct['(global)']) { + global[t] = funct; + if (is_own(implied, t)) { + if (option.latedef) + warning("'{a}' was used before it was defined.", nexttoken, t); + delete implied[t]; + } + } else { + scope[t] = funct; + } + } + + + function doOption() { + var b, obj, filter, o = nexttoken.value, t, v; + switch (o) { + case '*/': + error("Unbegun comment."); + break; + case '/*members': + case '/*member': + o = '/*members'; + if (!membersOnly) { + membersOnly = {}; + } + obj = membersOnly; + break; + case '/*jshint': + case '/*jslint': + obj = option; + filter = boolOptions; + break; + case '/*global': + obj = predefined; + break; + default: + error("What?"); + } + t = lex.token(); +loop: for (;;) { + for (;;) { + if (t.type === 'special' && t.value === '*/') { + break loop; + } + if (t.id !== '(endline)' && t.id !== ',') { + break; + } + t = lex.token(); + } + if (t.type !== '(string)' && t.type !== '(identifier)' && + o !== '/*members') { + error("Bad option.", t); + } + v = lex.token(); + if (v.id === ':') { + v = lex.token(); + if (obj === membersOnly) { + error("Expected '{a}' and instead saw '{b}'.", + t, '*/', ':'); + } + if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.white = true; + obj.indent = b; + } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxerr = b; + } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxlen = b; + } else if (t.value == 'validthis') { + if (funct['(global)']) { + error("Option 'validthis' can't be used in a global scope."); + } else { + if (v.value === 'true' || v.value === 'false') + obj[t.value] = v.value === 'true'; + else + error("Bad option value.", v); + } + } else if (v.value === 'true') { + obj[t.value] = true; + } else if (v.value === 'false') { + obj[t.value] = false; + } else { + error("Bad option value.", v); + } + t = lex.token(); + } else { + if (o === '/*jshint' || o === '/*jslint') { + error("Missing option value.", t); + } + obj[t.value] = false; + t = v; + } + } + if (filter) { + assume(); + } + } + + +// We need a peek function. If it has an argument, it peeks that much farther +// ahead. It is used to distinguish +// for ( var i in ... +// from +// for ( var i = ... + + function peek(p) { + var i = p || 0, j = 0, t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + + +// Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (token.id) { + case '(number)': + if (nexttoken.id === '.') { + warning("A dot following a number can be confused with a decimal point.", token); + } + break; + case '-': + if (nexttoken.id === '-' || nexttoken.id === '--') { + warning("Confusing minusses."); + } + break; + case '+': + if (nexttoken.id === '+' || nexttoken.id === '++') { + warning("Confusing plusses."); + } + break; + } + + if (token.type === '(string)' || token.identifier) { + anonname = token.value; + } + + if (id && nexttoken.id !== id) { + if (t) { + if (nexttoken.id === '(end)') { + warning("Unmatched '{a}'.", t, t.id); + } else { + warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + nexttoken, id, t.id, t.line, nexttoken.value); + } + } else if (nexttoken.type !== '(identifier)' || + nexttoken.value !== id) { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, id, nexttoken.value); + } + } + + prevtoken = token; + token = nexttoken; + for (;;) { + nexttoken = lookahead.shift() || lex.token(); + if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { + return; + } + if (nexttoken.type === 'special') { + doOption(); + } else { + if (nexttoken.id !== '(endline)') { + break; + } + } + } + } + + +// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it +// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is +// like .nud except that it is only used on the first token of a statement. +// Having .fud makes it much easier to define statement-oriented languages like +// JavaScript. I retained Pratt's nomenclature. + +// .nud Null denotation +// .fud First null denotation +// .led Left denotation +// lbp Left binding power +// rbp Right binding power + +// They are elements of the parsing method called Top Down Operator Precedence. + + function expression(rbp, initial) { + var left, isArray = false; + + if (nexttoken.id === '(end)') + error("Unexpected early end of program.", token); + + advance(); + if (initial) { + anonname = 'anonymous'; + funct['(verb)'] = token.value; + } + if (initial === true && token.fud) { + left = token.fud(); + } else { + if (token.nud) { + left = token.nud(); + } else { + if (nexttoken.type === '(number)' && token.id === '.') { + warning("A leading decimal point can be confused with a dot: '.{a}'.", + token, nexttoken.value); + advance(); + return token; + } else { + error("Expected an identifier and instead saw '{a}'.", + token, token.id); + } + } + while (rbp < nexttoken.lbp) { + isArray = token.value == 'Array'; + advance(); + if (isArray && token.id == '(' && nexttoken.id == ')') + warning("Use the array literal notation [].", token); + if (token.led) { + left = token.led(left); + } else { + error("Expected an operator and instead saw '{a}'.", + token, token.id); + } + } + } + return left; + } + + +// Functions for conformance of style. + + function adjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white) { + if (left.character !== right.from && left.line === right.line) { + warning("Unexpected space after '{a}'.", right, left.value); + } + } + } + + function nobreak(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && (left.character !== right.from || left.line !== right.line)) { + warning("Unexpected space before '{a}'.", right, right.value); + } + } + + function nospace(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && !left.comment) { + if (left.line === right.line) { + adjacent(left, right); + } + } + } + + function nonadjacent(left, right) { + if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.line === right.line && left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function nobreaknonadjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (!option.laxbreak && left.line !== right.line) { + warning("Bad line breaking before '{a}'.", right, right.id); + } else if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function indentation(bias) { + var i; + if (option.white && nexttoken.id !== '(end)') { + i = indent + (bias || 0); + if (nexttoken.from !== i) { + warning( +"Expected '{a}' to have an indentation at {b} instead at {c}.", + nexttoken, nexttoken.value, i, nexttoken.from); + } + } + } + + function nolinebreak(t) { + t = t || token; + if (t.line !== nexttoken.line) { + warning("Line breaking error '{a}'.", t, t.value); + } + } + + + function comma() { + if (token.line !== nexttoken.line) { + if (!option.laxbreak) { + warning("Bad line breaking before '{a}'.", token, nexttoken.id); + } + } else if (token.character !== nexttoken.from && option.white) { + warning("Unexpected space after '{a}'.", nexttoken, token.value); + } + advance(','); + nonadjacent(token, nexttoken); + } + + +// Functional constructors for making the symbols that will be inherited by +// tokens. + + function symbol(s, p) { + var x = syntax[s]; + if (!x || typeof x !== 'object') { + syntax[s] = x = { + id: s, + lbp: p, + value: s + }; + } + return x; + } + + + function delim(s) { + return symbol(s, 0); + } + + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + + function blockstmt(s, f) { + var x = stmt(s, f); + x.block = true; + return x; + } + + + function reserveName(x) { + var c = x.id.charAt(0); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + x.identifier = x.reserved = true; + } + return x; + } + + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + x.nud = (typeof f === 'function') ? f : function () { + this.right = expression(150); + this.arity = 'unary'; + if (this.id === '++' || this.id === '--') { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!this.right.identifier || this.right.reserved) && + this.right.id !== '.' && this.right.id !== '[') { + warning("Bad operand.", this); + } + } + return this; + }; + return x; + } + + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + + function reserve(s, f) { + var x = type(s, f); + x.identifier = x.reserved = true; + return x; + } + + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === 'function') { + v(this); + } + return this; + }); + } + + + function infix(s, f, p, w) { + var x = symbol(s, p); + reserveName(x); + x.led = function (left) { + if (!w) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + } + if (typeof f === 'function') { + return f(left, this); + } else { + this.left = left; + this.right = expression(p); + return this; + } + }; + return x; + } + + + function relation(s, f) { + var x = symbol(s, 100); + x.led = function (left) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + var right = expression(100); + if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) { + warning("Use the isNaN function to compare with NaN.", this); + } else if (f) { + f.apply(this, [left, right]); + } + if (left.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + if (right.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + this.left = left; + this.right = right; + return this; + }; + return x; + } + + + function isPoorRelation(node) { + return node && + ((node.type === '(number)' && +node.value === 0) || + (node.type === '(string)' && node.value === '') || + (node.type === 'null' && !option.eqnull) || + node.type === 'true' || + node.type === 'false' || + node.type === 'undefined'); + } + + + function assignop(s, f) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + var l; + that.left = left; + if (predefined[left.value] === false && + scope[left.value]['(global)'] === true) { + warning("Read only.", left); + } else if (left['function']) { + warning("'{a}' is a function.", left, left.value); + } + if (left) { + if (left.id === '.' || left.id === '[') { + if (!left.left || left.left.value === 'arguments') { + warning('Bad assignment.', that); + } + that.right = expression(19); + return that; + } else if (left.identifier && !left.reserved) { + if (funct[left.value] === 'exception') { + warning("Do not assign to the exception parameter.", left); + } + that.right = expression(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment and instead saw a function invocation.", + token); + } + } + error("Bad assignment.", that); + }, 20); + } + + + function bitwise(s, f, p) { + var x = symbol(s, p); + reserveName(x); + x.led = (typeof f === 'function') ? f : function (left) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", this, this.id); + } + this.left = left; + this.right = expression(p); + return this; + }; + return x; + } + + + function bitwiseassignop(s) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", that, that.id); + } + nonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + if (left) { + if (left.id === '.' || left.id === '[' || + (left.identifier && !left.reserved)) { + expression(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment, and instead saw a function invocation.", + token); + } + return that; + } + error("Bad assignment.", that); + }, 20); + } + + + function suffix(s, f) { + var x = symbol(s, 150); + x.led = function (left) { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!left.identifier || left.reserved) && + left.id !== '.' && left.id !== '[') { + warning("Bad operand.", this); + } + this.left = left; + return this; + }; + return x; + } + + + // fnparam means that this identifier is being defined as a function + // argument (see identifier()) + function optionalidentifier(fnparam) { + if (nexttoken.identifier) { + advance(); + if (token.reserved && !option.es5) { + // `undefined` as a function param is a common pattern to protect + // against the case when somebody does `undefined = true` and + // help with minification. More info: https://gist.github.com/315916 + if (!fnparam || token.value != 'undefined') { + warning("Expected an identifier and instead saw '{a}' (a reserved word).", + token, token.id); + } + } + return token.value; + } + } + + // fnparam means that this identifier is being defined as a function + // argument + function identifier(fnparam) { + var i = optionalidentifier(fnparam); + if (i) { + return i; + } + if (token.id === 'function' && nexttoken.id === '(') { + warning("Missing name in function declaration."); + } else { + error("Expected an identifier and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + } + + + function reachable(s) { + var i = 0, t; + if (nexttoken.id !== ';' || noreach) { + return; + } + for (;;) { + t = peek(i); + if (t.reach) { + return; + } + if (t.id !== '(endline)') { + if (t.id === 'function') { + warning( +"Inner functions should be listed at the top of the outer function.", t); + break; + } + warning("Unreachable '{a}' after '{b}'.", t, t.value, s); + break; + } + i += 1; + } + } + + + function statement(noindent) { + var i = indent, r, s = scope, t = nexttoken; + +// We don't like the empty statement. + + if (t.id === ';') { + warning("Unnecessary semicolon.", t); + advance(';'); + return; + } + +// Is this a labelled statement? + + if (t.identifier && !t.reserved && peek().id === ':') { + advance(); + advance(':'); + scope = Object.create(s); + addlabel(t.value, 'label'); + if (!nexttoken.labelled) { + warning("Label '{a}' on {b} statement.", + nexttoken, t.value, nexttoken.value); + } + if (jx.test(t.value + ':')) { + warning("Label '{a}' looks like a javascript url.", + t, t.value); + } + nexttoken.label = t.value; + t = nexttoken; + } + +// Parse the statement. + + if (!noindent) { + indentation(); + } + r = expression(0, true); + + // Look for the final semicolon. + if (!t.block) { + if (!option.expr && (!r || !r.exps)) { + warning("Expected an assignment or function call and instead saw an expression.", + token); + } else if (option.nonew && r.id === '(' && r.left.id === 'new') { + warning("Do not use 'new' for side effects."); + } + + if (nexttoken.id !== ';') { + if (!option.asi) { + // If this is the last statement in a block that ends on + // the same line *and* option lastsemic is on, ignore the warning. + // Otherwise, complain about missing semicolon. + if (!option.lastsemic || nexttoken.id != '}' || + nexttoken.line != token.line) { + warningAt("Missing semicolon.", token.line, token.from + + token.value.length); + } + } + if (!option.asi && !(option.lastsemic && nexttoken.id == '}' && + nexttoken.line == token.line)) { + + } + } else { + adjacent(token, nexttoken); + advance(';'); + nonadjacent(token, nexttoken); + } + } + +// Restore the indentation. + + indent = i; + scope = s; + return r; + } + + + function use_strict() { + if (nexttoken.value === 'use strict') { + if (strict_mode) { + warning("Unnecessary \"use strict\"."); + } + advance(); + advance(';'); + strict_mode = true; + option.newcap = true; + option.undef = true; + return true; + } else { + return false; + } + } + + + function statements(begin) { + var a = [], f, p; + + while (!nexttoken.reach && nexttoken.id !== '(end)') { + if (nexttoken.id === ';') { + warning("Unnecessary semicolon."); + advance(';'); + } else { + a.push(statement()); + } + } + return a; + } + + + /* + * Parses a single block. A block is a sequence of statements wrapped in + * braces. + * + * ordinary - true for everything but function bodies and try blocks. + * stmt - true if block can be a single statement (e.g. in if/for/while). + */ + function block(ordinary, stmt) { + var a, + b = inblock, + old_indent = indent, + m = strict_mode, + s = scope, + t; + + inblock = ordinary; + scope = Object.create(scope); + nonadjacent(token, nexttoken); + t = nexttoken; + + if (nexttoken.id === '{') { + advance('{'); + if (nexttoken.id !== '}' || token.line !== nexttoken.line) { + indent += option.indent; + while (!ordinary && nexttoken.from > indent) { + indent += option.indent; + } + if (!ordinary && !use_strict() && !m && option.strict && + funct['(context)']['(global)']) { + warning("Missing \"use strict\" statement."); + } + a = statements(); + strict_mode = m; + indent -= option.indent; + indentation(); + } + advance('}', t); + indent = old_indent; + } else if (!ordinary) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + } else { + if (!stmt || option.curly) + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + + noreach = true; + a = [statement()]; + noreach = false; + } + funct['(verb)'] = null; + scope = s; + inblock = b; + if (ordinary && option.noempty && (!a || a.length === 0)) { + warning("Empty block."); + } + return a; + } + + + function countMember(m) { + if (membersOnly && typeof membersOnly[m] !== 'boolean') { + warning("Unexpected /*member '{a}'.", token, m); + } + if (typeof member[m] === 'number') { + member[m] += 1; + } else { + member[m] = 1; + } + } + + + function note_implied(token) { + var name = token.value, line = token.line, a = implied[name]; + if (typeof a === 'function') { + a = false; + } + if (!a) { + a = [line]; + implied[name] = a; + } else if (a[a.length - 1] !== line) { + a.push(line); + } + } + + // Build the syntax table by declaring the syntactic elements of the language. + + type('(number)', function () { + return this; + }); + + type('(string)', function () { + return this; + }); + + syntax['(identifier)'] = { + type: '(identifier)', + lbp: 0, + identifier: true, + nud: function () { + var v = this.value, + s = scope[v], + f; + + if (typeof s === 'function') { + // Protection against accidental inheritance. + s = undefined; + } else if (typeof s === 'boolean') { + f = funct; + funct = functions[0]; + addlabel(v, 'var'); + s = funct; + funct = f; + } + + // The name is in scope and defined in the current function. + if (funct === s) { + // Change 'unused' to 'var', and reject labels. + switch (funct[v]) { + case 'unused': + funct[v] = 'var'; + break; + case 'unction': + funct[v] = 'function'; + this['function'] = true; + break; + case 'function': + this['function'] = true; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + } + } else if (funct['(global)']) { + // The name is not defined in the function. If we are in the global + // scope, then we have an undefined variable. + // + // Operators typeof and delete do not raise runtime errors even if + // the base object of a reference is null so no need to display warning + // if we're inside of typeof or delete. + if (anonname != 'typeof' && anonname != 'delete' && + option.undef && typeof predefined[v] !== 'boolean') { + warning("'{a}' is not defined.", token, v); + } + note_implied(token); + } else { + // If the name is already defined in the current + // function, but not as outer, then there is a scope error. + + switch (funct[v]) { + case 'closure': + case 'function': + case 'var': + case 'unused': + warning("'{a}' used out of scope.", token, v); + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + case 'outer': + case 'global': + break; + default: + // If the name is defined in an outer function, make an outer entry, + // and if it was unused, make it var. + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning("'{a}' is not allowed.", token, v); + note_implied(token); + } else if (typeof s !== 'object') { + // Operators typeof and delete do not raise runtime errors even + // if the base object of a reference is null so no need to + // display warning if we're inside of typeof or delete. + if (anonname != 'typeof' && anonname != 'delete' && option.undef) { + warning("'{a}' is not defined.", token, v); + } else { + funct[v] = true; + } + note_implied(token); + } else { + switch (s[v]) { + case 'function': + case 'unction': + this['function'] = true; + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'var': + case 'unused': + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'closure': + case 'parameter': + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + } + } + } + } + return this; + }, + led: function () { + error("Expected an operator and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + }; + + type('(regexp)', function () { + return this; + }); + + +// ECMAScript parser + + delim('(endline)'); + delim('(begin)'); + delim('(end)').reach = true; + delim(''); + delim('(error)').reach = true; + delim('}').reach = true; + delim(')'); + delim(']'); + delim('"').reach = true; + delim("'").reach = true; + delim(';'); + delim(':').reach = true; + delim(','); + delim('#'); + delim('@'); + reserve('else'); + reserve('case').reach = true; + reserve('catch'); + reserve('default').reach = true; + reserve('finally'); + reservevar('arguments', function (x) { + if (strict_mode && funct['(global)']) { + warning("Strict violation.", x); + } + }); + reservevar('eval'); + reservevar('false'); + reservevar('Infinity'); + reservevar('NaN'); + reservevar('null'); + reservevar('this', function (x) { + if (strict_mode && !option.validthis && ((funct['(statement)'] && + funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { + warning("Possible strict violation.", x); + } + }); + reservevar('true'); + reservevar('undefined'); + assignop('=', 'assign', 20); + assignop('+=', 'assignadd', 20); + assignop('-=', 'assignsub', 20); + assignop('*=', 'assignmult', 20); + assignop('/=', 'assigndiv', 20).nud = function () { + error("A regular expression literal can be confused with '/='."); + }; + assignop('%=', 'assignmod', 20); + bitwiseassignop('&=', 'assignbitand', 20); + bitwiseassignop('|=', 'assignbitor', 20); + bitwiseassignop('^=', 'assignbitxor', 20); + bitwiseassignop('<<=', 'assignshiftleft', 20); + bitwiseassignop('>>=', 'assignshiftright', 20); + bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20); + infix('?', function (left, that) { + that.left = left; + that.right = expression(10); + advance(':'); + that['else'] = expression(10); + return that; + }, 30); + + infix('||', 'or', 40); + infix('&&', 'and', 50); + bitwise('|', 'bitor', 70); + bitwise('^', 'bitxor', 80); + bitwise('&', 'bitand', 90); + relation('==', function (left, right) { + var eqnull = option.eqnull && (left.value == 'null' || right.value == 'null'); + + if (!eqnull && option.eqeqeq) + warning("Expected '{a}' and instead saw '{b}'.", this, '===', '=='); + else if (isPoorRelation(left)) + warning("Use '{a}' to compare with '{b}'.", this, '===', left.value); + else if (isPoorRelation(right)) + warning("Use '{a}' to compare with '{b}'.", this, '===', right.value); + + return this; + }); + relation('==='); + relation('!=', function (left, right) { + var eqnull = option.eqnull && + (left.value == 'null' || right.value == 'null'); + + if (!eqnull && option.eqeqeq) { + warning("Expected '{a}' and instead saw '{b}'.", + this, '!==', '!='); + } else if (isPoorRelation(left)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', left.value); + } else if (isPoorRelation(right)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', right.value); + } + return this; + }); + relation('!=='); + relation('<'); + relation('>'); + relation('<='); + relation('>='); + bitwise('<<', 'shiftleft', 120); + bitwise('>>', 'shiftright', 120); + bitwise('>>>', 'shiftrightunsigned', 120); + infix('in', 'in', 120); + infix('instanceof', 'instanceof', 120); + infix('+', function (left, that) { + var right = expression(130); + if (left && right && left.id === '(string)' && right.id === '(string)') { + left.value += right.value; + left.character = right.character; + if (!option.scripturl && jx.test(left.value)) { + warning("JavaScript URL.", left); + } + return left; + } + that.left = left; + that.right = right; + return that; + }, 130); + prefix('+', 'num'); + prefix('+++', function () { + warning("Confusing pluses."); + this.right = expression(150); + this.arity = 'unary'; + return this; + }); + infix('+++', function (left) { + warning("Confusing pluses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix('-', 'sub', 130); + prefix('-', 'neg'); + prefix('---', function () { + warning("Confusing minuses."); + this.right = expression(150); + this.arity = 'unary'; + return this; + }); + infix('---', function (left) { + warning("Confusing minuses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix('*', 'mult', 140); + infix('/', 'div', 140); + infix('%', 'mod', 140); + + suffix('++', 'postinc'); + prefix('++', 'preinc'); + syntax['++'].exps = true; + + suffix('--', 'postdec'); + prefix('--', 'predec'); + syntax['--'].exps = true; + prefix('delete', function () { + var p = expression(0); + if (!p || (p.id !== '.' && p.id !== '[')) { + warning("Variables should not be deleted."); + } + this.first = p; + return this; + }).exps = true; + + prefix('~', function () { + if (option.bitwise) { + warning("Unexpected '{a}'.", this, '~'); + } + expression(150); + return this; + }); + + prefix('!', function () { + this.right = expression(150); + this.arity = 'unary'; + if (bang[this.right.id] === true) { + warning("Confusing use of '{a}'.", this, '!'); + } + return this; + }); + prefix('typeof', 'typeof'); + prefix('new', function () { + var c = expression(155), i; + if (c && c.id !== 'function') { + if (c.identifier) { + c['new'] = true; + switch (c.value) { + case 'Object': + warning("Use the object literal notation {}.", token); + break; + case 'Number': + case 'String': + case 'Boolean': + case 'Math': + case 'JSON': + warning("Do not use {a} as a constructor.", token, c.value); + break; + case 'Function': + if (!option.evil) { + warning("The Function constructor is eval."); + } + break; + case 'Date': + case 'RegExp': + break; + default: + if (c.id !== 'function') { + i = c.value.substr(0, 1); + if (option.newcap && (i < 'A' || i > 'Z')) { + warning("A constructor name should start with "+ + "an uppercase letter.", token); + } + } + } + } else { + if (c.id !== '.' && c.id !== '[' && c.id !== '(') { + warning("Bad constructor.", token); + } + } + } else { + if (!option.supernew) + warning("Weird construction. Delete 'new'.", this); + } + adjacent(token, nexttoken); + if (nexttoken.id !== '(' && !option.supernew) { + warning("Missing '()' invoking a constructor."); + } + this.first = c; + return this; + }); + syntax['new'].exps = true; + + prefix('void').exps = true; + + infix('.', function (left, that) { + adjacent(prevtoken, token); + nobreak(); + var m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + that.left = left; + that.right = m; + if (left && left.value === 'arguments' && (m === 'callee' || m === 'caller')) { + if (option.noarg) + warning("Avoid arguments.{a}.", left, m); + else if (strict_mode) + error('Strict violation.'); + } else if (!option.evil && left && left.value === 'document' && + (m === 'write' || m === 'writeln')) { + warning("document.write can be a form of eval.", left); + } + if (!option.evil && (m === 'eval' || m === 'execScript')) { + warning('eval is evil.'); + } + return that; + }, 160, true); + + infix('(', function (left, that) { + if (prevtoken.id !== '}' && prevtoken.id !== ')') { + nobreak(prevtoken, token); + } + nospace(); + if (option.immed && !left.immed && left.id === 'function') { + warning("Wrap an immediate function invocation in parentheses " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself."); + } + var n = 0, + p = []; + if (left) { + if (left.type === '(identifier)') { + if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if (left.value !== 'Number' && left.value !== 'String' && + left.value !== 'Boolean' && + left.value !== 'Date') { + if (left.value === 'Math') { + warning("Math is not a function.", left); + } else if (option.newcap) { + warning( +"Missing 'new' prefix when invoking a constructor.", left); + } + } + } + } + } + if (nexttoken.id !== ')') { + for (;;) { + p[p.length] = expression(10); + n += 1; + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')'); + nospace(prevtoken, token); + if (typeof left === 'object') { + if (left.value === 'parseInt' && n === 1) { + warning("Missing radix parameter.", left); + } + if (!option.evil) { + if (left.value === 'eval' || left.value === 'Function' || + left.value === 'execScript') { + warning("eval is evil.", left); + } else if (p[0] && p[0].id === '(string)' && + (left.value === 'setTimeout' || + left.value === 'setInterval')) { + warning( + "Implied eval is evil. Pass a function instead of a string.", left); + } + } + if (!left.identifier && left.id !== '.' && left.id !== '[' && + left.id !== '(' && left.id !== '&&' && left.id !== '||' && + left.id !== '?') { + warning("Bad invocation.", left); + } + } + that.left = left; + return that; + }, 155, true).exps = true; + + prefix('(', function () { + nospace(); + if (nexttoken.id === 'function') { + nexttoken.immed = true; + } + var v = expression(0); + advance(')', this); + nospace(prevtoken, token); + if (option.immed && v.id === 'function') { + if (nexttoken.id === '(') { + warning( +"Move the invocation into the parens that contain the function.", nexttoken); + } else { + warning( +"Do not wrap function literals in parens unless they are to be immediately invoked.", + this); + } + } + return v; + }); + + infix('[', function (left, that) { + nobreak(prevtoken, token); + nospace(); + var e = expression(0), s; + if (e && e.type === '(string)') { + if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) { + warning("eval is evil.", that); + } + countMember(e.value); + if (!option.sub && ix.test(e.value)) { + s = syntax[e.value]; + if (!s || !s.reserved) { + warning("['{a}'] is better written in dot notation.", + e, e.value); + } + } + } + advance(']', that); + nospace(prevtoken, token); + that.left = left; + that.right = e; + return that; + }, 160, true); + + prefix('[', function () { + var b = token.line !== nexttoken.line; + this.first = []; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + while (nexttoken.id !== '(end)') { + while (nexttoken.id === ',') { + warning("Extra comma."); + advance(','); + } + if (nexttoken.id === ']') { + break; + } + if (b && token.line !== nexttoken.line) { + indentation(); + } + this.first.push(expression(10)); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ']' && !option.es5) { + warning("Extra comma.", token); + break; + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance(']', this); + return this; + }, 160); + + + function property_name() { + var id = optionalidentifier(true); + if (!id) { + if (nexttoken.id === '(string)') { + id = nexttoken.value; + advance(); + } else if (nexttoken.id === '(number)') { + id = nexttoken.value.toString(); + advance(); + } + } + return id; + } + + + function functionparams() { + var i, t = nexttoken, p = []; + advance('('); + nospace(); + if (nexttoken.id === ')') { + advance(')'); + nospace(prevtoken, token); + return; + } + for (;;) { + i = identifier(true); + p.push(i); + addlabel(i, 'parameter'); + if (nexttoken.id === ',') { + comma(); + } else { + advance(')', t); + nospace(prevtoken, token); + return p; + } + } + } + + + function doFunction(i, statement) { + var f, + oldOption = option, + oldScope = scope; + + option = Object.create(option); + scope = Object.create(scope); + + funct = { + '(name)' : i || '"' + anonname + '"', + '(line)' : nexttoken.line, + '(context)' : funct, + '(breakage)' : 0, + '(loopage)' : 0, + '(scope)' : scope, + '(statement)': statement + }; + f = funct; + token.funct = funct; + functions.push(funct); + if (i) { + addlabel(i, 'function'); + } + funct['(params)'] = functionparams(); + + block(false); + scope = oldScope; + option = oldOption; + funct['(last)'] = token.line; + funct = funct['(context)']; + return f; + } + + + (function (x) { + x.nud = function () { + var b, f, i, j, p, seen = {}, t; + + b = token.line !== nexttoken.line; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + for (;;) { + if (nexttoken.id === '}') { + break; + } + if (b) { + indentation(); + } + if (nexttoken.value === 'get' && peek().id !== ':') { + advance('get'); + if (!option.es5) { + error("get/set are ES5 features."); + } + i = property_name(); + if (!i) { + error("Missing property name."); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(); + if (!option.loopfunc && funct['(loopage)']) { + warning("Don't make functions within a loop.", t); + } + p = f['(params)']; + if (p) { + warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i); + } + adjacent(token, nexttoken); + advance(','); + indentation(); + advance('set'); + j = property_name(); + if (i !== j) { + error("Expected {a} and instead saw {b}.", token, i, j); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(); + p = f['(params)']; + if (!p || p.length !== 1 || p[0] !== 'value') { + warning("Expected (value) in set {a} function.", t, i); + } + } else { + i = property_name(); + if (typeof i !== 'string') { + break; + } + advance(':'); + nonadjacent(token, nexttoken); + expression(10); + } + if (seen[i] === true) { + warning("Duplicate member '{a}'.", nexttoken, i); + } + seen[i] = true; + countMember(i); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ',') { + warning("Extra comma.", token); + } else if (nexttoken.id === '}' && !option.es5) { + warning("Extra comma.", token); + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance('}', this); + return this; + }; + x.fud = function () { + error("Expected to see a statement and instead saw a block.", token); + }; + }(delim('{'))); + + var varstatement = stmt('var', function (prefix) { + // JavaScript does not have block scope. It only has function scope. So, + // declaring a variable in a block can have unexpected consequences. + var id, name, value; + + if (funct['(onevar)'] && option.onevar) { + warning("Too many var statements."); + } else if (!funct['(global)']) { + funct['(onevar)'] = true; + } + this.first = []; + for (;;) { + nonadjacent(token, nexttoken); + id = identifier(); + if (funct['(global)'] && predefined[id] === false) { + warning("Redefinition of '{a}'.", token, id); + } + addlabel(id, 'unused'); + if (prefix) { + break; + } + name = token; + this.first.push(token); + if (nexttoken.id === '=') { + nonadjacent(token, nexttoken); + advance('='); + nonadjacent(token, nexttoken); + if (nexttoken.id === 'undefined') { + warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id); + } + if (peek(0).id === '=' && nexttoken.identifier) { + error("Variable {a} was not declared correctly.", + nexttoken, nexttoken.value); + } + value = expression(0); + name.first = value; + } + if (nexttoken.id !== ',') { + break; + } + comma(); + } + return this; + }); + varstatement.exps = true; + + blockstmt('function', function () { + if (inblock) { + warning("Function declarations should not be placed in blocks. " + + "Use a function expression or move the statement to the top of " + + "the outer function.", token); + + } + var i = identifier(); + adjacent(token, nexttoken); + addlabel(i, 'unction'); + doFunction(i, true); + if (nexttoken.id === '(' && nexttoken.line === token.line) { + error( +"Function declarations are not invocable. Wrap the whole function invocation in parens."); + } + return this; + }); + + prefix('function', function () { + var i = optionalidentifier(); + if (i) { + adjacent(token, nexttoken); + } else { + nonadjacent(token, nexttoken); + } + doFunction(i); + if (!option.loopfunc && funct['(loopage)']) { + warning("Don't make functions within a loop."); + } + return this; + }); + + blockstmt('if', function () { + var t = nexttoken; + advance('('); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + if (nexttoken.id === 'else') { + nonadjacent(token, nexttoken); + advance('else'); + if (nexttoken.id === 'if' || nexttoken.id === 'switch') { + statement(true); + } else { + block(true, true); + } + } + return this; + }); + + blockstmt('try', function () { + var b, e, s; + + block(false); + if (nexttoken.id === 'catch') { + advance('catch'); + nonadjacent(token, nexttoken); + advance('('); + s = scope; + scope = Object.create(s); + e = nexttoken.value; + if (nexttoken.type !== '(identifier)') { + warning("Expected an identifier and instead saw '{a}'.", + nexttoken, e); + } else { + addlabel(e, 'exception'); + } + advance(); + advance(')'); + block(false); + b = true; + scope = s; + } + if (nexttoken.id === 'finally') { + advance('finally'); + block(false); + return; + } else if (!b) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'catch', nexttoken.value); + } + return this; + }); + + blockstmt('while', function () { + var t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }).labelled = true; + + reserve('with'); + + blockstmt('switch', function () { + var t = nexttoken, + g = false; + funct['(breakage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + this.condition = expression(20); + advance(')', t); + nospace(prevtoken, token); + nonadjacent(token, nexttoken); + t = nexttoken; + advance('{'); + nonadjacent(token, nexttoken); + indent += option.indent; + this.cases = []; + for (;;) { + switch (nexttoken.id) { + case 'case': + switch (funct['(verb)']) { + case 'break': + case 'case': + case 'continue': + case 'return': + case 'switch': + case 'throw': + break; + default: + // You can tell JSHint that you don't use break intentionally by + // adding a comment /* falls through */ on a line just before + // the next `case`. + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'case'.", + token); + } + } + indentation(-option.indent); + advance('case'); + this.cases.push(expression(20)); + g = true; + advance(':'); + funct['(verb)'] = 'case'; + break; + case 'default': + switch (funct['(verb)']) { + case 'break': + case 'continue': + case 'return': + case 'throw': + break; + default: + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'default'.", + token); + } + } + indentation(-option.indent); + advance('default'); + g = true; + advance(':'); + break; + case '}': + indent -= option.indent; + indentation(); + advance('}', t); + if (this.cases.length === 1 || this.condition.id === 'true' || + this.condition.id === 'false') { + if (!option.onecase) + warning("This 'switch' should be an 'if'.", this); + } + funct['(breakage)'] -= 1; + funct['(verb)'] = undefined; + return; + case '(end)': + error("Missing '{a}'.", nexttoken, '}'); + return; + default: + if (g) { + switch (token.id) { + case ',': + error("Each value should have its own case label."); + return; + case ':': + statements(); + break; + default: + error("Missing ':' on a case clause.", token); + } + } else { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'case', nexttoken.value); + } + } + } + }).labelled = true; + + stmt('debugger', function () { + if (!option.debug) { + warning("All 'debugger' statements should be removed."); + } + return this; + }).exps = true; + + (function () { + var x = stmt('do', function () { + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + this.first = block(true); + advance('while'); + var t = nexttoken; + nonadjacent(token, t); + advance('('); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + x.labelled = true; + x.exps = true; + }()); + + blockstmt('for', function () { + var s, t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement.fud.call(varstatement, true); + } else { + switch (funct[nexttoken.value]) { + case 'unused': + funct[nexttoken.value] = 'var'; + break; + case 'var': + break; + default: + warning("Bad for in variable '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance('in'); + expression(20); + advance(')', t); + s = block(true, true); + if (option.forin && (s.length > 1 || typeof s[0] !== 'object' || + s[0].value !== 'if')) { + warning("The body of a for in should be wrapped in an if statement to filter " + + "unwanted properties from the prototype.", this); + } + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } else { + if (nexttoken.id !== ';') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement.fud.call(varstatement); + } else { + for (;;) { + expression(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id !== ';') { + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id === ';') { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, ')', ';'); + } + if (nexttoken.id !== ')') { + for (;;) { + expression(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } + }).labelled = true; + + + stmt('break', function () { + var v = nexttoken.value; + + if (funct['(breakage)'] === 0) + warning("Unexpected '{a}'.", nexttoken, this.value); + + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } + reachable('break'); + return this; + }).exps = true; + + + stmt('continue', function () { + var v = nexttoken.value; + + if (funct['(breakage)'] === 0) + warning("Unexpected '{a}'.", nexttoken, this.value); + + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } else if (!funct['(loopage)']) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + reachable('continue'); + return this; + }).exps = true; + + + stmt('return', function () { + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id === '(regexp)') + warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator."); + + if (this.line === nexttoken.line || !option.asi) { + if (nexttoken.id !== ';' && !nexttoken.reach) { + nonadjacent(token, nexttoken); + this.first = expression(20); + } + } + + reachable('return'); + return this; + }).exps = true; + + + stmt('throw', function () { + nolinebreak(this); + nonadjacent(token, nexttoken); + this.first = expression(20); + reachable('throw'); + return this; + }).exps = true; + +// Superfluous reserved words + + reserve('class'); + reserve('const'); + reserve('enum'); + reserve('export'); + reserve('extends'); + reserve('import'); + reserve('super'); + + reserve('let'); + reserve('yield'); + reserve('implements'); + reserve('interface'); + reserve('package'); + reserve('private'); + reserve('protected'); + reserve('public'); + reserve('static'); + + +// Parse JSON + + function jsonValue() { + + function jsonObject() { + var o = {}, t = nexttoken; + advance('{'); + if (nexttoken.id !== '}') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing '}' to match '{' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === '}') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } else if (nexttoken.id !== '(string)') { + warning("Expected a string and instead saw {a}.", + nexttoken, nexttoken.value); + } + if (o[nexttoken.value] === true) { + warning("Duplicate key '{a}'.", + nexttoken, nexttoken.value); + } else if ((nexttoken.value === '__proto__' && + !option.proto) || (nexttoken.value === '__iterator__' && + !option.iterator)) { + warning("The '{a}' key may produce unexpected results.", + nexttoken, nexttoken.value); + } else { + o[nexttoken.value] = true; + } + advance(); + advance(':'); + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance('}'); + } + + function jsonArray() { + var t = nexttoken; + advance('['); + if (nexttoken.id !== ']') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing ']' to match '[' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === ']') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance(']'); + } + + switch (nexttoken.id) { + case '{': + jsonObject(); + break; + case '[': + jsonArray(); + break; + case 'true': + case 'false': + case 'null': + case '(number)': + case '(string)': + advance(); + break; + case '-': + advance('-'); + if (token.character !== nexttoken.from) { + warning("Unexpected space after '-'.", token); + } + adjacent(token, nexttoken); + advance('(number)'); + break; + default: + error("Expected a JSON value.", nexttoken); + } + } + + +// The actual JSHINT function itself. + + var itself = function (s, o, g) { + var a, i, k; + JSHINT.errors = []; + predefined = Object.create(standard); + combine(predefined, g || {}); + if (o) { + a = o.predef; + if (a) { + if (Array.isArray(a)) { + for (i = 0; i < a.length; i += 1) { + predefined[a[i]] = true; + } + } else if (typeof a === 'object') { + k = Object.keys(a); + for (i = 0; i < k.length; i += 1) { + predefined[k[i]] = !!a[k[i]]; + } + } + } + option = o; + } else { + option = {}; + } + option.indent = option.indent || 4; + option.maxerr = option.maxerr || 50; + + tab = ''; + for (i = 0; i < option.indent; i += 1) { + tab += ' '; + } + indent = 1; + global = Object.create(predefined); + scope = global; + funct = { + '(global)': true, + '(name)': '(global)', + '(scope)': scope, + '(breakage)': 0, + '(loopage)': 0 + }; + functions = [funct]; + urls = []; + src = false; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + jsonmode = false; + warnings = 0; + lex.init(s); + prereg = true; + strict_mode = false; + + prevtoken = token = nexttoken = syntax['(begin)']; + assume(); + + try { + advance(); + switch (nexttoken.id) { + case '{': + case '[': + option.laxbreak = true; + jsonmode = true; + jsonValue(); + break; + default: + if (nexttoken.value === 'use strict') { + if (!option.globalstrict) + warning("Use the function form of \"use strict\"."); + use_strict(); + } + + statements('lib'); + } + advance('(end)'); + } catch (e) { + if (e) { + JSHINT.errors.push({ + reason : e.message, + line : e.line || nexttoken.line, + character : e.character || nexttoken.from + }, null); + } + } + return JSHINT.errors.length === 0; + }; + + // Data summary. + itself.data = function () { + + var data = {functions: []}, fu, globals, implieds = [], f, i, j, + members = [], n, unused = [], v; + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (jsonmode) { + data.json = true; + } + + for (n in implied) { + if (is_own(implied, n)) { + implieds.push({ + name: n, + line: implied[n] + }); + } + } + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + for (n in f) { + if (is_own(f, n) && n.charAt(0) !== '(') { + v = f[n]; + if (v === 'unction') { + v = 'unused'; + } + if (Array.isArray(fu[v])) { + fu[v].push(n); + if (v === 'unused') { + unused.push({ + name: n, + line: f['(line)'], + 'function': f['(name)'] + }); + } + } + } + } + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + fu.name = f['(name)']; + fu.param = f['(params)']; + fu.line = f['(line)']; + fu.last = f['(last)']; + data.functions.push(fu); + } + + if (unused.length > 0) { + data.unused = unused; + } + + members = []; + for (n in member) { + if (typeof member[n] === 'number') { + data.member = member; + break; + } + } + + return data; + }; + + itself.report = function (option) { + var data = itself.data(); + + var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s; + + function detail(h, array) { + var b, i, singularity; + if (array) { + o.push('
' + h + ' '); + array = array.sort(); + for (i = 0; i < array.length; i += 1) { + if (array[i] !== singularity) { + singularity = array[i]; + o.push((b ? ', ' : '') + singularity); + b = true; + } + } + o.push('
'); + } + } + + + if (data.errors || data.implieds || data.unused) { + err = true; + o.push('
Error:'); + if (data.errors) { + for (i = 0; i < data.errors.length; i += 1) { + c = data.errors[i]; + if (c) { + e = c.evidence || ''; + o.push('

Problem' + (isFinite(c.line) ? ' at line ' + + c.line + ' character ' + c.character : '') + + ': ' + c.reason.entityify() + + '

' + + (e && (e.length > 80 ? e.slice(0, 77) + '...' : + e).entityify()) + '

'); + } + } + } + + if (data.implieds) { + s = []; + for (i = 0; i < data.implieds.length; i += 1) { + s[i] = '' + data.implieds[i].name + ' ' + + data.implieds[i].line + ''; + } + o.push('

Implied global: ' + s.join(', ') + '

'); + } + + if (data.unused) { + s = []; + for (i = 0; i < data.unused.length; i += 1) { + s[i] = '' + data.unused[i].name + ' ' + + data.unused[i].line + ' ' + + data.unused[i]['function'] + ''; + } + o.push('

Unused variable: ' + s.join(', ') + '

'); + } + if (data.json) { + o.push('

JSON: bad.

'); + } + o.push('
'); + } + + if (!option) { + + o.push('
'); + + if (data.urls) { + detail("URLs
", data.urls, '
'); + } + + if (data.json && !err) { + o.push('

JSON: good.

'); + } else if (data.globals) { + o.push('
Global ' + + data.globals.sort().join(', ') + '
'); + } else { + o.push('
No new global variables introduced.
'); + } + + for (i = 0; i < data.functions.length; i += 1) { + f = data.functions[i]; + + o.push('
' + f.line + '-' + + f.last + ' ' + (f.name || '') + '(' + + (f.param ? f.param.join(', ') : '') + ')
'); + detail('Unused', f.unused); + detail('Closure', f.closure); + detail('Variable', f['var']); + detail('Exception', f.exception); + detail('Outer', f.outer); + detail('Global', f.global); + detail('Label', f.label); + } + + if (data.member) { + a = Object.keys(data.member); + if (a.length) { + a = a.sort(); + m = '
/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '
'); + m = ' '; + l = 1; + } + l += n.length + 2; + if (data.member[k] === 1) { + n = '' + n + ''; + } + if (i < a.length - 1) { + n += ', '; + } + m += n; + } + o.push(m + '
*/
'); + } + o.push('
'); + } + } + return o.join(''); + }; + + itself.jshint = itself; + itself.edition = '2011-04-16'; + + return itself; +}()); +