diff --git a/Gemfile b/Gemfile index 3f5a46fb761..f0bcb313cb9 100644 --- a/Gemfile +++ b/Gemfile @@ -45,6 +45,8 @@ gem 'active_model_serializers', '~> 0.8.3' gem 'onebox' +gem 'http_accept_language', '~>2.0.5' + gem 'ember-rails' gem 'ember-source', '1.12.2' gem 'barber' diff --git a/Gemfile.lock b/Gemfile.lock index 2bc7adb15d5..d62fabd7e1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -127,6 +127,7 @@ GEM htmlentities (4.3.4) http-cookie (1.0.2) domain_name (~> 0.5) + http_accept_language (2.0.5) i18n (0.7.0) image_optim (0.20.2) exifr (~> 1.1, >= 1.1.3) @@ -427,6 +428,7 @@ DEPENDENCIES highline hiredis htmlentities + http_accept_language (~> 2.0.5) image_optim (= 0.20.2) librarian (>= 0.0.25) listen (= 0.7.3) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6d51b6a34f0..356610ff601 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -164,7 +164,15 @@ class ApplicationController < ActionController::Base end def set_locale - I18n.locale = current_user.try(:effective_locale) || SiteSetting.default_locale + if !current_user + if SiteSetting.allow_user_locale + I18n.locale = locale_from_header + else + I18n.locale = SiteSetting.default_locale + end + else + I18n.locale = current_user.effective_locale + end I18n.ensure_all_loaded! end @@ -309,6 +317,18 @@ class ApplicationController < ActionController::Base private + def locale_from_header + begin + # Rails I18n uses underscores between the locale and the region; the request + # headers use hyphens. + available_locales = I18n.available_locales.map { |locale| locale.to_s.gsub(/_/, '-') } + http_accept_language.language_region_compatible_from(available_locales).gsub(/-/, '_') + rescue + # If Accept-Language headers are not set. + I18n.default_locale + end + end + def preload_anonymous_data store_preloaded("site", Site.json_for(guardian)) store_preloaded("siteSettings", SiteSetting.client_settings_json) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 3a9f77942f4..3a187f109d3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -714,7 +714,12 @@ class UsersController < ApplicationController def user_params params.permit(:name, :email, :password, :username, :active) - .merge(ip_address: request.remote_ip, registration_ip_address: request.remote_ip) + .merge(ip_address: request.remote_ip, registration_ip_address: request.remote_ip, + locale: user_locale) + end + + def user_locale + I18n.locale end def fail_with(key) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d8b9634dcc3..4cc6834c5c8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -60,7 +60,7 @@ module ApplicationHelper end def rtl_class - RTL.new(current_user).css_class + rtl? ? 'rtl' : '' end def escape_unicode(javascript) @@ -111,7 +111,7 @@ module ApplicationHelper end def rtl? - ["ar", "fa_IR", "he"].include?(user_locale) + ["ar", "fa_IR", "he"].include? I18n.locale.to_s end def user_locale diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 48a217039c8..49ddc8f8145 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -13,6 +13,10 @@ describe TopicsController do request.env['HTTP_REFERER'] = ref end + def set_accept_language(locale) + request.env['HTTP_ACCEPT_LANGUAGE'] = locale + end + it "doesn't store an incoming link when there's no referer" do expect { get :show, id: topic.id @@ -33,7 +37,7 @@ describe TopicsController do end it "uses the application layout even with an escaped fragment param" do - get :show, {'topic_id' => topic.id, 'slug' => topic.slug, '_escaped_fragment_' => 'true'} + get :show, {'topic_id' => topic.id, 'slug' => topic.slug, '_escaped_fragment_' => 'true'} expect(response).to render_template(layout: 'application') assert_select "meta[name=fragment]", false, "it doesn't have the meta tag" end @@ -51,7 +55,7 @@ describe TopicsController do end it "uses the crawler layout when there's an _escaped_fragment_ param" do - get :show, topic_id: topic.id, slug: topic.slug, _escaped_fragment_: 'true' + get :show, topic_id: topic.id, slug: topic.slug, _escaped_fragment_: 'true' expect(response).to render_template(layout: 'crawler') assert_select "meta[name=fragment]", false, "it doesn't have the meta tag" end @@ -114,25 +118,87 @@ describe TopicsController do end end - describe 'set_locale' do - it 'sets the one the user prefers' do - SiteSetting.stubs(:allow_user_locale).returns(true) + describe "set_locale" do + context "allow_user_locale disabled" do + context "accept-language header differs from default locale" do + before do + SiteSetting.stubs(:allow_user_locale).returns(false) + SiteSetting.stubs(:default_locale).returns("en") + set_accept_language("fr") + end - user = Fabricate(:user, locale: :fr) - log_in_user(user) + context "with an anonymous user" do + it "uses the default locale" do + get :show, {topic_id: topic.id} - get :show, {topic_id: topic.id} + expect(I18n.locale).to eq(:en) + end + end - expect(I18n.locale).to eq(:fr) + context "with a logged in user" do + it "it uses the default locale" do + user = Fabricate(:user, locale: :fr) + log_in_user(user) + + get :show, {topic_id: topic.id} + + expect(I18n.locale).to eq(:en) + end + end + end end - it 'is sets the default locale when the setting not enabled' do - user = Fabricate(:user, locale: :fr) - log_in_user(user) + context "allow_user_locale enabled" do + context "accept-language header differs from default locale" do + before do + SiteSetting.stubs(:allow_user_locale).returns(true) + SiteSetting.stubs(:default_locale).returns("en") + set_accept_language("fr") + end - get :show, {topic_id: topic.id} + context "with an anonymous user" do + it "uses the locale from the headers" do + get :show, {topic_id: topic.id} - expect(I18n.locale).to eq(:en) + expect(I18n.locale).to eq(:fr) + end + end + + context "with a logged in user" do + it "uses the user's preferred locale" do + user = Fabricate(:user, locale: :fr) + log_in_user(user) + + get :show, {topic_id: topic.id} + + expect(I18n.locale).to eq(:fr) + end + end + end + + context "the preferred locale includes a region" do + it "returns the locale and region separated by an underscore" do + SiteSetting.stubs(:allow_user_locale).returns(true) + SiteSetting.stubs(:default_locale).returns("en") + set_accept_language("zh-CN") + + get :show, {topic_id: topic.id} + + expect(I18n.locale).to eq(:zh_CN) + end + end + + context 'accept-language header is not set' do + it 'uses the site default locale' do + SiteSetting.stubs(:allow_user_locale).returns(true) + SiteSetting.stubs(:default_locale).returns('en') + set_accept_language('') + + get :show, {topic_id: topic.id} + + expect(I18n.locale).to eq(:en) + end + end end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index eb83ce115f5..d4d4965efbf 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -479,6 +479,15 @@ describe UsersController do email: @user.email end + context 'when creating a user' do + it 'sets the user locale to I18n.locale' do + SiteSetting.stubs(:default_locale).returns('en') + I18n.stubs(:locale).returns(:fr) + post_user + expect(User.find_by(username: @user.username).locale).to eq('fr') + end + end + context 'when creating a non active user (unconfirmed email)' do it 'returns a 500 when local logins are disabled' do @@ -1181,6 +1190,19 @@ describe UsersController do end + context 'a locale is chosen that differs from I18n.locale' do + it "updates the user's locale" do + I18n.stubs(:locale).returns('fr') + + put :update, + username: user.username, + locale: :fa_IR + + expect(User.find_by(username: user.username).locale).to eq('fa_IR') + end + + end + context "with user fields" do context "an editable field" do let!(:user_field) { Fabricate(:user_field) } diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index fb97b0454cc..5931ec0d3a6 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -84,5 +84,16 @@ describe ApplicationHelper do end end + describe '#rtl_class' do + it "returns 'rtl' when the I18n.locale is rtl" do + I18n.stubs(:locale).returns(:he) + expect(helper.rtl_class).to eq('rtl') + end + + it 'returns an empty string when the I18n.locale is not rtl' do + I18n.stubs(:locale).returns(:zh_TW) + expect(helper.rtl_class).to eq('') + end + end end