diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 2426ed3338c..dbfbc5eeeb3 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -246,6 +246,7 @@ export interface GrafanaConfig { * Grafana's supported language. */ language: string | undefined; + locale: string; } export interface SqlConnectionLimits { diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index e6444cdb80c..8b0beab714f 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -208,6 +208,12 @@ export class GrafanaBootConfig implements GrafanaConfig { */ language: string | undefined; + /** + * Locale used in Grafana's UI. Default to 'es-US' in the backend and overwritten when the user select a different one in SharedPreferences. + * This is the locale that is used for date formatting and other locale-specific features. + */ + locale: string; + constructor(options: GrafanaBootConfig) { this.bootData = options.bootData; @@ -243,6 +249,8 @@ export class GrafanaBootConfig implements GrafanaConfig { this.theme2 = getThemeById(this.bootData.user.theme); this.bootData.user.lightTheme = this.theme2.isLight; this.theme = this.theme2.v1; + + this.locale = options.bootData.user.locale; } geomapDefaultBaseLayer?: MapLayerOptions | undefined; listDashboardScopesEndpoint?: string | undefined; diff --git a/pkg/api/index.go b/pkg/api/index.go index 68423b8d0f8..016a78366c6 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -69,7 +69,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV // Locale is used for some number and date/time formatting, whereas language is used just for // translating words in the interface acceptLangHeader := c.Req.Header.Get("Accept-Language") - locale := "en-US" // default to en-US formatting, but use the accept-lang header or user's preference + locale := "en-US" // default to en formatting, but use the accept-lang header or user's preference language := "" // frontend will set the default language urlPrefs := getURLPrefs(c) @@ -85,6 +85,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV } if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagLocaleFormatPreference) { + locale = "en" // default to "en", not "en-US", matching the locale code if urlPrefs.Locale != "" { locale = urlPrefs.Locale } else if prefs.JSONData.Locale != "" { diff --git a/pkg/services/preference/prefimpl/pref_test.go b/pkg/services/preference/prefimpl/pref_test.go index afc40f9e37f..85a859935ed 100644 --- a/pkg/services/preference/prefimpl/pref_test.go +++ b/pkg/services/preference/prefimpl/pref_test.go @@ -92,7 +92,7 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) { WeekStart: &weekStartOne, JSONData: &pref.PreferenceJSONData{ Language: "en-GB", - Locale: "en-US", + Locale: "en", }, }, pref.Preference{ @@ -104,7 +104,7 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) { WeekStart: &weekStartTwo, JSONData: &pref.PreferenceJSONData{ Language: "en-AU", - Locale: "es-ES", + Locale: "es", }, }, ) @@ -120,7 +120,7 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) { HomeDashboardID: 4, JSONData: &pref.PreferenceJSONData{ Language: "en-AU", - Locale: "es-ES", + Locale: "es", }, } if diff := cmp.Diff(expected, preference); diff != "" { @@ -140,7 +140,7 @@ func TestGetWithDefaults_withUserAndOrgPrefs(t *testing.T) { HomeDashboardID: 1, JSONData: &pref.PreferenceJSONData{ Language: "en-GB", - Locale: "en-US", + Locale: "en", }, } if diff := cmp.Diff(expected, preference); diff != "" { @@ -162,7 +162,7 @@ func TestGetDefaults_JSONData(t *testing.T) { Language: "en-GB", } orgPreferencesWithLocaleJsonData := pref.PreferenceJSONData{ - Locale: "en-US", + Locale: "en", } team2PreferencesJsonData := pref.PreferenceJSONData{} team1PreferencesJsonData := pref.PreferenceJSONData{} @@ -248,7 +248,7 @@ func TestGetDefaults_JSONData(t *testing.T) { require.Equal(t, &pref.Preference{ WeekStart: &weekStart, JSONData: &pref.PreferenceJSONData{ - Locale: "en-US", + Locale: "en", QueryHistory: queryPreference, }, }, preference) diff --git a/public/app/app.ts b/public/app/app.ts index ef7460001e7..647124ce8dc 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -135,7 +135,7 @@ export class GrafanaApp { // This needs to be done after the `initEchoSrv` since it is being used under the hood. startMeasure('frontend_app_init'); - setLocale(config.bootData.user.locale); + setLocale(config.locale); setWeekStart(config.bootData.user.weekStart); setPanelRenderer(PanelRenderer); setPluginPage(PluginPage); diff --git a/public/app/core/internationalization/dates.ts b/public/app/core/internationalization/dates.ts index 8f0aaa14d7c..bdadd9ba863 100644 --- a/public/app/core/internationalization/dates.ts +++ b/public/app/core/internationalization/dates.ts @@ -2,16 +2,16 @@ import '@formatjs/intl-durationformat/polyfill'; import deepEqual from 'fast-deep-equal'; import memoize from 'micro-memoize'; -import { getI18next } from './index'; +import { config } from 'app/core/config'; const deepMemoize: typeof memoize = (fn) => memoize(fn, { isEqual: deepEqual }); -const createDateTimeFormatter = deepMemoize((language: string, options: Intl.DateTimeFormatOptions) => { - return new Intl.DateTimeFormat(language, options); +const createDateTimeFormatter = deepMemoize((locale: string, options: Intl.DateTimeFormatOptions) => { + return new Intl.DateTimeFormat(locale, options); }); -const createDurationFormatter = deepMemoize((language: string, options: Intl.DurationFormatOptions) => { - return new Intl.DurationFormat(language, options); +const createDurationFormatter = deepMemoize((locale: string, options: Intl.DurationFormatOptions) => { + return new Intl.DurationFormat(locale, options); }); export const formatDate = deepMemoize( @@ -20,17 +20,17 @@ export const formatDate = deepMemoize( return formatDate(new Date(value), format); } - const i18n = getI18next(); - const dateFormatter = createDateTimeFormatter(i18n.language, format); + const locale = config.locale; + const dateFormatter = createDateTimeFormatter(locale, format); return dateFormatter.format(value); } ); export const formatDuration = deepMemoize( (duration: Intl.DurationInput, options: Intl.DurationFormatOptions = {}): string => { - const i18n = getI18next(); + const locale = config.locale; - const dateFormatter = createDurationFormatter(i18n.language, options); + const dateFormatter = createDurationFormatter(locale, options); return dateFormatter.format(duration); } );