mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
I18n: Add server config to detect browser language (#69396)
* I18N: Add browser language detector * Improve style * No new property for type check * Add dependency * Suggested doc change Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Fix prettier * 'detect' as special language, no cache and only navigator detector As per PR suggestion comments * Update language configuration doc * Suggested change in doc From @chri2547 Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Fix import * Revert public/app/types/explore.ts changes * Prettier write --------- Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> Co-authored-by: joshhunt <josh@trtr.co>
This commit is contained in:
parent
e7e5ae2ee0
commit
5654359813
@ -827,7 +827,9 @@ Sets the default UI theme: `dark`, `light`, or `system`. The default theme is `d
|
|||||||
|
|
||||||
### default_language
|
### default_language
|
||||||
|
|
||||||
This setting configures the default UI language, which must be a supported IETF language tag, such as `en-US`.
|
This option will set the default UI language if a supported IETF language tag like `en-US` is available.
|
||||||
|
If set to `detect`, the default UI language will be determined by browser preference.
|
||||||
|
The default is `en-US`.
|
||||||
|
|
||||||
### home_page
|
### home_page
|
||||||
|
|
||||||
|
@ -337,6 +337,7 @@
|
|||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"hoist-non-react-statics": "3.3.2",
|
"hoist-non-react-statics": "3.3.2",
|
||||||
"i18next": "^22.0.0",
|
"i18next": "^22.0.0",
|
||||||
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
"immer": "10.0.2",
|
"immer": "10.0.2",
|
||||||
"immutable": "4.3.0",
|
"immutable": "4.3.0",
|
||||||
"jquery": "3.7.0",
|
"jquery": "3.7.0",
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"hoist-non-react-statics": "3.3.2",
|
"hoist-non-react-statics": "3.3.2",
|
||||||
"i18next": "^22.0.0",
|
"i18next": "^22.0.0",
|
||||||
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
"immutable": "4.3.0",
|
"immutable": "4.3.0",
|
||||||
"is-hotkey": "0.2.0",
|
"is-hotkey": "0.2.0",
|
||||||
"jquery": "3.7.0",
|
"jquery": "3.7.0",
|
||||||
|
@ -14,7 +14,8 @@ import { Trans as I18NextTrans, initReactI18next } from 'react-i18next'; // esli
|
|||||||
// Creates a default, english i18next instance when running outside of grafana.
|
// Creates a default, english i18next instance when running outside of grafana.
|
||||||
// we don't support changing the locale of grafana ui when outside of Grafana
|
// we don't support changing the locale of grafana ui when outside of Grafana
|
||||||
function initI18n() {
|
function initI18n() {
|
||||||
if (!i18next.options.lng) {
|
// resources is undefined by default and set either by grafana app.ts or here
|
||||||
|
if (typeof i18next.options.resources !== 'object') {
|
||||||
i18next.use(initReactI18next).init({
|
i18next.use(initReactI18next).init({
|
||||||
resources: {},
|
resources: {},
|
||||||
returnEmptyString: false,
|
returnEmptyString: false,
|
||||||
|
@ -1,37 +1,34 @@
|
|||||||
import i18n, { BackendModule } from 'i18next';
|
import i18n, { BackendModule, InitOptions } from 'i18next';
|
||||||
|
import LanguageDetector, { DetectorOptions } from 'i18next-browser-languagedetector';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Trans as I18NextTrans, initReactI18next } from 'react-i18next'; // eslint-disable-line no-restricted-imports
|
import { Trans as I18NextTrans, initReactI18next } from 'react-i18next'; // eslint-disable-line no-restricted-imports
|
||||||
|
|
||||||
import { DEFAULT_LANGUAGE, LANGUAGES, VALID_LANGUAGES } from './constants';
|
import { LANGUAGES, VALID_LANGUAGES } from './constants';
|
||||||
|
|
||||||
|
const getLanguagePartFromCode = (code: string) => code.split('-')[0].toLowerCase();
|
||||||
|
|
||||||
const loadTranslations: BackendModule = {
|
const loadTranslations: BackendModule = {
|
||||||
type: 'backend',
|
type: 'backend',
|
||||||
init() {},
|
init() {},
|
||||||
async read(language, namespace, callback) {
|
async read(language, namespace, callback) {
|
||||||
const localeDef = LANGUAGES.find((v) => v.code === language);
|
let localeDef = LANGUAGES.find((v) => v.code === language);
|
||||||
|
if (!localeDef) {
|
||||||
|
localeDef = LANGUAGES.find((v) => getLanguagePartFromCode(v.code) === getLanguagePartFromCode(language));
|
||||||
|
}
|
||||||
if (!localeDef) {
|
if (!localeDef) {
|
||||||
return callback(new Error('No message loader available for ' + language), null);
|
return callback(new Error('No message loader available for ' + language), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = await localeDef.loader();
|
const messages = await localeDef.loader();
|
||||||
callback(null, messages);
|
callback(null, messages);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function initializeI18n(language: string) {
|
export function initializeI18n(language: string) {
|
||||||
const validLocale = VALID_LANGUAGES.includes(language) ? language : DEFAULT_LANGUAGE;
|
|
||||||
|
|
||||||
// This is a placeholder so we can put a 'comment' in the message json files.
|
// This is a placeholder so we can put a 'comment' in the message json files.
|
||||||
// Starts with an underscore so it's sorted to the top of the file. Even though it is in a comment the following line is still extracted
|
// Starts with an underscore so it's sorted to the top of the file. Even though it is in a comment the following line is still extracted
|
||||||
// t('_comment', 'This file is the source of truth for English strings. Edit this to change plurals and other phrases for the UI.');
|
// t('_comment', 'This file is the source of truth for English strings. Edit this to change plurals and other phrases for the UI.');
|
||||||
|
|
||||||
return i18n
|
const options: InitOptions = {
|
||||||
.use(loadTranslations)
|
|
||||||
.use(initReactI18next) // passes i18n down to react-i18next
|
|
||||||
.init({
|
|
||||||
lng: validLocale,
|
|
||||||
|
|
||||||
// We don't bundle any translations, we load them async
|
// We don't bundle any translations, we load them async
|
||||||
partialBundledLanguages: true,
|
partialBundledLanguages: true,
|
||||||
resources: {},
|
resources: {},
|
||||||
@ -40,11 +37,23 @@ export function initializeI18n(language: string) {
|
|||||||
returnEmptyString: false,
|
returnEmptyString: false,
|
||||||
|
|
||||||
pluralSeparator: '__',
|
pluralSeparator: '__',
|
||||||
});
|
};
|
||||||
|
let init = i18n;
|
||||||
|
if (language === 'detect') {
|
||||||
|
init = init.use(LanguageDetector);
|
||||||
|
const detection: DetectorOptions = { order: ['navigator'], caches: [] };
|
||||||
|
options.detection = detection;
|
||||||
|
} else {
|
||||||
|
options.lng = VALID_LANGUAGES.includes(language) ? language : undefined;
|
||||||
|
}
|
||||||
|
return init
|
||||||
|
.use(loadTranslations)
|
||||||
|
.use(initReactI18next) // passes i18n down to react-i18next
|
||||||
|
.init(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changeLanguage(locale: string) {
|
export function changeLanguage(locale: string) {
|
||||||
const validLocale = VALID_LANGUAGES.includes(locale) ? locale : DEFAULT_LANGUAGE;
|
const validLocale = VALID_LANGUAGES.includes(locale) ? locale : undefined;
|
||||||
return i18n.changeLanguage(validLocale);
|
return i18n.changeLanguage(validLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +72,6 @@ export const i18nDate = (value: number | Date | string, format: Intl.DateTimeFor
|
|||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
return i18nDate(new Date(value), format);
|
return i18nDate(new Date(value), format);
|
||||||
}
|
}
|
||||||
const locale = i18n.options.lng ?? DEFAULT_LANGUAGE;
|
const dateFormatter = new Intl.DateTimeFormat(i18n.language, format);
|
||||||
|
|
||||||
const dateFormatter = new Intl.DateTimeFormat(locale, format);
|
|
||||||
return dateFormatter.format(value);
|
return dateFormatter.format(value);
|
||||||
};
|
};
|
||||||
|
13
yarn.lock
13
yarn.lock
@ -2219,7 +2219,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/runtime@npm:7.22.3, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
|
"@babel/runtime@npm:7.22.3, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.19.4, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
|
||||||
version: 7.22.3
|
version: 7.22.3
|
||||||
resolution: "@babel/runtime@npm:7.22.3"
|
resolution: "@babel/runtime@npm:7.22.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4184,6 +4184,7 @@ __metadata:
|
|||||||
expose-loader: 4.1.0
|
expose-loader: 4.1.0
|
||||||
hoist-non-react-statics: 3.3.2
|
hoist-non-react-statics: 3.3.2
|
||||||
i18next: ^22.0.0
|
i18next: ^22.0.0
|
||||||
|
i18next-browser-languagedetector: ^7.0.2
|
||||||
immutable: 4.3.0
|
immutable: 4.3.0
|
||||||
is-hotkey: 0.2.0
|
is-hotkey: 0.2.0
|
||||||
jquery: 3.7.0
|
jquery: 3.7.0
|
||||||
@ -19410,6 +19411,7 @@ __metadata:
|
|||||||
http-server: 14.1.1
|
http-server: 14.1.1
|
||||||
husky: 8.0.3
|
husky: 8.0.3
|
||||||
i18next: ^22.0.0
|
i18next: ^22.0.0
|
||||||
|
i18next-browser-languagedetector: ^7.0.2
|
||||||
i18next-parser: 6.6.0
|
i18next-parser: 6.6.0
|
||||||
immer: 10.0.2
|
immer: 10.0.2
|
||||||
immutable: 4.3.0
|
immutable: 4.3.0
|
||||||
@ -20272,6 +20274,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"i18next-browser-languagedetector@npm:^7.0.2":
|
||||||
|
version: 7.0.2
|
||||||
|
resolution: "i18next-browser-languagedetector@npm:7.0.2"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": ^7.19.4
|
||||||
|
checksum: ea6c5254308965ff9fe9870c16b546b67d065cc00bd3af961f1414ba73fbe323a7d1b76fe7524b1581307f7849fa6d04c36336693818a23d3690ebefc60d9071
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"i18next-parser@npm:6.6.0":
|
"i18next-parser@npm:6.6.0":
|
||||||
version: 6.6.0
|
version: 6.6.0
|
||||||
resolution: "i18next-parser@npm:6.6.0"
|
resolution: "i18next-parser@npm:6.6.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user