diff --git a/Gemfile b/Gemfile index f49be1f494a..514329c0a59 100644 --- a/Gemfile +++ b/Gemfile @@ -21,7 +21,7 @@ gem 'fastimage' gem 'fog', require: false gem 'has_ip_address' gem 'hiredis' -gem 'i18n-js' + # note: for image_optim to correctly work you need # sudo apt-get install -y advancecomp gifsicle jpegoptim libjpeg-progs optipng pngcrush gem 'image_optim' diff --git a/Gemfile.lock b/Gemfile.lock index 1de496553b6..a5a628f603d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -203,8 +203,6 @@ GEM hiredis (0.4.5) httpauth (0.2.0) i18n (0.6.1) - i18n-js (2.1.2) - i18n image_optim (0.7.2) fspath (~> 2.0.3) image_size (~> 1.1) @@ -479,7 +477,6 @@ DEPENDENCIES guard-spork has_ip_address hiredis - i18n-js image_optim jasminerice jquery-rails diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index e4ca919bbce..8525d0bed50 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -20,11 +20,6 @@ // The rest of the externals //= require_tree ./external -//= require i18n - -// The following needs to go right after including i18n, because other scripts -// below require correctly set locale already -//= require init_locale //= require ./discourse/helpers/i18n_helpers //= require ./discourse diff --git a/app/assets/javascripts/init_locale.js b/app/assets/javascripts/init_locale.js deleted file mode 100644 index e6ad0a3ccdc..00000000000 --- a/app/assets/javascripts/init_locale.js +++ /dev/null @@ -1 +0,0 @@ -I18n.locale = window.currentLocale; diff --git a/app/assets/javascripts/locales/en.js.erb b/app/assets/javascripts/locales/en.js.erb index 0158aa80b9e..df19d22fa84 100644 --- a/app/assets/javascripts/locales/en.js.erb +++ b/app/assets/javascripts/locales/en.js.erb @@ -1,2 +1,3 @@ //= depend_on 'client.en.yml' -<%= JsLocaleHelper.output_locale(:en); %> +//= require locales/i18n +<%= JsLocaleHelper.output_locale(:en) %> diff --git a/app/assets/javascripts/locales/fr.js.erb b/app/assets/javascripts/locales/fr.js.erb index 6f49253c8e9..67b11dd3895 100644 --- a/app/assets/javascripts/locales/fr.js.erb +++ b/app/assets/javascripts/locales/fr.js.erb @@ -1,2 +1,3 @@ //= depend_on 'client.fr.yml' -<%= JsLocaleHelper.output_locale(:fr); %> +//= require locales/i18n +<%= JsLocaleHelper.output_locale(:fr) %> diff --git a/app/assets/javascripts/locales/i18n.js b/app/assets/javascripts/locales/i18n.js new file mode 100644 index 00000000000..944c55d34b8 --- /dev/null +++ b/app/assets/javascripts/locales/i18n.js @@ -0,0 +1,531 @@ +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function(searchElement /*, fromIndex */) { + "use strict"; + + if (this === void 0 || this === null) { + throw new TypeError(); + } + + var t = Object(this); + var len = t.length >>> 0; + + if (len === 0) { + return -1; + } + + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n !== n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n !== 0 && n !== (Infinity) && n !== -(Infinity)) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + + if (n >= len) { + return -1; + } + + var k = n >= 0 + ? n + : Math.max(len - Math.abs(n), 0); + + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + + return -1; + }; +} + +// Instantiate the object +var I18n = I18n || {}; + +// Set default locale to english +I18n.defaultLocale = "en"; + +// Set default handling of translation fallbacks to false +I18n.fallbacks = false; + +// Set default separator +I18n.defaultSeparator = "."; + +// Set current locale to null +I18n.locale = null; + +// Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`. +I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm; + +I18n.fallbackRules = { +}; + +I18n.pluralizationRules = { + en: function (n) { + return n == 0 ? ["zero", "none", "other"] : n == 1 ? "one" : "other"; + } +}; + +I18n.getFallbacks = function(locale) { + if (locale === I18n.defaultLocale) { + return []; + } else if (!I18n.fallbackRules[locale]) { + var rules = [] + , components = locale.split("-"); + + for (var l = 1; l < components.length; l++) { + rules.push(components.slice(0, l).join("-")); + } + + rules.push(I18n.defaultLocale); + + I18n.fallbackRules[locale] = rules; + } + + return I18n.fallbackRules[locale]; +} + +I18n.isValidNode = function(obj, node, undefined) { + return obj[node] !== null && obj[node] !== undefined; +}; + +I18n.lookup = function(scope, options) { + var options = options || {} + , lookupInitialScope = scope + , translations = this.prepareOptions(I18n.translations) + , locale = options.locale || I18n.currentLocale() + , messages = translations[locale] || {} + , options = this.prepareOptions(options) + , currentScope + ; + + if (typeof(scope) == "object") { + scope = scope.join(this.defaultSeparator); + } + + if (options.scope) { + scope = options.scope.toString() + this.defaultSeparator + scope; + } + + scope = scope.split(this.defaultSeparator); + + while (messages && scope.length > 0) { + currentScope = scope.shift(); + messages = messages[currentScope]; + } + + if (!messages) { + if (I18n.fallbacks) { + var fallbacks = this.getFallbacks(locale); + for (var fallback = 0; fallback < fallbacks.length; fallbacks++) { + messages = I18n.lookup(lookupInitialScope, this.prepareOptions({locale: fallbacks[fallback]}, options)); + if (messages) { + break; + } + } + } + + if (!messages && this.isValidNode(options, "defaultValue")) { + messages = options.defaultValue; + } + } + + return messages; +}; + +// Merge serveral hash options, checking if value is set before +// overwriting any value. The precedence is from left to right. +// +// I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"}); +// #=> {name: "John Doe", role: "user"} +// +I18n.prepareOptions = function() { + var options = {} + , opts + , count = arguments.length + ; + + for (var i = 0; i < count; i++) { + opts = arguments[i]; + + if (!opts) { + continue; + } + + for (var key in opts) { + if (!this.isValidNode(options, key)) { + options[key] = opts[key]; + } + } + } + + return options; +}; + +I18n.interpolate = function(message, options) { + options = this.prepareOptions(options); + var matches = message.match(this.PLACEHOLDER) + , placeholder + , value + , name + ; + + if (!matches) { + return message; + } + + for (var i = 0; placeholder = matches[i]; i++) { + name = placeholder.replace(this.PLACEHOLDER, "$1"); + + value = options[name]; + + if (!this.isValidNode(options, name)) { + value = "[missing " + placeholder + " value]"; + } + + regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}")); + message = message.replace(regex, value); + } + + return message; +}; + +I18n.translate = function(scope, options) { + options = this.prepareOptions(options); + var translation = this.lookup(scope, options); + + try { + if (typeof(translation) == "object") { + if (typeof(options.count) == "number") { + return this.pluralize(options.count, scope, options); + } else { + return translation; + } + } else { + return this.interpolate(translation, options); + } + } catch(err) { + return this.missingTranslation(scope); + } +}; + +I18n.localize = function(scope, value) { + switch (scope) { + case "currency": + return this.toCurrency(value); + case "number": + scope = this.lookup("number.format"); + return this.toNumber(value, scope); + case "percentage": + return this.toPercentage(value); + default: + if (scope.match(/^(date|time)/)) { + return this.toTime(scope, value); + } else { + return value.toString(); + } + } +}; + +I18n.parseDate = function(date) { + var matches, convertedDate; + + // we have a date, so just return it. + if (typeof(date) == "object") { + return date; + }; + + // it matches the following formats: + // yyyy-mm-dd + // yyyy-mm-dd[ T]hh:mm::ss + // yyyy-mm-dd[ T]hh:mm::ss + // yyyy-mm-dd[ T]hh:mm::ssZ + // yyyy-mm-dd[ T]hh:mm::ss+0000 + // + matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/); + + if (matches) { + for (var i = 1; i <= 6; i++) { + matches[i] = parseInt(matches[i], 10) || 0; + } + + // month starts on 0 + matches[2] -= 1; + + if (matches[7]) { + convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6])); + } else { + convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]); + } + } else if (typeof(date) == "number") { + // UNIX timestamp + convertedDate = new Date(); + convertedDate.setTime(date); + } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) { + // a valid javascript format with timezone info + convertedDate = new Date(); + convertedDate.setTime(Date.parse(date)) + } else { + // an arbitrary javascript string + convertedDate = new Date(); + convertedDate.setTime(Date.parse(date)); + } + + return convertedDate; +}; + +I18n.toTime = function(scope, d) { + var date = this.parseDate(d) + , format = this.lookup(scope) + ; + + if (date.toString().match(/invalid/i)) { + return date.toString(); + } + + if (!format) { + return date.toString(); + } + + return this.strftime(date, format); +}; + +I18n.strftime = function(date, format) { + var options = this.lookup("date"); + + if (!options) { + return date.toString(); + } + + options.meridian = options.meridian || ["AM", "PM"]; + + var weekDay = date.getDay() + , day = date.getDate() + , year = date.getFullYear() + , month = date.getMonth() + 1 + , hour = date.getHours() + , hour12 = hour + , meridian = hour > 11 ? 1 : 0 + , secs = date.getSeconds() + , mins = date.getMinutes() + , offset = date.getTimezoneOffset() + , absOffsetHours = Math.floor(Math.abs(offset / 60)) + , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60) + , timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes) + ; + + if (hour12 > 12) { + hour12 = hour12 - 12; + } else if (hour12 === 0) { + hour12 = 12; + } + + var padding = function(n) { + var s = "0" + n.toString(); + return s.substr(s.length - 2); + }; + + var f = format; + f = f.replace("%a", options.abbr_day_names[weekDay]); + f = f.replace("%A", options.day_names[weekDay]); + f = f.replace("%b", options.abbr_month_names[month]); + f = f.replace("%B", options.month_names[month]); + f = f.replace("%d", padding(day)); + f = f.replace("%e", day); + f = f.replace("%-d", day); + f = f.replace("%H", padding(hour)); + f = f.replace("%-H", hour); + f = f.replace("%I", padding(hour12)); + f = f.replace("%-I", hour12); + f = f.replace("%m", padding(month)); + f = f.replace("%-m", month); + f = f.replace("%M", padding(mins)); + f = f.replace("%-M", mins); + f = f.replace("%p", options.meridian[meridian]); + f = f.replace("%S", padding(secs)); + f = f.replace("%-S", secs); + f = f.replace("%w", weekDay); + f = f.replace("%y", padding(year)); + f = f.replace("%-y", padding(year).replace(/^0+/, "")); + f = f.replace("%Y", year); + f = f.replace("%z", timezoneoffset); + + return f; +}; + +I18n.toNumber = function(number, options) { + options = this.prepareOptions( + options, + this.lookup("number.format"), + {precision: 3, separator: ".", delimiter: ",", strip_insignificant_zeros: false} + ); + + var negative = number < 0 + , string = Math.abs(number).toFixed(options.precision).toString() + , parts = string.split(".") + , precision + , buffer = [] + , formattedNumber + ; + + number = parts[0]; + precision = parts[1]; + + while (number.length > 0) { + buffer.unshift(number.substr(Math.max(0, number.length - 3), 3)); + number = number.substr(0, number.length -3); + } + + formattedNumber = buffer.join(options.delimiter); + + if (options.precision > 0) { + formattedNumber += options.separator + parts[1]; + } + + if (negative) { + formattedNumber = "-" + formattedNumber; + } + + if (options.strip_insignificant_zeros) { + var regex = { + separator: new RegExp(options.separator.replace(/\./, "\\.") + "$") + , zeros: /0+$/ + }; + + formattedNumber = formattedNumber + .replace(regex.zeros, "") + .replace(regex.separator, "") + ; + } + + return formattedNumber; +}; + +I18n.toCurrency = function(number, options) { + options = this.prepareOptions( + options, + this.lookup("number.currency.format"), + this.lookup("number.format"), + {unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."} + ); + + number = this.toNumber(number, options); + number = options.format + .replace("%u", options.unit) + .replace("%n", number) + ; + + return number; +}; + +I18n.toHumanSize = function(number, options) { + var kb = 1024 + , size = number + , iterations = 0 + , unit + , precision + ; + + while (size >= kb && iterations < 4) { + size = size / kb; + iterations += 1; + } + + if (iterations === 0) { + unit = this.t("number.human.storage_units.units.byte", {count: size}); + precision = 0; + } else { + unit = this.t("number.human.storage_units.units." + [null, "kb", "mb", "gb", "tb"][iterations]); + precision = (size - Math.floor(size) === 0) ? 0 : 1; + } + + options = this.prepareOptions( + options, + {precision: precision, format: "%n%u", delimiter: ""} + ); + + number = this.toNumber(size, options); + number = options.format + .replace("%u", unit) + .replace("%n", number) + ; + + return number; +}; + +I18n.toPercentage = function(number, options) { + options = this.prepareOptions( + options, + this.lookup("number.percentage.format"), + this.lookup("number.format"), + {precision: 3, separator: ".", delimiter: ""} + ); + + number = this.toNumber(number, options); + return number + "%"; +}; + +I18n.pluralizer = function(locale) { + pluralizer = this.pluralizationRules[locale]; + if (pluralizer !== undefined) return pluralizer; + return this.pluralizationRules["en"]; +}; + +I18n.findAndTranslateValidNode = function(keys, translation) { + for (i = 0; i < keys.length; i++) { + key = keys[i]; + if (this.isValidNode(translation, key)) return translation[key]; + } + return null; +}; + +I18n.pluralize = function(count, scope, options) { + var translation; + + try { + translation = this.lookup(scope, options); + } catch (error) {} + + if (!translation) { + return this.missingTranslation(scope); + } + + var message; + options = this.prepareOptions(options); + options.count = count.toString(); + + pluralizer = this.pluralizer(this.currentLocale()); + key = pluralizer(Math.abs(count)); + keys = ((typeof key == "object") && (key instanceof Array)) ? key : [key]; + + message = this.findAndTranslateValidNode(keys, translation); + if (message == null) message = this.missingTranslation(scope, keys[0]); + + return this.interpolate(message, options); +}; + +I18n.missingTranslation = function() { + var message = '[missing "' + this.currentLocale() + , count = arguments.length + ; + + for (var i = 0; i < count; i++) { + message += "." + arguments[i]; + } + + message += '" translation]'; + + return message; +}; + +I18n.currentLocale = function() { + return (I18n.locale || I18n.defaultLocale); +}; + +// shortcuts +I18n.t = I18n.translate; +I18n.l = I18n.localize; +I18n.p = I18n.pluralize; diff --git a/app/assets/javascripts/locales/nl.js.erb b/app/assets/javascripts/locales/nl.js.erb index 7257ebe0281..3016458bfa8 100644 --- a/app/assets/javascripts/locales/nl.js.erb +++ b/app/assets/javascripts/locales/nl.js.erb @@ -1,2 +1,3 @@ //= depend_on 'client.nl.yml' -<%= JsLocaleHelper.output_locale(:nl); %> +//= require locales/i18n +<%= JsLocaleHelper.output_locale(:nl) %> diff --git a/app/assets/javascripts/locales/pseudo.js.erb b/app/assets/javascripts/locales/pseudo.js.erb index 7e7f795a6d4..5defc799636 100644 --- a/app/assets/javascripts/locales/pseudo.js.erb +++ b/app/assets/javascripts/locales/pseudo.js.erb @@ -1,2 +1,3 @@ //= depend_on 'client.pseudo.yml' -<%= JsLocaleHelper.output_locale(:pseudo); %> +//= require locales/i18n +<%= JsLocaleHelper.output_locale(:pseudo) %> diff --git a/app/assets/javascripts/locales/sv.js.erb b/app/assets/javascripts/locales/sv.js.erb new file mode 100644 index 00000000000..e404277bc62 --- /dev/null +++ b/app/assets/javascripts/locales/sv.js.erb @@ -0,0 +1,3 @@ +//= depend_on 'client.sv.yml' +//= require locales/i18n +<%= JsLocaleHelper.output_locale(:sv) %> diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8fa7b01b888..89687395190 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -21,6 +21,7 @@ class ApplicationController < ActionController::Base before_filter :store_incoming_links before_filter :preload_json before_filter :check_xhr + before_filter :set_locale rescue_from Exception do |exception| unless [ ActiveRecord::RecordNotFound, ActionController::RoutingError, @@ -77,6 +78,11 @@ class ApplicationController < ActionController::Base render file: 'public/403', formats: [:html], layout: false, status: 403 end + + def set_locale + I18n.locale = SiteSetting.default_locale + end + def store_preloaded(key, json) @preloaded ||= {} # I dislike that there is a gsub as opposed to a gsub! diff --git a/app/helpers/js_locale_helper.rb b/app/helpers/js_locale_helper.rb index 06eceab9e23..75552fe9717 100644 --- a/app/helpers/js_locale_helper.rb +++ b/app/helpers/js_locale_helper.rb @@ -2,20 +2,22 @@ module JsLocaleHelper def self.output_locale(locale) - SimplesIdeias::I18n.assert_usable_configuration! + locale_str = locale.to_s - s = "var I18n = I18n || {};" - segment = "app/assets/javascripts/i18n/#{locale}.js" - s += "I18n.translations = " + SimplesIdeias::I18n.translation_segments[segment].to_json + ";" + translations = YAML::load(File.open("#{Rails.root}/config/locales/client.#{locale_str}.yml")) - segment = "app/assets/javascripts/i18n/admin.#{locale}.js" - admin = SimplesIdeias::I18n.translation_segments[segment] - admin[locale][:js] = admin[locale].delete(:admin_js) + # We used to split the admin versus the client side, but it's much simpler to just + # include both for now due to the small size of the admin section. + # + # For now, let's leave it split out in the translation file in case we want to split + # it again later, so we'll merge the JSON ourselves. + admin_contents = translations[locale_str].delete('admin_js') - s += "jQuery.extend(true, I18n.translations, " + admin.to_json + ");" - - s + translations[locale_str]['js'].merge!(admin_contents) if admin_contents.present? + result = "I18n.translations = #{translations.to_json};\n" + result << "I18n.locale = '#{locale_str}'\n" + result end end diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index 773f2af25ea..e0c3927fffc 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -149,6 +149,9 @@ class SiteSetting < ActiveRecord::Base setting(:title_fancy_entities, true) + # The default locale for the site + setting(:default_locale, 'en') + client_setting(:educate_until_posts, 2) def self.call_discourse_hub? diff --git a/app/views/common/_discourse_javascript.html.erb b/app/views/common/_discourse_javascript.html.erb index df639828771..87670cece30 100644 --- a/app/views/common/_discourse_javascript.html.erb +++ b/app/views/common/_discourse_javascript.html.erb @@ -16,10 +16,6 @@ <%# load the selected locale before any other scripts %> <%= javascript_include_tag "locales/#{I18n.locale}" %> -<%# store the locale into a variable, because the scripts need it, see init_locale.js %> - <%- if mini_profiler_enabled? %> <%- Rack::MiniProfiler.step "application" do %> diff --git a/config/application.rb b/config/application.rb index 982868bf031..7f925540892 100644 --- a/config/application.rb +++ b/config/application.rb @@ -34,6 +34,11 @@ module Discourse 'jquery.js', 'defer/html-sanitizer-bundle.js' ] + # Precompile all available locales + Dir.glob("app/assets/javascripts/locales/*.js.erb").each do |file| + config.assets.precompile << file.match(/([a-z]+\.js)\.erb$/)[1] + end + # Activate observers that should always be running. config.active_record.observers = [ :user_email_observer, diff --git a/config/i18n-js.yml b/config/i18n-js.yml deleted file mode 100644 index 2b3b22304e6..00000000000 --- a/config/i18n-js.yml +++ /dev/null @@ -1,38 +0,0 @@ -# Split context in several files. -# By default only one file with all translations is exported and -# no configuration is required. Your settings for asset pipeline -# are automatically recognized. -# -# If you want to split translations into several files or specify -# locale contexts that will be exported, just use this file to do -# so. -# -# If you're going to use the Rails 3.1 asset pipeline, change -# the following configuration to something like this: -# -# translations: -# - file: "app/assets/javascripts/i18n/translations.js" -# -# If you're running an old version, you can use something -# like this: -# -# translations: -# - file: "public/javascripts/translations.js" -# only: "*" -# - -translations: - - file: 'app/assets/javascripts/i18n/en.js' - only: 'en.js.*' - - file: 'app/assets/javascripts/i18n/admin.en.js' - only: 'en.admin_js.*' - - - file: 'app/assets/javascripts/i18n/pseudo.js' - only: 'pseudo.js.*' - - file: 'app/assets/javascripts/i18n/admin.pseudo.js' - only: 'pseudo.admin_js.*' - - - file: 'app/assets/javascripts/i18n/fr.js' - only: 'fr.js.*' - - file: 'app/assets/javascripts/i18n/admin.fr.js' - only: 'fr.admin_js.*' diff --git a/config/jshint.yml b/config/jshint.yml index 9d572e964d9..5e248f1df49 100644 --- a/config/jshint.yml +++ b/config/jshint.yml @@ -15,6 +15,7 @@ exclude_paths: - app/assets/javascripts/external/* - app/assets/javascripts/external_production/* - app/assets/javascripts/defer/* + - app/assets/javascripts/locales/i18n.js # ------------ jshint options ------------ diff --git a/config/locales/client.pseudo.yml b/config/locales/client.pseudo.yml index bdccf217dec..0f741ac3ca9 100644 --- a/config/locales/client.pseudo.yml +++ b/config/locales/client.pseudo.yml @@ -241,6 +241,10 @@ pseudo: title: ! '[[ Łóǧ Íɳ ŵíťĥ Ýáĥóó ]]' message: ! '[[ Áůťĥéɳťíčáťíɳǧ ŵíťĥ Ýáĥóó (ɱáǩé šůřé ƿóƿ ůƿ ƀłóčǩéřš ářé ɳóť éɳáƀłéď) ]]' + github: + title: ! '[[ Łóǧ Íɳ ŵíťĥ Ǧíťĥůƀ ]]' + message: ! '[[ Áůťĥéɳťíčáťíɳǧ ŵíťĥ Ǧíťĥůƀ (ɱáǩé šůřé ƿóƿ ůƿ ƀłóčǩéřš ářé ɳóť + éɳáƀłéď) ]]' composer: saving_draft_tip: ! '[[ šáνíɳǧ ]]' saved_draft_tip: ! '[[ šáνéď ]]' @@ -353,7 +357,9 @@ pseudo: unread_posts: ! '[[ ýóů ĥáνé {{unread}} ůɳřéáď ółď ƿóšťš íɳ ťĥíš ťóƿíč ]]' new_posts: ! '[[ ťĥéřé ářé {{new_posts}} ɳéŵ ƿóšťš íɳ ťĥíš ťóƿíč šíɳčé ýóů łášť řéáď íť ]]' - likes: ! '[[ ťĥéřé ářé {{likes}} łíǩéš íɳ ťĥíš ťóƿíč ]]' + likes: + one: ! '[[ ťĥéřé íš 1 łíǩé íɳ ťĥíš ťóƿíč ]]' + other: ! '[[ ťĥéřé ářé {{count}} łíǩéš íɳ ťĥíš ťóƿíč ]]' back_to_list: ! '[[ Ɓáčǩ ťó Ťóƿíč Łíšť ]]' options: ! '[[ Ťóƿíč Óƿťíóɳš ]]' show_links: ! '[[ šĥóŵ łíɳǩš ŵíťĥíɳ ťĥíš ťóƿíč ]]' diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 88e1ffff4e6..50212c8bd89 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -237,6 +237,7 @@ en: yaxis: "Visits" site_settings: + default_locale: "The default language of this Discourse instance (ISO 639-1 Code)" min_post_length: "Minimum post length in characters" max_post_length: "Maximum post length in characters" min_topic_title_length: "Minimum topic title length in characters" diff --git a/config/locales/server.pseudo.yml b/config/locales/server.pseudo.yml index 3ee8183b1d8..1ef82cbac15 100644 --- a/config/locales/server.pseudo.yml +++ b/config/locales/server.pseudo.yml @@ -2,6 +2,7 @@ pseudo: title: ! '[[ Ďíščóůřšé ]]' topics: ! '[[ Ťóƿíčš ]]' + via: ! '[[ %{username} νíá %{site_name} ]]' is_reserved: ! '[[ íš řéšéřνéď ]]' too_many_mentions: ! '[[ ĥáš ťóó ɱáɳý ůšéřš ɱéɳťíóɳéď ]]' too_many_images: ! '[[ ĥáš ťóó ɱáɳý íɱáǧéš ]]' @@ -225,7 +226,14 @@ pseudo: action: ! '[[ Řé-Šůƀščříƀé ]]' title: ! '[[ Řé-Šůƀščříƀéď! ]]' description: ! '[[ Ýóů ĥáνé ƀééɳ řé-šůƀščříƀéď. ]]' + reports: + visits: + title: ! '[[ Ůšéřš Ѷíšíťš ƀý Ďáý ]]' + xaxis: ! '[[ Ďáý ]]' + yaxis: ! '[[ Ѷíšíťš ]]' site_settings: + default_locale: ! '[[ Ťĥé ďéƒáůłť łáɳǧůáǧé óƒ ťĥíš Ďíščóůřšé íɳšťáɳčé (ÍŠÓ 639-1 + Čóďé) ]]' min_post_length: ! '[[ Ϻíɳíɱůɱ ƿóšť łéɳǧťĥ íɳ čĥářáčťéřš ]]' max_post_length: ! '[[ Ϻáхíɱůɱ ƿóšť łéɳǧťĥ íɳ čĥářáčťéřš ]]' min_topic_title_length: ! '[[ Ϻíɳíɱůɱ ťóƿíč ťíťłé łéɳǧťĥ íɳ čĥářáčťéřš ]]' @@ -239,7 +247,13 @@ pseudo: ɳíčǩɳáɱé řéǧíšťřý áť ďíščóůřšé.óřǧ ]]' educate_until_posts: ! '[[ Šĥóŵ ƿóƿ-ůƿ čóɱƿóšéř éďůčáťíóɳ ƿáɳéł ůɳťíł ťĥé ůšéř ĥáš ɱáďé ťĥíš ɱáɳý ƿóšťš ]]' - title: ! '[[ Ťíťłé óƒ ťĥíš ŵéƀšíťé ]]' + title: ! '[[ Ťíťłé óƒ ťĥíš šíťé, ŵíłł ƀé ůšéď íɳ ťĥé ťíťłé ťáǧ áɳď éłšéŵĥéřé ]]' + company_full_name: ! '[[ Ťĥé ƒůłł ɳáɱé óƒ ťĥé čóɱƿáɳý ťĥáť řůɳš ťĥíš šíťé, ůšéď + íɳ łéǧáł ďóčůɱéɳťš łíǩé ťĥé /ťóš ]]' + company_short_name: ! '[[ Ťĥé šĥóřť ɳáɱé óƒ ťĥé čóɱƿáɳý ťĥáť řůɳš ťĥíš šíťé, ůšéď + íɳ łéǧáł ďóčůɱéɳťš łíǩé ťĥé /ťóš ]]' + company_domain: ! '[[ Ťĥé ďóɱáíɳ ɳáɱé óŵɳéď ƀý ťĥé čóɱƿáɳý ťĥáť řůɳš ťĥíš šíťé, + ůšéď íɳ łéǧáł ďóčůɱéɳťš łíǩé ťĥé /ťóš ]]' restrict_access: ! '[[ Řéšťříčť ƒóřůɱ áččéšš ůɳłéšš á ƿáššŵóřď íš éɳťéřéď ]]' access_password: ! '[[ Ŵĥéɳ řéšťříčťéď áččéšš íš éɳáƀłéď, ťĥíš ƿáššŵóřď ɱůšť ƀé éɳťéřéď ]]' @@ -328,23 +342,35 @@ pseudo: řóƀóťš.ťхť) ]]' email_domains_blacklist: ! '[[ Á ƿíƿé-ďéłíɱíťéď łíšť óƒ éɱáíł ďóɱáíɳš ťĥáť ářé ɳóť áłłóŵéď. Éхáɱƿłé: ɱáíłíɳáťóř.čóɱ|ťřášĥɱáíł.ɳéť ]]' - version_checks: ! '[[ Рíɳǧ Ďíščóůřšé Ĥůƀ ƒóř νéřšíóɳ ůƿďáťéš áɳď řéƿóřť ťĥéɱ óɳ - ťĥé áďɱíɳ ďášĥƀóářď. ]]' - port: ! '[[ ̓ ýóů''ď łíǩé ťó šƿéčíƒý á ƿóřť íɳ ťĥé ŮŘŁ. Ůšéƒůł íɳ ďéνéłóƿɱéɳť - ɱóďé. Łéáνé ƀłáɳǩ ƒóř ɳóɳé ]]' - force_hostname: ! '[[ ̓ ýóů''ď łíǩé ťó šƿéčíƒý á ĥóšťɳáɱé íɳ ťĥé ŮŘŁ. Ůšéƒůł - íɳ ďéνéłóƿɱéɳť ɱóďé. Łéáνé ƀłáɳǩ ƒóř ɳóɳé ]]' + version_checks: ! '[[ Рíɳǧ ťĥé Ďíščóůřšé Ĥůƀ ƒóř νéřšíóɳ ůƿďáťéš áɳď šĥóŵ νéřšíóɳ + ɱéššáǧéš óɳ ťĥé /áďɱíɳ ďášĥƀóářď ]]' + port: ! '[[ Ůšé ťĥíš ĤŤŤР ƿóřť řáťĥéř ťĥáɳ ťĥé ďéƒáůłť óƒ ƿóřť 80. Łéáνé ƀłáɳǩ + ƒóř ɳóɳé, ɱáíɳłý ůšéƒůł ƒóř ďéνéłóƿɱéɳť ]]' + force_hostname: ! '[[ Šƿéčíƒý á ĥóšťɳáɱé íɳ ťĥé ŮŘŁ. Łéáνé ƀłáɳǩ ƒóř ɳóɳé, ɱáíɳłý + ůšéƒůł ƒóř ďéνéłóƿɱéɳť ]]' invite_expiry_days: ! '[[ Ĥóŵ łóɳǧ ůšéř íɳνíťáťíóɳ ǩéýš ářé νáłíď, íɳ ďáýš ]]' + enable_google_logins: ! '[[ Éɳáƀłé Ǧóóǧłé áůťĥéɳťíčáťíóɳ ]]' + enable_yahoo_logins: ! '[[ Éɳáƀłé Ýáĥóó áůťĥéɳťíčáťíóɳ ]]' + enable_twitter_logins: ! '[[ Éɳáƀłé Ťŵíťťéř áůťĥéɳťíčáťíóɳ, řéƣůířéš ťŵíťťéř_čóɳšůɱéř_ǩéý + áɳď ťŵíťťéř_čóɳšůɱéř_šéčřéť ]]' twitter_consumer_key: ! '[[ Čóɳšůɱéř ǩéý ƒóř Ťŵíťťéř áůťĥéɳťíčáťíóɳ, řéǧíšťéřéď áť ĥťťƿ://ďéν.ťŵíťťéř.čóɱ ]]' twitter_consumer_secret: ! '[[ Čóɳšůɱéř šéčřéť ƒóř Ťŵíťťéř áůťĥéɳťíčáťíóɳ, řéǧíšťéřéď áť ĥťťƿ://ďéν.ťŵíťťéř.čóɱ ]]' + enable_facebook_logins: ! '[[ Éɳáƀłé Ƒáčéƀóóǩ áůťĥéɳťíčáťíóɳ, řéƣůířéš ƒáčéƀóóǩ_áƿƿ_íď + áɳď ƒáčéƀóóǩ_áƿƿ_šéčřéť ]]' facebook_app_id: ! '[[ Áƿƿ íď ƒóř Ƒáčéƀóóǩ áůťĥéɳťíčáťíóɳ, řéǧíšťéřéď áť ĥťťƿš://ďéνéłóƿéřš.ƒáčéƀóóǩ.čóɱ/áƿƿš ]]' facebook_app_secret: ! '[[ Áƿƿ šéčřéť ƒóř Ƒáčéƀóóǩ áůťĥéɳťíčáťíóɳ, řéǧíšťéřéď áť ĥťťƿš://ďéνéłóƿéřš.ƒáčéƀóóǩ.čóɱ/áƿƿš ]]' + enable_github_logins: ! '[[ Éɳáƀłé Ǧíťĥůƀ áůťĥéɳťíčáťíóɳ, řéƣůířéš ǧíťĥůƀ_čłíéɳť_íď + áɳď ǧíťĥůƀ_čłíéɳť_šéčřéť ]]' + github_client_id: ! '[[ Čłíéɳť íď ƒóř Ǧíťĥůƀ áůťĥéɳťíčáťíóɳ, řéǧíšťéřéď áť ĥťťƿš://ǧíťĥůƀ.čóɱ/šéťťíɳǧš/áƿƿłíčáťíóɳš + ]]' + github_client_secret: ! '[[ Čłíéɳť šéčřéť ƒóř Ǧíťĥůƀ áůťĥéɳťíčáťíóɳ, řéǧíšťéřéď + áť ĥťťƿš://ǧíťĥůƀ.čóɱ/šéťťíɳǧš/áƿƿłíčáťíóɳš ]]' allow_import: ! '[[ Áłłóŵ íɱƿóřť, ŵĥíčĥ čáɳ řéƿłáčé ÁŁŁ šíťé ďáťá; łéáνé ƒáłšé - ůɳłéšš ýóů ƿłáɳ ťó ďó íɱƿóřťš ]]' + ůɳłéšš ýóů ƿłáɳ ťó ďó ďáťá íɱƿóřťš ]]' active_user_rate_limit_secs: ! '[[ Ĥóŵ ƒřéƣůéɳťłý ŵé ůƿďáťé ťĥé ''łášť_šééɳ_áť'' ƒíéłď, íɳ šéčóɳďš ]]' previous_visit_timeout_hours: ! '[[ Ĥóŵ łóɳǧ á νíšíť łášťš ƀéƒóřé ŵé čóɳšíďéř @@ -357,21 +383,20 @@ pseudo: ýóů čáɳ čřéáťé áɳóťĥéř ťóƿíč ]]' rate_limit_create_post: ! '[[ Ĥóŵ ɱáɳý šéčóɳďš, áƒťéř čřéáťíɳǧ á ƿóšť, ƀéƒóřé ýóů čáɳ čřéáťé áɳóťĥéř ƿóšť ]]' - max_likes_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ łíǩéš á ůšéř čáɳ ƿéřƒóřɱ íɳ á ďáý ]]' - max_flags_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ ƒłáǧš á ůšéř čáɳ ƿéřƒóřɱ íɳ á ďáý ]]' - max_bookmarks_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ ƀóóǩɱářǩš á ůšéř čáɳ čřéáťé íɳ - á ďáý ]]' - max_edits_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ éďíťš á ůšéř čáɳ ƿéřƒóřɱ íɳ á ďáý ]]' - max_favorites_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ ťóƿíčš ťĥáť čáɳ ƀé ƒáνóříťéď íɳ - á ďáý ]]' - max_topics_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ ťóƿíčš á ůšéř čáɳ čřéáťé íɳ á ďáý - ]]' - max_private_messages_per_day: ! '[[ Ťĥé ɱáхíɱůɱ áɱóůɳť óƒ ƿříνáťé ɱéššáǧéš ýóů - čáɳ šéɳď íɳ á ďáý ]]' - suggested_topics: ! '[[ Ťĥé ɳůɱƀéř óƒ šůǧǧéšťéď ťóƿíčš áť ťĥé ƀóťťóɱ óƒ á ťóƿíč - ]]' + max_likes_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ łíǩéš ƿéř ůšéř ƿéř ďáý ]]' + max_flags_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ ƒłáǧš ƿéř ůšéř ƿéř ďáý ]]' + max_bookmarks_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ ƀóóǩɱářǩš ƿéř ůšéř ƿéř ďáý ]]' + max_edits_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ éďíťš ƿéř ůšéř ƿéř ďáý ]]' + max_favorites_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ ťóƿíčš ťĥáť čáɳ ƀé ƒáνóříťéď ƿéř + ůšéř ƿéř ďáý ]]' + max_topics_per_day: ! '[[ Ϻáхíɱůɱ ɳůɱƀéř óƒ ťóƿíčš á ůšéř čáɳ čřéáťé ƿéř ďáý ]]' + max_private_messages_per_day: ! '[[ Ťĥé ɱáхíɱůɱ áɱóůɳť óƒ ƿříνáťé ɱéššáǧéš ůšéřš + čáɳ čřéáťé ƿéř ďáý ]]' + suggested_topics: ! '[[ Ťĥé ɳůɱƀéř óƒ šůǧǧéšťéď ťóƿíčš šĥóŵɳ áť ťĥé ƀóťťóɱ óƒ + á ťóƿíč ]]' enable_s3_uploads: ! '[[ Рłáčé ůƿłóáďš óɳ Áɱážóɳ Š3 ]]' - s3_upload_bucket: ! '[[ Ťĥé Áɱážóɳ Š3 ƀůčǩéť ƒíłéš ŵíłł ƀé ůƿłóáďéď íɳťó ]]' + s3_upload_bucket: ! '[[ Ťĥé Áɱážóɳ Š3 ƀůčǩéť ɳáɱé ťĥáť ƒíłéš ŵíłł ƀé ůƿłóáďéď + íɳťó ]]' default_invitee_trust_level: ! '[[ Ďéƒáůłť ťřůšť łéνéł (0-5) ƒóř íɳνíťéď ůšéřš ]]' default_trust_level: ! '[[ Ďéƒáůłť ťřůšť łéνéł (0-5) ƒóř ůšéřš ]]' @@ -389,8 +414,10 @@ pseudo: šéčóɳďš ]]' max_word_length: ! '[[ Ťĥé ɱáхíɱůɱ áłłóŵéď ŵóřď łéɳǧťĥ, íɳ čĥářáčťéřš, íɳ á ťóƿíč ťíťłé ]]' - title_min_entropy: ! '[[ Ťĥé ɱíɳíɱůɱ áłłóŵéď éɳťřóƿý ƒóř á ťóƿíč ťíťłé ]]' - body_min_entropy: ! '[[ Ťĥé ɱíɳíɱůɱ áłłóŵéď éɳťřóƿý ƒóř ƿóšť ƀóďý ]]' + title_min_entropy: ! '[[ Ťĥé ɱíɳíɱůɱ áłłóŵéď éɳťřóƿý (ůɳíƣůé čĥářáčťéřš) řéƣůířéď + ƒóř á ťóƿíč ťíťłé ]]' + body_min_entropy: ! '[[ Ťĥé ɱíɳíɱůɱ áłłóŵéď éɳťřóƿý (ůɳíƣůé čĥářáčťéřš) řéƣůířéď + ƒóř á ƿóšť ƀóďý ]]' new_user_period_days: ! '[[ Ĥóŵ łóɳǧ á ůšéř íš ĥíǧĥłíǧĥťéď áš ƀéíɳǧ ɳéŵ, íɳ ďáýš ]]' title_fancy_entities: ! '[[ Čóɳνéřť ƒáɳčý ĤŤϺŁ éɳťíťíéš íɳ ťóƿíč ťíťłéš ]]' @@ -629,6 +656,11 @@ pseudo: ]]' text_body_template: ! "[[ %{username} ɱéɳťíóɳéď ýóů íɳ '%{topic_title}' óɳ %{site_name}:\n\n---\n%{message}\n\n---\nРłéášé νíšíť ťĥíš łíɳǩ ťó řéšƿóɳď: %{base_url}%{url}\n ]]" + user_posted: + subject_template: ! '[[ [%{site_name}] %{username} ƿóšťéď íɳ ''%{topic_title}'' + ]]' + text_body_template: ! "[[ %{username} ƿóšťéď íɳ '%{topic_title}' óɳ %{site_name}:\n\n---\n%{message}\n\n---\nРłéášé + νíšíť ťĥíš łíɳǩ ťó řéšƿóɳď: %{base_url}%{url}\n ]]" digest: why: ! '[[ Ĥéřé''š á ƀříéƒ šůɱɱářý óƒ ŵĥáť ĥáƿƿéɳéď óɳ %{site_link} šíɳčé ŵé łášť šáŵ ýóů óɳ %{last_seen_at}. ]]' diff --git a/lib/discourse_iife.rb b/lib/discourse_iife.rb index cd3ca206998..d3e42376cfd 100644 --- a/lib/discourse_iife.rb +++ b/lib/discourse_iife.rb @@ -4,7 +4,7 @@ class DiscourseIIFE < Sprockets::Processor def evaluate(context, locals) path = context.pathname.to_s - + # Only discourse or admin paths return data unless (path =~ /\/javascripts\/discourse/ or path =~ /\/javascripts\/admin/) @@ -13,8 +13,8 @@ class DiscourseIIFE < Sprockets::Processor # We don't add IIFEs to handlebars return data if path =~ /\.handlebars/ - return data if path =~ /\.shbrs/ - return data if path =~ /\.hbrs/ + return data if path =~ /\.shbrs/ + return data if path =~ /\.hbrs/ "(function () {\n\nvar $ = window.jQuery;\n\n#{data}\n\n})(this);" end diff --git a/spec/javascripts/spec.js b/spec/javascripts/spec.js index ad0dce77311..7a2a282b1cd 100644 --- a/spec/javascripts/spec.js +++ b/spec/javascripts/spec.js @@ -17,7 +17,6 @@ // The rest of the externals //= require_tree ../../app/assets/javascripts/external -//= require i18n //= require ../../app/assets/javascripts/discourse/helpers/i18n_helpers //= require ../../app/assets/javascripts/discourse diff --git a/sublime-project b/sublime-project index abfe4484f60..54d1c420621 100644 --- a/sublime-project +++ b/sublime-project @@ -3,7 +3,8 @@ [ { "path": "app", - "folder_exclude_patterns": ["external", "external_production", "images", "imported", "fonts", "defer"] + "folder_exclude_patterns": ["external", "external_production", "images", "imported", "fonts", "defer"], + "file_exclude_patterns": ["i18n.js"] }, { "path": "config" }, { @@ -22,4 +23,4 @@ "translate_tabs_to_spaces": true, "trim_trailing_white_space_on_save": true, } -} \ No newline at end of file +}