PERF: Compile main locale bundles just-in-time (#32335)

Previously all locale bundles would be built & compressed during
assets:precompile. For most sites, only one of these languages was
actually used, so this is fairly wasteful.

This commit moves the main locale bundle into the
ExtraLocalesController, which has recently undergone many improvements
to make it more efficient. This allows locale files to be bundled "just
in time" when they're first accessed.

Now that brotli level=6 is enabled for these assets in our nginx config,
this change should have no impact on the locale bundle size.
This commit is contained in:
David Taylor 2025-04-28 10:31:27 +01:00 committed by GitHub
parent 3b9d9f5893
commit c62a4a4759
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
68 changed files with 37 additions and 317 deletions

View File

@ -117,12 +117,6 @@ gem "net-imap", require: false
gem "net-pop", require: false gem "net-pop", require: false
gem "digest", require: false gem "digest", require: false
# Gems used only for assets and not required in production environments by default.
# Allow everywhere for now cause we are allowing asset debugging in production
group :assets do
gem "uglifier"
end
group :test do group :test do
gem "capybara", require: false gem "capybara", require: false
gem "webmock", require: false gem "webmock", require: false

View File

@ -610,8 +610,6 @@ GEM
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
tzinfo-data (1.2025.2) tzinfo-data (1.2025.2)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
uglifier (4.2.1)
execjs (>= 0.3.0, < 3)
unf (0.2.0) unf (0.2.0)
unicode-display_width (3.1.4) unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (~> 4.0, >= 4.0.4)
@ -791,7 +789,6 @@ DEPENDENCIES
thor thor
trilogy trilogy
tzinfo-data tzinfo-data
uglifier
unf unf
unicorn unicorn
web-push web-push
@ -1085,7 +1082,6 @@ CHECKSUMS
trilogy (2.9.0) sha256=a2d63b663ba68a4758e15d1f9afb228f5d16efc7fe7cea68699e1c106ef6067f trilogy (2.9.0) sha256=a2d63b663ba68a4758e15d1f9afb228f5d16efc7fe7cea68699e1c106ef6067f
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
tzinfo-data (1.2025.2) sha256=a92375a1fbb47d38fe88fd514c40a38cc8f97d168da2a6479f15185e86470939 tzinfo-data (1.2025.2) sha256=a92375a1fbb47d38fe88fd514c40a38cc8f97d168da2a6479f15185e86470939
uglifier (4.2.1) sha256=75d42b81b10bfd21e7a427fabb1d49ff5ea7bda3c4a5039ddb2a78d194c6f5aa
unf (0.2.0) sha256=e6bcc2e101d80e3f9459753db747d5926aada1aaaf61e629e93359da9a5b04ab unf (0.2.0) sha256=e6bcc2e101d80e3f9459753db747d5926aada1aaaf61e629e93359da9a5b04ab
unicode-display_width (3.1.4) sha256=8caf2af1c0f2f07ec89ef9e18c7d88c2790e217c482bfc78aaa65eadd5415ac1 unicode-display_width (3.1.4) sha256=8caf2af1c0f2f07ec89ef9e18c7d88c2790e217c482bfc78aaa65eadd5415ac1
unicode-emoji (4.0.4) sha256=2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a unicode-emoji (4.0.4) sha256=2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a

View File

@ -216,9 +216,6 @@ if (themeTestPages) {
} else { } else {
// Running with ember cli, but we want to pass through plugin request to Rails // Running with ember cli, but we want to pass through plugin request to Rails
module.exports.proxies = { module.exports.proxies = {
"/assets/locales/*.js": {
target,
},
"/assets/plugins/*_extra.js": { "/assets/plugins/*_extra.js": {
target, target,
}, },

View File

@ -53,7 +53,7 @@
<script src="{{rootURL}}assets/discourse.js"></script> <script src="{{rootURL}}assets/discourse.js"></script>
<script src="{{rootURL}}assets/locales/en.js" data-embroider-ignore></script> <script src="{{rootURL}}extra-locales/0000000000000000000000000000000000000000/en/main.js" data-embroider-ignore></script>
<script src="{{rootURL}}extra-locales/0000000000000000000000000000000000000000/en/mf.js" data-embroider-ignore></script> <script src="{{rootURL}}extra-locales/0000000000000000000000000000000000000000/en/mf.js" data-embroider-ignore></script>
<script src="{{rootURL}}extra-locales/0000000000000000000000000000000000000000/en/admin.js" data-embroider-ignore></script> <script src="{{rootURL}}extra-locales/0000000000000000000000000000000000000000/en/admin.js" data-embroider-ignore></script>
<script src="{{rootURL}}extra-locales/0000000000000000000000000000000000000000/en/wizard.js" data-embroider-ignore></script> <script src="{{rootURL}}extra-locales/0000000000000000000000000000000000000000/en/wizard.js" data-embroider-ignore></script>

View File

@ -1,11 +0,0 @@
//= depend_on 'client.ar.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:ar) %>
// original moment.js implementation can be found here:
// https://github.com/moment/moment/blob/b7ec8e2ec068e03de4f832f28362675bb9e02261/locale/ar.js#L185-L191
moment.updateLocale("ar", {
postformat(string) {
return string;
}
});

View File

@ -1,3 +0,0 @@
//= depend_on 'client.be.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:be) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.bg.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:bg) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.bs_BA.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:bs_BA) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.ca.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:ca) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.cs.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:cs) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.da.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:da) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.de.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:de) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.el.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:el) %>

View File

@ -1,5 +0,0 @@
//= depend_on 'client.en.yml'
//= require locales/i18n
<% JsLocaleHelper.reloadable_plugins(:en, self) %>
<%= JsLocaleHelper.output_locale(:en) %>

View File

@ -1,6 +0,0 @@
//= depend_on 'client.en_GB.yml'
//= depend_on 'client.en.yml'
//= require locales/i18n
<% JsLocaleHelper.reloadable_plugins(:en_GB, self) %>
<%= JsLocaleHelper.output_locale(:en_GB) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.es.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:es) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.et.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:et) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.fa_IR.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:fa_IR) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.fi.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:fi) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.fr.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:fr) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.gl.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:gl) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.he.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:he) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.hr.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:hr) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.hu.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:hu) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.hy.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:hy) %>

View File

@ -1,2 +0,0 @@
require("discourse/loader-shims");
require("discourse-i18n");

View File

@ -1,3 +0,0 @@
//= depend_on 'client.id.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:id) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.it.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:it) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.ja.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:ja) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.ko.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:ko) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.lt.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:lt) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.lv.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:lv) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.nb_NO.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:nb_NO) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.nl.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:nl) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.pl_PL.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:pl_PL) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.pt.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:pt) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.pt_BR.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:pt_BR) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.ro.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:ro) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.ru.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:ru) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.sk.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:sk) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.sl.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:sl) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.sq.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:sq) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.sr.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:sr) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.sv.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:sv) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.sw.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:sw) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.te.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:te) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.th.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:th) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.tr_TR.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:tr_TR) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.ug.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:ug) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.uk.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:uk) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.ur.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:ur) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.vi.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:vi) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.zh_CN.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:zh_CN) %>

View File

@ -1,3 +0,0 @@
//= depend_on 'client.zh_TW.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:zh_TW) %>

View File

@ -11,12 +11,13 @@ class ExtraLocalesController < ApplicationController
OVERRIDES_BUNDLE = "overrides" OVERRIDES_BUNDLE = "overrides"
SHA1_HASH_LENGTH = 40 SHA1_HASH_LENGTH = 40
MAIN_BUNDLE = "main"
MF_BUNDLE = "mf" MF_BUNDLE = "mf"
ADMIN_BUNDLE = "admin" ADMIN_BUNDLE = "admin"
WIZARD_BUNDLE = "wizard" WIZARD_BUNDLE = "wizard"
SITE_SPECIFIC_BUNDLES = [OVERRIDES_BUNDLE, MF_BUNDLE] SITE_SPECIFIC_BUNDLES = [OVERRIDES_BUNDLE, MF_BUNDLE]
SHARED_BUNDLES = [ADMIN_BUNDLE, WIZARD_BUNDLE] SHARED_BUNDLES = [MAIN_BUNDLE, ADMIN_BUNDLE, WIZARD_BUNDLE]
class << self class << self
def js_digests def js_digests
@ -63,6 +64,8 @@ class ExtraLocalesController < ApplicationController
JsLocaleHelper.output_client_overrides(locale_str) JsLocaleHelper.output_client_overrides(locale_str)
when MF_BUNDLE when MF_BUNDLE
JsLocaleHelper.output_MF(locale_str) JsLocaleHelper.output_MF(locale_str)
when MAIN_BUNDLE
JsLocaleHelper.output_locale(locale_str)
else else
JsLocaleHelper.output_extra_locales(bundle_str, locale_str) JsLocaleHelper.output_extra_locales(bundle_str, locale_str)
end end

View File

@ -36,7 +36,7 @@
<%= preload_script file %> <%= preload_script file %>
<%- end %> <%- end %>
<%= preload_script "locales/#{I18n.locale}" %> <%= preload_script_url ExtraLocalesController.url("main") %>
<%= preload_script_url ExtraLocalesController.url("mf") %> <%= preload_script_url ExtraLocalesController.url("mf") %>
<%- if ExtraLocalesController.client_overrides_exist? %> <%- if ExtraLocalesController.client_overrides_exist? %>
<%= preload_script_url ExtraLocalesController.url("overrides") %> <%= preload_script_url ExtraLocalesController.url("overrides") %>

View File

@ -10,7 +10,7 @@
<%= preload_script "test-support" %> <%= preload_script "test-support" %>
<%= preload_script "discourse" %> <%= preload_script "discourse" %>
<%= preload_script "test" %> <%= preload_script "test" %>
<%= preload_script "locales/#{I18n.locale}" %> <%= preload_script_url ExtraLocalesController.url("main") %>
<%= preload_script_url ExtraLocalesController.url("mf") %> <%= preload_script_url ExtraLocalesController.url("mf") %>
<%= preload_script "admin" %> <%= preload_script "admin" %>
<%- Discourse.find_plugin_js_assets(include_disabled: true).each do |file| %> <%- Discourse.find_plugin_js_assets(include_disabled: true).each do |file| %>

View File

@ -117,9 +117,6 @@ module Discourse
# :all can be used as a placeholder for all plugins not explicitly named. # :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ] # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Allows us to skip minification on some files
config.assets.skip_minification = []
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
config.time_zone = "UTC" config.time_zone = "UTC"

View File

@ -9,7 +9,6 @@ Rails.application.config.assets.enabled = true
Rails.application.config.assets.version = "2-#{GlobalSetting.asset_url_salt}" Rails.application.config.assets.version = "2-#{GlobalSetting.asset_url_salt}"
# Add additional assets to the asset load path. # Add additional assets to the asset load path.
Rails.application.config.assets.paths << "#{Rails.root}/config/locales"
Rails.application.config.assets.paths << "#{Rails.root}/public/javascripts" Rails.application.config.assets.paths << "#{Rails.root}/public/javascripts"
# Precompile additional assets. # Precompile additional assets.
@ -23,29 +22,12 @@ Rails.application.config.assets.precompile += [
end, end,
] ]
Rails.application.config.assets.precompile += %w[ Rails.application.config.assets.precompile += %w[break_string.js scripts/discourse-test-listen-boot]
break_string.js
locales/i18n.js
scripts/discourse-test-listen-boot
]
Rails.application.config.assets.precompile << lambda do |logical_path, filename| Rails.application.config.assets.precompile << lambda do |logical_path, filename|
filename.start_with?(EmberCli.dist_dir) && EmberCli.assets.include?(logical_path) filename.start_with?(EmberCli.dist_dir) && EmberCli.assets.include?(logical_path)
end end
# Precompile all available locales
unless GlobalSetting.try(:omit_base_locales)
Dir
.glob("#{Rails.root}/app/assets/javascripts/locales/*.js.erb")
.each do |file|
Rails
.application
.config
.assets
.precompile << "locales/#{file.match(/([a-z_A-Z]+\.js)\.erb$/)[1]}"
end
end
# out of the box sprockets 3 grabs loose files that are hanging in assets, # out of the box sprockets 3 grabs loose files that are hanging in assets,
# the exclusion list does not include hbs so you double compile all this stuff # the exclusion list does not include hbs so you double compile all this stuff
Rails.application.config.assets.precompile.delete(Sprockets::Railtie::LOOSE_APP_ASSETS) Rails.application.config.assets.precompile.delete(Sprockets::Railtie::LOOSE_APP_ASSETS)

View File

@ -187,7 +187,10 @@ module JsLocaleHelper
translations = translations_for(locale_str) translations = translations_for(locale_str)
remove_message_formats!(translations, locale) remove_message_formats!(translations, locale)
result = +"" result = +<<~JS
require("discourse/loader-shims");
require("discourse-i18n");
JS
translations.keys.each do |l| translations.keys.each do |l|
translations[l].keys.each { |k| translations[l].delete(k) unless k == "js" } translations[l].keys.each { |k| translations[l].delete(k) unless k == "js" }

View File

@ -1429,7 +1429,6 @@ class Plugin::Instance
"" ""
opts[:server_locale_file] = Dir["#{root_path}/config/locales/server*.#{locale}.yml"].first || opts[:server_locale_file] = Dir["#{root_path}/config/locales/server*.#{locale}.yml"].first ||
"" ""
opts[:js_locale_file] = File.join(root_path, "assets/locales/#{locale}.js.erb")
locale_chain = opts[:fallbackLocale] ? [locale, opts[:fallbackLocale]] : [locale] locale_chain = opts[:fallbackLocale] ? [locale, opts[:fallbackLocale]] : [locale]
lib_locale_path = File.join(root_path, "lib/javascripts/locale") lib_locale_path = File.join(root_path, "lib/javascripts/locale")
@ -1522,8 +1521,7 @@ class Plugin::Instance
def valid_locale?(custom_locale) def valid_locale?(custom_locale)
File.exist?(custom_locale[:client_locale_file]) && File.exist?(custom_locale[:client_locale_file]) &&
File.exist?(custom_locale[:server_locale_file]) && File.exist?(custom_locale[:server_locale_file]) && custom_locale[:moment_js]
File.exist?(custom_locale[:js_locale_file]) && custom_locale[:moment_js]
end end
def find_locale_file(locale_chain, path) def find_locale_file(locale_chain, path)

View File

@ -34,7 +34,6 @@ task "assets:precompile:before": %w[
assets:precompile:prereqs assets:precompile:prereqs
assets:precompile:build assets:precompile:build
] do ] do
require "uglifier"
require "open3" require "open3"
# Ensure we ALWAYS do a clean build # Ensure we ALWAYS do a clean build
@ -42,13 +41,8 @@ task "assets:precompile:before": %w[
STDERR.puts "Purging temp files" STDERR.puts "Purging temp files"
`rm -fr #{Rails.root}/tmp/cache` `rm -fr #{Rails.root}/tmp/cache`
$node_compress = !ENV["SKIP_NODE_UGLIFY"] Rails.configuration.assets.js_compressor = nil
Rails.configuration.assets.gzip = false
unless ENV["USE_SPROCKETS_UGLIFY"]
$bypass_sprockets_uglify = true
Rails.configuration.assets.js_compressor = nil
Rails.configuration.assets.gzip = false
end
STDERR.puts "Bundling assets" STDERR.puts "Bundling assets"
@ -127,54 +121,12 @@ def cdn_relative_path(p)
global_path_klass.cdn_relative_path(p) global_path_klass.cdn_relative_path(p)
end end
def compress_node(from, to)
to_path = "#{assets_path}/#{to}"
assets = cdn_relative_path("/assets")
assets_additional_path = (d = File.dirname(from)) == "." ? "" : "/#{d}"
source_map_root = assets + assets_additional_path
source_map_url = "#{File.basename(to)}.map"
base_source_map = assets_path + assets_additional_path
cmd = <<~SH
pnpm terser '#{assets_path}/#{from}' -m -c -o '#{to_path}' --source-map "base='#{base_source_map}',root='#{source_map_root}',url='#{source_map_url}',includeSources=true"
SH
STDERR.puts cmd
result = `#{cmd} 2>&1`
unless $?.success?
STDERR.puts result
exit 1
end
result
end
def compress_ruby(from, to)
data = File.read("#{assets_path}/#{from}")
uglified, map =
Uglifier.new(
comments: :none,
source_map: {
filename: File.basename(from),
output_filename: File.basename(to),
},
).compile_with_map(data)
dest = "#{assets_path}/#{to}"
File.write(dest, uglified << "\n//# sourceMappingURL=#{cdn_path "/assets/#{to}.map"}")
File.write(dest + ".map", map)
GC.start
end
def gzip(path) def gzip(path)
STDERR.puts "gzip -f -c -9 #{path} > #{path}.gz" STDERR.puts "gzip -f -c -9 #{path} > #{path}.gz"
STDERR.puts `gzip -f -c -9 #{path} > #{path}.gz`.strip STDERR.puts `gzip -f -c -9 #{path} > #{path}.gz`.strip
raise "gzip compression failed: exit code #{$?.exitstatus}" if $?.exitstatus != 0 raise "gzip compression failed: exit code #{$?.exitstatus}" if $?.exitstatus != 0
end end
# different brotli versions use different parameters
def brotli_command(path) def brotli_command(path)
compression_quality = ENV["DISCOURSE_ASSETS_PRECOMPILE_DEFAULT_BROTLI_QUALITY"] || "6" compression_quality = ENV["DISCOURSE_ASSETS_PRECOMPILE_DEFAULT_BROTLI_QUALITY"] || "6"
"brotli -f --quality=#{compression_quality} #{path} --output=#{path}.br" "brotli -f --quality=#{compression_quality} #{path} --output=#{path}.br"
@ -188,21 +140,6 @@ def brotli(path)
raise "chmod failed: exit code #{$?.exitstatus}" if $?.exitstatus != 0 raise "chmod failed: exit code #{$?.exitstatus}" if $?.exitstatus != 0
end end
def max_compress?(path, locales)
return false if Rails.configuration.assets.skip_minification.include? path
return false if EmberCli.is_ember_cli_asset?(path)
return true if path.exclude? "locales/"
path_locale = path.delete_prefix("locales/").delete_suffix(".js")
return true if locales.include? path_locale
false
end
def compress(from, to)
$node_compress ? compress_node(from, to) : compress_ruby(from, to)
end
def concurrent? def concurrent?
if ENV["SPROCKETS_CONCURRENT"] == "1" if ENV["SPROCKETS_CONCURRENT"] == "1"
concurrent_compressors = [] concurrent_compressors = []
@ -249,30 +186,13 @@ task "assets:precompile:compress_js": "environment" do
.select { |k, v| k =~ /\.js\z/ } .select { |k, v| k =~ /\.js\z/ }
.each do |file, info| .each do |file, info|
path = "#{assets_path}/#{file}" path = "#{assets_path}/#{file}"
_file = if file.include? "discourse/tests"
(
if (d = File.dirname(file)) == "."
"_#{file}"
else
"#{d}/_#{File.basename(file)}"
end
)
_path = "#{assets_path}/#{_file}"
max_compress = max_compress?(info["logical_path"], locales)
if File.exist?(_path)
STDERR.puts "Skipping: #{file} already compressed"
elsif file.include? "discourse/tests"
STDERR.puts "Skipping: #{file}" STDERR.puts "Skipping: #{file}"
else else
proc.call do proc.call do
log_task_duration(file) do log_task_duration(file) do
STDERR.puts "Compressing: #{file}" STDERR.puts "Compressing: #{file}"
if max_compress
FileUtils.mv(path, _path)
compress(_file, file)
end
info["size"] = File.size(path) info["size"] = File.size(path)
info["mtime"] = File.mtime(path).iso8601 info["mtime"] = File.mtime(path).iso8601
gzip(path) gzip(path)

View File

@ -1,2 +0,0 @@
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:foo_BAR) %>

View File

@ -1,2 +0,0 @@
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:tlh) %>

View File

@ -1,2 +0,0 @@
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:tup) %>

View File

@ -22,12 +22,9 @@ RSpec.describe JsLocaleHelper do
ctx.eval <<~JS ctx.eval <<~JS
define("discourse/loader-shims", () => {}) define("discourse/loader-shims", () => {})
define("discourse/lib/load-moment", () => {}) define("discourse/lib/load-moment", () => {})
moment = { defineLocale: () => {}, fn: {}, tz: {} } require("discourse-i18n");
globalThis.moment = { defineLocale: () => {}, fn: {}, tz: {} }
JS JS
# As there are circular references in the return value, this raises an
# error if we let MiniRacer try to convert the value to JSON. Forcing
# returning `null` from `#eval` will prevent that.
ctx.eval("#{File.read("#{Rails.root}/app/assets/javascripts/locales/i18n.js")};null")
ctx ctx
end end

View File

@ -607,7 +607,6 @@ TEXT
config/locales/client.foo_BAR.yml config/locales/client.foo_BAR.yml
config/locales/server.foo_BAR.yml config/locales/server.foo_BAR.yml
lib/javascripts/locale/moment_js/foo_BAR.js lib/javascripts/locale/moment_js/foo_BAR.js
assets/locales/foo_BAR.js.erb
].each do |path| ].each do |path|
it "does not register a new locale when #{path} is missing" do it "does not register a new locale when #{path} is missing" do
path = "#{plugin_path}/#{path}" path = "#{plugin_path}/#{path}"

View File

@ -1074,11 +1074,13 @@ RSpec.describe ApplicationController do
{ HTTP_ACCEPT_LANGUAGE: locale } { HTTP_ACCEPT_LANGUAGE: locale }
end end
def locale_scripts(body) def main_locale_scripts(body)
Nokogiri::HTML5 Nokogiri::HTML5
.parse(body) .parse(body)
.css('script[src*="assets/locales/"]') .css('script[src*="extra-locales/"]')
.map { |script| script.attributes["src"].value } .filter_map do |script|
script.attributes["src"].to_s[%r{extra-locales/[^/]+/([^/]+)/main.js}, 1]
end
end end
context "with allow_user_locale disabled" do context "with allow_user_locale disabled" do
@ -1092,7 +1094,7 @@ RSpec.describe ApplicationController do
it "uses the default locale" do it "uses the default locale" do
get "/latest", headers: headers("fr") get "/latest", headers: headers("fr")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/en.js") expect(main_locale_scripts(response.body)).to contain_exactly("en")
end end
end end
@ -1103,7 +1105,7 @@ RSpec.describe ApplicationController do
get "/latest", headers: headers("fr") get "/latest", headers: headers("fr")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/en.js") expect(main_locale_scripts(response.body)).to contain_exactly("en")
end end
end end
end end
@ -1121,13 +1123,13 @@ RSpec.describe ApplicationController do
it "uses the locale from the headers" do it "uses the locale from the headers" do
get "/latest", headers: headers("fr") get "/latest", headers: headers("fr")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/fr.js") expect(main_locale_scripts(response.body)).to contain_exactly("fr")
end end
it "doesn't leak after requests" do it "doesn't leak after requests" do
get "/latest", headers: headers("fr") get "/latest", headers: headers("fr")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/fr.js") expect(main_locale_scripts(response.body)).to contain_exactly("fr")
expect(I18n.locale.to_s).to eq(SiteSettings::DefaultsProvider::DEFAULT_LOCALE) expect(I18n.locale.to_s).to eq(SiteSettings::DefaultsProvider::DEFAULT_LOCALE)
end end
end end
@ -1140,7 +1142,7 @@ RSpec.describe ApplicationController do
it "uses the user's preferred locale" do it "uses the user's preferred locale" do
get "/latest", headers: headers("fr") get "/latest", headers: headers("fr")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/fr.js") expect(main_locale_scripts(response.body)).to contain_exactly("fr")
end end
it "serves a 404 page in the preferred locale" do it "serves a 404 page in the preferred locale" do
@ -1153,7 +1155,7 @@ RSpec.describe ApplicationController do
it "serves a RenderEmpty page in the preferred locale" do it "serves a RenderEmpty page in the preferred locale" do
get "/u/#{user.username}/preferences/interface" get "/u/#{user.username}/preferences/interface"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.body).to have_tag("script", with: { src: "/assets/locales/fr.js" }) expect(main_locale_scripts(response.body)).to contain_exactly("fr")
end end
end end
end end
@ -1166,7 +1168,7 @@ RSpec.describe ApplicationController do
get "/latest", headers: headers("zh-CN") get "/latest", headers: headers("zh-CN")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/zh_CN.js") expect(main_locale_scripts(response.body)).to contain_exactly("zh_CN")
end end
end end
@ -1177,7 +1179,7 @@ RSpec.describe ApplicationController do
get "/latest", headers: headers("") get "/latest", headers: headers("")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/en.js") expect(main_locale_scripts(response.body)).to contain_exactly("en")
end end
end end
end end
@ -1194,7 +1196,7 @@ RSpec.describe ApplicationController do
it "uses the locale from the cookie" do it "uses the locale from the cookie" do
get "/latest", headers: { Cookie: "locale=es" } get "/latest", headers: { Cookie: "locale=es" }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/es.js") expect(main_locale_scripts(response.body)).to contain_exactly("es")
expect(I18n.locale.to_s).to eq(SiteSettings::DefaultsProvider::DEFAULT_LOCALE) # doesn't leak after requests expect(I18n.locale.to_s).to eq(SiteSettings::DefaultsProvider::DEFAULT_LOCALE) # doesn't leak after requests
end end
end end
@ -1203,7 +1205,7 @@ RSpec.describe ApplicationController do
it "returns the locale and region separated by an underscore" do it "returns the locale and region separated by an underscore" do
get "/latest", headers: { Cookie: "locale=zh-CN" } get "/latest", headers: { Cookie: "locale=zh-CN" }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/zh_CN.js") expect(main_locale_scripts(response.body)).to contain_exactly("zh_CN")
end end
end end
end end
@ -1215,7 +1217,7 @@ RSpec.describe ApplicationController do
get "/latest", headers: { Cookie: "" } get "/latest", headers: { Cookie: "" }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/en.js") expect(main_locale_scripts(response.body)).to contain_exactly("en")
end end
end end
end end
@ -1232,7 +1234,7 @@ RSpec.describe ApplicationController do
it "uses the locale from the param" do it "uses the locale from the param" do
get "/latest?lang=es" get "/latest?lang=es"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/es.js") expect(main_locale_scripts(response.body)).to contain_exactly("es")
expect(I18n.locale.to_s).to eq(SiteSettings::DefaultsProvider::DEFAULT_LOCALE) # doesn't leak after requests expect(I18n.locale.to_s).to eq(SiteSettings::DefaultsProvider::DEFAULT_LOCALE) # doesn't leak after requests
end end
end end
@ -1241,7 +1243,7 @@ RSpec.describe ApplicationController do
it "returns the locale and region separated by an underscore" do it "returns the locale and region separated by an underscore" do
get "/latest?lang=zh-CN" get "/latest?lang=zh-CN"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/zh_CN.js") expect(main_locale_scripts(response.body)).to contain_exactly("zh_CN")
end end
end end
end end
@ -1253,7 +1255,7 @@ RSpec.describe ApplicationController do
get "/latest" get "/latest"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/en.js") expect(main_locale_scripts(response.body)).to contain_exactly("en")
end end
end end
end end