Skip to content

Commit 615daa4

Browse files
committed
feat: add translation ability, #653
1 parent 3d9ee8f commit 615daa4

File tree

30 files changed

+476
-178
lines changed

30 files changed

+476
-178
lines changed

packages/api/typings/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export type UIConfig = Partial<{
199199
boardLogo: { path: string; width?: number | string; height?: number | string };
200200
miscLinks: Array<IMiscLink>;
201201
favIcon: FavIcon;
202+
locale: { lng?: string };
202203
}>;
203204

204205
export type FavIcon = {

packages/ui/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
"fork-ts-checker-webpack-plugin": "^7.2.7",
5959
"highlight.js": "^11.5.1",
6060
"html-webpack-plugin": "^5.5.0",
61+
"i18next": "^23.7.7",
62+
"i18next-hmr": "^3.0.3",
63+
"i18next-http-backend": "^2.4.2",
6164
"mini-css-extract-plugin": "^2.6.0",
6265
"nanoid": "^4.0.1",
6366
"postcss": "^8.4.12",
@@ -66,6 +69,7 @@
6669
"pretty-bytes": "^6.0.0",
6770
"react": "^17.0.0",
6871
"react-dom": "^17.0.0",
72+
"react-i18next": "^13.5.0",
6973
"react-paginate": "^8.1.3",
7074
"react-refresh": "^0.12.0",
7175
"react-router-dom": "^5.3.1",

packages/ui/src/components/Button/Button.module.css

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
cursor: pointer;
77
outline: none;
88
white-space: nowrap;
9-
padding: .65em .92857143em;
9+
padding: 0.65em 0.92857143em;
1010
color: inherit;
1111
font-family: inherit;
1212
vertical-align: baseline;
@@ -18,7 +18,7 @@
1818
}
1919

2020
.button + .button {
21-
margin-inline-start: 0.25em;
21+
margin-inline-start: 0.5rem;
2222
}
2323

2424
.button.default > svg {
@@ -81,4 +81,3 @@
8181
.button.primary:focus {
8282
background-color: hsl(216, 15%, 37%);
8383
}
84-

packages/ui/src/components/ConfirmModal/ConfirmModal.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from '@radix-ui/react-alert-dialog';
1111
import cn from 'clsx';
1212
import React from 'react';
13+
import { useTranslation } from 'react-i18next';
1314
import s from './ConfirmModal.module.css';
1415
import modalStyles from '../Modal/Modal.module.css';
1516
import { Button } from '../Button/Button';
@@ -23,6 +24,7 @@ export interface ConfirmProps {
2324
}
2425

2526
export const ConfirmModal = ({ open, onConfirm, title, onCancel, description }: ConfirmProps) => {
27+
const { t } = useTranslation();
2628
const closeOnOpenChange = (open: boolean) => {
2729
if (!open) {
2830
onCancel();
@@ -44,12 +46,12 @@ export const ConfirmModal = ({ open, onConfirm, title, onCancel, description }:
4446
<div className={modalStyles.actions}>
4547
<Action asChild>
4648
<Button theme="primary" onClick={onConfirm}>
47-
Confirm
49+
{t('CONFIRM.CONFIRM_BTN')}
4850
</Button>
4951
</Action>
5052
<Cancel asChild>
5153
<Button theme="basic" onClick={onCancel}>
52-
Cancel
54+
{t('CONFIRM.CANCEL_BTN')}
5355
</Button>
5456
</Cancel>
5557
</div>

packages/ui/src/components/Header/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const Header = ({ children }: PropsWithChildren<any>) => {
1212

1313
return (
1414
<header className={s.header}>
15-
<NavLink to={'/'} className={s.logo}>
15+
<NavLink to="/" className={s.logo}>
1616
{!!logoPath && (
1717
<img
1818
src={logoPath}

packages/ui/src/components/JobCard/Details/Details.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
23
import { useDetailsTabs } from '../../../hooks/useDetailsTabs';
34
import { Button } from '../../Button/Button';
45
import s from './Details.module.css';
@@ -13,6 +14,7 @@ interface DetailsProps {
1314

1415
export const Details = ({ status, job, actions }: DetailsProps) => {
1516
const { tabs, selectedTab } = useDetailsTabs(status, job.isFailed);
17+
const { t } = useTranslation();
1618

1719
if (tabs.length === 0) {
1820
return null;
@@ -24,7 +26,7 @@ export const Details = ({ status, job, actions }: DetailsProps) => {
2426
{tabs.map((tab) => (
2527
<li key={tab.title}>
2628
<Button onClick={tab.selectTab} isActive={tab.isActive}>
27-
{tab.title}
29+
{t(`JOB.TABS.${tab.title.toUpperCase()}`)}
2830
</Button>
2931
</li>
3032
))}

packages/ui/src/components/JobCard/Details/DetailsContent/DetailsContent.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AppJob } from '@bull-board/api/typings/app';
22
import React, { useState } from 'react';
3+
import { useTranslation } from 'react-i18next';
34
import { TabsType } from '../../../../hooks/useDetailsTabs';
45
import { useSettingsStore } from '../../../../hooks/useSettings';
56
import { Highlight } from '../../../Highlight/Highlight';
@@ -16,6 +17,7 @@ interface DetailsContentProps {
1617
}
1718

1819
export const DetailsContent = ({ selectedTab, job, actions }: DetailsContentProps) => {
20+
const { t } = useTranslation();
1921
const { collapseJobData, collapseJobOptions, collapseJobError } = useSettingsStore();
2022
const [collapseState, setCollapse] = useState({ data: false, options: false, error: false });
2123
const { stacktrace, data, returnValue, opts, failedReason } = job;
@@ -24,27 +26,27 @@ export const DetailsContent = ({ selectedTab, job, actions }: DetailsContentProp
2426
case 'Data':
2527
return collapseJobData && !collapseState.data ? (
2628
<Button onClick={() => setCollapse({ ...collapseState, data: true })}>
27-
Show data <ArrowDownIcon />
29+
{t('JOB.SHOW_DATA_BTN')} <ArrowDownIcon />
2830
</Button>
2931
) : (
3032
<Highlight language="json">{JSON.stringify({ data, returnValue }, null, 2)}</Highlight>
3133
);
3234
case 'Options':
3335
return collapseJobOptions && !collapseState.options ? (
3436
<Button onClick={() => setCollapse({ ...collapseState, options: true })}>
35-
Show options <ArrowDownIcon />
37+
{t('JOB.SHOW_OPTIONS_BTN')} <ArrowDownIcon />
3638
</Button>
3739
) : (
3840
<Highlight language="json">{JSON.stringify(opts, null, 2)}</Highlight>
3941
);
4042
case 'Error':
4143
if (stacktrace.length === 0) {
42-
return <div className="error">{!!failedReason ? failedReason : 'NA'}</div>;
44+
return <div className="error">{!!failedReason ? failedReason : t('JOB.NA')}</div>;
4345
}
4446

4547
return collapseJobError && !collapseState.error ? (
4648
<Button onClick={() => setCollapse({ ...collapseState, error: true })}>
47-
Show errors <ArrowDownIcon />
49+
{t('JOB.SHOW_ERRORS_BTN')} <ArrowDownIcon />
4850
</Button>
4951
) : (
5052
<Highlight language="stacktrace" key="stacktrace">

packages/ui/src/components/JobCard/Details/DetailsContent/JobLogs/JobLogs.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AppJob } from '@bull-board/api/typings/app';
22
import React, { SyntheticEvent, useEffect, useRef, useState } from 'react';
3+
import { useTranslation } from 'react-i18next';
34
import { useInterval } from '../../../../../hooks/useInterval';
45
import { InputField } from '../../../../Form/InputField/InputField';
56
import { FullscreenIcon } from '../../../../Icons/Fullscreen';
@@ -39,6 +40,7 @@ function formatLogs(logs: string[]) {
3940
}
4041

4142
export const JobLogs = ({ actions, job }: JobLogsProps) => {
43+
const { t } = useTranslation();
4244
const [logs, setLogs] = useState<LogType[]>([]);
4345
const [liveLogs, setLiveLogs] = useState(false);
4446
const [keyword, setKeyword] = useState('');
@@ -96,7 +98,7 @@ export const JobLogs = ({ actions, job }: JobLogsProps) => {
9698
className={s.searchBar}
9799
name="searchQuery"
98100
type="search"
99-
placeholder="Filters"
101+
placeholder={t('JOB.LOGS.FILTER_PLACEHOLDER')}
100102
onChange={onSearch}
101103
/>
102104
</form>

packages/ui/src/components/JobCard/JobActions/JobActions.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { STATUSES } from '@bull-board/api/src/constants/statuses';
22
import { Status } from '@bull-board/api/typings/app';
33
import React from 'react';
4+
import { useTranslation } from 'react-i18next';
45
import { Button } from '../../Button/Button';
56
import { PromoteIcon } from '../../Icons/Promote';
67
import { RetryIcon } from '../../Icons/Retry';
@@ -39,6 +40,7 @@ const statusToButtonsMap: Record<string, ButtonType[]> = {
3940

4041
export const JobActions = ({ actions, status, allowRetries }: JobActionsProps) => {
4142
let buttons = statusToButtonsMap[status];
43+
const { t } = useTranslation();
4244
if (!buttons) {
4345
return null;
4446
}
@@ -51,7 +53,7 @@ export const JobActions = ({ actions, status, allowRetries }: JobActionsProps) =
5153
<ul className={s.jobActions}>
5254
{buttons.map((type) => (
5355
<li key={type.title}>
54-
<Tooltip title={type.title}>
56+
<Tooltip title={t(`JOB.ACTIONS.${type.title.toUpperCase()}`)}>
5557
<Button onClick={actions[type.actionKey]} className={s.button}>
5658
<type.Icon />
5759
</Button>
Lines changed: 45 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { STATUSES } from '@bull-board/api/src/constants/statuses';
22
import { AppJob, Status } from '@bull-board/api/typings/app';
33
import React from 'react';
4+
import { useTranslation } from 'react-i18next';
45
import { Link } from 'react-router-dom';
56
import { Card } from '../Card/Card';
67
import { Details } from './Details/Details';
@@ -32,46 +33,51 @@ export const JobCard = ({
3233
readOnlyMode,
3334
allowRetries,
3435
jobUrl,
35-
}: JobCardProps) => (
36-
<Card className={s.card}>
37-
<div className={s.sideInfo}>
38-
{jobUrl ? (
39-
<Link className={s.jobLink} to={jobUrl}>
36+
}: JobCardProps) => {
37+
const { t } = useTranslation();
38+
return (
39+
<Card className={s.card}>
40+
<div className={s.sideInfo}>
41+
{jobUrl ? (
42+
<Link className={s.jobLink} to={jobUrl}>
43+
<span title={`#${job.id}`}>#{job.id}</span>
44+
</Link>
45+
) : (
4046
<span title={`#${job.id}`}>#{job.id}</span>
41-
</Link>
42-
) : (
43-
<span title={`#${job.id}`}>#{job.id}</span>
44-
)}
45-
<Timeline job={job} status={status} />
46-
</div>
47-
<div className={s.contentWrapper}>
48-
<div className={s.title}>
49-
<h4>
50-
{job.name}
51-
{job.attempts > 1 && <span>attempt #{job.attempts}</span>}
52-
{!!job.opts?.repeat?.count && (
53-
<span>
54-
repeat {job.opts?.repeat?.count}
55-
{!!job.opts?.repeat?.limit && ` / ${job.opts?.repeat?.limit}`}
56-
</span>
57-
)}
58-
</h4>
59-
{!readOnlyMode && (
60-
<JobActions status={status} actions={actions} allowRetries={allowRetries} />
6147
)}
48+
<Timeline job={job} status={status} />
6249
</div>
63-
<div className={s.content}>
64-
<Details status={status} job={job} actions={actions} />
65-
{typeof job.progress === 'number' && (
66-
<Progress
67-
percentage={job.progress}
68-
status={
69-
job.isFailed && !greenStatuses.includes(status as any) ? STATUSES.failed : status
70-
}
71-
className={s.progress}
72-
/>
73-
)}
50+
<div className={s.contentWrapper}>
51+
<div className={s.title}>
52+
<h4>
53+
{job.name}
54+
{job.attempts > 1 && <span>{t('JOB.ATTEMPTS', { attempts: job.attempts })}</span>}
55+
{!!job.opts?.repeat?.count && (
56+
<span>
57+
{t(`JOB.REPEAT${!!job.opts?.repeat?.limit ? '_WITH_LIMIT' : ''}`, {
58+
count: job.opts.repeat.count,
59+
limit: job.opts?.repeat?.limit,
60+
})}
61+
</span>
62+
)}
63+
</h4>
64+
{!readOnlyMode && (
65+
<JobActions status={status} actions={actions} allowRetries={allowRetries} />
66+
)}
67+
</div>
68+
<div className={s.content}>
69+
<Details status={status} job={job} actions={actions} />
70+
{typeof job.progress === 'number' && (
71+
<Progress
72+
percentage={job.progress}
73+
status={
74+
job.isFailed && !greenStatuses.includes(status as any) ? STATUSES.failed : status
75+
}
76+
className={s.progress}
77+
/>
78+
)}
79+
</div>
7480
</div>
75-
</div>
76-
</Card>
77-
);
81+
</Card>
82+
);
83+
};

0 commit comments

Comments
 (0)