I18n: Add Chinese (Simplified) (#56739)
* Prevent _old.json files from being created * Add contrib docs for adding new locale * add chinese (simplified)
This commit is contained in:
@@ -38,6 +38,18 @@ const ErrorMessage = ({ id, message }) => <Trans id={`errors.${id}`}>There was a
|
|||||||
|
|
||||||
3. Before submitting your PR, run the `yarn i18n:extract` command to extract the messages you added into the `messages.po` file and make them available for translation.
|
3. Before submitting your PR, run the `yarn i18n:extract` command to extract the messages you added into the `messages.po` file and make them available for translation.
|
||||||
|
|
||||||
|
## How to add a new language
|
||||||
|
|
||||||
|
1. Add new locale in Crowdin and sync files to repo
|
||||||
|
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
|
||||||
|
|
||||||
## How translations work in Grafana
|
## How translations work in Grafana
|
||||||
|
|
||||||
Grafana uses the [LinguiJS](https://github.com/lingui/js-lingui) framework for managing translating phrases in the Grafana frontend. It:
|
Grafana uses the [LinguiJS](https://github.com/lingui/js-lingui) framework for managing translating phrases in the Grafana frontend. It:
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ import {
|
|||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { DashboardPicker } from 'app/core/components/Select/DashboardPicker';
|
import { DashboardPicker } from 'app/core/components/Select/DashboardPicker';
|
||||||
import { t, Trans } from 'app/core/internationalization';
|
import { t, Trans } from 'app/core/internationalization';
|
||||||
import { ENGLISH_US, FRENCH_FRANCE, PSEUDO_LOCALE, SPANISH_SPAIN } from 'app/core/internationalization/constants';
|
import {
|
||||||
|
CHINESE_SIMPLIFIED,
|
||||||
|
ENGLISH_US,
|
||||||
|
FRENCH_FRANCE,
|
||||||
|
PSEUDO_LOCALE,
|
||||||
|
SPANISH_SPAIN,
|
||||||
|
} from 'app/core/internationalization/constants';
|
||||||
import { PreferencesService } from 'app/core/services/PreferencesService';
|
import { PreferencesService } from 'app/core/services/PreferencesService';
|
||||||
import { UserPreferencesDTO } from 'app/types';
|
import { UserPreferencesDTO } from 'app/types';
|
||||||
|
|
||||||
@@ -43,15 +49,19 @@ const languages: Array<SelectableValue<string>> = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: ENGLISH_US,
|
value: ENGLISH_US,
|
||||||
label: t('common.locale.en', 'English'),
|
label: t('common.locale.en-US', 'English'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: SPANISH_SPAIN,
|
value: SPANISH_SPAIN,
|
||||||
label: t('common.locale.es', 'Spanish'),
|
label: t('common.locale.es-ES', 'Spanish'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: FRENCH_FRANCE,
|
value: FRENCH_FRANCE,
|
||||||
label: t('common.locale.fr', 'French'),
|
label: t('common.locale.fr-FR', 'French'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: CHINESE_SIMPLIFIED,
|
||||||
|
label: t('common.locale.zh-Hans', 'Chinese (Simplified)'),
|
||||||
},
|
},
|
||||||
// TODO: dev only
|
// TODO: dev only
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
export const ENGLISH_US = 'en-US';
|
export const ENGLISH_US = 'en-US';
|
||||||
export const FRENCH_FRANCE = 'fr-FR';
|
export const FRENCH_FRANCE = 'fr-FR';
|
||||||
export const SPANISH_SPAIN = 'es-ES';
|
export const SPANISH_SPAIN = 'es-ES';
|
||||||
|
export const CHINESE_SIMPLIFIED = 'zh-Hans';
|
||||||
export const PSEUDO_LOCALE = 'pseudo-LOCALE';
|
export const PSEUDO_LOCALE = 'pseudo-LOCALE';
|
||||||
|
|
||||||
export const DEFAULT_LOCALE = ENGLISH_US;
|
export const DEFAULT_LOCALE = ENGLISH_US;
|
||||||
|
|
||||||
export const VALID_LOCALES: string[] = [ENGLISH_US, FRENCH_FRANCE, SPANISH_SPAIN, PSEUDO_LOCALE];
|
export const VALID_LOCALES: string[] = [ENGLISH_US, FRENCH_FRANCE, SPANISH_SPAIN, CHINESE_SIMPLIFIED, PSEUDO_LOCALE];
|
||||||
|
|||||||
@@ -2,12 +2,21 @@ import i18n, { BackendModule, ResourceKey } from 'i18next';
|
|||||||
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_LOCALE, ENGLISH_US, FRENCH_FRANCE, SPANISH_SPAIN, PSEUDO_LOCALE, VALID_LOCALES } from './constants';
|
import {
|
||||||
|
DEFAULT_LOCALE,
|
||||||
|
ENGLISH_US,
|
||||||
|
FRENCH_FRANCE,
|
||||||
|
SPANISH_SPAIN,
|
||||||
|
PSEUDO_LOCALE,
|
||||||
|
VALID_LOCALES,
|
||||||
|
CHINESE_SIMPLIFIED,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
const messageLoaders: Record<string, () => Promise<ResourceKey>> = {
|
const messageLoaders: Record<string, () => Promise<ResourceKey>> = {
|
||||||
[ENGLISH_US]: () => import('../../../locales/en-US/grafana.json'),
|
[ENGLISH_US]: () => import('../../../locales/en-US/grafana.json'),
|
||||||
[FRENCH_FRANCE]: () => import('../../../locales/fr-FR/grafana.json'),
|
[FRENCH_FRANCE]: () => import('../../../locales/fr-FR/grafana.json'),
|
||||||
[SPANISH_SPAIN]: () => import('../../../locales/es-ES/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'),
|
[PSEUDO_LOCALE]: () => import('../../../locales/pseudo-LOCALE/grafana.json'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"en": "English",
|
"en-US": "English",
|
||||||
"es": "Spanish",
|
"es-ES": "Spanish",
|
||||||
"fr": "French"
|
"fr-FR": "French",
|
||||||
|
"zh-Hans": "Chinese (Simplified)"
|
||||||
},
|
},
|
||||||
"save": "Save"
|
"save": "Save"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "",
|
"default": "",
|
||||||
"en": "",
|
"en-US": "",
|
||||||
"es": "",
|
"es-ES": "",
|
||||||
"fr": ""
|
"fr-FR": "",
|
||||||
|
"zh-Hans": ""
|
||||||
},
|
},
|
||||||
"save": ""
|
"save": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "",
|
"default": "",
|
||||||
"en": "",
|
"en-US": "",
|
||||||
"es": "",
|
"es-ES": "",
|
||||||
"fr": ""
|
"fr-FR": "",
|
||||||
|
"zh-Hans": ""
|
||||||
},
|
},
|
||||||
"save": ""
|
"save": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module.exports = {
|
|||||||
// Default namespace used in your i18next config
|
// Default namespace used in your i18next config
|
||||||
defaultNamespace: 'grafana',
|
defaultNamespace: 'grafana',
|
||||||
|
|
||||||
locales: ['en-US', 'fr-FR', 'es-ES', 'pseudo-LOCALE'],
|
locales: ['en-US', 'fr-FR', 'es-ES', "zh-Hans", 'pseudo-LOCALE'],
|
||||||
|
|
||||||
output: './public/locales/$LOCALE/$NAMESPACE.json',
|
output: './public/locales/$LOCALE/$NAMESPACE.json',
|
||||||
|
|
||||||
@@ -10,6 +10,8 @@ module.exports = {
|
|||||||
|
|
||||||
sort: true,
|
sort: true,
|
||||||
|
|
||||||
|
createOldCatalogs: false,
|
||||||
|
|
||||||
// Don't include default values for English, they'll remain in the source code
|
// Don't include default values for English, they'll remain in the source code
|
||||||
skipDefaultValues: (locale) => locale !== 'en-US',
|
skipDefaultValues: (locale) => locale !== 'en-US',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "Đęƒäūľŧ",
|
"default": "Đęƒäūľŧ",
|
||||||
"en": "Ēʼnģľįşĥ",
|
"en-US": "Ēʼnģľįşĥ",
|
||||||
"es": "Ŝpäʼnįşĥ",
|
"es-ES": "Ŝpäʼnįşĥ",
|
||||||
"fr": "Fřęʼnčĥ"
|
"fr-FR": "Fřęʼnčĥ",
|
||||||
|
"zh-Hans": "Cĥįʼnęşę (Ŝįmpľįƒįęđ)"
|
||||||
},
|
},
|
||||||
"save": "Ŝävę"
|
"save": "Ŝävę"
|
||||||
},
|
},
|
||||||
@@ -441,4 +442,4 @@
|
|||||||
"user-sessions": {
|
"user-sessions": {
|
||||||
"loading": "Ŀőäđįʼnģ şęşşįőʼnş..."
|
"loading": "Ŀőäđįʼnģ şęşşįőʼnş..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"default": "",
|
"default": "",
|
||||||
"en": "",
|
"en-US": "",
|
||||||
"es": "",
|
"es-ES": "",
|
||||||
"fr": ""
|
"fr-FR": "",
|
||||||
|
"zh-Hans": ""
|
||||||
},
|
},
|
||||||
"save": ""
|
"save": ""
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user