New option: auto-select language by HTTP header

I’m running a private, international forum for a club where most content is German and some is mixed. Users can choose their preferred language in their user settings, but this is non-obvious for visitors (and doubly so for most of “my” visitors).

The website we are running selects the display language based on the HTTP Accept-Language header field. I’d like to see a new option where I can advise Discourse to auto-select the language based on the header field. Bonus points if users are able to override the automatic detection with their preferred language.
Is this a viable option?

Thank you for your feedback!

This sort of exists.

3 Likes

Awesome! However, in our case, the forum requires a login and uses SSO auth (with the website), so users are always logged in but most do not have the language set by themselves. Is this use case possible with the addon?

Not sure, but you might be able to utilize what the addon is doing to fork it to alter the language for your scenario.

There is a similar request here: https://meta.discourse.org/t/setting-user-locale-via-sso/35528

Would it work for you to have the signed-in user’s locale default to the locale from their HTTP headers, but also give them the option to set their locale for Discourse in their user preferences?

If so, it looks like this can be easily done. I have a working prototype for this now. The only downside that I can see for doing it this way is that until the user sets their language preferences with Discourse, the emails they get from the system will be in the forum’s default language.

Another option is to change how Discourse single-sign-on works so that it accepts a locale parameter and uses that to create the Discourse user from the SSO parameters.

Thanks for the link, I wanted to request that, too (however it will probably not help much in my case) …

Yes, that’s exactly what I need.

Huh. What about caching the last “known” language? This obviously needs to be a different field from the user preference.
Or better yet, have a heuristic based on how often you access the forum with a specific language? That’s increasing complexity a lot, however a simple solution could be to store an array with “known” languages and a weighting (value between 0 and 1), which is updated every time a user logs in: increase weighting for the current language and decrease it for the other ones in the array … well, not that simple.

Or just cache the language the HTTP header field contains and once the user visits with a different language, show a prompt saying something like “We are having trouble identifying your main language. Please choose your preferred language from the list below:” and let that update the user preference. Then we know for sure and the user is informed / will not be surprised if the language changes because he was on vacation for too long …

Another option would be to update the user’s locale preference from their HTTP headers on login.

I think the best/simplest solution for doing this in a plugin is to ask the user to confirm their language preferences from the locale select input. If you are concerned that your users won’t be able to find that in their preferences, it could be added to the header for users who have not yet confirmed their language.

Actually, it looks like all system emails are sent in the forum’s default language (they use config.locale)


    # discourse/lib/freedom_patches/translate_accelerator.rb

    def translate(key, *args)
      load_locale(config.locale) unless @loaded_locales.include?(config.locale)
      return translate_no_cache(key, *args) if args.length > 0

      @cache ||= LruRedux::ThreadSafeCache.new(LRU_CACHE_SIZE)
      k = "#{key}#{config.locale}#{config.backend.object_id}"

      @cache.getset(k) do
        translate_no_cache(key).freeze
      end
    end
 

Other than dealing with that, this plugin should do close to what you are looking for.

The logic is that if the site-settings allow user locales:

  • if there is no logged in user use the local from the accept-language headers
  • if there is a logged in user and the user has selected their preferred language in the Discourse preferences, use that language
  • if there is a logged in user and the user has not selected a preferred language, use the language from the accept-language header

https://github.com/scossar/variable-language

4 Likes

I’ll try that plugin and report back here, thanks a lot!

Uh-oh, that should be fixed to use the user’s preference if it was selected, @eviltrout .

I’m sorry but there is an error somewhere:

Started GET "/" for x.x.x.x at 2015-11-19 13:44:56 +0000
Processing by CategoriesController#index as HTML
Completed 500 Internal Server Error in 3ms (ActiveRecord: 0.0ms)
NoMethodError (undefined method `ensure_all_loaded!' for I18n:Module)
/var/www/discourse/plugins/variable-language/plugin.rb:35:in `set_locale'

I added the plugin in the container configuration file:

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - mkdir -p plugins
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/scossar/variable-language.git

… and rebuilt the container. Afterwards, I received a 500 (see log above).

May you have a look at that, please?

I think you need to update your version of discourse. ensure_all_loaded! was recently added.

Here’s the main method that the plugin is overriding from application_controller.rb

  def set_locale
    I18n.locale = current_user.try(:effective_locale) || SiteSetting.default_locale
    I18n.ensure_all_loaded!
  end

I’m on 1.4.2. Was this added in 1.5?

Yes, before that it was using I18n.fallbacks.ensure_loaded!. If you can’t update to 1.5 you could probably get the plugin to work by changing its set_locale method to this:

    def set_locale
      if SiteSetting.allow_user_locale
        if !current_user
          I18n.locale = locale_from_http_header
        else
          if current_user.preferred_locale?
            I18n.locale = current_user.preferred_locale
          else
            I18n.locale = locale_from_http_header
          end
        end
      else
        I18n.locale = SiteSetting.default_locale
      end
      I18n.fallbacks.ensure_loaded!
    end

I haven’t tested this though.

1 Like

Thanks, I think I can wait until 1.5, hoping it will release around New Year.

On the other hand, would it be possible to make the plugin downwards-compatible by checking for the existance of ensure_all_loaded and falling back to fallbacks.ensure_loaded otherwise?

Don’t get your hopes up, unless you are celebrating New Year’s Day in March. :wink:

2 Likes

What about it , content wise ?
I wouldn’t recommend a website layout in French when the content is Arabic. Which is very likely to happen.

That depends on what you want or how your community is structured. Especially when your community is private.

It might not make sense for most discussion forums, but it makes sense for forums that are providing a service to clients who may speak different languages.

1 Like

Sure, I’ll do that in the next couple of days. Also, I found a problem with the plugin where it is causing an error when the site is accessed without the accept-language headers being set. I’ll fix that too.

2 Likes

I’ve updated this. I haven’t tested it on 1.4.2. Let me know if there are any problems. You may need to clear your browser cache to get a new locale from the headers.

There is an issue with the login_required_welcome_message being cached in the first locale that accesses it. I assume that’s not a problem if you are using SSO.

1 Like