-
Notifications
You must be signed in to change notification settings - Fork 172
i18n support for sicp #3133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
i18n support for sicp #3133
Changes from all commits
5b990d8
f796bda
da13343
8546da1
32df0bd
ee2eae2
9ed14ba
859f560
f02d7dd
eb61bfd
2e63b78
62bc3dc
7356b7b
a1b188a
620e8ff
255dc72
0221f4d
17c2745
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -13,9 +13,12 @@ import { SicpSection } from 'src/features/sicp/chatCompletion/chatCompletion'; | |||||||||||||
import { parseArr, ParseJsonError } from 'src/features/sicp/parser/ParseJson'; | ||||||||||||||
import { getNext, getPrev } from 'src/features/sicp/TableOfContentsHelper'; | ||||||||||||||
import { | ||||||||||||||
readSicpLangLocalStorage, | ||||||||||||||
readSicpSectionLocalStorage, | ||||||||||||||
setSicpLangLocalStorage, | ||||||||||||||
setSicpSectionLocalStorage, | ||||||||||||||
SICP_CACHE_KEY, | ||||||||||||||
SICP_DEF_TB_LANG, | ||||||||||||||
SICP_INDEX | ||||||||||||||
} from 'src/features/sicp/utils/SicpUtils'; | ||||||||||||||
|
||||||||||||||
|
@@ -35,11 +38,24 @@ export const CodeSnippetContext = React.createContext({ | |||||||||||||
|
||||||||||||||
const loadingComponent = <NonIdealState title="Loading Content" icon={<Spinner />} />; | ||||||||||||||
|
||||||||||||||
const AVAILABLE_SICP_TB_LANGS: readonly string[] = ['en', 'zh_CN']; | ||||||||||||||
|
||||||||||||||
const loadInitialLang = () => { | ||||||||||||||
const saved = readSicpLangLocalStorage(); | ||||||||||||||
if (AVAILABLE_SICP_TB_LANGS.includes(saved)) { | ||||||||||||||
return saved; | ||||||||||||||
} else { | ||||||||||||||
setSicpLangLocalStorage(SICP_DEF_TB_LANG); | ||||||||||||||
return SICP_DEF_TB_LANG; | ||||||||||||||
} | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
const Sicp: React.FC = () => { | ||||||||||||||
const [data, setData] = useState(<></>); | ||||||||||||||
const [loading, setLoading] = useState(false); | ||||||||||||||
const [active, setActive] = useState('0'); | ||||||||||||||
const { section } = useParams<{ section: string }>(); | ||||||||||||||
const { paramLang, section } = useParams<{ paramLang: string; section: string }>(); | ||||||||||||||
const [lang, setLang] = useState(loadInitialLang()); | ||||||||||||||
const parentRef = useRef<HTMLDivElement>(null); | ||||||||||||||
RichDom2185 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
const refs = useRef<Record<string, HTMLElement | null>>({}); | ||||||||||||||
const navigate = useNavigate(); | ||||||||||||||
|
@@ -88,6 +104,23 @@ const Sicp: React.FC = () => { | |||||||||||||
|
||||||||||||||
// Handle loading of latest viewed section and fetch json data | ||||||||||||||
React.useEffect(() => { | ||||||||||||||
if (paramLang || (section && AVAILABLE_SICP_TB_LANGS.includes(section))) { | ||||||||||||||
const pLang = (paramLang ? paramLang : section)!; | ||||||||||||||
if (AVAILABLE_SICP_TB_LANGS.includes(pLang)) { | ||||||||||||||
setLang(pLang); | ||||||||||||||
setSicpLangLocalStorage(pLang); | ||||||||||||||
} else { | ||||||||||||||
setLang(SICP_DEF_TB_LANG); | ||||||||||||||
setSicpLangLocalStorage(SICP_DEF_TB_LANG); | ||||||||||||||
} | ||||||||||||||
if (paramLang) { | ||||||||||||||
navigate(`/sicpjs/${section}`, { replace: true }); | ||||||||||||||
} else { | ||||||||||||||
navigate(`/sicpjs/${readSicpSectionLocalStorage()}`, { replace: true }); | ||||||||||||||
Comment on lines
+117
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When handling a language param, this redirect drops the language segment. It should include both, e.g.,
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||
} | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if (!section) { | ||||||||||||||
/** | ||||||||||||||
* Handles rerouting to the latest viewed section when clicking from | ||||||||||||||
|
@@ -105,7 +138,11 @@ const Sicp: React.FC = () => { | |||||||||||||
|
||||||||||||||
setLoading(true); | ||||||||||||||
|
||||||||||||||
fetch(baseUrl + section + extension) | ||||||||||||||
if (!AVAILABLE_SICP_TB_LANGS.includes(lang)) { | ||||||||||||||
setLang(SICP_DEF_TB_LANG); | ||||||||||||||
setSicpLangLocalStorage(SICP_DEF_TB_LANG); | ||||||||||||||
} | ||||||||||||||
fetch(baseUrl + lang + '/' + section + extension) | ||||||||||||||
.then(response => { | ||||||||||||||
if (!response.ok) { | ||||||||||||||
throw Error(response.statusText); | ||||||||||||||
|
@@ -138,7 +175,7 @@ const Sicp: React.FC = () => { | |||||||||||||
.finally(() => { | ||||||||||||||
setLoading(false); | ||||||||||||||
}); | ||||||||||||||
}, [section, navigate]); | ||||||||||||||
}, [paramLang, section, lang, navigate]); | ||||||||||||||
|
||||||||||||||
// Scroll to correct position | ||||||||||||||
React.useEffect(() => { | ||||||||||||||
|
@@ -163,10 +200,33 @@ const Sicp: React.FC = () => { | |||||||||||||
dispatch(WorkspaceActions.resetWorkspace('sicp')); | ||||||||||||||
dispatch(WorkspaceActions.toggleUsingSubst(false, 'sicp')); | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
const handleLanguageToggle = () => { | ||||||||||||||
const newLang = lang === 'en' ? 'zh_CN' : 'en'; | ||||||||||||||
setLang(newLang); | ||||||||||||||
setSicpLangLocalStorage(newLang); | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
const handleNavigation = (sect: string) => { | ||||||||||||||
navigate('/sicpjs/' + sect); | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
// Language toggle button with fixed position | ||||||||||||||
const languageToggle = ( | ||||||||||||||
<div | ||||||||||||||
style={{ | ||||||||||||||
position: 'sticky', | ||||||||||||||
top: '20px', | ||||||||||||||
left: '20px', | ||||||||||||||
zIndex: 0 | ||||||||||||||
}} | ||||||||||||||
> | ||||||||||||||
<Button onClick={handleLanguageToggle} intent="primary" small> | ||||||||||||||
{lang === 'en' ? '切换到中文' : 'Switch to English'} | ||||||||||||||
</Button> | ||||||||||||||
</div> | ||||||||||||||
); | ||||||||||||||
|
||||||||||||||
// `section` is defined due to the navigate logic in the useEffect above | ||||||||||||||
const navigationButtons = ( | ||||||||||||||
<div className="sicp-navigation-buttons"> | ||||||||||||||
|
@@ -186,6 +246,7 @@ const Sicp: React.FC = () => { | |||||||||||||
> | ||||||||||||||
<SicpErrorBoundary> | ||||||||||||||
<CodeSnippetContext.Provider value={{ active: active, setActive: handleSnippetEditorOpen }}> | ||||||||||||||
{languageToggle} | ||||||||||||||
{loading ? ( | ||||||||||||||
<div className="sicp-content">{loadingComponent}</div> | ||||||||||||||
) : section === 'index' ? ( | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,9 +1,15 @@ | ||||||||||||||
import { Tree, TreeNodeInfo } from '@blueprintjs/core'; | ||||||||||||||
import { NonIdealState, Spinner } from '@blueprintjs/core'; | ||||||||||||||
import { cloneDeep } from 'lodash'; | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||
import React, { useState } from 'react'; | ||||||||||||||
import { useNavigate } from 'react-router'; | ||||||||||||||
import Constants from 'src/commons/utils/Constants'; | ||||||||||||||
import { readSicpLangLocalStorage } from 'src/features/sicp/utils/SicpUtils'; | ||||||||||||||
|
||||||||||||||
import toc from '../../../features/sicp/data/toc.json'; | ||||||||||||||
import fallbackToc from '../../../features/sicp/data/toc.json'; | ||||||||||||||
|
||||||||||||||
const baseUrl = Constants.sicpBackendUrl + 'json/'; | ||||||||||||||
const loadingComponent = <NonIdealState title="Loading Content" icon={<Spinner />} />; | ||||||||||||||
|
||||||||||||||
type TocProps = OwnProps; | ||||||||||||||
|
||||||||||||||
|
@@ -14,8 +20,9 @@ type OwnProps = { | |||||||||||||
/** | ||||||||||||||
* Table of contents of SICP. | ||||||||||||||
*/ | ||||||||||||||
const SicpToc: React.FC<TocProps> = props => { | ||||||||||||||
const [sidebarContent, setSidebarContent] = useState(toc as TreeNodeInfo[]); | ||||||||||||||
|
||||||||||||||
const Toc: React.FC<{ toc: TreeNodeInfo[]; props: TocProps }> = ({ toc, props }) => { | ||||||||||||||
const [sidebarContent, setSidebarContent] = useState(toc); | ||||||||||||||
const navigate = useNavigate(); | ||||||||||||||
RichDom2185 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
const handleNodeExpand = (_node: TreeNodeInfo, path: integer[]) => { | ||||||||||||||
RichDom2185 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
@@ -40,15 +47,61 @@ const SicpToc: React.FC<TocProps> = props => { | |||||||||||||
[navigate, props] | ||||||||||||||
); | ||||||||||||||
|
||||||||||||||
return ( | ||||||||||||||
<Tree | ||||||||||||||
className="sicp-toc-tree" | ||||||||||||||
contents={sidebarContent} | ||||||||||||||
onNodeClick={handleNodeClicked} | ||||||||||||||
onNodeCollapse={handleNodeCollapse} | ||||||||||||||
onNodeExpand={handleNodeExpand} | ||||||||||||||
/> | ||||||||||||||
); | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
const SicpToc: React.FC<TocProps> = props => { | ||||||||||||||
const [lang, setLang] = useState(readSicpLangLocalStorage()); | ||||||||||||||
const [toc, setToc] = useState([] as TreeNodeInfo[]); | ||||||||||||||
const [loading, setLoading] = useState(true); | ||||||||||||||
const [error, setError] = useState(false); | ||||||||||||||
|
||||||||||||||
React.useEffect(() => { | ||||||||||||||
const handleLangChange = () => { | ||||||||||||||
setLang(readSicpLangLocalStorage()); | ||||||||||||||
}; | ||||||||||||||
window.addEventListener('sicp-tb-lang-change', handleLangChange); | ||||||||||||||
return () => window.removeEventListener('sicp-tb-lang-change', handleLangChange); | ||||||||||||||
}, []); | ||||||||||||||
|
||||||||||||||
React.useEffect(() => { | ||||||||||||||
setLoading(true); | ||||||||||||||
fetch(baseUrl + lang + '/toc.json') | ||||||||||||||
.then(response => { | ||||||||||||||
if (!response.ok) { | ||||||||||||||
throw Error(response.statusText); | ||||||||||||||
} | ||||||||||||||
return response.json(); | ||||||||||||||
}) | ||||||||||||||
.then(json => { | ||||||||||||||
setToc(json as TreeNodeInfo[]); | ||||||||||||||
}) | ||||||||||||||
.catch(error => { | ||||||||||||||
console.log(error); | ||||||||||||||
setError(true); | ||||||||||||||
}) | ||||||||||||||
.finally(() => { | ||||||||||||||
setLoading(false); | ||||||||||||||
}); | ||||||||||||||
}, [lang]); | ||||||||||||||
RichDom2185 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
return ( | ||||||||||||||
<div className="sicp-toc"> | ||||||||||||||
<Tree | ||||||||||||||
className="sicp-toc-tree" | ||||||||||||||
contents={sidebarContent} | ||||||||||||||
onNodeClick={handleNodeClicked} | ||||||||||||||
onNodeCollapse={handleNodeCollapse} | ||||||||||||||
onNodeExpand={handleNodeExpand} | ||||||||||||||
/> | ||||||||||||||
{loading ? ( | ||||||||||||||
<div className="sicp-content">{loadingComponent}</div> | ||||||||||||||
) : error ? ( | ||||||||||||||
<Toc toc={fallbackToc as TreeNodeInfo[]} props={props} /> | ||||||||||||||
) : ( | ||||||||||||||
<Toc toc={toc} props={props} /> | ||||||||||||||
Comment on lines
+101
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] Rather than passing a single
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||
)} | ||||||||||||||
</div> | ||||||||||||||
); | ||||||||||||||
}; | ||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.