I18n: Show languages in local names (#57367)

* I18n: Show languages in local names

* fixed test
This commit is contained in:
Josh Hunt 2022-10-21 10:29:03 +01:00 committed by GitHub
parent 5c710a5590
commit fc75076b72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 84 additions and 84 deletions

View File

@ -68,11 +68,9 @@ While the `t` function can technically be used outside of React functions (e.g,
1. Grafana OSS Crowdin project -> "dot dot dot" menu in top right -> Target languages
2. Grafana OSS Crowdin project -> Integrations -> Github -> Sync Now
3. If Crowdin's locale code is different from our IETF language tag, add a custom mapping in Project Settings -> Language mapping
2. Update `public/app/core/internationalization/constants.ts` (add new constant, and add to `VALID_LOCALES`)
3. Update `public/app/core/internationalization/index.tsx` to add the message loader for the new locale
4. Update `public/app/core/components/SharedPreferences/SharedPreferences.tsx` to add the new locale to the options.
5. Update `public/locales/i18next-parser.config.js` to add the new locale to `locales`
6. Run `yarn i18n:extract` and commit the result
2. Update `public/app/core/internationalization/constants.ts` (add new constant, and add to `LOCALES`)
3. Update `public/locales/i18next-parser.config.js` to add the new locale to `locales`
4. Run `yarn i18n:extract` and commit the result
## How translations work in Grafana

View File

@ -163,7 +163,7 @@ describe('SharedPreferences', () => {
await selectOptionInTest(screen.getByLabelText('Timezone'), 'Australia/Sydney');
await selectOptionInTest(screen.getByLabelText('Week start'), 'Saturday');
await selectOptionInTest(screen.getByLabelText(/language/i), 'French');
await selectOptionInTest(screen.getByLabelText(/language/i), 'Français');
await userEvent.click(screen.getByText('Save'));
expect(mockPrefsUpdate).toHaveBeenCalledWith({

View File

@ -19,13 +19,7 @@ import {
} from '@grafana/ui';
import { DashboardPicker } from 'app/core/components/Select/DashboardPicker';
import { t, Trans } from 'app/core/internationalization';
import {
CHINESE_SIMPLIFIED,
ENGLISH_US,
FRENCH_FRANCE,
PSEUDO_LOCALE,
SPANISH_SPAIN,
} from 'app/core/internationalization/constants';
import { LOCALES } from 'app/core/internationalization/constants';
import { PreferencesService } from 'app/core/services/PreferencesService';
import { UserPreferencesDTO } from 'app/types';
@ -43,36 +37,19 @@ const themes: SelectableValue[] = [
];
function getLanguageOptions(): Array<SelectableValue<string>> {
const languageOptions = LOCALES.map((v) => ({
value: v.code,
label: v.name,
}));
const options = [
{
value: '',
label: t('common.locale.default', 'Default'),
},
{
value: ENGLISH_US,
label: t('common.locale.en-US', 'English'),
},
{
value: SPANISH_SPAIN,
label: t('common.locale.es-ES', 'Spanish'),
},
{
value: FRENCH_FRANCE,
label: t('common.locale.fr-FR', 'French'),
},
{
value: CHINESE_SIMPLIFIED,
label: t('common.locale.zh-Hans', 'Chinese (Simplified)'),
},
...languageOptions,
];
if (process.env.NODE_ENV === 'development') {
options.push({
value: PSEUDO_LOCALE,
label: 'Pseudo-locale', // no need to translate this key
});
}
return options;
}

View File

@ -0,0 +1,14 @@
import { uniqBy } from 'lodash';
import { LOCALES, VALID_LOCALES } from './constants';
describe('internationalization constants', () => {
it('should not have duplicate languages codes', () => {
const uniqLocales = uniqBy(LOCALES, (v) => v.code);
expect(LOCALES).toHaveLength(uniqLocales.length);
});
it('should have a correct list of valid locale codes', () => {
expect(VALID_LOCALES).toEqual(LOCALES.map((v) => v.code));
});
});

View File

@ -1,3 +1,5 @@
import { ResourceKey } from 'i18next';
export const ENGLISH_US = 'en-US';
export const FRENCH_FRANCE = 'fr-FR';
export const SPANISH_SPAIN = 'es-ES';
@ -6,4 +8,49 @@ export const PSEUDO_LOCALE = 'pseudo-LOCALE';
export const DEFAULT_LOCALE = ENGLISH_US;
export const VALID_LOCALES: string[] = [ENGLISH_US, FRENCH_FRANCE, SPANISH_SPAIN, CHINESE_SIMPLIFIED, PSEUDO_LOCALE];
interface LocaleDefinition {
/** IETF language tag for the language e.g. en-US */
code: string;
/** Language name to show in the UI. Should be formatted local to that language e.g. Français for French */
name: string;
/** Function to load translations */
loader: () => Promise<ResourceKey>;
}
export const LOCALES: LocaleDefinition[] = [
{
code: ENGLISH_US,
name: 'English',
loader: () => Promise.resolve({}),
},
{
code: FRENCH_FRANCE,
name: 'Français',
loader: () => import('../../../locales/fr-FR/grafana.json'),
},
{
code: SPANISH_SPAIN,
name: 'Español',
loader: () => import('../../../locales/es-ES/grafana.json'),
},
{
code: CHINESE_SIMPLIFIED,
name: '中文(简体)',
loader: () => import('../../../locales/zh-Hans/grafana.json'),
},
];
if (process.env.NODE_ENV === 'development') {
LOCALES.push({
code: PSEUDO_LOCALE,
name: 'Pseudo-locale',
loader: () => import('../../../locales/pseudo-LOCALE/grafana.json'),
});
}
export const VALID_LOCALES = LOCALES.map((v) => v.code);

View File

@ -1,36 +1,20 @@
import i18n, { BackendModule, ResourceKey } from 'i18next';
import i18n, { BackendModule } from 'i18next';
import React from 'react';
import { Trans as I18NextTrans, initReactI18next } from 'react-i18next'; // eslint-disable-line no-restricted-imports
import {
DEFAULT_LOCALE,
ENGLISH_US,
FRENCH_FRANCE,
SPANISH_SPAIN,
PSEUDO_LOCALE,
VALID_LOCALES,
CHINESE_SIMPLIFIED,
} from './constants';
const messageLoaders: Record<string, () => Promise<ResourceKey>> = {
// English phrases are the default fallback string in the source, so we don't need to load the catalogue
[ENGLISH_US]: () => Promise.resolve({}),
[FRENCH_FRANCE]: () => import('../../../locales/fr-FR/grafana.json'),
[SPANISH_SPAIN]: () => import('../../../locales/es-ES/grafana.json'),
[CHINESE_SIMPLIFIED]: () => import('../../../locales/zh-Hans/grafana.json'),
[PSEUDO_LOCALE]: () => import('../../../locales/pseudo-LOCALE/grafana.json'),
};
import { DEFAULT_LOCALE, LOCALES, VALID_LOCALES } from './constants';
const loadTranslations: BackendModule = {
type: 'backend',
init() {},
async read(language, namespace, callback) {
const loader = messageLoaders[language];
if (!loader) {
const localeDef = LOCALES.find((v) => v.code === language);
if (!localeDef) {
return callback(new Error('No message loader available for ' + language), null);
}
const messages = await loader();
const messages = await localeDef.loader();
callback(null, messages);
},
};

View File

@ -2,11 +2,7 @@
"_comment": "Do not manually edit this file, or update these source phrases in Crowdin. The source of truth for English strings are in the code source",
"common": {
"locale": {
"default": "Default",
"en-US": "English",
"es-ES": "Spanish",
"fr-FR": "French",
"zh-Hans": "Chinese (Simplified)"
"default": "Default"
},
"save": "Save"
},

View File

@ -2,11 +2,7 @@
"_comment": "Do not manually edit this file. Translations must be made in Crowdin which will sync them back into this file",
"common": {
"locale": {
"default": "Por defecto",
"en-US": "Inglés",
"es-ES": "Español",
"fr-FR": "Francés",
"zh-Hans": "Chino (simplificado)"
"default": "Por defecto"
},
"save": "Guardar"
},

View File

@ -2,11 +2,7 @@
"_comment": "Do not manually edit this file. Translations must be made in Crowdin which will sync them back into this file",
"common": {
"locale": {
"default": "Par défaut",
"en-US": "Anglais",
"es-ES": "Espagnol",
"fr-FR": "Français",
"zh-Hans": "Chinois (simplifié)"
"default": "Par défaut"
},
"save": "Enregistrer"
},

View File

@ -2,11 +2,7 @@
"_comment": "Đő ʼnőŧ mäʼnūäľľy ęđįŧ ŧĥįş ƒįľę, őř ūpđäŧę ŧĥęşę şőūřčę pĥřäşęş įʼn Cřőŵđįʼn. Ŧĥę şőūřčę őƒ ŧřūŧĥ ƒőř Ēʼnģľįşĥ şŧřįʼnģş äřę įʼn ŧĥę čőđę şőūřčę",
"common": {
"locale": {
"default": "Đęƒäūľŧ",
"en-US": "Ēʼnģľįşĥ",
"es-ES": "Ŝpäʼnįşĥ",
"fr-FR": "Fřęʼnčĥ",
"zh-Hans": "Cĥįʼnęşę (Ŝįmpľįƒįęđ)"
"default": "Đęƒäūľŧ"
},
"save": "Ŝävę"
},

View File

@ -2,11 +2,7 @@
"_comment": "Do not manually edit this file. Translations must be made in Crowdin which will sync them back into this file",
"common": {
"locale": {
"default": "默认",
"en-US": "英语",
"es-ES": "西班牙语",
"fr-FR": "法语",
"zh-Hans": "中文(简体)"
"default": "默认"
},
"save": "保存"
},