2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-03-04 01:15:08 +08:00
discourse/app/controllers/extra_locales_controller.rb
David Taylor d7de0f6735
DEV: Refactor locale bundle loading (#37114)
Previously, locale bundles were using `require()` directly, and were
very sensitive to load order. This made it very hard to refactor things,
especially for our upcoming move to Vite.

This commit updates the four types of locale bundle to contain only very
simple JS code, with no dependencies at the top level. They simply add
POJO and functions to the `window._discourse_locale_data` global. When
`discourse-i18n` is loaded, it checks that global, loads up the data,
and executes the functions (passing in any dependencies like
messageformat/runtime where required).

Changes in the HTML files are to bring the locale bundles up to the
first position, since they no longer have any dependencies, and we need
to be 100% sure they're loaded before discourse-i18n.

Tests are updated for the new bundle format, and the most complex ones
have been converted to system specs, so that we no longer need to create
a mock environment for executing messageformat/discourse-i18n/etc.

`lib/deprecated` changes (and associated pretty-text changes) are to
make the dependencies more formalized, because `require()` is subject to
race conditions, which started being hit following the locale refactor.

---------

Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2026-01-16 11:45:14 +00:00

121 lines
3.5 KiB
Ruby

# frozen_string_literal: true
class ExtraLocalesController < ApplicationController
layout false
skip_before_action :check_xhr,
:preload_json,
:redirect_to_login_if_required,
:redirect_to_profile_if_required,
:verify_authenticity_token
before_action :is_asset_path, :apply_cdn_headers
OVERRIDES_BUNDLE = "overrides"
SHA1_HASH_LENGTH = 40
MAIN_BUNDLE = "main"
MF_BUNDLE = "mf"
ADMIN_BUNDLE = "admin"
WIZARD_BUNDLE = "wizard"
CACHE_VERSION = 3
SITE_SPECIFIC_BUNDLES = [OVERRIDES_BUNDLE, MF_BUNDLE]
SHARED_BUNDLES = [MAIN_BUNDLE, ADMIN_BUNDLE, WIZARD_BUNDLE]
class << self
def js_digests
@js_digests ||= { site_specific: {}, shared: {} }
end
def bundle_js_hash(bundle, locale:)
bundle_key = "#{bundle}_#{locale}"
if bundle.in?(SITE_SPECIFIC_BUNDLES)
site = RailsMultisite::ConnectionManagement.current_db
js_digests[:site_specific][site] ||= {}
js_digests[:site_specific][site][bundle_key] ||= begin
js = bundle_js(bundle, locale: locale)
js.present? ? digest_for_content(js) : nil
end
elsif bundle.in?(SHARED_BUNDLES)
js_digests[:shared][bundle_key] ||= digest_for_content(bundle_js(bundle, locale: locale))
else
raise "Unknown bundle: #{bundle}"
end
end
def url(bundle, locale: I18n.locale)
hash = bundle_js_hash(bundle, locale:)
base = "#{GlobalSetting.cdn_url}#{Discourse.base_path}"
path = "/extra-locales/#{hash}/#{locale}/#{bundle}.js"
query = SITE_SPECIFIC_BUNDLES.include?(bundle) ? "?__ws=#{Discourse.current_hostname}" : ""
"#{base}#{path}#{query}"
end
def client_overrides_exist?(locale: I18n.locale)
bundle_js_hash(OVERRIDES_BUNDLE, locale: locale).present?
end
def bundle_js(bundle, locale:)
locale_str = locale.to_s
bundle_str = "#{bundle}_js"
I18n.with_locale(locale) do
case bundle
when OVERRIDES_BUNDLE
JsLocaleHelper.output_client_overrides(locale_str)
when MF_BUNDLE
JsLocaleHelper.output_MF(locale_str)
when MAIN_BUNDLE
JsLocaleHelper.output_locale(locale_str)
else
JsLocaleHelper.output_extra_locales(bundle_str, locale_str)
end
end
end
def bundle_js_with_hash(bundle, locale:)
js = bundle_js(bundle, locale: locale)
[js, digest_for_content(js)]
end
def clear_cache!(all_sites: false)
site = RailsMultisite::ConnectionManagement.current_db
if all_sites
js_digests[:site_specific].clear
js_digests[:shared].clear
else
js_digests[:site_specific].delete(site)
end
end
def digest_for_content(js)
Digest::SHA1.hexdigest("#{CACHE_VERSION}|#{js}")
end
end
def show
bundle = params[:bundle]
raise Discourse::NotFound if !valid_bundle?(bundle)
locale = params[:locale]
raise Discourse::NotFound if !I18n.available_locales.include?(locale.to_sym)
digest = params[:digest]
if digest.present?
raise Discourse::InvalidParameters.new(:digest) unless digest.to_s.size == SHA1_HASH_LENGTH
end
content, hash = ExtraLocalesController.bundle_js_with_hash(bundle, locale:)
immutable_for(1.year) if hash == digest
render plain: content, content_type: "application/javascript"
end
private
def valid_bundle?(bundle)
bundle.in?(SITE_SPECIFIC_BUNDLES) || bundle.in?(SHARED_BUNDLES)
end
end