Rails:根据IP地址适当设置语言环境

3
我正在尝试使用request.location (geocoder gem),根据客户端的IP地址适当设置区域设置。

这是我得到的内容:

app/controllers/application_controller.rb

before_action :set_locale

private

def set_locale
    # get location with geocoder
    location = request.location

    # set locale through URL
    if params.has_key?(:locale)
        I18n.locale = params[:locale] || I18n.default_locale
    # set locale through user preference
    elsif current_user && current_user.locale.present?
        I18n.locale = current_user.try(:locale) || I18n.default_locale
    # set locale through geocoder detection of location
    elsif location.present? && location.country_code.present? && I18n.available_locales.include?(location.country_code)
        if location.country_code.include? "-"
            I18n.locale = location.country_code
        else
            I18n.locale = location.country_code.downcase
        end
    # set locale through HTTP detection of location
    elsif (request.env["HTTP_ACCEPT_LANGUAGE"] || "en").scan(/^[a-z]{2}/).first.present? && I18n.available_locales.include?((request.env["HTTP_ACCEPT_LANGUAGE"] || "en").scan(/^[a-z]{2}/).first)
        I18n.locale = (request.env["HTTP_ACCEPT_LANGUAGE"] || "en").scan(/^[a-z]{2}/).first
    end
end


config/application.rb

# i18n Translations
## load the subfolders in the locales
config.i18n.load_path += Dir["#{Rails.root.to_s}/config/locales/**/**/**/**/**/*.{rb,yml}"]
## set default locale
config.i18n.default_locale = 'en'
## provide locale fallbacks
config.i18n.enforce_available_locales = false
config.i18n.fallbacks = {
    'de-AT' => 'de', 'de-CH' => 'de', 'de-DE' => 'de',
    'en-AU' => 'en', 'en-CA' => 'en', 'en-GB' => 'en', 'en-IE' => 'en', 'en-IN' => 'en', 'en-NZ' => 'en', 'en-US' => 'en', 'en-ZA' => 'en'
}

使用参数params[:locale],一切都很好。但是如果没有参数,它总是默认为en。我在这里做错了什么?
1个回答

6
默认情况下,I18n.locale在请求之间不会被记住 - 您需要通过将其保存到会话中来实现自己的记忆。设置了 I18n.locale 后,您可以使用以下方法:
session[:locale] = I18n.locale

然后把它拉回来:
I18n.locale = params[:locale] || session[:locale] || I18n.default_locale
# explicit params take precedence
# otherwise use the remembered session value
# fallback to the default for new users

小提示:考虑将location = request.location移动到其他位置,以免其在每次请求时都运行。即使您不使用数据,您也会在每个请求上承受小的性能损失(和地理编码服务查找)。


以下是一个示例,展示了一种方法来实现此操作:

def set_locale
    # explicit param can always override existing setting
    # otherwise, make sure to allow a user preference to override any automatic detection
    # then detect by location, and header
    # if all else fails, fall back to default
    I18n.locale = params[:locale] || user_pref_locale || session[:locale] || location_detected_locale || header_detected_locale || I18n.default_locale

    # save to session
    session[:locale] = I18n.locale
end

# these could potentially do with a bit of tidying up
# remember to return `nil` to indicate no match

def user_pref_locale
    return nil unless current_user && current_user.locale.present?
    current_user.locale
end

def location_detected_locale
    location = request.location
    return nil unless location.present? && location.country_code.present? && I18n.available_locales.include?(location.country_code)
    location.country_code.include?("-") ? location.country_code : location.country_code.downcase
end

def header_detected_locale
    return nil unless (request.env["HTTP_ACCEPT_LANGUAGE"] || "en").scan(/^[a-z]{2}/).first.present? && I18n.available_locales.include?((request.env["HTTP_ACCEPT_LANGUAGE"] || "en").scan(/^[a-z]{2}/).first)
    (request.env["HTTP_ACCEPT_LANGUAGE"] || "en").scan(/^[a-z]{2}/).first
end

奇怪的是,如果我添加以下内容:session[:locale] = params[:locale] if params[:locale] != session[:locale] 来更新会话数据,当设置了新的语言环境时,应用程序会在每个请求中回退到 en。Rails 在后台设置了这个参数吗? - heroxav
@jonhue:session将在请求之间被记住,但您的params可能包含或不包含locale。要在每个页面上包含它,您需要在生成URL时从控制器传递它到视图中。最好的方法是将params[:locale]视为任何现有设置的显式、一次性覆盖,否则始终依赖于session[:locale](如果已设置)。 - gwcodes
顺便提一下,在I18n文档部分(http://stackoverflow.com/documentation/ruby-on-rails/2772/i18n-internationalization/9336/set-locale-through-requests#t=201703111203188547129)中有很好的代码示例,这可能有助于给出更完整的解释。 - gwcodes
是的,在你的帖子之后,我尝试按照那种方式来做。我遇到的问题是当session[:locale]已经被设置时,我无法通过仅检查 unless session[:locale] 来覆盖它。我需要使用类似于 if params[:locale] 的东西来准备好。而且感觉 Rails 在后台自动设置了这个,因为即使在 URL 中没有设置参数,它也会运行那段代码。 - heroxav
@jonhue:我已经更新了答案,现在包括一个示例,希望这能让事情更清楚!但要注意:这还没有经过测试,但原则应该是正确的。 - gwcodes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接