fix(i18n): 崩溃兜底页不再停留旧语言
ErrorBoundary / PanelErrorBoundary 是 class 组件(拥有错误生命周期), 不能用 hook 订阅 settings store;根边界还是 App 的父级,语言切换时 App 的重渲染传导不到它——崩溃兜底页因此停留在启动时的语言,恰好是 用户最需要读得懂的一屏。 修法:导出改为一层薄函数包装(订阅 language 传 prop),class 内部经 tFor(language, …) 翻译。错误边界行为不变(边界仍是 class、仍在 children 之上)。使用方(main.tsx / TerminalPanel)无需改动。 ROADMAP 已知待办补录两项 i18n 质量专项:zh-TW 用语统一(命令/指令、 口令/密碼 等)、ja 片假名长音符号统一(フォルダ/フォルダー)。
This commit is contained in:
1 parent
b344af4691
commit
7c2044fdf4
2 files changed
+40
-9
No files matched your search
@@ -162,6 +162,8 @@
|
|||||||
- [x] ~~布局塌陷~~(v0.2.1 修复:antd `<App>` 包裹层 `.ant-app` 无高度导致百分比高度链断裂)
|
- [x] ~~布局塌陷~~(v0.2.1 修复:antd `<App>` 包裹层 `.ant-app` 无高度导致百分比高度链断裂)
|
||||||
- [x] ~~关闭标签页整窗崩溃~~(v0.2.1 修复:xterm IDisposable 对象被当函数调用;并新增 ErrorBoundary 兜底 + 渲染层报错转发主进程日志)
|
- [x] ~~关闭标签页整窗崩溃~~(v0.2.1 修复:xterm IDisposable 对象被当函数调用;并新增 ErrorBoundary 兜底 + 渲染层报错转发主进程日志)
|
||||||
- [x] ~~首启动空屏~~(v0.2.1:启动自动打开一个终端)
|
- [x] ~~首启动空屏~~(v0.2.1:启动自动打开一个终端)
|
||||||
|
- [ ] zh-TW 用语统一(i18n 质量专项):`命令/指令` 全文件混用(panels/ssh/settings/terminal,台湾惯用「指令」)、`口令`→`密碼`(ssh.secret.passphrase)、顺带通读 服务器→伺服器 / 默认→預設 / 网络→網路 / 软件→軟體。需要一次全局连贯的专项,零散改会制造新的不一致
|
||||||
|
- [ ] ja 片假名长音符号统一:`フォルダ`(6) / `フォルダー`(7) 混用,二选一全局统一
|
||||||
- [ ] autoWrap=false 的固定列宽模式(xterm.js 无原生 wrapMode,需自定义列数控制)
|
- [ ] autoWrap=false 的固定列宽模式(xterm.js 无原生 wrapMode,需自定义列数控制)
|
||||||
- [ ] 终端标题跟随 OSC 序列(窗格标题显示当前目录/命令)
|
- [ ] 终端标题跟随 OSC 序列(窗格标题显示当前目录/命令)
|
||||||
- [ ] 内置等宽字体打包(OFL 许可:JetBrains Mono / Fira Code 等)
|
- [ ] 内置等宽字体打包(OFL 许可:JetBrains Mono / Fira Code 等)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component } from 'react'
|
import { Component } from 'react'
|
||||||
import type { ErrorInfo, ReactNode } from 'react'
|
import type { ErrorInfo, ReactNode } from 'react'
|
||||||
import { t } from '@shared/i18n'
|
import { DEFAULT_LANGUAGE, tFor, type Language } from '@shared/i18n'
|
||||||
|
import { useSettingsStore } from './settings/store'
|
||||||
|
|
||||||
interface ErrorBoundaryProps {
|
interface ErrorBoundaryProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -11,10 +12,29 @@ interface ErrorBoundaryState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last-resort boundary: a render/effect crash must never take the whole app
|
* Error boundaries must stay class components (they own the error lifecycle),
|
||||||
* down to a black window. Shows the error and a reload escape instead.
|
* and class components cannot subscribe to the settings store the way function
|
||||||
|
* components do — nor do they re-render when App re-renders on a language
|
||||||
|
* switch (the root boundary is App's *parent*). So the exported boundaries are
|
||||||
|
* thin function wrappers that subscribe to the language and hand it down as a
|
||||||
|
* prop; the classes translate through `tFor(language, …)`. Without this, a
|
||||||
|
* crashed panel showed its fallback screen in whatever language the app was
|
||||||
|
* launched in — the one screen where reading it matters most.
|
||||||
*/
|
*/
|
||||||
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
function useBoundaryLanguage(): Language {
|
||||||
|
return useSettingsStore((s) => s.settings.system.language ?? DEFAULT_LANGUAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Last-resort boundary: a render/effect crash must never take the whole app
|
||||||
|
* down to a black window. Shows the error and a reload escape instead. */
|
||||||
|
export function ErrorBoundary(props: ErrorBoundaryProps): ReactNode {
|
||||||
|
const language = useBoundaryLanguage()
|
||||||
|
return <ErrorBoundaryClass {...props} language={language} />
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorBoundaryClassProps = ErrorBoundaryProps & { language: Language }
|
||||||
|
|
||||||
|
class ErrorBoundaryClass extends Component<ErrorBoundaryClassProps, ErrorBoundaryState> {
|
||||||
state: ErrorBoundaryState = { error: null }
|
state: ErrorBoundaryState = { error: null }
|
||||||
|
|
||||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||||
@@ -39,7 +59,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|||||||
color: '#cccccc'
|
color: '#cccccc'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>{t('workspace.error.title')}</div>
|
<div>{tFor(this.props.language, 'workspace.error.title')}</div>
|
||||||
<pre
|
<pre
|
||||||
style={{
|
style={{
|
||||||
maxWidth: 720,
|
maxWidth: 720,
|
||||||
@@ -57,7 +77,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
style={{ padding: '6px 18px', cursor: 'pointer' }}
|
style={{ padding: '6px 18px', cursor: 'pointer' }}
|
||||||
>
|
>
|
||||||
{t('workspace.error.reload')}
|
{tFor(this.props.language, 'workspace.error.reload')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -78,7 +98,14 @@ interface PanelErrorBoundaryProps {
|
|||||||
* other live session. A crashed pane is replaced in place and can be remounted
|
* other live session. A crashed pane is replaced in place and can be remounted
|
||||||
* on retry; the rest of the workspace is untouched.
|
* on retry; the rest of the workspace is untouched.
|
||||||
*/
|
*/
|
||||||
export class PanelErrorBoundary extends Component<PanelErrorBoundaryProps, ErrorBoundaryState> {
|
export function PanelErrorBoundary(props: PanelErrorBoundaryProps): ReactNode {
|
||||||
|
const language = useBoundaryLanguage()
|
||||||
|
return <PanelErrorBoundaryClass {...props} language={language} />
|
||||||
|
}
|
||||||
|
|
||||||
|
type PanelErrorBoundaryClassProps = PanelErrorBoundaryProps & { language: Language }
|
||||||
|
|
||||||
|
class PanelErrorBoundaryClass extends Component<PanelErrorBoundaryClassProps, ErrorBoundaryState> {
|
||||||
state: ErrorBoundaryState = { error: null }
|
state: ErrorBoundaryState = { error: null }
|
||||||
|
|
||||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||||
@@ -98,10 +125,12 @@ export class PanelErrorBoundary extends Component<PanelErrorBoundaryProps, Error
|
|||||||
if (!this.state.error) return this.props.children
|
if (!this.state.error) return this.props.children
|
||||||
return (
|
return (
|
||||||
<div className="panel-error">
|
<div className="panel-error">
|
||||||
<div className="panel-error-title">{this.props.label ?? t('workspace.error.panelTitle')}</div>
|
<div className="panel-error-title">
|
||||||
|
{this.props.label ?? tFor(this.props.language, 'workspace.error.panelTitle')}
|
||||||
|
</div>
|
||||||
<pre className="panel-error-message">{this.state.error.message}</pre>
|
<pre className="panel-error-message">{this.state.error.message}</pre>
|
||||||
<button type="button" className="panel-error-retry" onClick={this.retry}>
|
<button type="button" className="panel-error-retry" onClick={this.retry}>
|
||||||
{t('workspace.error.panelRetry')}
|
{tFor(this.props.language, 'workspace.error.panelRetry')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user