CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/app/localize.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { ConfigProvider as AntdConfigProvider } from "antd";
7
import type { Locale as AntdLocale } from "antd/lib/locale";
8
import enUS from "antd/locale/en_US";
9
import { isEmpty } from "lodash";
10
import { createContext, useContext, useRef, useState } from "react";
11
import { IntlProvider } from "react-intl";
12
import useAsyncEffect from "use-async-effect";
13
14
type OnErrorFn = (typeof IntlProvider.defaultProps)["onError"];
15
16
import { Loading, Paragraph, Text } from "@cocalc/frontend/components";
17
import {
18
DEFAULT_LOCALE,
19
loadLocaleMessages,
20
Locale,
21
LOCALIZATIONS,
22
Messages,
23
sanitizeLocale,
24
} from "@cocalc/frontend/i18n";
25
import { unreachable } from "@cocalc/util/misc";
26
import { useAntdStyleProvider } from "./context";
27
28
interface LanguageContextInterface {
29
setLocale: (language: string) => void;
30
locale: Locale;
31
}
32
33
export const LocalizationContext = createContext<LanguageContextInterface>({
34
locale: DEFAULT_LOCALE,
35
setLocale: () => {
36
console.warn("LanguageContext.changeLanguage not implemented");
37
},
38
});
39
40
// This configures AntD (locale+style) and react-intl
41
export function Localize({ children }: { children: React.ReactNode }) {
42
const [locale, setLocale] = useState<Locale>(DEFAULT_LOCALE);
43
const [antdLoc, setAntdLoc] = useState<AntdLocale | undefined>(undefined);
44
const [messages, setMessages] = useState<Messages | undefined>(undefined);
45
const { antdTheme } = useAntdStyleProvider();
46
const uniqueKey = useRef<{ [tag: string]: number }>({});
47
48
// Note: this is e.g. necessary to render text in a modal, where some caching happens, apparently
49
function getKey(tag: string): number {
50
const n = (uniqueKey.current[tag] ?? 0) + 1;
51
uniqueKey.current[tag] = n;
52
return n;
53
}
54
55
useAsyncEffect(async () => {
56
setMessages(await loadLocaleMessages(locale));
57
}, [locale]);
58
59
useAsyncEffect(async () => {
60
setAntdLoc(await loadAntdLocale(locale));
61
}, [locale]);
62
63
function renderApp() {
64
// NOTE: the locale will be set from the other_settings, on the "page".
65
// So, for the default (english) we always have to render it, and then, maybe, a locale is set...
66
if (locale === DEFAULT_LOCALE) {
67
return children;
68
} else {
69
if (isEmpty(messages)) {
70
return (
71
<Loading
72
theme="medium"
73
delay={1000}
74
text={`Loading support for ${LOCALIZATIONS[locale].name}…`}
75
/>
76
);
77
} else {
78
return children;
79
}
80
}
81
}
82
83
function onError(err: Parameters<OnErrorFn>[0]): ReturnType<OnErrorFn> {
84
if (process.env.NODE_ENV !== "production") {
85
console.log(err.message);
86
}
87
}
88
89
return (
90
<LocalizationContext.Provider
91
value={{
92
setLocale: (locale: unknown) => setLocale(sanitizeLocale(locale)),
93
locale,
94
}}
95
>
96
<AntdConfigProvider theme={antdTheme} locale={antdLoc}>
97
<IntlProvider
98
locale={locale}
99
messages={messages}
100
defaultLocale={DEFAULT_LOCALE}
101
onError={onError}
102
defaultRichTextElements={{
103
strong: (ch) => (
104
<Text strong key={getKey("strong")}>
105
{ch}
106
</Text>
107
),
108
b: (ch) => (
109
<Text strong key={getKey("b")}>
110
{ch}
111
</Text>
112
),
113
i: (ch) => (
114
<Text italic key={getKey("i")}>
115
{ch}
116
</Text>
117
),
118
p: (ch) => <Paragraph key={getKey("p")}>{ch}</Paragraph>,
119
code: (ch) => (
120
<Text code key={getKey("code")}>
121
{ch}
122
</Text>
123
),
124
ul: (e) => <ul key={getKey("ul")}>{e}</ul>,
125
ol: (e) => <ol key={getKey("ol")}>{e}</ol>,
126
li: (e) => <li key={getKey("li")}>{e}</li>,
127
}}
128
>
129
{renderApp()}
130
</IntlProvider>
131
</AntdConfigProvider>
132
</LocalizationContext.Provider>
133
);
134
}
135
136
export function useLocalizationCtx() {
137
return useContext(LocalizationContext);
138
}
139
140
function loadAntdLocale(locale: Locale): Promise<AntdLocale> {
141
return (() => {
142
switch (locale) {
143
case "en":
144
// English is "baked in", because it is the default. Other languages are splitted up...
145
return enUS;
146
case "de":
147
// DEV: all those imports needs to be explicit full strings, and point to the pkg to resolve
148
return import("antd/locale/de_DE");
149
case "zh":
150
return import("antd/locale/zh_CN");
151
case "es":
152
return import("antd/locale/es_ES");
153
case "nl":
154
return import("antd/locale/nl_NL");
155
case "ru":
156
return import("antd/locale/ru_RU");
157
case "fr":
158
return import("antd/locale/fr_FR");
159
case "it":
160
return import("antd/locale/it_IT");
161
case "ja":
162
return import("antd/locale/ja_JP");
163
case "pt":
164
return import("antd/locale/pt_PT");
165
case "ko":
166
return import("antd/locale/ko_KR");
167
case "pl":
168
return import("antd/locale/pl_PL");
169
case "tr":
170
return import("antd/locale/tr_TR");
171
case "he":
172
return import("antd/locale/he_IL");
173
case "hi":
174
return import("antd/locale/hi_IN");
175
case "hu":
176
return import("antd/locale/hu_HU");
177
case "ar":
178
return import("antd/locale/ar_EG");
179
default:
180
unreachable(locale);
181
throw new Error(`Unknown locale '${locale}.`);
182
}
183
})() as any as Promise<AntdLocale>;
184
}
185
186