Skip to content

Commit 7d4ccca

Browse files
committed
fix
1 parent fc1b029 commit 7d4ccca

79 files changed

Lines changed: 3440 additions & 561 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { configureStore } from '@reduxjs/toolkit';
2+
import { render, screen } from '@testing-library/react';
3+
import React from 'react';
4+
import { Provider } from 'react-redux';
5+
6+
import i18n from '../i18n';
7+
import dayjs from '../i18n/dayjs';
8+
import AchievementBadge from '../widgets/components/AchievementBadge';
9+
import InvitesList from '../widgets/components/InvitesList';
10+
import TournamentDescription from '../widgets/components/TournamentDescription';
11+
import TournamentPreviewPanel from '../widgets/components/TournamentPreviewPanel';
12+
import { localizeTournamentName } from '../widgets/utils/localizeTournamentName';
13+
14+
const translations = [
15+
['Points', 'Очки'],
16+
['Today', 'Сегодня'],
17+
['Back', 'Назад'],
18+
['Next', 'Далее'],
19+
['Month', 'Месяц'],
20+
['Day', 'День'],
21+
['Agenda', 'Расписание'],
22+
['Season Points Distribution', 'Распределение очков сезона'],
23+
['Duration', 'Длительность'],
24+
['Winner', 'Победитель'],
25+
['Ranking Points', 'Рейтинговые очки'],
26+
['Tournament chat', 'Чат турнира'],
27+
['Easy', 'Лёгкий'],
28+
['Tournament: %{name}', 'Турнир: %{name}'],
29+
['Open Tournament', 'Открыть турнир'],
30+
['View League Ranking Points System', 'Система рейтинговых очков лиги'],
31+
['Replay settings', 'Настройки повтора'],
32+
['Tournament details', 'Подробности турнира'],
33+
['Function Signature', 'Сигнатура функции'],
34+
['Fastest Solutions', 'Самые быстрые решения'],
35+
['Forgot your password?', 'Забыли пароль?'],
36+
['Restricted Content', 'Ограниченный доступ'],
37+
['No completed games', 'Нет завершённых игр'],
38+
['Live tournaments', 'Активные турниры'],
39+
['Editor settings', 'Настройки редактора'],
40+
['Start tournament confirmation', 'Подтверждение запуска турнира'],
41+
] as const;
42+
43+
beforeAll(async () => {
44+
await i18n.changeLanguage('ru');
45+
dayjs.locale('ru');
46+
});
47+
48+
afterAll(async () => {
49+
await i18n.changeLanguage('en');
50+
dayjs.locale('en');
51+
});
52+
53+
test.each(translations)('translates %s into Russian', (key, translation) => {
54+
expect(i18n.t(key)).toBe(translation);
55+
});
56+
57+
test('localizes the best streak achievement', () => {
58+
render(<AchievementBadge achievement={{ type: 'best_win_streak', meta: { count: 7 } }} />);
59+
60+
expect(screen.getByText('Лучшая серия')).toBeInTheDocument();
61+
expect(screen.getByTitle('Лучшая серия побед')).toBeInTheDocument();
62+
});
63+
64+
test('localizes the empty invites state', () => {
65+
const store = configureStore({ reducer: () => ({}) });
66+
67+
render(
68+
<Provider store={store}>
69+
<InvitesList list={[]} currentUserId={1} />
70+
</Provider>,
71+
);
72+
73+
expect(screen.getByText('Нет приглашений')).toBeInTheDocument();
74+
});
75+
76+
test('localizes tournament preview dates and point labels', () => {
77+
render(
78+
<TournamentPreviewPanel
79+
tournament={{ grade: 'rookie' }}
80+
start="2026-08-05T02:00:00"
81+
end="2026-08-05T02:15:00"
82+
/>,
83+
);
84+
85+
expect(screen.getByText('Дата начала: 5 августа 2026')).toBeInTheDocument();
86+
expect(screen.getByText('Время: 02:00 - 02:15')).toBeInTheDocument();
87+
expect(screen.getByText('Очки за первое место: 8')).toBeInTheDocument();
88+
});
89+
90+
test('localizes tournament highlights and grade names', () => {
91+
render(<TournamentDescription tournament={{ grade: 'rookie' }} />);
92+
93+
expect(screen.getByText('Главное о турнире:')).toBeInTheDocument();
94+
expect(screen.getByText('Задачи: 4 уникальных алгоритмических задач')).toBeInTheDocument();
95+
expect(screen.getByText('Система рейтинговых очков лиги')).toBeInTheDocument();
96+
expect(screen.getByText('Новичок(*)')).toBeInTheDocument();
97+
expect(screen.getByText('Челленджер')).toBeInTheDocument();
98+
expect(screen.getByText('Гранд-слэм')).toBeInTheDocument();
99+
});
100+
101+
test('localizes only system-generated tournament names', () => {
102+
expect(localizeTournamentName('Challenger Tournament #12', 'challenger')).toBe(
103+
'Турнир «Челленджер» №12',
104+
);
105+
expect(localizeTournamentName('Grand_slam Tournament #9', 'grand_slam')).toBe(
106+
'Турнир «Гранд-слэм» №9',
107+
);
108+
expect(localizeTournamentName('Rookie', 'rookie')).toBe('Новичок');
109+
expect(localizeTournamentName('My custom tournament', 'rookie')).toBe('My custom tournament');
110+
});

apps/codebattle/assets/js/__tests__/Registration.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ describe('sign up', () => {
8888
await userEvent.type(nameInput, value);
8989
}
9090

91-
const submitButton = getByLabelText('SubmitForm');
91+
const submitButton = getByLabelText('Submit form');
9292
await user.click(submitButton);
9393

9494
expect(await findByText(validationMessage)).toBeInTheDocument();
@@ -108,7 +108,7 @@ describe('sign up', () => {
108108
await userEvent.type(getByLabelText('password'), data.password);
109109
await userEvent.type(getByLabelText('passwordConfirmation'), data.passwordConfirmation);
110110

111-
const submitButton = getByLabelText('SubmitForm');
111+
const submitButton = getByLabelText('Submit form');
112112
await user.click(submitButton);
113113

114114
await waitFor(() => {
@@ -130,7 +130,7 @@ describe('sign up', () => {
130130
const { getByLabelText, findByText, user } = setup(<Registration />);
131131

132132
await user.type(getByLabelText('email'), 'user@example.com');
133-
await user.click(getByLabelText('SubmitForm'));
133+
await user.click(getByLabelText('Submit form'));
134134

135135
const confirmation = await findByText(
136136
'We have sent you an email with instructions on how to reset your password',

apps/codebattle/assets/js/__tests__/TournamentListItem.test.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { render, screen } from '@testing-library/react';
22
import '@testing-library/jest-dom';
33
import React from 'react';
44

5+
import i18n from '../i18n';
6+
import dayjs from '../i18n/dayjs';
57
import TournamentListItem from '../widgets/pages/lobby/TournamentListItem';
68

79
vi.mock('@/inertia/pageProps', () => ({
@@ -12,6 +14,12 @@ vi.mock('@fortawesome/react-fontawesome', () => ({
1214
FontAwesomeIcon: 'img',
1315
}));
1416

17+
afterEach(async () => {
18+
await i18n.changeLanguage('en');
19+
dayjs.locale('en');
20+
vi.useRealTimers();
21+
});
22+
1523
const baseTournament = {
1624
id: 1,
1725
name: 'Rookie',
@@ -28,3 +36,28 @@ test('does not render invalid date for finished tournament without last round en
2836
expect(screen.getByText('Rookie')).toBeInTheDocument();
2937
expect(screen.getAllByText(/at /).length).toBeGreaterThan(0);
3038
});
39+
40+
test('localizes the tournament date, action, and countdown label', async () => {
41+
vi.useFakeTimers();
42+
vi.setSystemTime(new Date('2026-08-01T00:00:00Z'));
43+
await i18n.changeLanguage('ru');
44+
dayjs.locale('ru');
45+
46+
const { rerender } = render(<TournamentListItem tournament={baseTournament} />);
47+
48+
expect(screen.getByRole('link', { name: 'Открыть' })).toBeInTheDocument();
49+
expect(screen.getAllByText(/июл/).length).toBeGreaterThan(0);
50+
51+
rerender(
52+
<TournamentListItem
53+
tournament={{
54+
...baseTournament,
55+
state: 'upcoming',
56+
startsAt: '2026-08-01T12:00:00Z',
57+
}}
58+
/>,
59+
);
60+
61+
expect(screen.getByText(/начнётся через/)).toBeInTheDocument();
62+
expect(screen.getAllByText(/авг/).length).toBeGreaterThan(0);
63+
});

apps/codebattle/assets/js/__tests__/UserSettings.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ describe('UserSettings test cases', () => {
137137
<UserSettings />
138138
</Provider>,
139139
);
140-
const submitButton = getByLabelText('SubmitForm');
140+
const submitButton = getByLabelText('Submit form');
141141
const nameInput = getByTestId('nameInput');
142142
const codeLangSelect = getByTestId('code-langSelect');
143143

@@ -183,7 +183,7 @@ describe('UserSettings test cases', () => {
183183
<UserSettings />
184184
</Provider>,
185185
);
186-
const submitButton = getByLabelText('SubmitForm');
186+
const submitButton = getByLabelText('Submit form');
187187
const localeSelect = getByTestId('localeSelect');
188188

189189
await user.click(localeSelect);
@@ -209,7 +209,7 @@ describe('UserSettings test cases', () => {
209209
<UserSettings />
210210
</Provider>,
211211
);
212-
const submitButton = getByLabelText('SubmitForm');
212+
const submitButton = getByLabelText('Submit form');
213213
const nameInput = getByTestId('nameInput');
214214

215215
await user.clear(nameInput);
@@ -266,7 +266,7 @@ describe('UserSettings test cases', () => {
266266
</Provider>,
267267
);
268268

269-
const submitButton = getByLabelText('SubmitForm');
269+
const submitButton = getByLabelText('Submit form');
270270
await user.click(getByLabelText('Silent'));
271271

272272
expect(submitButton).toBeEnabled();
@@ -323,7 +323,7 @@ describe('UserSettings test cases', () => {
323323
await user.type(getByTestId('currentPasswordInput'), 'old-password-secure!');
324324
await user.type(getByTestId('passwordInput'), 'new-password-secure!');
325325
await user.type(getByTestId('passwordConfirmationInput'), 'new-password-secure!');
326-
await user.click(getByLabelText('SubmitForm'));
326+
await user.click(getByLabelText('Submit form'));
327327

328328
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2));
329329

apps/codebattle/assets/js/widgets/components/AccordeonBox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ function SubMenu({
200200
{assert.status}
201201
</span>
202202
<OverlayTrigger
203-
overlay={<Tooltip id={String(assert.id)}>Execution Time</Tooltip>}
203+
overlay={<Tooltip id={String(assert.id)}>{i18n.t('Execution Time')}</Tooltip>}
204204
placement="top"
205205
>
206206
{executionTime !== undefined && Number(executionTime) !== 0 ? (

apps/codebattle/assets/js/widgets/components/AchievementBadge.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22

3+
import i18n from '../../i18n';
34
import langIconNames from '../config/langIconNames';
45

56
import LanguageIcon from './LanguageIcon';
@@ -117,8 +118,11 @@ function AchievementBadge({ achievement }: AchievementBadgeProps) {
117118
const gradeIconUrl = grade ? gradeIcon[grade] : undefined;
118119

119120
return (
120-
<div className={`cb-achievement-badge cb-achievement-badge--${badge.tone}`} title={badge.title}>
121-
<div className="cb-achievement-badge__label">{badge.label}</div>
121+
<div
122+
className={`cb-achievement-badge cb-achievement-badge--${badge.tone}`}
123+
title={i18n.t(badge.title)}
124+
>
125+
<div className="cb-achievement-badge__label">{i18n.t(badge.label)}</div>
122126
{isHighestGrade && grade && gradeIconUrl && (
123127
<div className="cb-achievement-badge__grade-icon">
124128
<img

apps/codebattle/assets/js/widgets/components/ChatContextMenu.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -149,19 +149,19 @@ function ChatContextMenu({
149149
<>
150150
{children}
151151
<Menu className="cb-bg-panel cb-border-color cb-rounded text-white" role="menu" id={menuId}>
152-
<Item role="menuitem" tabIndex={-1} aria-label="Copy Name" onClick={handleCopy}>
152+
<Item role="menuitem" tabIndex={-1} aria-label={i18n.t('Copy Name')} onClick={handleCopy}>
153153
<FontAwesomeIcon className="mr-2 text-white" icon="copy" />
154-
<span className="text-white">Copy Name</span>
154+
<span className="text-white">{i18n.t('Copy Name')}</span>
155155
</Item>
156-
<Item role="menuitem" tabIndex={-1} aria-label="Info" onClick={handleShowInfo}>
156+
<Item role="menuitem" tabIndex={-1} aria-label={i18n.t('Info')} onClick={handleShowInfo}>
157157
<FontAwesomeIcon className="mr-2 text-white" icon="user" />
158-
<span className="text-white">Info</span>
158+
<span className="text-white">{i18n.t('Info')}</span>
159159
</Item>
160160
{githubName && (
161161
<Item
162162
role="menuitem"
163163
tabIndex={-1}
164-
aria-label="Github account"
164+
aria-label={i18n.t('Github account')}
165165
onClick={handleShowGithubProfile}
166166
>
167167
<FontAwesomeIcon className="mr-2 text-white" icon={['fab', 'github']} />
@@ -172,28 +172,28 @@ function ChatContextMenu({
172172
<Item
173173
role="menuitem"
174174
tabIndex={-1}
175-
aria-label={isFollowing ? 'Unfollow' : 'Follow'}
175+
aria-label={i18n.t(isFollowing ? 'Unfollow' : 'Follow')}
176176
onClick={handleFollow}
177177
>
178178
<FontAwesomeIcon className="mr-2 text-white" icon="binoculars" />
179-
<span className="text-white">{isFollowing ? 'Unfollow' : 'Follow'}</span>
179+
<span className="text-white">{i18n.t(isFollowing ? 'Unfollow' : 'Follow')}</span>
180180
</Item>
181181
)}
182182
{canCreatePrivateRoom ? (
183183
<Item
184184
role="menuitem"
185-
aria-label="Direct message"
185+
aria-label={i18n.t('Direct message')}
186186
onClick={handleOpenDirect}
187187
disabled={!canCreatePrivateRoom}
188188
>
189189
<FontAwesomeIcon className="mr-2 text-white" icon="comment-alt" />
190-
<span className="text-white">Direct message</span>
190+
<span className="text-white">{i18n.t('Direct message')}</span>
191191
</Item>
192192
) : null}
193193
{canInvite && (
194194
<Item
195195
role="menuitem"
196-
aria-label="Send an invite"
196+
aria-label={i18n.t('Send an invite')}
197197
onClick={handleCreateInviteModal}
198198
onMouseEnter={handleSelectInvateMenuItem}
199199
onMouseLeave={handleBlurInvateMenuItem}
@@ -202,21 +202,21 @@ function ChatContextMenu({
202202
disabled={inviteSendDisabled}
203203
>
204204
<img
205-
alt="invite"
205+
alt={i18n.t('Invite')}
206206
src={whiteSwordSrc}
207207
style={{ width: 14, height: 16 }}
208208
className={cn('mr-2', {
209209
'text-muted': !inviteSendDisabled,
210210
})}
211211
/>
212-
<span className="text-white">Send an invite</span>
212+
<span className="text-white">{i18n.t('Send an invite')}</span>
213213
</Item>
214214
)}
215215
{currentUserIsAdmin && <Separator />}
216216
{currentUserIsAdmin && (
217-
<Item aria-label="Ban" onClick={handleBanClick} disabled={isBot}>
217+
<Item aria-label={i18n.t('Ban')} onClick={handleBanClick} disabled={isBot}>
218218
<FontAwesomeIcon className="mr-2 text-white" icon="ban" />
219-
<span className="text-white">Ban</span>
219+
<span className="text-white">{i18n.t('Ban')}</span>
220220
</Item>
221221
)}
222222
</Menu>

0 commit comments

Comments
 (0)