I18n: Add default locale server config option (#51035)

* I18n: Set default locale in server config and expose in grafanaBootData

* put default locale behind feature flag

* update tests now that default locale is behind feature flag

* little bit of PR feedback

* update sample.ini
pull/51159/head
Josh Hunt 3 years ago committed by GitHub
parent 370d6a6f7b
commit dcf786f3a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      conf/defaults.ini
  2. 5
      conf/sample.ini
  3. 12
      pkg/api/index.go
  4. 21
      pkg/services/preference/prefimpl/pref.go
  5. 87
      pkg/services/preference/prefimpl/pref_test.go
  6. 2
      pkg/setting/setting.go

@ -376,6 +376,9 @@ password_hint = password
# Default UI theme ("dark" or "light")
default_theme = dark
# Default locale (supported IETF language tag, such as en-US)
default_locale = en-US
# Path to a custom home page. Users are only redirected to this if the default home dashboard is used. It should match a frontend route and contain a leading slash.
home_page =

@ -376,8 +376,11 @@
# Default UI theme ("dark" or "light")
;default_theme = dark
# Default locale (supported IETF language tag, such as en-US)
;default_locale = en-US
# Path to a custom home page. Users are only redirected to this if the default home dashboard is used. It should match a frontend route and contain a leading slash.
; home_page =
;home_page =
# External user management, these options affect the organization users view
;external_manage_link_url =

@ -691,12 +691,16 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
return nil, err
}
// Read locale from accept-language
acceptLang := c.Req.Header.Get("Accept-Language")
// Set locale to the preference, otherwise fall back to the accept language header.
// In practice, because the preference has configuration-backed default, the header
// shouldn't frequently be used
acceptLangHeader := c.Req.Header.Get("Accept-Language")
locale := "en-US"
if len(acceptLang) > 0 {
parts := strings.Split(acceptLang, ",")
if hs.Features.IsEnabled(featuremgmt.FlagInternationalization) && prefs.JSONData.Locale != "" {
locale = prefs.JSONData.Locale
} else if len(acceptLangHeader) > 0 {
parts := strings.Split(acceptLangHeader, ",")
locale = parts[0]
}

@ -5,6 +5,7 @@ import (
"errors"
"time"
"github.com/grafana/grafana/pkg/services/featuremgmt"
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/setting"
@ -13,14 +14,16 @@ import (
type Service struct {
store store
cfg *setting.Cfg
features *featuremgmt.FeatureManager
}
func ProvideService(db db.DB, cfg *setting.Cfg) pref.Service {
func ProvideService(db db.DB, cfg *setting.Cfg, features *featuremgmt.FeatureManager) pref.Service {
return &Service{
store: &sqlStore{
db: db,
},
cfg: cfg,
features: features,
}
}
@ -51,7 +54,17 @@ func (s *Service) GetWithDefaults(ctx context.Context, query *pref.GetPreference
res.HomeDashboardID = p.HomeDashboardID
}
if p.JSONData != nil {
res.JSONData = p.JSONData
if p.JSONData.Locale != "" {
res.JSONData.Locale = p.JSONData.Locale
}
if len(p.JSONData.Navbar.SavedItems) > 0 {
res.JSONData.Navbar = p.JSONData.Navbar
}
if p.JSONData.QueryHistory.HomeTab != "" {
res.JSONData.QueryHistory.HomeTab = p.JSONData.QueryHistory.HomeTab
}
}
}
@ -217,5 +230,9 @@ func (s *Service) GetDefaults() *pref.Preference {
JSONData: &pref.PreferenceJSONData{},
}
if s.features.IsEnabled(featuremgmt.FlagInternationalization) {
defaults.JSONData.Locale = s.cfg.DefaultLocale
}
return defaults
}

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/featuremgmt"
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/setting"
)
@ -16,6 +17,7 @@ func TestGet_empty(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
}
preference, err := prefService.Get(context.Background(), &pref.GetPreferenceQuery{})
require.NoError(t, err)
@ -29,7 +31,9 @@ func TestGetDefaults(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
}
prefService.cfg.DefaultLocale = "en-US"
prefService.cfg.DefaultTheme = "light"
prefService.cfg.DateFormats.DefaultTimezone = "UTC"
@ -62,11 +66,40 @@ func TestGetDefaults(t *testing.T) {
})
}
func TestGetDefaultsWithI18nFeatureFlag(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(featuremgmt.FlagInternationalization),
}
prefService.cfg.DefaultLocale = "en-US"
prefService.cfg.DefaultTheme = "light"
prefService.cfg.DateFormats.DefaultTimezone = "UTC"
t.Run("GetDefaults", func(t *testing.T) {
preference := prefService.GetDefaults()
expected := &pref.Preference{
Theme: "light",
Timezone: "UTC",
HomeDashboardID: 0,
JSONData: &pref.PreferenceJSONData{
Locale: "en-US",
},
}
if diff := cmp.Diff(expected, preference); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
}
func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
}
prefService.cfg.DefaultLocale = "en-US"
insertPrefs(t, prefService.store,
pref.Preference{
OrgID: 1,
@ -74,6 +107,9 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
Theme: "dark",
Timezone: "UTC",
WeekStart: "1",
JSONData: &pref.PreferenceJSONData{
Locale: "en-GB",
},
},
pref.Preference{
OrgID: 1,
@ -82,6 +118,9 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
Theme: "light",
Timezone: "browser",
WeekStart: "2",
JSONData: &pref.PreferenceJSONData{
Locale: "en-AU",
},
},
)
@ -94,7 +133,9 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
Timezone: "browser",
WeekStart: "2",
HomeDashboardID: 4,
JSONData: &pref.PreferenceJSONData{},
JSONData: &pref.PreferenceJSONData{
Locale: "en-AU",
},
}
if diff := cmp.Diff(expected, preference); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
@ -111,7 +152,9 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
Timezone: "UTC",
WeekStart: "1",
HomeDashboardID: 1,
JSONData: &pref.PreferenceJSONData{},
JSONData: &pref.PreferenceJSONData{
Locale: "en-GB",
},
}
if diff := cmp.Diff(expected, preference); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
@ -158,6 +201,10 @@ func TestGetDefaults_JSONData(t *testing.T) {
orgPreferencesJsonData := pref.PreferenceJSONData{
Navbar: orgNavbarPreferences,
}
orgPreferencesWithLocaleJsonData := pref.PreferenceJSONData{
Navbar: orgNavbarPreferences,
Locale: "en-GB",
}
team2PreferencesJsonData := pref.PreferenceJSONData{
Navbar: team2NavbarPreferences,
}
@ -169,6 +216,7 @@ func TestGetDefaults_JSONData(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
}
insertPrefs(t, prefService.store,
@ -191,10 +239,42 @@ func TestGetDefaults_JSONData(t *testing.T) {
}, preference)
})
t.Run("user JSONData with missing locale does not override org preference", func(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
}
insertPrefs(t, prefService.store,
pref.Preference{
OrgID: 1,
JSONData: &orgPreferencesWithLocaleJsonData,
},
pref.Preference{
OrgID: 1,
UserID: 1,
JSONData: &userPreferencesJsonData,
},
)
query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, UserID: 1}
preference, err := prefService.GetWithDefaults(context.Background(), query)
require.NoError(t, err)
require.Equal(t, &pref.Preference{
JSONData: &pref.PreferenceJSONData{
Locale: "en-GB",
Navbar: userNavbarPreferences,
QueryHistory: queryPreference,
},
}, preference)
})
t.Run("teams have precedence over org and are read in ascending order", func(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
}
insertPrefs(t, prefService.store,
@ -229,6 +309,7 @@ func TestGetWithDefaults_teams(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
}
insertPrefs(t, prefService.store,
pref.Preference{
@ -275,6 +356,7 @@ func TestPatch_toCreate(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
}
themeValue := "light"
@ -295,6 +377,7 @@ func TestSave(t *testing.T) {
prefService := &Service{
store: newFake(),
cfg: setting.NewCfg(),
features: featuremgmt.WithFeatures(),
}
t.Run("insert", func(t *testing.T) {

@ -398,6 +398,7 @@ type Cfg struct {
Quota QuotaSettings
DefaultTheme string
DefaultLocale string
HomePage string
AutoAssignOrg bool
@ -1356,6 +1357,7 @@ func readUserSettings(iniFile *ini.File, cfg *Cfg) error {
LoginHint = valueAsString(users, "login_hint", "")
PasswordHint = valueAsString(users, "password_hint", "")
cfg.DefaultTheme = valueAsString(users, "default_theme", "")
cfg.DefaultLocale = valueAsString(users, "default_locale", "")
cfg.HomePage = valueAsString(users, "home_page", "")
ExternalUserMngLinkUrl = valueAsString(users, "external_manage_link_url", "")
ExternalUserMngLinkName = valueAsString(users, "external_manage_link_name", "")

Loading…
Cancel
Save