Skip to content

Commit 1b45070

Browse files
author
sunlinyao
committed
i18n support
1 parent 62f114d commit 1b45070

33 files changed

+553
-324
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@
225225
"file-saver": "1.3.8",
226226
"hoist-non-react-statics": "3.3.0",
227227
"i18next": "19.0.2",
228+
"i18next-xhr-backend": "^3.2.2",
229+
"i18next-browser-languagedetector":"4.0.1",
228230
"immutable": "3.8.2",
229231
"is-hotkey": "0.1.4",
230232
"jquery": "3.4.1",

packages/grafana-ui/src/components/TimePicker/TimePicker.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { withTheme, useTheme } from '../../themes/ThemeContext';
1616
import { isDateTime, DateTime, rangeUtil, GrafanaTheme, TIME_FORMAT } from '@grafana/data';
1717
import { TimeRange, TimeOption, TimeZone, dateMath } from '@grafana/data';
1818
import { Themeable } from '../../types';
19+
import { Translation } from "react-i18next";
1920

2021
const quickOptions: TimeOption[] = [
2122
{ from: 'now-5m', to: 'now', display: 'Last 5 minutes', section: 3 },
@@ -198,7 +199,11 @@ export class UnthemedTimePicker extends PureComponent<Props, State> {
198199

199200
const ZoomOutTooltip = () => (
200201
<>
201-
Time range zoom out <br /> CTRL+Z
202+
<Translation>
203+
{
204+
(t) => <>{t("Time range zoom out")} <br /> CTRL+Z</>
205+
}
206+
</Translation>
202207
</>
203208
);
204209

packages/grafana-ui/src/components/TimePicker/TimePickerContent/TimePickerContent.tsx

+16-10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CustomScrollbar } from '../../CustomScrollbar/CustomScrollbar';
99
import { TimeRangeList } from './TimeRangeList';
1010
import { mapRangeToTimeOption } from './mapper';
1111
import { getThemeColors } from './colors';
12+
import { Trans, useTranslation } from "react-i18next";
1213

1314
const getStyles = stylesFactory((theme: GrafanaTheme) => {
1415
const colors = getThemeColors(theme);
@@ -140,6 +141,7 @@ interface FormProps extends Omit<Props, 'history'> {
140141
}
141142

142143
export const TimePickerContentWithScreenSize: React.FC<PropsWithScreenSize> = props => {
144+
const { t } = useTranslation();
143145
const theme = useTheme();
144146
const styles = getStyles(theme);
145147
const historyOptions = mapToHistoryOptions(props.history, props.timeZone);
@@ -153,15 +155,15 @@ export const TimePickerContentWithScreenSize: React.FC<PropsWithScreenSize> = pr
153155
<CustomScrollbar className={styles.rightSide}>
154156
<NarrowScreenForm {...props} visible={!isFullscreen} historyOptions={historyOptions} />
155157
<TimeRangeList
156-
title="Relative time ranges"
158+
title={t("Relative time ranges")}
157159
options={quickOptions}
158160
onSelect={props.onChange}
159161
value={props.value}
160162
timeZone={props.timeZone}
161163
/>
162164
<div className={styles.spacing} />
163165
<TimeRangeList
164-
title="Other quick ranges"
166+
title={t("Other quick ranges")}
165167
options={otherOptions}
166168
onSelect={props.onChange}
167169
value={props.value}
@@ -180,6 +182,7 @@ export const TimePickerContent: React.FC<Props> = props => {
180182
};
181183

182184
const NarrowScreenForm: React.FC<FormProps> = props => {
185+
const { t } = useTranslation();
183186
if (!props.visible) {
184187
return null;
185188
}
@@ -192,7 +195,9 @@ const NarrowScreenForm: React.FC<FormProps> = props => {
192195
return (
193196
<>
194197
<div className={styles.header} onClick={() => setCollapsed(!collapsed)}>
195-
<TimePickerTitle>Absolute time range</TimePickerTitle>
198+
<TimePickerTitle>
199+
{t("Absolute time range")}
200+
</TimePickerTitle>
196201
{collapsed ? <i className="fa fa-caret-up" /> : <i className="fa fa-caret-down" />}
197202
</div>
198203
{collapsed && (
@@ -206,7 +211,7 @@ const NarrowScreenForm: React.FC<FormProps> = props => {
206211
/>
207212
</div>
208213
<TimeRangeList
209-
title="Recently used absolute ranges"
214+
title={t("Recently used absolute ranges")}
210215
options={props.historyOptions || []}
211216
onSelect={props.onChange}
212217
value={props.value}
@@ -220,6 +225,7 @@ const NarrowScreenForm: React.FC<FormProps> = props => {
220225
};
221226

222227
const FullScreenForm: React.FC<FormProps> = props => {
228+
const { t } = useTranslation();
223229
if (!props.visible) {
224230
return null;
225231
}
@@ -231,13 +237,13 @@ const FullScreenForm: React.FC<FormProps> = props => {
231237
<>
232238
<div className={styles.container}>
233239
<div className={styles.title}>
234-
<TimePickerTitle>Absolute time range</TimePickerTitle>
240+
<TimePickerTitle>{t("Absolute time range")}</TimePickerTitle>
235241
</div>
236242
<TimeRangeForm value={props.value} timeZone={props.timeZone} onApply={props.onChange} isFullscreen={true} />
237243
</div>
238244
<div className={styles.recent}>
239245
<TimeRangeList
240-
title="Recently used absolute ranges"
246+
title={t("Recently used absolute ranges")}
241247
options={props.historyOptions || []}
242248
onSelect={props.onChange}
243249
value={props.value}
@@ -250,22 +256,22 @@ const FullScreenForm: React.FC<FormProps> = props => {
250256
};
251257

252258
const EmptyRecentList = memo(() => {
259+
const { t } = useTranslation();
253260
const theme = useTheme();
254261
const styles = getEmptyListStyles(theme);
255262

256263
return (
257264
<div className={styles.container}>
258265
<div>
259266
<span>
260-
It looks like you haven't used this timer picker before. As soon as you enter some time intervals, recently
261-
used intervals will appear here.
267+
<Trans>time.history.null</Trans>
262268
</span>
263269
</div>
264270
<div>
265271
<a className={styles.link} href="https://grafana.com/docs/grafana/latest/reference/timerange/" target="_new">
266-
Read the documentation
272+
{t("Read the documentation")}
267273
</a>
268-
<span> to find out more about how to enter custom time ranges.</span>
274+
<span> {t("to find out more about how to enter custom time ranges.")}</span>
269275
</div>
270276
</div>
271277
);

packages/grafana-ui/src/components/TimePicker/TimePickerContent/TimeRangeForm.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { mapStringsToTimeRange } from './mapper';
55
import { TimePickerCalendar } from './TimePickerCalendar';
66
import Forms from '../../Forms';
77
import { isMathString } from '@grafana/data/src/datetime/datemath';
8+
import { useTranslation } from "react-i18next";
89

910
interface Props {
1011
isFullscreen: boolean;
@@ -22,6 +23,7 @@ interface InputState {
2223
const errorMessage = 'Please enter a past date or "now"';
2324

2425
export const TimeRangeForm: React.FC<Props> = props => {
26+
const { t } = useTranslation();
2527
const { value, isFullscreen = false, timeZone, roundup } = props;
2628

2729
const [from, setFrom] = useState<InputState>(valueToState(value.raw.from, false, timeZone));
@@ -65,7 +67,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
6567

6668
return (
6769
<>
68-
<Forms.Field label="From" invalid={from.invalid} error={errorMessage}>
70+
<Forms.Field label={t("time.From")} invalid={from.invalid} error={errorMessage}>
6971
<Forms.Input
7072
onClick={event => event.stopPropagation()}
7173
onFocus={onFocus}
@@ -74,7 +76,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
7476
value={from.value}
7577
/>
7678
</Forms.Field>
77-
<Forms.Field label="To" invalid={to.invalid} error={errorMessage}>
79+
<Forms.Field label={t("time.To")} invalid={to.invalid} error={errorMessage}>
7880
<Forms.Input
7981
onClick={event => event.stopPropagation()}
8082
onFocus={onFocus}
@@ -83,7 +85,7 @@ export const TimeRangeForm: React.FC<Props> = props => {
8385
value={to.value}
8486
/>
8587
</Forms.Field>
86-
<Forms.Button onClick={onApply}>Apply time range</Forms.Button>
88+
<Forms.Button onClick={onApply}>{t("Apply time range")}</Forms.Button>
8789

8890
<TimePickerCalendar
8991
isFullscreen={isFullscreen}

public/app/app.ts

-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import 'react-dom';
1313
import 'vendor/bootstrap/bootstrap';
1414
import 'vendor/angular-other/angular-strap';
1515

16-
import i18n from './core/i18n';
17-
1816
import $ from 'jquery';
1917
import angular from 'angular';
2018
import config from 'app/core/config';
@@ -58,7 +56,6 @@ export class GrafanaApp {
5856
preBootModules: any[] | null;
5957

6058
constructor() {
61-
console.log("Current: " + i18n.locale);
6259
addClassIfNoOverlayScrollbar('no-overlay-scrollbar');
6360
this.preBootModules = [];
6461
this.registerFunctions = {};

public/app/core/components/EmptyListCTA/EmptyListCTA.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { MouseEvent, useContext } from 'react';
22
import { CallToActionCard, LinkButton, ThemeContext } from '@grafana/ui';
33
import { css } from 'emotion';
4+
import { Trans } from 'react-i18next';
45

56
export interface Props {
67
title: string;
@@ -46,7 +47,7 @@ const EmptyListCTA: React.FunctionComponent<Props> = ({
4647
{proTip ? (
4748
<span key="proTipFooter">
4849
<i className="fa fa-rocket" />
49-
<> ProTip: {proTip} </>
50+
<> <Trans>ProTip</Trans>: {proTip} </>
5051
<a href={proTipLink} target={proTipTarget} className="text-link">
5152
{proTipLinkTitle}
5253
</a>

public/app/core/i18n/index.ts

+18-8
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,41 @@ import { initReactI18next } from "react-i18next";
33
import config from '../config';
44

55

6-
async function LoadLanguage() {
6+
function LoadLanguage(callback: () => void) {
77
let name = config.bootData.user.language;
88
if (name === "") {
99
name = "en_US";
1010
}
11-
config.bootData.ready = false;
1211
let locale: any;
1312
switch (name) {
1413
case "zh_CN":
1514
require.ensure([], (require: NodeRequire) => {
1615
locale = require("./translate/zh_CN.js");
1716
initI18n(locale, name);
18-
config.bootData.ready = true;
17+
callback();
1918
}, "zh_CN");
2019
break;
2120
default:
2221
require.ensure([], (require: NodeRequire) => {
2322
locale = require("./translate/en_US.js");
2423
initI18n(locale, name);
25-
config.bootData.ready = true;
24+
callback();
2625
}, "en_US");
2726
break;
2827
}
29-
return await locale;
3028
}
3129

30+
export const T = (text: string) => {
31+
const locale = config.bootData.translate;
32+
if (locale.hasOwnProperty(text)) {
33+
return Object.getOwnPropertyDescriptor(locale, text).value;
34+
}
35+
return text;
36+
};
3237

3338
export const initI18n = (locale: object, name: string) => {
3439
try {
40+
config.bootData.translate = locale;
3541
const resources: { [index: string]: any } = {};
3642
resources[name] = {
3743
translation: locale
@@ -41,7 +47,11 @@ export const initI18n = (locale: object, name: string) => {
4147
.init({
4248
resources,
4349
lng: name,
44-
50+
saveMissing: false,
51+
react: {
52+
useSuspense: true,
53+
},
54+
debug: true,
4555
keySeparator: false, // we do not use keys in form messages.welcome
4656

4757
interpolation: {
@@ -86,6 +96,6 @@ function depthTrans(locale: object, nav: NavTree[]) {
8696
});
8797
}
8898

89-
LoadLanguage();
9099

91-
export default { "locale": config.bootData.user.language };
100+
101+
export default LoadLanguage;
+31
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,35 @@
11
module.exports = {
22
"Full name": "Name",
33
"Login Name": "Login",
4+
"time.history.null": "It looks like you haven't used this timer picker before. As soon as you enter some time intervals, recentlyused intervals will appear here.",
5+
"playlist.tip": "You can use playlists to cycle dashboards on TVs without user control",
6+
"playlist.description": "A playlist rotates through a pre-selected list of Dashboards. A Playlist can be a great way to build situational awareness, or just show off your metrics to your team or visitors.",
7+
"playlist.dashboards.empty": "Playlist is empty, add dashboards below.",
8+
"variavble.describe": ` <p>
9+
Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or
10+
sensor names in your metric queries you can use variables in their place. Variables are shown as dropdown
11+
select boxes at the top of the dashboard. These dropdowns make it easy to change the data being displayed in
12+
your dashboard. Check out the
13+
<a class="external-link" href="http://docs.grafana.org/reference/templating/" target="_blank">
14+
Templating documentation
15+
</a>
16+
for more information.
17+
</p>`,
18+
"dashboard.link.describe": `<p>
19+
Dashboard Links allow you to place links to other dashboards and web sites directly in below the dashboard
20+
header.
21+
</p>`,
22+
"dashboard.annotation.describe": `<p>Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines
23+
and icons on all graph panels. When you hover over an annotation icon you can get event text &amp; tags for
24+
the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or
25+
drag region). These will be stored in Grafana's annotation database.
26+
</p>
27+
Checkout the
28+
<a class='external-link' target='_blank' href='http://docs.grafana.org/reference/annotations/'
29+
>Annotations documentation</a
30+
>
31+
for more information.`,
32+
"time.delay.tooltip": "Enter 1m to ignore the last minute (because it can contain incomplete metrics)",
33+
"dashboard.editable.tooltip": "Uncheck, then save and reload to disable all dashboard editing",
34+
"dashboard.tooltip.tooltip": "Cycle between options using Shortcut: CTRL+O or CMD+O",
435
};

0 commit comments

Comments
 (0)