Module: Controllers::Localizable

Extended by:
ActiveSupport::Concern
Included in:
ApplicationController
Defined in:
app/concerns/controllers/localizable.rb

Instance Method Summary collapse

Instance Method Details

#cloudflare_country_localeObject



136
137
138
139
140
141
142
143
144
145
146
# File 'app/concerns/controllers/localizable.rb', line 136

def cloudflare_country_locale
  return nil unless request.env['HTTP_CF_IPCOUNTRY'].present?
  return nil if request.env['HTTP_CF_IPCOUNTRY'] == 'XX'

  case request.env['HTTP_CF_IPCOUNTRY'].upcase
  when 'CA', 'CANADA'
    :'en-CA'
  else
    :'en-US'
  end
end

#determine_request_localeObject



177
178
179
180
181
182
183
184
185
# File 'app/concerns/controllers/localizable.rb', line 177

def determine_request_locale
  # In case one day you have french vs english in canada you might need this.
  # browser_locale = request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}-[A-Z]{2}/).first.try(:to_sym) if request.env['HTTP_ACCEPT_LANGUAGE'].present?
  request_locale = param_locale
  request_locale ||= warmlyyours_ip_locale if is_crm_request? # Ramie 072622 just skip this nanny featurefor non-CRM requests
  request_locale ||= cloudflare_country_locale
  request_locale ||= geocoder_locale
  request_locale
end

#geocoder_locale(location = nil) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'app/concerns/controllers/localizable.rb', line 148

def geocoder_locale(location = nil)
  location ||= request.location
  return unless location

  country = location.country
  return unless country

  case country.upcase
  when 'CA', 'CANADA'
    :'en-CA'
  else
    :'en-US'
  end
end

#guest_user_locale_check(param_locale) ⇒ Object

Check if the guest user's locale matches the current set locale
If not then we will attempt to switch catalog
Method returns the locale to use for the guest user
If a change of catalog was not possible for instance
the returned value will be the original locale
otherwise it will be the new locale



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'app/concerns/controllers/localizable.rb', line 193

def guest_user_locale_check(param_locale)
  # Only check if context user is a guest and if its not an exception
  # This method is just for party typpes responding to guest
  logger.tagged('guest_user_locale_check') do
    # For bots, always honor the URL locale if provided to avoid cross-locale locking
    if bot_request? && param_locale.present?
      logger.debug "Bot request detected, honoring param locale #{param_locale}"
      return param_locale
    end
    # Never change catalog for non-guest active customers (e.g., Prospects). Always keep their
    # existing locale and let set_locale redirect to the matching URL.
    unless @context_user.state.to_s == 'guest'
      logger.debug "Non-guest user (state=#{@context_user.state}) detected; preserving locale #{@context_user.locale}"
      return @context_user.locale
    end
    unless @context_user.can_change_country?
      logger.debug "Context user can't change country, returning #{@context_user.locale}"
      return @context_user.locale
    end
    unless param_locale.present?
      logger.debug "No param locale set, returning guest's native locale"
      return @context_user.locale
    end
    param_locale_catalog = Catalog.locale_to_catalog(param_locale)
    if Catalog.locale_to_catalog(@context_user.locale) != param_locale_catalog
      logger.debug "Guest wants a different locale/catalog than previously set, guest user locale: #{@context_user.locale}, requested locale #{param_locale}"
      # Guest user gets converted, others get redirected
      new_catalog = param_locale_catalog
      result = @context_user.change_catalog(new_catalog)
      if result.catalog_assigned?
        logger.debug "Successfully switched to requested catalog #{new_catalog.id}"
        @context_user.reload
      else
        logger.error "Failed to switch to requested catalog #{new_catalog.id} failed #{result.messages.to_sentence}"
        flash[:warning] = "Your account could not be switched to #{param_locale}. #{result.messages.to_sentence}"
      end
    end
    return @context_user.locale
  end
end

#locale_optional_www_auth_path?Boolean

True when the request uses www locale-optional auth paths without a /en-US/ prefix.

Returns:

  • (Boolean)


256
257
258
259
260
261
262
263
264
# File 'app/concerns/controllers/localizable.rb', line 256

def locale_optional_www_auth_path?
  return false unless is_www_request?
  return false if params[:locale].present?

  # /masquerade/* is whitelisted here so the signed, single-use handoff token
  # is NOT re-issued through a locale redirect, which would cause the token
  # to be consumed twice (failing the second request with a ReplayError).
  request.path =~ %r{\A/(accounts|customer_sessions|masquerade)(/|\z)}i
end

#param_localeObject



173
174
175
# File 'app/concerns/controllers/localizable.rb', line 173

def param_locale
  params[:locale].presence&.to_sym
end

#set_localeObject

Phase 2: User-aware locale finalization (runs AFTER init_current_user)
Handles logged-in user locale preferences, guest locale switching, and redirects



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/concerns/controllers/localizable.rb', line 44

def set_locale
  logger.tagged 'set_locale' do
    return true if skip_localization?

    logger.debug "request.fullpath: #{request.fullpath}"
    logger.debug "params[:locale]: #{params[:locale]}"
    valid_locales = LocaleUtility.service_locales
    request_locale = determine_request_locale
    if valid_locales.include?(request_locale)
      logger.debug "Request locale valid: #{request_locale || 'undeclared'}"
      param_locale = params[:locale]&.to_sym
    else
      logger.error "Request locale invalid: #{request_locale || 'undeclared'}"
      request_locale = nil
      param_locale = nil
    end
    # If URL carries a locale, honor it for bots/guests to keep server locale aligned with the path
    # This prevents cases where a previously stored guest locale forces CAD on en-US URLs for crawlers
    #
    # Use the already-validated `param_locale` (nil when params[:locale]
    # was absent or rejected by the valid_locales check above) so an
    # unsupported URL locale doesn't bypass that gate and reach the
    # locale writers.
    if param_locale.present? && (bot_request? || current_user.nil?)
      apply_locale(param_locale)
    end

    # Is there a current logged in user? we always use that user's locale and there's no switching
    reason_for_switch = nil
    if current_user
      previous_context_user_locale = current_user.locale
      apply_locale(current_user.locale) # which is catalog based
      logger.debug "Current user detected with locale #{current_user.locale}, setting locale to #{I18n.locale}"
      reason_for_switch = "params locale #{param_locale} is different from the logged in user's current_user.locale of #{current_user.locale} which must stay in effect" if previous_context_user_locale&.to_sym != param_locale
    # Guest user or previously logged in user
    elsif @context_user && param_locale.present?
      previous_context_user_locale = @context_user.locale
      apply_locale(guest_user_locale_check(param_locale)) # Check if this locale switch is possible
      reason_for_switch = "params locale #{param_locale} is different from the user's previously set locale/catalog #{previous_context_user_locale}" if previous_context_user_locale&.to_sym != param_locale
    # Unlikely but a visit without a context user
    elsif request_locale
      apply_locale(request_locale)
    else # Fallback default when all else fails since most users are US
      apply_locale(:'en-US')
    end
    logger.debug "Final Locale: #{I18n.locale}"
    # Suppress reason logging for CRM requests
    reason_for_switch = nil if is_crm_request?
    logger.debug "User locale switched to #{I18n.locale}, reason: #{reason_for_switch}" if reason_for_switch.present?

    return true if request.xhr? # No redirection needed
    # CRM locale is driven by the logged-in user, not the URL. CRM routes
    # have no locale prefix, so param_locale is always nil there. Skip the
    # locale-mismatch redirect entirely for CRM to avoid an infinite loop.
    return true if is_crm_request?

    # Devise routes live under scope '(/:locale)' in www routes, so /accounts/… and
    # /en-US/accounts/… are both valid. When the locale segment is omitted,
    # params[:locale] is nil while I18n.locale is still set (e.g. en-US) from
    # set_request_locale — the mismatch branch below would redirect POST
    # /accounts/password to GET /en-US/accounts/password and halt before the action
    # (no email sent, CMS 404 on follow-up).
    return true if locale_optional_www_auth_path?

    if param_locale == I18n.locale
      # This instance variable is a default country switcher, in some circumstances, another controller action
      # might want to modify this, for instance, if a product is not available in the other locale, then you
      # would want to change the href to a generic product page.
      @country_switch_options = {
        'USA' => {
          href: cms_link(request.url, :'en-US', strip_params: true),
          locale: :'en-US',
          flag_image_src: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==',
          svg_src: 'svgs/custom/us.svg'
        },
        'Canada' => {
          href: cms_link(request.url, :'en-CA', strip_params: true),
          locale: :'en-CA',
          flag_image_src: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==',
          svg_src: 'svgs/custom/ca.svg'
        }
      }
      return true
    else
      logger.debug "User locale switched to #{I18n.locale}"
      # flash[:info] = "Your locale was set to #{I18n.locale}"
      redirect_to(cms_link(request.fullpath)) # The locale redirection should be a 302 by default not 301
    end
  end
end

#set_request_localeObject

Phase 1: Set basic locale from request before user initialization
This runs BEFORE init_current_user so guest user creation can use I18n.locale
Only sets I18n.locale based on URL params, Cloudflare geo, or geocoder
Does NOT do any user-specific logic or redirects



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'app/concerns/controllers/localizable.rb', line 10

def set_request_locale
  # Unconditional baseline write BEFORE any early return. Guarantees every
  # request that touches this concern starts at a known-good locale,
  # regardless of:
  #   - what the previous request on this Puma thread set (Thread.current
  #     leak),
  #   - whether CurrentScope's before_reset hook fired (it always does
  #     between requests, but defending in depth here is cheap),
  #   - whether skip_localization? returns true and bails out below
  #     (the API path needs a sane I18n.locale too if any code reads it).
  apply_locale(:'en-US')

  return if skip_localization?

  # Priority:
  # 1. URL locale param (e.g., /en-US/products)
  # 2. Cloudflare geo header
  # 3. Geocoder lookup
  # 4. Default to en-US
  locale = param_locale
  locale ||= cloudflare_country_locale
  locale ||= geocoder_locale
  locale ||= :'en-US'

  # Validate locale before setting
  valid_locales = LocaleUtility.service_locales
  locale = :'en-US' unless valid_locales.include?(locale)

  apply_locale(locale)
  logger.debug "[set_request_locale] Set I18n.locale to #{I18n.locale}"
end

#skip_localization?Boolean

Returns:

  • (Boolean)


249
250
251
252
253
# File 'app/concerns/controllers/localizable.rb', line 249

def skip_localization?
  request.path =~ %r{^/api} || request.path =~ %r{/accounts/auth/} || request.subdomain =~ /^api/ ||
    request.path =~ %r{\.(js|css|map|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|avif|webp)(\?|$)} ||
    request.path =~ %r{^/dist/|^/packs/|^/assets/|^/media/|^/secure-media/}
end

#warmlyyours_ip_localeObject



163
164
165
166
167
168
169
170
171
# File 'app/concerns/controllers/localizable.rb', line 163

def warmlyyours_ip_locale
  if warmlyyours_canada_ip?
    :'en-CA'
  elsif warmlyyours_ip?
    :'en-US'
  else
    nil
  end
end