Skip to content

Commit f404663

Browse files
authored
Merge pull request #7761 from sagemathinc/i18n
I18N
2 parents 8753b91 + 5cb2bf7 commit f404663

File tree

114 files changed

+12588
-1712
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+12588
-1712
lines changed

src/packages/frontend/account/account-button.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6-
import React from "react";
76
import { Popconfirm, Popover } from "antd";
7+
import React from "react";
8+
import { FormattedMessage, useIntl } from "react-intl";
89

9-
import { AccountActions } from "../account";
10+
import { AccountActions } from "@cocalc/frontend/account";
11+
import { labels } from "@cocalc/frontend/i18n";
12+
import { CancelText } from "@cocalc/frontend/i18n/components";
1013

1114
interface Props {
1215
icon: React.ReactNode; // When clicked, show popover
@@ -28,6 +31,9 @@ interface AccountTabProps {
2831

2932
export const AccountTabDropdown: React.FC<Props> = (props: AccountTabProps) => {
3033
const { icon, links, label_class, show_label, is_active, user_label } = props;
34+
const intl = useIntl();
35+
36+
const label = intl.formatMessage(labels.account);
3137

3238
// If icon is a string then use the Icon component
3339
// Else (it is a node already) just render icon
@@ -52,7 +58,7 @@ export const AccountTabDropdown: React.FC<Props> = (props: AccountTabProps) => {
5258
>
5359
{icon}
5460
<span style={{ marginLeft: 5 }} className={label_class}>
55-
{show_label ? "Account" : undefined}
61+
{show_label ? label : undefined}
5662
</span>
5763
</div>
5864
</Popover>
@@ -145,10 +151,20 @@ export const DefaultAccountDropDownLinks: React.FC<LinksProps> = ({
145151
</li>
146152
<li>
147153
<Popconfirm
148-
title={"Sign out of your account?"}
154+
title={
155+
<FormattedMessage
156+
id="account.account-button.confirm.title"
157+
defaultMessage={"Sign out of your account?"}
158+
/>
159+
}
149160
onConfirm={() => account_actions.sign_out(false, false)}
150-
okText={"Yes, sign out"}
151-
cancelText={"Cancel"}
161+
okText={
162+
<FormattedMessage
163+
id="account.account-button.confirm.ok"
164+
defaultMessage={"Yes, sign out"}
165+
/>
166+
}
167+
cancelText={<CancelText />}
152168
>
153169
<a
154170
style={{

src/packages/frontend/account/account-page.tsx

Lines changed: 162 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,34 @@ for different account related information
1010
and configuration.
1111
*/
1212

13+
import { DownOutlined } from "@ant-design/icons";
14+
import { Button, Dropdown, MenuProps, Modal, Space, Tooltip } from "antd";
15+
import { useIntl } from "react-intl";
16+
1317
import { SignOut } from "@cocalc/frontend/account/sign-out";
1418
import { AntdTabItem, Col, Row, Tabs } from "@cocalc/frontend/antd-bootstrap";
15-
import { React, redux, useTypedRedux } from "@cocalc/frontend/app-framework";
16-
import { Icon, Loading } from "@cocalc/frontend/components";
19+
import {
20+
React,
21+
redux,
22+
useTypedRedux,
23+
useWindowDimensions,
24+
} from "@cocalc/frontend/app-framework";
25+
import { useLocalizationCtx } from "@cocalc/frontend/app/localize";
26+
import { Icon, Loading, Paragraph } from "@cocalc/frontend/components";
27+
import { cloudFilesystemsEnabled } from "@cocalc/frontend/compute";
28+
import CloudFilesystems from "@cocalc/frontend/compute/cloud-filesystem/cloud-filesystems";
29+
import {
30+
getLocale,
31+
labels,
32+
Locale,
33+
LOCALIZATIONS,
34+
OTHER_SETTINGS_LOCALE_KEY,
35+
} from "@cocalc/frontend/i18n";
1736
import { LandingPage } from "@cocalc/frontend/landing-page/landing-page";
1837
import { local_storage_length } from "@cocalc/frontend/misc/local-storage";
38+
import PurchasesPage from "@cocalc/frontend/purchases/purchases-page";
39+
import StatementsPage from "@cocalc/frontend/purchases/statements-page";
40+
import SubscriptionsPage from "@cocalc/frontend/purchases/subscriptions-page";
1941
import { SupportTickets } from "@cocalc/frontend/support";
2042
import {
2143
KUCALC_COCALC_COM,
@@ -26,13 +48,15 @@ import { LicensesPage } from "./licenses/licenses-page";
2648
import { PublicPaths } from "./public-paths/public-paths";
2749
import { SSHKeysPage } from "./ssh-keys/global-ssh-keys";
2850
import { UpgradesPage } from "./upgrades/upgrades-page";
29-
import PurchasesPage from "@cocalc/frontend/purchases/purchases-page";
30-
import SubscriptionsPage from "@cocalc/frontend/purchases/subscriptions-page";
31-
import StatementsPage from "@cocalc/frontend/purchases/statements-page";
32-
import { cloudFilesystemsEnabled } from "@cocalc/frontend/compute";
33-
import CloudFilesystems from "@cocalc/frontend/compute/cloud-filesystem/cloud-filesystems";
3451

3552
export const AccountPage: React.FC = () => {
53+
const intl = useIntl();
54+
const { setLocale, locale } = useLocalizationCtx();
55+
56+
const { width: windowWidth } = useWindowDimensions();
57+
const isWide = windowWidth > 800;
58+
59+
const other_settings = useTypedRedux("account", "other_settings");
3660
const active_page = useTypedRedux("account", "active_page");
3761
const is_logged_in = useTypedRedux("account", "is_logged_in");
3862
const account_id = useTypedRedux("account", "account_id");
@@ -61,6 +85,7 @@ export const AccountPage: React.FC = () => {
6185
const ssh_gateway = useTypedRedux("customize", "ssh_gateway");
6286
const is_commercial = useTypedRedux("customize", "is_commercial");
6387
const get_api_key = useTypedRedux("page", "get_api_key");
88+
const i18n_enabled = useTypedRedux("customize", "i18n");
6489

6590
// for each exclusive domain, tell the user which strategy to use
6691
const exclusive_sso_domains = React.useMemo(() => {
@@ -116,7 +141,7 @@ export const AccountPage: React.FC = () => {
116141
key: "account",
117142
label: (
118143
<span>
119-
<Icon name="wrench" /> Preferences
144+
<Icon name="wrench" /> {intl.formatMessage(labels.preferences)}
120145
</span>
121146
),
122147
children: (active_page == null || active_page === "account") && (
@@ -137,7 +162,7 @@ export const AccountPage: React.FC = () => {
137162
key: "purchases",
138163
label: (
139164
<span>
140-
<Icon name="money" /> Purchases
165+
<Icon name="money" /> {intl.formatMessage(labels.purchases)}
141166
</span>
142167
),
143168
children: active_page === "purchases" && <PurchasesPage />,
@@ -146,7 +171,7 @@ export const AccountPage: React.FC = () => {
146171
key: "subscriptions",
147172
label: (
148173
<span>
149-
<Icon name="calendar" /> Subscriptions
174+
<Icon name="calendar" /> {intl.formatMessage(labels.subscriptions)}
150175
</span>
151176
),
152177
children: active_page === "subscriptions" && <SubscriptionsPage />,
@@ -155,7 +180,7 @@ export const AccountPage: React.FC = () => {
155180
key: "statements",
156181
label: (
157182
<span>
158-
<Icon name="money" /> Statements
183+
<Icon name="money" /> {intl.formatMessage(labels.statements)}
159184
</span>
160185
),
161186
children: active_page === "statements" && <StatementsPage />,
@@ -171,7 +196,7 @@ export const AccountPage: React.FC = () => {
171196
key: "licenses",
172197
label: (
173198
<span>
174-
<Icon name="key" /> Licenses
199+
<Icon name="key" /> {intl.formatMessage(labels.licenses)}
175200
</span>
176201
),
177202
children: active_page === "licenses" && <LicensesPage />,
@@ -183,7 +208,7 @@ export const AccountPage: React.FC = () => {
183208
key: "ssh-keys",
184209
label: (
185210
<span>
186-
<Icon name="key" /> SSH Keys
211+
<Icon name="key" /> {intl.formatMessage(labels.ssh_keys)}
187212
</span>
188213
),
189214
children: active_page === "ssh-keys" && <SSHKeysPage />,
@@ -194,7 +219,7 @@ export const AccountPage: React.FC = () => {
194219
key: "support",
195220
label: (
196221
<span>
197-
<Icon name="medkit" /> Support
222+
<Icon name="medkit" /> {intl.formatMessage(labels.support)}
198223
</span>
199224
),
200225
children: active_page === "support" && <SupportTickets />,
@@ -204,7 +229,8 @@ export const AccountPage: React.FC = () => {
204229
key: "public-files",
205230
label: (
206231
<span>
207-
<Icon name="share-square" /> Public Files
232+
<Icon name="share-square" />{" "}
233+
{intl.formatMessage(labels.published_files)}
208234
</span>
209235
),
210236
children: active_page === "public-files" && <PublicPaths />,
@@ -214,7 +240,8 @@ export const AccountPage: React.FC = () => {
214240
key: "upgrades",
215241
label: (
216242
<span>
217-
<Icon name="arrow-circle-up" /> Upgrades
243+
<Icon name="arrow-circle-up" />{" "}
244+
{intl.formatMessage(labels.upgrades)}
218245
</span>
219246
),
220247
children: active_page === "upgrades" && <UpgradesPage />,
@@ -225,7 +252,8 @@ export const AccountPage: React.FC = () => {
225252
key: "cloud-filesystems",
226253
label: (
227254
<>
228-
<Icon name="disk-round" /> Cloud File Systems
255+
<Icon name="disk-round" />{" "}
256+
{intl.formatMessage(labels.cloud_file_system)}
229257
</>
230258
),
231259
children: <CloudFilesystems />,
@@ -235,6 +263,122 @@ export const AccountPage: React.FC = () => {
235263
return items;
236264
}
237265

266+
function renderI18N(): JSX.Element | null {
267+
if (
268+
i18n_enabled == null ||
269+
i18n_enabled.isEmpty() ||
270+
(i18n_enabled.size === 1 && i18n_enabled.includes("en"))
271+
) {
272+
return null;
273+
}
274+
275+
const i18n: Locale = getLocale(other_settings);
276+
277+
const items: MenuProps["items"] =
278+
Object.entries(LOCALIZATIONS)
279+
.filter(([key, _]) => i18n_enabled.includes(key as any))
280+
.map(([key, { name, trans, native, flag }]) => {
281+
const other = key === locale ? name : intl.formatMessage(trans);
282+
return { key, label: `${flag} ${native} (${other})` };
283+
}) ?? [];
284+
285+
items.push({ type: "divider" });
286+
items.push({
287+
key: "help",
288+
label: (
289+
<Space>
290+
<Icon name="translation-outlined" />
291+
{intl.formatMessage({
292+
id: "account.account_page.translation.info.label",
293+
defaultMessage: "Translation Info...",
294+
description: "Label of translation information modal in dropdown",
295+
})}
296+
</Space>
297+
),
298+
onClick: () =>
299+
Modal.info({
300+
width: "min(90vw, 600px)",
301+
title: intl.formatMessage({
302+
id: "account.account_page.translation.info.title",
303+
defaultMessage: "Translation Information",
304+
description: "Title of translation information modal",
305+
}),
306+
content: (
307+
<Paragraph>
308+
{intl.formatMessage({
309+
id: "account.account_page.translation.info.content",
310+
defaultMessage: `
311+
We're excited to start offering our application in multiple languages! Here's what you need to know:
312+
313+
<ul>
314+
<li><b>Work in Progress</b>: Our translation effort is just beginning. Many parts of the application are not yet translated.</li>
315+
<li><b>Gradual Improvement</b>: We're continuously working to expand our language coverage. You'll see more content translated over time.</li>
316+
<li><b>Your Help is Welcome</b>: We value our community's input. If you're fluent in multiple languages and would like to contribute to our translation efforts, we'd love to hear from you!</li>
317+
<li><b>Contact Us</b>: To learn more about contributing to translations or to report any issues, please reach out to our support team.</li>
318+
</ul>
319+
320+
Thank you for your patience and understanding as we work to make our application accessible to a global audience!`,
321+
description: "Content of translation information modal",
322+
})}
323+
</Paragraph>
324+
),
325+
}),
326+
});
327+
328+
const menu: MenuProps = {
329+
items,
330+
onClick: ({ key }) => {
331+
if (key in LOCALIZATIONS) {
332+
redux
333+
.getActions("account")
334+
.set_other_settings(OTHER_SETTINGS_LOCALE_KEY, key);
335+
setLocale(key);
336+
}
337+
},
338+
};
339+
340+
const lang_icon = LOCALIZATIONS[i18n]?.flag;
341+
342+
const title =
343+
i18n in LOCALIZATIONS
344+
? intl.formatMessage(LOCALIZATIONS[i18n].trans)
345+
: i18n;
346+
347+
const cur = `${title} (${LOCALIZATIONS[i18n]?.name ?? i18n})`;
348+
const msg = intl.formatMessage(labels.account_language_tooltip);
349+
const tooltip = (
350+
<>
351+
{cur}
352+
<br />
353+
{msg}
354+
<br />({labels.account_language_tooltip.defaultMessage})
355+
</>
356+
);
357+
358+
return (
359+
<Tooltip title={tooltip} trigger={["hover"]}>
360+
<Dropdown menu={menu} trigger={["click"]}>
361+
<Button>
362+
<Space>
363+
{lang_icon}
364+
{isWide ? title : undefined}
365+
<DownOutlined />
366+
</Space>
367+
</Button>
368+
</Dropdown>
369+
</Tooltip>
370+
);
371+
}
372+
373+
function renderExtraContent() {
374+
return (
375+
<Space>
376+
{renderI18N()}
377+
<SignOut everywhere={false} highlight={true} narrow={!isWide} />
378+
</Space>
379+
);
380+
}
381+
238382
function render_logged_in_view(): JSX.Element {
239383
if (!account_id) {
240384
return (
@@ -263,7 +407,7 @@ export const AccountPage: React.FC = () => {
263407
activeKey={active_page ?? "account"}
264408
onSelect={handle_select}
265409
animation={false}
266-
tabBarExtraContent={<SignOut everywhere={false} highlight={true} />}
410+
tabBarExtraContent={renderExtraContent()}
267411
items={tabs}
268412
/>
269413
</Col>

0 commit comments

Comments
 (0)