# frozen_string_literal: true class Admin::SiteTextsController < Admin::AdminController def self.preferred_keys %w[ system_messages.usage_tips.text_body_template education.new-topic education.new-reply login_required.welcome_message ] end def self.restricted_keys %w[ user_notifications.confirm_old_email.title user_notifications.confirm_old_email.subject_template user_notifications.confirm_old_email.text_body_template ] end def index overridden = params[:overridden] == "true" outdated = params[:outdated] == "true" untranslated = params[:untranslated] == "true" only_selected_locale = params[:only_selected_locale] == "true" extras = {} query = params[:q] || "" locale = fetch_locale(params[:locale]) if query.blank? && !overridden && !outdated && !untranslated extras[:recommended] = true results = self.class.preferred_keys.map { |k| record_for(key: k, locale: locale) } else results = find_translations(query, overridden, outdated, locale, untranslated, only_selected_locale) if results.any? extras[:regex] = I18n::Backend::DiscourseI18n.create_search_regexp(query, as_string: true) end results.sort! do |x, y| if x[:value].casecmp(query) == 0 -1 elsif y[:value].casecmp(query) == 0 1 else (x[:id].size + x[:value].size) <=> (y[:id].size + y[:value].size) end end end page = params[:page].to_i raise Discourse::InvalidParameters.new(:page) if page < 0 per_page = 50 first = page * per_page last = first + per_page extras[:has_more] = true if results.size > last if LocaleSiteSetting.fallback_locale(locale).present? extras[:fallback_locale] = LocaleSiteSetting.fallback_locale(locale) end overridden = overridden_keys(locale) render_serialized( results[first..last - 1], SiteTextSerializer, root: "site_texts", rest_serializer: true, extras: extras, overridden_keys: overridden, ) end def show locale = fetch_locale(params[:locale]) site_text = find_site_text(locale) render_serialized(site_text, SiteTextSerializer, root: "site_text", rest_serializer: true) end def update locale = fetch_locale(params.dig(:site_text, :locale)) site_text = find_site_text(locale) value = site_text[:value] = params.dig(:site_text, :value) id = site_text[:id] old_value = I18n.with_locale(locale) { I18n.t(id) } translation_override = TranslationOverride.upsert!(locale, id, value) if translation_override.errors.empty? StaffActionLogger.new(current_user).log_site_text_change(id, value, old_value) system_badge_id = Badge.find_system_badge_id_from_translation_key(id) if system_badge_id.present? && is_badge_title?(id) Jobs.enqueue( :bulk_user_title_update, new_title: value, granted_badge_id: system_badge_id, action: Jobs::BulkUserTitleUpdate::UPDATE_ACTION, ) end render_serialized(site_text, SiteTextSerializer, root: "site_text", rest_serializer: true) else render json: failed_json.merge(message: translation_override.errors.full_messages.join("\n\n")), status: 422 end end def revert locale = fetch_locale(params[:locale]) site_text = find_site_text(locale) id = site_text[:id] old_text = I18n.with_locale(locale) { I18n.t(id) } TranslationOverride.revert!(locale, id) site_text = find_site_text(locale) StaffActionLogger.new(current_user).log_site_text_change(id, site_text[:value], old_text) system_badge_id = Badge.find_system_badge_id_from_translation_key(id) if system_badge_id.present? Jobs.enqueue( :bulk_user_title_update, granted_badge_id: system_badge_id, action: Jobs::BulkUserTitleUpdate::RESET_ACTION, ) end render_serialized(site_text, SiteTextSerializer, root: "site_text", rest_serializer: true) end def dismiss_outdated locale = fetch_locale(params[:locale]) override = TranslationOverride.find_by(locale: locale, translation_key: params[:id]) raise Discourse::NotFound if override.blank? if override.make_up_to_date! render json: success_json else render json: failed_json.merge(message: "Can only dismiss outdated translations"), status: 422 end end def get_reseed_options render_json_dump( categories: SeedData::Categories.with_default_locale.reseed_options, topics: SeedData::Topics.with_default_locale.reseed_options, ) end def reseed hijack do if params[:category_ids].present? SeedData::Categories.with_default_locale.update(site_setting_names: params[:category_ids]) end if params[:topic_ids].present? SeedData::Topics.with_default_locale.update(site_setting_names: params[:topic_ids]) end render json: success_json end end protected def is_badge_title?(id = "") badge_parts = id.split(".") badge_parts[0] == "badges" && badge_parts[2] == "name" end def record_for(key:, value: nil, locale:) en_key = TranslationOverride.transform_pluralized_key(key) value ||= I18n.with_locale(locale) { I18n.t(key) } interpolation_keys = I18nInterpolationKeysFinder.find(I18n.overrides_disabled { I18n.t(en_key, locale: :en) }) custom_keys = TranslationOverride.custom_interpolation_keys(en_key) { id: key, value: value, locale: locale, interpolation_keys: interpolation_keys + custom_keys } end PLURALIZED_REGEX = /(.*)\.(zero|one|two|few|many|other)\z/ def find_site_text(locale) if self.class.restricted_keys.include?(params[:id]) raise Discourse::InvalidAccess.new( nil, nil, custom_message: "email_template_cant_be_modified", ) end if I18n.exists?(params[:id], locale) || TranslationOverride.exists?(locale: locale, translation_key: params[:id]) return record_for(key: params[:id], locale: locale) end if PLURALIZED_REGEX.match(params[:id]) value = fix_plural_keys($1, {}, locale).detect { |plural| plural[0] == $2.to_sym } return record_for(key: params[:id], value: value[1], locale: value[2]) if value end raise Discourse::NotFound end def find_translations(query, overridden, outdated, locale, untranslated, only_selected_locale) translations = Hash.new { |hash, key| hash[key] = {} } search_results = I18n.with_locale(locale) do I18n.search(query, only_overridden: overridden, only_untranslated: untranslated) end if outdated outdated_keys = TranslationOverride.where(status: %i[outdated invalid_interpolation_keys]).pluck( :translation_key, ) search_results.select! { |k, _| outdated_keys.include?(k) } end search_results.each do |key, value| if PLURALIZED_REGEX.match(key) translations[$1][$2] = value else translations[key] = value end end results = [] translations.each do |key, value| next unless I18n.exists?(key, :en) if value.is_a?(Hash) fix_plural_keys(key, value, locale, only_selected_locale).each do |plural| plural_key = plural[0] plural_value = plural[1] results << record_for( key: "#{key}.#{plural_key}", value: plural_value, locale: plural.last, ) end else value = I18n.with_locale(locale) { I18n.t(key) } if only_selected_locale results << record_for(key: key, value: value, locale: locale) end end results end def fix_plural_keys(key, value, locale, only_selected_locale = false) value = value.with_indifferent_access plural_keys = I18n.with_locale(locale) { I18n.t("i18n.plural.keys") } return value if value.keys.size == plural_keys.size && plural_keys.all? { |k| value.key?(k) } fallback_value = I18n.t(key, locale: :en, default: {}) plural_keys.map do |k| if value[k] value[k] = I18n.with_locale(locale) { I18n.t("#{key}.#{k}") } if only_selected_locale [k, value[k], locale] else [k, fallback_value[k] || fallback_value[:other], :en] end end end def overridden_keys(locale) TranslationOverride.where(locale: locale).pluck(:translation_key) end def fetch_locale(locale_from_params) locale_from_params.tap do |locale| if locale.blank? || !I18n.locale_available?(locale) raise Discourse::InvalidParameters.new(:locale) end end end end