Browse Source

feat: offline-friendly error handling with chunk retry and network banner (#11756)

* feat: add offline-friendly error handling and network status awareness

Add ErrorBoundary to catch render errors (including chunk load failures from
React.lazy), replacing the previous white-screen crash with a friendly Result
page. Add a network status model (useModel('network')) and OfflineBanner that
shows when the user goes offline. Make request error messages offline-aware.
Clean up dead PWA/ServiceWorker code in global.tsx that never fired.

- ErrorBoundary: catches all render errors, distinguishes chunk load failures
  (offline vs online messages), uses antd Result with retry/home buttons
- OfflineBanner: Alert banner in ProLayout content area when offline
- network model: navigator.onLine + online/offline event listeners
- requestErrorConfig: offline-aware error messages in errorHandler
- global.tsx: remove dead sw.offline/sw.updated event listeners
- defaultSettings.ts: remove unused pwa flag
- locales: replace dead PWA i18n keys with network/error keys (8 locales)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: resolve intermittent "Can't resolve 'tailwindcss'" on Windows (#11747)

Set globalThis.__tw_resolve in postcss.config.js to bypass
enhanced-resolve's CachedInputFileSystem, which caches stale
negative results on Windows (e.g. when Windows Defender scans
node_modules). Also fix @source path from "./src" to "."
since the directive is relative to the CSS file location.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address PR review feedback for offline features

- Use getIntl() with i18n key in requestErrorConfig instead of hardcoded
  English strings (matches CodeRabbit, Gemini, Copilot feedback)
- Add SSR guard for navigator.onLine in requestErrorConfig (Gemini)
- Replace <a> wrapping <Button> with <Button href> for valid HTML
- Extract getSubTitleId helper to eliminate nested ternary
- Move OfflineBanner to rootContainer layer to cover layout:false
  routes (login, register) that lack ProLayout

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* revert: remove unrelated tailwindcss and config changes

These changes belong in a separate PR:
- postcss.config.js: Windows tailwindcss resolve workaround
- src/tailwind.css: @source path change
- config/md-raw-loader.cjs: trailing newline fix

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: offlineBanner uses getIntl() and inline network state

OfflineBanner is rendered inside rootContainer, which is the outermost
wrapper in Umi's provider chain. useModel('network') and useIntl()
require provider context (dataflowProvider, i18nProvider) that doesn't
exist at this level, causing runtime crashes. Switch to getIntl() (which
accesses the global intl singleton) and inline navigator.onLine state.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: make ErrorBoundary reactive to network state and move OfflineBanner outside

- ErrorBoundary now tracks isOnline via state + event listeners instead
  of reading navigator.onLine once at render time, so the subtitle
  updates when the user goes back online
- OfflineBanner moved outside ErrorBoundary in rootContainer so it
  remains visible even when ErrorBoundary shows the error fallback

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: add defaultMessage fallback to requestErrorConfig formatMessage calls

Ensures a readable string is shown when the locale key is missing,
instead of the raw key id.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: match umi chunk error format and add defaultMessage fallbacks

1. isChunkLoadError regex now matches "Failed to load chunk" (Umi format)
   in addition to "Loading chunk" (webpack format), fixing the error page
   showing generic "Something went wrong" instead of friendly offline text
2. All formatMessage calls in ErrorBoundary and OfflineBanner include
   defaultMessage fallbacks, fixing empty Alert banner when getIntl()
   returns before locale initialization completes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: show offline-friendly error page when network is unavailable

When offline and navigating to an unvisited route, the ErrorBoundary
displayed a generic "Something went wrong" page instead of the friendly
offline/crunk-load error message. This happened because:

1. isChunkLoadError could miss errors when React wraps them or the error
   name doesn't exactly match — now also checks error.stack and adds a
   case-insensitive "chunkloaderror" pattern
2. When offline, ANY render error is overwhelmingly likely a chunk load
   failure — new isNetworkRelatedError() treats offline as network-related
3. The subtitle defaultMessage was wrong: it used isOffline instead of
   combining both networkRelated and isOffline dimensions
4. handleRetry only reloaded for isChunkLoadError — now also reloads when
   offline (resetting state can't re-fetch the chunk)
5. Auto-reloads the page when network comes back online during an error

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: replace deprecated Alert `message` prop with `title`

antd v6 deprecated the `message` prop on Alert in favor of `title`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: replace ProLayout's default ErrorBoundary with our offline-aware version

ProLayout has a built-in ErrorBoundary (@ant-design/pro-utils) that
catches chunk load errors BEFORE our root-level ErrorBoundary, rendering
a generic "Something went wrong." Result page. This masked our custom
offline-friendly error handling entirely.

Pass our ErrorBoundary as ProLayout's ErrorBoundary prop so it catches
errors inside the content area with proper offline/chunk-error detection,
while the root-level ErrorBoundary still guards the outer app shell.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: simplify ErrorBoundary, remove over-engineering

Remove speculative additions that didn't address the root cause:
- isNetworkRelatedError: assuming all render errors are chunk errors
  when offline masks genuine bugs
- CHUNK_ERROR_PATTERNS const and error.stack scanning: the original
  isChunkLoadError already matches utoopack's ChunkLoadError format
- getTitleId/getSubTitleId helpers: single-call-site abstractions
- Card wrapper: unnecessary inside ProLayout content area
- Offline-aware handleRetry: chunk errors should be the only trigger
  for reload, not any error while offline

Kept the real improvements:
- Correct 3-way defaultMessage for subtitle (offline chunk / online
  chunk / render error)
- Auto-reload on network recovery when in error state
- ProLayout ErrorBoundary prop (the actual fix in previous commit)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: simplify offline handling and fix OfflineBanner not showing

1. OfflineBanner: use `useModel('network')` instead of duplicating
   online/offline listener logic, fix `title` → `message` prop
   (Alert in banner mode uses `message`, not `title`)

2. ErrorBoundary: extract shared renderErrorFallback(), split into
   ErrorBoundaryClass (for ProLayout's ComponentClass prop) and
   ErrorBoundary (FC wrapper using useModel('network')). When isOnline
   prop is provided, class skips own event listeners; when unset,
   falls back to navigator.onLine tracking.

3. requestErrorConfig: consolidate duplicate offline check — check
   navigator.onLine once before the error.request branch instead of
   duplicating the same intl message in two branches.

4. ProLayout now uses ErrorBoundaryClass directly (satisfies the
   ComponentClass type requirement).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: useModel context null error — rootContainer is outside model provider

rootContainer is the outermost React wrapper, rendered BEFORE the
dataflow/model provider. Components inside it cannot use useModel().

- ErrorBoundary: revert to pure class component with own online/offline
  listeners (no hooks). ProLayout ErrorBoundary prop requires
  ComponentClass anyway.
- OfflineBanner: revert to own useState+useEffect listeners (same reason
  — outside model provider in rootContainer).
- Delete unused src/models/network.ts (no consumers left).
- Remove ErrorBoundaryClass named export, keep default export only.
- Fix Alert `title` → `message` prop (banner mode uses message).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: move OfflineBanner into ProLayout childrenRender

rootContainer is outside ProLayout, causing layout issues (empty
ant-layout-header inserted). Move OfflineBanner into childrenRender
where it renders inside the ProLayout content area as intended.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: wrap error Result in Card, style OfflineBanner with margin and showIcon

- ErrorBoundary: wrap Result in <Card variant="borderless"> for proper
  card styling
- OfflineBanner: remove `banner` prop (was causing header layout issues),
  add `showIcon` for warning icon, add `marginBottom: 16` for spacing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: error boundary wrap in Layout.Content for padding, move offline banner to page top

- errorBoundary: replace Card with Layout.Content (padding: 24) to match
  the 403/exception page style, since ErrorBoundary renders outside
  ProLayout's Layout.Content
- offlineBanner: move back to rootContainer (above ProLayout) to display
  at page top as a full-width banner, restore `banner` prop

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: error boundary use Card style matching 403 page, offline banner fixed top center

- errorBoundary: use Card variant="borderless" wrapping Result, matching
  the 403/404 exception page style for consistency
- offlineBanner: use fixed positioning at top center of header area with
  pointer-events passthrough, max-width 480px, small margin

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: add padding around error boundary Card for consistent layout

ErrorBoundary replaces Layout.Content when catching errors, so the
Card has no outer padding. Wrap in a div with padding: 24 to match
the ProLayout content area spacing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat: add soft retry for chunk errors and update all locales

Split the single retry button into two actions:
- "Retry" (soft): clears failed chunk scripts and resets ErrorBoundary
  state for chunk load errors only
- "Reload Page" (hard): full page refresh, available for all errors

Update all 8 locale files with new app.error.reload key and change
app.error.retry from "refresh/reload" to "retry" semantics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: remove non-functional soft retry and simplify ErrorBoundary

- Remove removeFailedChunkScripts() — nothing marks scripts with
  data-failed and the bundler caches rejected promises, so soft retry
  was a no-op that misleads users
- Remove Retry button; keep Reload Page + Back Home for all errors
- Remove unused app.error.retry locale key from all 8 locales
- Evaluate navigator.onLine directly in render instead of caching in
  instance field, so error UI always reflects current network status
- Limit handleOnline auto-reload to chunk load errors only, preventing
  data loss from indiscriminate reload on render errors
- Remove offline event listener (no longer needed without isOnline cache)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: make ErrorBoundary reactive to network state changes

- Move isOnline into component state so error fallback UI updates
  when network status changes (online/offline events trigger re-render)
- Tighten isChunkLoadError regex: replace broad /imported module/i
  with specific /Failed to fetch dynamically imported module/i to
  avoid false positives on unrelated runtime errors

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat: add retry button for chunk errors, simplify DOM, rename pwa→network

- Add Retry button for chunk load errors that remounts children via
  key increment, forcing React.lazy to re-execute import() without
  a full page reload
- Remove wrapper divs: Card padding applied directly via style,
  Alert positioned directly without outer container
- Rename pwa.ts → network.ts across all 8 locales (keys no longer
  PWA-specific) and update imports in locale index files
- Add app.error.retry locale key to all 8 locales

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style: remove unnecessary comment in components index and ErrorBoundary

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
pull/11767/head
Alex Zhu 4 months ago
committed by GitHub
parent
commit
c4094c0185
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      config/defaultSettings.ts
  2. 14
      src/app.tsx
  3. 161
      src/components/ErrorBoundary/index.tsx
  4. 45
      src/components/OfflineBanner/index.tsx
  5. 2
      src/components/index.ts
  6. 93
      src/global.tsx
  7. 4
      src/locales/bn-BD.ts
  8. 16
      src/locales/bn-BD/network.ts
  9. 7
      src/locales/bn-BD/pwa.ts
  10. 4
      src/locales/en-US.ts
  11. 17
      src/locales/en-US/network.ts
  12. 7
      src/locales/en-US/pwa.ts
  13. 4
      src/locales/fa-IR.ts
  14. 17
      src/locales/fa-IR/network.ts
  15. 7
      src/locales/fa-IR/pwa.ts
  16. 4
      src/locales/id-ID.ts
  17. 17
      src/locales/id-ID/network.ts
  18. 7
      src/locales/id-ID/pwa.ts
  19. 4
      src/locales/ja-JP.ts
  20. 17
      src/locales/ja-JP/network.ts
  21. 7
      src/locales/ja-JP/pwa.ts
  22. 4
      src/locales/pt-BR.ts
  23. 17
      src/locales/pt-BR/network.ts
  24. 7
      src/locales/pt-BR/pwa.ts
  25. 4
      src/locales/zh-CN.ts
  26. 14
      src/locales/zh-CN/network.ts
  27. 6
      src/locales/zh-CN/pwa.ts
  28. 4
      src/locales/zh-TW.ts
  29. 15
      src/locales/zh-TW/network.ts
  30. 6
      src/locales/zh-TW/pwa.ts
  31. 13
      src/requestErrorConfig.ts

2
config/defaultSettings.ts

@ -4,7 +4,6 @@ import type { ProLayoutProps } from '@ant-design/pro-components';
* @name
*/
const Settings: ProLayoutProps & {
pwa?: boolean;
logo?: string;
} = {
navTheme: 'light',
@ -16,7 +15,6 @@ const Settings: ProLayoutProps & {
fixSiderbar: true,
colorWeak: false,
title: 'Ant Design Pro',
pwa: true,
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
iconfontUrl: '',
token: {

14
src/app.tsx

@ -13,8 +13,10 @@ dayjs.extend(relativeTime);
import {
AvatarDropdown,
DocLink,
ErrorBoundary,
Footer,
LangDropdown,
OfflineBanner,
VersionDropdown,
} from '@/components';
import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api';
@ -139,6 +141,9 @@ export const layout: RunTimeLayoutConfig = ({
</Link>,
]
: [],
// Replace ProLayout's default ErrorBoundary with our offline-aware version,
// so chunk load errors show friendly messages instead of "Something went wrong."
ErrorBoundary,
menuHeaderRender: undefined,
// 自定义 403 页面
// unAccessible: <div>unAccessible</div>,
@ -182,3 +187,12 @@ export const request: RequestConfig = {
baseURL: isDev ? '' : 'https://pro-api.ant-design-demo.workers.dev',
...errorConfig,
};
export function rootContainer(container: React.ReactNode) {
return (
<>
<OfflineBanner />
<ErrorBoundary>{container}</ErrorBoundary>
</>
);
}

161
src/components/ErrorBoundary/index.tsx

@ -0,0 +1,161 @@
import { getIntl } from '@umijs/max';
import { Button, Card, Result } from 'antd';
import React from 'react';
function isChunkLoadError(error: Error): boolean {
return (
error.name === 'ChunkLoadError' ||
/(?:loading|failed to load) (?:css )?chunk/i.test(error.message) ||
/Failed to fetch dynamically imported module/i.test(error.message)
);
}
function getSubTitleId(isChunkError: boolean, isOffline: boolean): string {
if (!isChunkError) return 'app.error.render.description';
return isOffline
? 'app.error.chunk.description.offline'
: 'app.error.chunk.description.online';
}
function renderErrorFallback(
error: Error,
isOnline: boolean,
onRetry: () => void,
onReload: () => void,
) {
const intl = getIntl();
const isOffline = !isOnline;
const isChunkError = isChunkLoadError(error);
return (
<Card variant="borderless" style={{ margin: 24 }}>
<Result
status="error"
title={intl.formatMessage({
id: isChunkError ? 'app.error.chunk.title' : 'app.error.render.title',
defaultMessage: isChunkError
? 'Failed to load page'
: 'Something went wrong',
})}
subTitle={intl.formatMessage({
id: getSubTitleId(isChunkError, isOffline),
defaultMessage:
isChunkError && isOffline
? 'Your network connection has been lost. Please check your connection and reload.'
: isChunkError
? 'Page resources failed to load. Please reload and try again.'
: 'Sorry, an error occurred on this page. Please reload or go back to the home page.',
})}
extra={[
isChunkError && (
<Button type="primary" key="retry" onClick={onRetry}>
{intl.formatMessage({
id: 'app.error.retry',
defaultMessage: 'Retry',
})}
</Button>
),
<Button
type={isChunkError ? 'default' : 'primary'}
key="reload"
onClick={onReload}
>
{intl.formatMessage({
id: 'app.error.reload',
defaultMessage: 'Reload Page',
})}
</Button>,
<Button href="/" key="home">
{intl.formatMessage({
id: 'app.error.home',
defaultMessage: 'Back Home',
})}
</Button>,
].filter(Boolean)}
/>
</Card>
);
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
isOnline: boolean;
retryCount: number;
}
export default class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
ErrorBoundaryState
> {
state: ErrorBoundaryState = {
hasError: false,
error: null,
isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,
retryCount: 0,
};
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
return { hasError: true, error };
}
componentDidMount() {
window.addEventListener('online', this.handleOnline);
window.addEventListener('offline', this.handleOffline);
}
componentWillUnmount() {
window.removeEventListener('online', this.handleOnline);
window.removeEventListener('offline', this.handleOffline);
}
handleOnline = () => {
this.setState({ isOnline: true });
if (
this.state.hasError &&
this.state.error &&
isChunkLoadError(this.state.error)
) {
window.location.reload();
}
};
handleOffline = () => {
this.setState({ isOnline: false });
};
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('[ErrorBoundary]', error, info.componentStack);
}
handleRetry = () => {
// Incrementing retryCount changes the key on the children fragment,
// forcing React to unmount and remount all lazy components.
// This causes React.lazy to re-execute import() for the failed chunk.
this.setState((prev) => ({
hasError: false,
error: null,
retryCount: prev.retryCount + 1,
}));
};
handleReload = () => {
window.location.reload();
};
render() {
if (!this.state.hasError || !this.state.error) {
return (
<React.Fragment key={this.state.retryCount}>
{this.props.children}
</React.Fragment>
);
}
return renderErrorFallback(
this.state.error,
this.state.isOnline,
this.handleRetry,
this.handleReload,
);
}
}

45
src/components/OfflineBanner/index.tsx

@ -0,0 +1,45 @@
import { getIntl } from '@umijs/max';
import { Alert } from 'antd';
import { useEffect, useState } from 'react';
const OfflineBanner: React.FC = () => {
const [isOnline, setIsOnline] = useState(
typeof navigator !== 'undefined' ? navigator.onLine : true,
);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
if (isOnline) return null;
return (
<Alert
type="warning"
showIcon
closable={false}
style={{
position: 'fixed',
top: 8,
left: '50%',
transform: 'translateX(-50%)',
zIndex: 999,
maxWidth: 480,
}}
message={getIntl().formatMessage({
id: 'app.network.offline',
defaultMessage:
'You are currently offline. Some features may be unavailable.',
})}
/>
);
};
export default OfflineBanner;

2
src/components/index.ts

@ -14,6 +14,8 @@ import { AvatarDropdown } from './RightContent/AvatarDropdown';
*/
export { default as ArticleListContent } from './ArticleListContent';
export { default as AvatarList } from './AvatarList';
export { default as ErrorBoundary } from './ErrorBoundary';
export { default as OfflineBanner } from './OfflineBanner';
export { default as StandardFormRow } from './StandardFormRow';
export { default as TagSelect } from './TagSelect';

93
src/global.tsx

@ -1,94 +1 @@
import './tailwind.css';
import { useIntl } from '@umijs/max';
import { Button, message, notification } from 'antd';
import defaultSettings from '../config/defaultSettings';
const { pwa } = defaultSettings;
const isHttps = document.location.protocol === 'https:';
const clearCache = () => {
// remove all caches
if (window.caches) {
caches
.keys()
.then((keys) => {
keys.forEach((key) => {
caches.delete(key);
});
})
.catch((e) => console.log(e));
}
};
// if pwa is true
if (pwa) {
// Notify user if offline now
window.addEventListener('sw.offline', () => {
message.warning(useIntl().formatMessage({ id: 'app.pwa.offline' }));
});
// Pop up a prompt on the page asking the user if they want to use the latest version
window.addEventListener('sw.updated', (event: Event) => {
const e = event as CustomEvent;
const reloadSW = async () => {
// Check if there is sw whose state is waiting in ServiceWorkerRegistration
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
const worker = e.detail?.waiting;
if (!worker) {
return true;
}
// Send skip-waiting event to waiting SW with MessageChannel
await new Promise((resolve, reject) => {
const channel = new MessageChannel();
channel.port1.onmessage = (msgEvent) => {
if (msgEvent.data.error) {
reject(msgEvent.data.error);
} else {
resolve(msgEvent.data);
}
};
worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
});
clearCache();
window.location.reload();
return true;
};
const key = `open${Date.now()}`;
const btn = (
<Button
type="primary"
onClick={() => {
notification.destroy(key);
reloadSW();
}}
>
{useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
</Button>
);
notification.open({
title: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated' }),
description: useIntl().formatMessage({
id: 'app.pwa.serviceworker.updated.hint',
}),
btn,
key,
onClose: async () => null,
});
});
} else if ('serviceWorker' in navigator && isHttps) {
// unregister service worker
const { serviceWorker } = navigator;
if (serviceWorker.getRegistrations) {
serviceWorker.getRegistrations().then((sws) => {
sws.forEach((sw) => {
sw.unregister();
});
});
}
serviceWorker.getRegistration().then((sw) => {
if (sw) sw.unregister();
});
clearCache();
}

4
src/locales/bn-BD.ts

@ -1,8 +1,8 @@
import component from './bn-BD/component';
import globalHeader from './bn-BD/globalHeader';
import menu from './bn-BD/menu';
import network from './bn-BD/network';
import pages from './bn-BD/pages';
import pwa from './bn-BD/pwa';
import settingDrawer from './bn-BD/settingDrawer';
import settings from './bn-BD/settings';
@ -16,7 +16,7 @@ export default {
...menu,
...settingDrawer,
...settings,
...pwa,
...network,
...component,
...pages,
};

16
src/locales/bn-BD/network.ts

@ -0,0 +1,16 @@
export default {
'app.network.offline': 'আপনি এখন অফলাইন। কিছু বৈশিষ্ট্য অনুপলব্ধ হতে পারে।',
'app.error.chunk.title': 'পৃষ্ঠা লোড করতে ব্যর্থ',
'app.error.chunk.description.offline':
'আপনার নেটওয়ার্ক সংযোগ বিচ্ছিন্ন হয়েছে। আপনার সংযোগ পরীক্ষা করুন এবং পৃষ্ঠাটি রিফ্রেশ করুন।',
'app.error.chunk.description.online':
'পৃষ্ঠা সম্পদ লোড করতে ব্যর্থ। দয়া করে রিফ্রেশ করুন এবং আবার চেষ্টা করুন।',
'app.error.render.title': 'কিছু ভুল হয়েছে',
'app.error.render.description':
'দুঃখিত, এই পৃষ্ঠায় একটি ত্রুটি ঘটেছে। দয়া করে রিফ্রেশ করুন বা হোম পৃষ্ঠায় ফিরে যান।',
'app.error.retry': 'আবার চেষ্টা করুন',
'app.error.reload': 'পৃষ্ঠা রিফ্রেশ করুন',
'app.error.home': 'হোমে ফিরে যান',
'app.request.offline':
'নেটওয়ার্ক অনুপলব্ধ। আপনার সংযোগ পরীক্ষা করুন এবং আবার চেষ্টা করুন।',
};

7
src/locales/bn-BD/pwa.ts

@ -1,7 +0,0 @@
export default {
'app.pwa.offline': 'আপনি এখন অফলাইন',
'app.pwa.serviceworker.updated': 'নতুন সামগ্রী উপলব্ধ',
'app.pwa.serviceworker.updated.hint':
'বর্তমান পৃষ্ঠাটি পুনরায় লোড করতে দয়া করে "রিফ্রেশ" বোতাম টিপুন',
'app.pwa.serviceworker.updated.ok': 'রিফ্রেশ',
};

4
src/locales/en-US.ts

@ -1,8 +1,8 @@
import component from './en-US/component';
import globalHeader from './en-US/globalHeader';
import menu from './en-US/menu';
import network from './en-US/network';
import pages from './en-US/pages';
import pwa from './en-US/pwa';
import settingDrawer from './en-US/settingDrawer';
import settings from './en-US/settings';
@ -16,7 +16,7 @@ export default {
...menu,
...settingDrawer,
...settings,
...pwa,
...network,
...component,
...pages,
};

17
src/locales/en-US/network.ts

@ -0,0 +1,17 @@
export default {
'app.network.offline':
'You are currently offline. Some features may be unavailable.',
'app.error.chunk.title': 'Failed to load page',
'app.error.chunk.description.offline':
'Your network connection has been lost. Please check your connection and reload.',
'app.error.chunk.description.online':
'Page resources failed to load. Please reload and try again.',
'app.error.render.title': 'Something went wrong',
'app.error.render.description':
'Sorry, an error occurred on this page. Please reload or go back to the home page.',
'app.error.retry': 'Retry',
'app.error.reload': 'Reload Page',
'app.error.home': 'Back Home',
'app.request.offline':
'Network unavailable. Please check your connection and try again.',
};

7
src/locales/en-US/pwa.ts

@ -1,7 +0,0 @@
export default {
'app.pwa.offline': 'You are offline now',
'app.pwa.serviceworker.updated': 'New content is available',
'app.pwa.serviceworker.updated.hint':
'Please press the "Refresh" button to reload current page',
'app.pwa.serviceworker.updated.ok': 'Refresh',
};

4
src/locales/fa-IR.ts

@ -1,8 +1,8 @@
import component from './fa-IR/component';
import globalHeader from './fa-IR/globalHeader';
import menu from './fa-IR/menu';
import network from './fa-IR/network';
import pages from './fa-IR/pages';
import pwa from './fa-IR/pwa';
import settingDrawer from './fa-IR/settingDrawer';
import settings from './fa-IR/settings';
@ -16,7 +16,7 @@ export default {
...menu,
...settingDrawer,
...settings,
...pwa,
...network,
...component,
...pages,
};

17
src/locales/fa-IR/network.ts

@ -0,0 +1,17 @@
export default {
'app.network.offline':
'شما اکنون آفلاین هستید. برخی از قابلیت‌ها ممکن است در دسترس نباشند.',
'app.error.chunk.title': 'بارگذاری صفحه ناموفق بود',
'app.error.chunk.description.offline':
'اتصال شبکه شما قطع شده است. اتصال خود را بررسی کنید و صفحه را بارگذاری مجدد کنید.',
'app.error.chunk.description.online':
'بارگذاری منابع صفحه ناموفق بود. لطفاً صفحه را بارگذاری مجدد کنید.',
'app.error.render.title': 'خطایی رخ داد',
'app.error.render.description':
'متأسفانه، در این صفحه خطایی رخ داد. لطفاً صفحه را بارگذاری مجدد کنید یا به صفحه اصلی بازگردید.',
'app.error.retry': 'تلاش مجدد',
'app.error.reload': 'بارگذاری مجدد صفحه',
'app.error.home': 'بازگشت به صفحه اصلی',
'app.request.offline':
'شبکه در دسترس نیست. لطفاً اتصال خود را بررسی و دوباره تلاش کنید.',
};

7
src/locales/fa-IR/pwa.ts

@ -1,7 +0,0 @@
export default {
'app.pwa.offline': 'شما اکنون آفلاین هستید',
'app.pwa.serviceworker.updated': 'مطالب جدید در دسترس است',
'app.pwa.serviceworker.updated.hint':
'لطفاً برای بارگیری مجدد صفحه فعلی ، دکمه "تازه سازی" را فشار دهید',
'app.pwa.serviceworker.updated.ok': 'تازه سازی',
};

4
src/locales/id-ID.ts

@ -1,8 +1,8 @@
import component from './id-ID/component';
import globalHeader from './id-ID/globalHeader';
import menu from './id-ID/menu';
import network from './id-ID/network';
import pages from './id-ID/pages';
import pwa from './id-ID/pwa';
import settingDrawer from './id-ID/settingDrawer';
import settings from './id-ID/settings';
@ -16,7 +16,7 @@ export default {
...menu,
...settingDrawer,
...settings,
...pwa,
...network,
...component,
...pages,
};

17
src/locales/id-ID/network.ts

@ -0,0 +1,17 @@
export default {
'app.network.offline':
'Anda sedang offline. Beberapa fitur mungkin tidak tersedia.',
'app.error.chunk.title': 'Gagal memuat halaman',
'app.error.chunk.description.offline':
'Koneksi jaringan terputus. Periksa koneksi Anda dan muat ulang halaman.',
'app.error.chunk.description.online':
'Gagal memuat sumber daya halaman. Muat ulang dan coba lagi.',
'app.error.render.title': 'Terjadi kesalahan',
'app.error.render.description':
'Maaf, terjadi kesalahan pada halaman ini. Muat ulang halaman atau kembali ke beranda.',
'app.error.retry': 'Coba Lagi',
'app.error.reload': 'Muat Ulang Halaman',
'app.error.home': 'Kembali ke Beranda',
'app.request.offline':
'Jaringan tidak tersedia. Periksa koneksi Anda dan coba lagi.',
};

7
src/locales/id-ID/pwa.ts

@ -1,7 +0,0 @@
export default {
'app.pwa.offline': 'Koneksi anda terputus',
'app.pwa.serviceworker.updated': 'Konten baru sudah tersedia',
'app.pwa.serviceworker.updated.hint':
'Silahkan klik tombol "Refresh" untuk memuat ulang halaman ini',
'app.pwa.serviceworker.updated.ok': 'Memuat ulang',
};

4
src/locales/ja-JP.ts

@ -1,8 +1,8 @@
import component from './ja-JP/component';
import globalHeader from './ja-JP/globalHeader';
import menu from './ja-JP/menu';
import network from './ja-JP/network';
import pages from './ja-JP/pages';
import pwa from './ja-JP/pwa';
import settingDrawer from './ja-JP/settingDrawer';
import settings from './ja-JP/settings';
@ -17,7 +17,7 @@ export default {
...menu,
...settingDrawer,
...settings,
...pwa,
...network,
...component,
...pages,
};

17
src/locales/ja-JP/network.ts

@ -0,0 +1,17 @@
export default {
'app.network.offline':
'現在オフラインです。一部の機能が利用できない場合があります',
'app.error.chunk.title': 'ページの読み込みに失敗しました',
'app.error.chunk.description.offline':
'ネットワーク接続が切断されています。接続を確認してページを再読み込みしてください。',
'app.error.chunk.description.online':
'ページリソースの読み込みに失敗しました。ページを再読み込みしてください。',
'app.error.render.title': 'エラーが発生しました',
'app.error.render.description':
'申し訳ありません、ページでエラーが発生しました。ページを再読み込みするか、ホームに戻ってください。',
'app.error.retry': '再試行',
'app.error.reload': 'ページを再読み込み',
'app.error.home': 'ホームに戻る',
'app.request.offline':
'ネットワークに接続できません。接続を確認して再試行してください。',
};

7
src/locales/ja-JP/pwa.ts

@ -1,7 +0,0 @@
export default {
'app.pwa.offline': 'あなたは今オフラインです',
'app.pwa.serviceworker.updated': '新しいコンテンツが利用可能です',
'app.pwa.serviceworker.updated.hint':
'現在のページをリロードするには、「更新」ボタンを押してください',
'app.pwa.serviceworker.updated.ok': 'リフレッシュ',
};

4
src/locales/pt-BR.ts

@ -1,8 +1,8 @@
import component from './pt-BR/component';
import globalHeader from './pt-BR/globalHeader';
import menu from './pt-BR/menu';
import network from './pt-BR/network';
import pages from './pt-BR/pages';
import pwa from './pt-BR/pwa';
import settingDrawer from './pt-BR/settingDrawer';
import settings from './pt-BR/settings';
@ -16,7 +16,7 @@ export default {
...menu,
...settingDrawer,
...settings,
...pwa,
...network,
...component,
...pages,
};

17
src/locales/pt-BR/network.ts

@ -0,0 +1,17 @@
export default {
'app.network.offline':
'Você está offline no momento. Alguns recursos podem estar indisponíveis.',
'app.error.chunk.title': 'Falha ao carregar a página',
'app.error.chunk.description.offline':
'Sua conexão de rede foi perdida. Verifique sua conexão e atualize a página.',
'app.error.chunk.description.online':
'Falha ao carregar os recursos da página. Atualize e tente novamente.',
'app.error.render.title': 'Algo deu errado',
'app.error.render.description':
'Desculpe, ocorreu um erro nesta página. Atualize a página ou volte para a página inicial.',
'app.error.retry': 'Tentar novamente',
'app.error.reload': 'Atualizar página',
'app.error.home': 'Voltar ao Início',
'app.request.offline':
'Rede indisponível. Verifique sua conexão e tente novamente.',
};

7
src/locales/pt-BR/pwa.ts

@ -1,7 +0,0 @@
export default {
'app.pwa.offline': 'Você está offline agora',
'app.pwa.serviceworker.updated': 'Novo conteúdo está disponível',
'app.pwa.serviceworker.updated.hint':
'Por favor, pressione o botão "Atualizar" para recarregar a página atual',
'app.pwa.serviceworker.updated.ok': 'Atualizar',
};

4
src/locales/zh-CN.ts

@ -1,8 +1,8 @@
import component from './zh-CN/component';
import globalHeader from './zh-CN/globalHeader';
import menu from './zh-CN/menu';
import network from './zh-CN/network';
import pages from './zh-CN/pages';
import pwa from './zh-CN/pwa';
import settingDrawer from './zh-CN/settingDrawer';
import settings from './zh-CN/settings';
@ -17,6 +17,6 @@ export default {
...menu,
...settingDrawer,
...settings,
...pwa,
...network,
...component,
};

14
src/locales/zh-CN/network.ts

@ -0,0 +1,14 @@
export default {
'app.network.offline': '当前处于离线状态,部分功能可能不可用',
'app.error.chunk.title': '页面加载失败',
'app.error.chunk.description.offline':
'网络连接已断开,请检查网络后重新加载。',
'app.error.chunk.description.online': '页面资源加载失败,请重新加载重试。',
'app.error.render.title': '页面出现错误',
'app.error.render.description':
'抱歉,页面遇到了一些问题,请刷新页面或返回首页。',
'app.error.retry': '重试',
'app.error.reload': '刷新页面',
'app.error.home': '返回首页',
'app.request.offline': '网络不可用,请检查网络连接后重试。',
};

6
src/locales/zh-CN/pwa.ts

@ -1,6 +0,0 @@
export default {
'app.pwa.offline': '当前处于离线状态',
'app.pwa.serviceworker.updated': '有新内容',
'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
'app.pwa.serviceworker.updated.ok': '刷新',
};

4
src/locales/zh-TW.ts

@ -1,8 +1,8 @@
import component from './zh-TW/component';
import globalHeader from './zh-TW/globalHeader';
import menu from './zh-TW/menu';
import network from './zh-TW/network';
import pages from './zh-TW/pages';
import pwa from './zh-TW/pwa';
import settingDrawer from './zh-TW/settingDrawer';
import settings from './zh-TW/settings';
@ -17,6 +17,6 @@ export default {
...menu,
...settingDrawer,
...settings,
...pwa,
...network,
...component,
};

15
src/locales/zh-TW/network.ts

@ -0,0 +1,15 @@
export default {
'app.network.offline': '當前處於離線狀態,部分功能可能不可用',
'app.error.chunk.title': '頁面載入失敗',
'app.error.chunk.description.offline':
'網路連線已中斷,請檢查網路後重新整理頁面。',
'app.error.chunk.description.online':
'頁面資源載入失敗,請重新整理頁面重試。',
'app.error.render.title': '頁面出現錯誤',
'app.error.render.description':
'抱歉,頁面遇到了一些問題,請重新整理頁面或返回首頁。',
'app.error.retry': '重試',
'app.error.reload': '重新整理頁面',
'app.error.home': '返回首頁',
'app.request.offline': '網路不可用,請檢查網路連線後重試。',
};

6
src/locales/zh-TW/pwa.ts

@ -1,6 +0,0 @@
export default {
'app.pwa.offline': '當前處於離線狀態',
'app.pwa.serviceworker.updated': '有新內容',
'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
'app.pwa.serviceworker.updated.ok': '刷新',
};

13
src/requestErrorConfig.ts

@ -1,5 +1,6 @@
import type { RequestOptions } from '@@/plugin-request/request';
import type { RequestConfig } from '@umijs/max';
import { getIntl } from '@umijs/max';
import { message, notification } from 'antd';
// 错误处理方案: 错误类型
@ -73,13 +74,17 @@ export const errorConfig: RequestConfig = {
// Axios 的错误
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
message.error(`Response status:${error.response.status}`);
} else if (typeof navigator !== 'undefined' && !navigator.onLine) {
message.error(
getIntl().formatMessage({
id: 'app.request.offline',
defaultMessage:
'Network unavailable. Please check your connection and try again.',
}),
);
} else if (error.request) {
// 请求已经成功发起,但没有收到响应
// \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
// 而在node.js中是 http.ClientRequest 的实例
message.error('None response! Please retry.');
} else {
// 发送请求时出了点问题
message.error('Request error, please retry.');
}
},

Loading…
Cancel
Save