Module: Controllers::Localizable

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

Overview

ActiveSupport::Concern mixin: localizable.

Instance Method Summary collapse

Instance Method Details

#cloudflare_country_localeSymbol?

Returns:

  • (Symbol, nil)

    :'en-CA' for Canadian IPs, :'en-US' for
    other countries, or nil when the HTTP_CF_IPCOUNTRY header is
    missing or XX (unknown)



145
146
147
148
149
150
151
152
153
154
155
# File 'app/concerns/controllers/localizable.rb', line 145

def cloudflare_country_locale
  return nil if request.env['HTTP_CF_IPCOUNTRY'].blank?
  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_localeSymbol?

Resolves the request's locale by priority, without any user logic.

Priority: URL locale param → WarmlyYours office IP (CRM requests only)
→ Cloudflare geo header → geocoder lookup. May return nil when no
source yields a locale.

Returns:

  • (Symbol, nil)

    the resolved locale symbol, or nil



211
212
213
214
215
216
217
218
219
# File 'app/concerns/controllers/localizable.rb', line 211

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) ⇒ Symbol?

Resolves a service locale from a geocoder location.

Defaults to the current request's geocoder lookup when no location is
given. Only Canada maps to en-CA; everything else is en-US.

Parameters:

  • location (Geocoder::Result::Base, nil) (defaults to: nil)

    optional pre-resolved
    geocoder location; nil falls back to request.location

Returns:

  • (Symbol, nil)

    :'en-CA' or :'en-US', or nil when no
    location/country is available



166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'app/concerns/controllers/localizable.rb', line 166

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) ⇒ Symbol, ...

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
Checks whether a guest user's locale matches the requested locale and
attempts a catalog switch when it doesn't.

Parameters:

  • param_locale (Symbol, nil)

    the requested locale from the URL
    params; nil/blank keeps the guest's existing locale

Returns:

  • (Symbol, String, nil)

    the locale to use for the guest user —
    the requested locale when the catalog switch succeeded (or for bot
    requests), otherwise the guest user's original locale



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'app/concerns/controllers/localizable.rb', line 235

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
    if param_locale.blank?
      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 storefront locale-optional auth paths without a /en-US/ prefix.

Gated on "not CRM" rather than "subdomain matches /^www/" so the guard fires
on bare-apex (warmlyyours.com) and dev hosts (localhost, www.warmlyyours.me
under a non-www route) too, not just www.warmlyyours.com. Without this,
POST /accounts/password from any non-www-subdomain host gets 303'd to GET
/en-US/accounts/password, halts before the controller action, drops the
form body, and falls through to the CMS catch-all.

Returns:

  • (Boolean)

    true when on a non-CRM host without a locale segment AND
    the path is one of /accounts/…, /customer_sessions/…, or /masquerade/…
    — i.e. a route mounted under scope '(/:locale)' in config/routes/www.rb
    that set_locale must NOT redirect to the locale-prefixed variant.



321
322
323
324
325
326
327
328
329
# File 'app/concerns/controllers/localizable.rb', line 321

def locale_optional_www_auth_path?
  return false if is_crm_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_localeSymbol?

Locale symbol carried in the URL params (e.g. /en-US/products).

Returns:

  • (Symbol, nil)

    the params[:locale] value as a symbol, or nil
    when absent or blank



200
201
202
# File 'app/concerns/controllers/localizable.rb', line 200

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

#set_localetrue, Object

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

Returns:

  • (true, Object)

    true when no redirect is needed, otherwise the
    result of redirect_to



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
134
135
136
137
138
# File 'app/concerns/controllers/localizable.rb', line 51

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.
    apply_locale(param_locale) if param_locale.present? && (bot_request? || current_user.nil?)

    # 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_localevoid

This method returns an undefined value.

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



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
41
42
43
44
# File 'app/concerns/controllers/localizable.rb', line 14

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, ...

Whether localization should be skipped for this request.

True for API paths/subdomains, OAuth handshake paths
(/accounts/auth/), and static asset or build-output paths.

Returns:

  • (Boolean, Integer, nil)

    truthy when the request should bypass
    locale resolution, falsy otherwise



302
303
304
305
306
# File 'app/concerns/controllers/localizable.rb', line 302

def skip_localization?
  request.path =~ %r{^/api} || request.path =~ %r{/accounts/auth/} || request.subdomain =~ /^api/ ||
    request.path =~ /\.(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_localeSymbol?

Resolves a service locale from WarmlyYours office IPs.

Used only for CRM requests (see #determine_request_locale).

Returns:

  • (Symbol, nil)

    :'en-CA' for the Canada office IP, :'en-US'
    for other known office IPs, or nil when the request IP is not a
    known WarmlyYours IP



188
189
190
191
192
193
194
# File 'app/concerns/controllers/localizable.rb', line 188

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