mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
@@ -376,6 +376,9 @@ password_hint = password
|
|||||||
# Default UI theme ("dark" or "light")
|
# Default UI theme ("dark" or "light")
|
||||||
default_theme = dark
|
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.
|
# 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 =
|
||||||
|
|
||||||
|
|||||||
@@ -376,8 +376,11 @@
|
|||||||
# Default UI theme ("dark" or "light")
|
# Default UI theme ("dark" or "light")
|
||||||
;default_theme = dark
|
;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.
|
# 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 user management, these options affect the organization users view
|
||||||
;external_manage_link_url =
|
;external_manage_link_url =
|
||||||
|
|||||||
@@ -691,12 +691,16 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read locale from accept-language
|
// Set locale to the preference, otherwise fall back to the accept language header.
|
||||||
acceptLang := c.Req.Header.Get("Accept-Language")
|
// 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"
|
locale := "en-US"
|
||||||
|
|
||||||
if len(acceptLang) > 0 {
|
if hs.Features.IsEnabled(featuremgmt.FlagInternationalization) && prefs.JSONData.Locale != "" {
|
||||||
parts := strings.Split(acceptLang, ",")
|
locale = prefs.JSONData.Locale
|
||||||
|
} else if len(acceptLangHeader) > 0 {
|
||||||
|
parts := strings.Split(acceptLangHeader, ",")
|
||||||
locale = parts[0]
|
locale = parts[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,22 +5,25 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/db"
|
"github.com/grafana/grafana/pkg/services/sqlstore/db"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
store store
|
store store
|
||||||
cfg *setting.Cfg
|
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{
|
return &Service{
|
||||||
store: &sqlStore{
|
store: &sqlStore{
|
||||||
db: db,
|
db: db,
|
||||||
},
|
},
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
features: features,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +54,17 @@ func (s *Service) GetWithDefaults(ctx context.Context, query *pref.GetPreference
|
|||||||
res.HomeDashboardID = p.HomeDashboardID
|
res.HomeDashboardID = p.HomeDashboardID
|
||||||
}
|
}
|
||||||
if p.JSONData != nil {
|
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{},
|
JSONData: &pref.PreferenceJSONData{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.features.IsEnabled(featuremgmt.FlagInternationalization) {
|
||||||
|
defaults.JSONData.Locale = s.cfg.DefaultLocale
|
||||||
|
}
|
||||||
|
|
||||||
return defaults
|
return defaults
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,16 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGet_empty(t *testing.T) {
|
func TestGet_empty(t *testing.T) {
|
||||||
prefService := &Service{
|
prefService := &Service{
|
||||||
store: newFake(),
|
store: newFake(),
|
||||||
cfg: setting.NewCfg(),
|
cfg: setting.NewCfg(),
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
preference, err := prefService.Get(context.Background(), &pref.GetPreferenceQuery{})
|
preference, err := prefService.Get(context.Background(), &pref.GetPreferenceQuery{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -27,9 +29,11 @@ func TestGet_empty(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetDefaults(t *testing.T) {
|
func TestGetDefaults(t *testing.T) {
|
||||||
prefService := &Service{
|
prefService := &Service{
|
||||||
store: newFake(),
|
store: newFake(),
|
||||||
cfg: setting.NewCfg(),
|
cfg: setting.NewCfg(),
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
|
prefService.cfg.DefaultLocale = "en-US"
|
||||||
prefService.cfg.DefaultTheme = "light"
|
prefService.cfg.DefaultTheme = "light"
|
||||||
prefService.cfg.DateFormats.DefaultTimezone = "UTC"
|
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) {
|
func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
|
||||||
prefService := &Service{
|
prefService := &Service{
|
||||||
store: newFake(),
|
store: newFake(),
|
||||||
cfg: setting.NewCfg(),
|
cfg: setting.NewCfg(),
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
|
prefService.cfg.DefaultLocale = "en-US"
|
||||||
|
|
||||||
insertPrefs(t, prefService.store,
|
insertPrefs(t, prefService.store,
|
||||||
pref.Preference{
|
pref.Preference{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
@@ -74,6 +107,9 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
|
|||||||
Theme: "dark",
|
Theme: "dark",
|
||||||
Timezone: "UTC",
|
Timezone: "UTC",
|
||||||
WeekStart: "1",
|
WeekStart: "1",
|
||||||
|
JSONData: &pref.PreferenceJSONData{
|
||||||
|
Locale: "en-GB",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
pref.Preference{
|
pref.Preference{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
@@ -82,6 +118,9 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
|
|||||||
Theme: "light",
|
Theme: "light",
|
||||||
Timezone: "browser",
|
Timezone: "browser",
|
||||||
WeekStart: "2",
|
WeekStart: "2",
|
||||||
|
JSONData: &pref.PreferenceJSONData{
|
||||||
|
Locale: "en-AU",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,7 +133,9 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
|
|||||||
Timezone: "browser",
|
Timezone: "browser",
|
||||||
WeekStart: "2",
|
WeekStart: "2",
|
||||||
HomeDashboardID: 4,
|
HomeDashboardID: 4,
|
||||||
JSONData: &pref.PreferenceJSONData{},
|
JSONData: &pref.PreferenceJSONData{
|
||||||
|
Locale: "en-AU",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(expected, preference); diff != "" {
|
if diff := cmp.Diff(expected, preference); diff != "" {
|
||||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||||
@@ -111,7 +152,9 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) {
|
|||||||
Timezone: "UTC",
|
Timezone: "UTC",
|
||||||
WeekStart: "1",
|
WeekStart: "1",
|
||||||
HomeDashboardID: 1,
|
HomeDashboardID: 1,
|
||||||
JSONData: &pref.PreferenceJSONData{},
|
JSONData: &pref.PreferenceJSONData{
|
||||||
|
Locale: "en-GB",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(expected, preference); diff != "" {
|
if diff := cmp.Diff(expected, preference); diff != "" {
|
||||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||||
@@ -158,6 +201,10 @@ func TestGetDefaults_JSONData(t *testing.T) {
|
|||||||
orgPreferencesJsonData := pref.PreferenceJSONData{
|
orgPreferencesJsonData := pref.PreferenceJSONData{
|
||||||
Navbar: orgNavbarPreferences,
|
Navbar: orgNavbarPreferences,
|
||||||
}
|
}
|
||||||
|
orgPreferencesWithLocaleJsonData := pref.PreferenceJSONData{
|
||||||
|
Navbar: orgNavbarPreferences,
|
||||||
|
Locale: "en-GB",
|
||||||
|
}
|
||||||
team2PreferencesJsonData := pref.PreferenceJSONData{
|
team2PreferencesJsonData := pref.PreferenceJSONData{
|
||||||
Navbar: team2NavbarPreferences,
|
Navbar: team2NavbarPreferences,
|
||||||
}
|
}
|
||||||
@@ -167,8 +214,9 @@ func TestGetDefaults_JSONData(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("users have precedence over org", func(t *testing.T) {
|
t.Run("users have precedence over org", func(t *testing.T) {
|
||||||
prefService := &Service{
|
prefService := &Service{
|
||||||
store: newFake(),
|
store: newFake(),
|
||||||
cfg: setting.NewCfg(),
|
cfg: setting.NewCfg(),
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
|
|
||||||
insertPrefs(t, prefService.store,
|
insertPrefs(t, prefService.store,
|
||||||
@@ -191,10 +239,42 @@ func TestGetDefaults_JSONData(t *testing.T) {
|
|||||||
}, preference)
|
}, 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) {
|
t.Run("teams have precedence over org and are read in ascending order", func(t *testing.T) {
|
||||||
prefService := &Service{
|
prefService := &Service{
|
||||||
store: newFake(),
|
store: newFake(),
|
||||||
cfg: setting.NewCfg(),
|
cfg: setting.NewCfg(),
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
|
|
||||||
insertPrefs(t, prefService.store,
|
insertPrefs(t, prefService.store,
|
||||||
@@ -227,8 +307,9 @@ func TestGetDefaults_JSONData(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetWithDefaults_teams(t *testing.T) {
|
func TestGetWithDefaults_teams(t *testing.T) {
|
||||||
prefService := &Service{
|
prefService := &Service{
|
||||||
store: newFake(),
|
store: newFake(),
|
||||||
cfg: setting.NewCfg(),
|
cfg: setting.NewCfg(),
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
insertPrefs(t, prefService.store,
|
insertPrefs(t, prefService.store,
|
||||||
pref.Preference{
|
pref.Preference{
|
||||||
@@ -273,8 +354,9 @@ func TestGetWithDefaults_teams(t *testing.T) {
|
|||||||
|
|
||||||
func TestPatch_toCreate(t *testing.T) {
|
func TestPatch_toCreate(t *testing.T) {
|
||||||
prefService := &Service{
|
prefService := &Service{
|
||||||
store: newFake(),
|
store: newFake(),
|
||||||
cfg: setting.NewCfg(),
|
cfg: setting.NewCfg(),
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
|
|
||||||
themeValue := "light"
|
themeValue := "light"
|
||||||
@@ -293,8 +375,9 @@ func TestPatch_toCreate(t *testing.T) {
|
|||||||
|
|
||||||
func TestSave(t *testing.T) {
|
func TestSave(t *testing.T) {
|
||||||
prefService := &Service{
|
prefService := &Service{
|
||||||
store: newFake(),
|
store: newFake(),
|
||||||
cfg: setting.NewCfg(),
|
cfg: setting.NewCfg(),
|
||||||
|
features: featuremgmt.WithFeatures(),
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("insert", func(t *testing.T) {
|
t.Run("insert", func(t *testing.T) {
|
||||||
|
|||||||
@@ -397,8 +397,9 @@ type Cfg struct {
|
|||||||
|
|
||||||
Quota QuotaSettings
|
Quota QuotaSettings
|
||||||
|
|
||||||
DefaultTheme string
|
DefaultTheme string
|
||||||
HomePage string
|
DefaultLocale string
|
||||||
|
HomePage string
|
||||||
|
|
||||||
AutoAssignOrg bool
|
AutoAssignOrg bool
|
||||||
AutoAssignOrgId int
|
AutoAssignOrgId int
|
||||||
@@ -1356,6 +1357,7 @@ func readUserSettings(iniFile *ini.File, cfg *Cfg) error {
|
|||||||
LoginHint = valueAsString(users, "login_hint", "")
|
LoginHint = valueAsString(users, "login_hint", "")
|
||||||
PasswordHint = valueAsString(users, "password_hint", "")
|
PasswordHint = valueAsString(users, "password_hint", "")
|
||||||
cfg.DefaultTheme = valueAsString(users, "default_theme", "")
|
cfg.DefaultTheme = valueAsString(users, "default_theme", "")
|
||||||
|
cfg.DefaultLocale = valueAsString(users, "default_locale", "")
|
||||||
cfg.HomePage = valueAsString(users, "home_page", "")
|
cfg.HomePage = valueAsString(users, "home_page", "")
|
||||||
ExternalUserMngLinkUrl = valueAsString(users, "external_manage_link_url", "")
|
ExternalUserMngLinkUrl = valueAsString(users, "external_manage_link_url", "")
|
||||||
ExternalUserMngLinkName = valueAsString(users, "external_manage_link_name", "")
|
ExternalUserMngLinkName = valueAsString(users, "external_manage_link_name", "")
|
||||||
|
|||||||
Reference in New Issue
Block a user