@@ -10,12 +10,34 @@ for different account related information
1010and 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+
1317import { SignOut } from "@cocalc/frontend/account/sign-out" ;
1418import { 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" ;
1736import { LandingPage } from "@cocalc/frontend/landing-page/landing-page" ;
1837import { 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" ;
1941import { SupportTickets } from "@cocalc/frontend/support" ;
2042import {
2143 KUCALC_COCALC_COM ,
@@ -26,13 +48,15 @@ import { LicensesPage } from "./licenses/licenses-page";
2648import { PublicPaths } from "./public-paths/public-paths" ;
2749import { SSHKeysPage } from "./ssh-keys/global-ssh-keys" ;
2850import { 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
3552export 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