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:
Pierre Baumard 2023-07-14 12:24:50 +02:00 committed by GitHub
parent e7e5ae2ee0
commit 5654359813
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 27 deletions

View File

@ -827,7 +827,9 @@ Sets the default UI theme: `dark`, `light`, or `system`. The default theme is `d
### 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

View File

@ -337,6 +337,7 @@
"history": "4.10.1",
"hoist-non-react-statics": "3.3.2",
"i18next": "^22.0.0",
"i18next-browser-languagedetector": "^7.0.2",
"immer": "10.0.2",
"immutable": "4.3.0",
"jquery": "3.7.0",

View File

@ -71,6 +71,7 @@
"date-fns": "2.30.0",
"hoist-non-react-statics": "3.3.2",
"i18next": "^22.0.0",
"i18next-browser-languagedetector": "^7.0.2",
"immutable": "4.3.0",
"is-hotkey": "0.2.0",
"jquery": "3.7.0",

View File

@ -14,7 +14,8 @@ import { Trans as I18NextTrans, initReactI18next } from 'react-i18next'; // esli
// 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
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({
resources: {},
returnEmptyString: false,

View File

@ -1,50 +1,59 @@
import i18n, { BackendModule } from 'i18next';
import i18n, { BackendModule, InitOptions } from 'i18next';
import LanguageDetector, { DetectorOptions } from 'i18next-browser-languagedetector';
import React from 'react';
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 = {
type: 'backend',
init() {},
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) {
return callback(new Error('No message loader available for ' + language), null);
}
const messages = await localeDef.loader();
callback(null, messages);
},
};
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.
// 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.');
return i18n
const options: InitOptions = {
// We don't bundle any translations, we load them async
partialBundledLanguages: true,
resources: {},
// If translations are empty strings (no translation), fall back to the default value in source code
returnEmptyString: false,
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({
lng: validLocale,
// We don't bundle any translations, we load them async
partialBundledLanguages: true,
resources: {},
// If translations are empty strings (no translation), fall back to the default value in source code
returnEmptyString: false,
pluralSeparator: '__',
});
.init(options);
}
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);
}
@ -63,8 +72,6 @@ export const i18nDate = (value: number | Date | string, format: Intl.DateTimeFor
if (typeof value === 'string') {
return i18nDate(new Date(value), format);
}
const locale = i18n.options.lng ?? DEFAULT_LANGUAGE;
const dateFormatter = new Intl.DateTimeFormat(locale, format);
const dateFormatter = new Intl.DateTimeFormat(i18n.language, format);
return dateFormatter.format(value);
};

View File

@ -2219,7 +2219,7 @@ __metadata:
languageName: node
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
resolution: "@babel/runtime@npm:7.22.3"
dependencies:
@ -4184,6 +4184,7 @@ __metadata:
expose-loader: 4.1.0
hoist-non-react-statics: 3.3.2
i18next: ^22.0.0
i18next-browser-languagedetector: ^7.0.2
immutable: 4.3.0
is-hotkey: 0.2.0
jquery: 3.7.0
@ -19410,6 +19411,7 @@ __metadata:
http-server: 14.1.1
husky: 8.0.3
i18next: ^22.0.0
i18next-browser-languagedetector: ^7.0.2
i18next-parser: 6.6.0
immer: 10.0.2
immutable: 4.3.0
@ -20272,6 +20274,15 @@ __metadata:
languageName: node
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":
version: 6.6.0
resolution: "i18next-parser@npm:6.6.0"