refactor: migrate account pages to client i18n - #7515
Conversation
Coverage Report
File CoverageNo changed files found. |
|
✅ Admin Preview Image Ready! 🕒 Time: 2026-08-14 10:38:47 (UTC+8) |
1139d32 to
4ab0d50
Compare
There was a problem hiding this comment.
Pull request overview
This PR migrates the account and pricing experiences toward client-only rendering with on-demand, cached i18n namespace loading, centralizing the client i18n boot flow in @fastgpt/web and removing the legacy promotion entrypoints.
Changes:
- Add generated dynamic JSON resource loaders + Vitest coverage in
packages/web/i18n, with repo scripts to generate/check the generated file. - Route selected account/price pages through a client-only app shell and replace many
next-i18nextusages withuseClientTranslation. - Remove the account promotion page/config and adjust i18n namespace naming (
apikey,discount_coupon,price).
Reviewed changes
Copilot reviewed 145 out of 145 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/generate-i18n-resource-loaders.mjs | Generates typed i18n loader map |
| scripts/check-i18n-resource-loaders.mjs | CI-friendly loader up-to-date check |
| projects/marketplace/src/web/common/i18n/utils.ts | Drops SSR deviceSize from translations props |
| projects/app/test/web/support/user/useUserStore.test.ts | Reorders user fixture fields |
| projects/app/src/web/support/activity/promotion/api.ts | Removes promotion API client |
| projects/app/src/web/context/ClientOnlyAppShell.tsx | Client-only shell with i18n gates |
| projects/app/src/web/context/ClientBootLoading.tsx | Client boot loading UI |
| projects/app/src/web/context/AppShell.tsx | Extracts app shell from _app |
| projects/app/src/web/common/i18n/utils.ts | Drops SSR deviceSize from translations props |
| projects/app/src/pages/price/index.tsx | Client-only i18n migration for price page |
| projects/app/src/pages/app/detail/index.tsx | Updates SSR namespaces list |
| projects/app/src/pages/account/usage/index.tsx | Removes SSR props; client i18n hook |
| projects/app/src/pages/account/thirdParty/index.tsx | Removes SSR props; client i18n hook |
| projects/app/src/pages/account/team/index.tsx | Removes SSR props; client i18n hook |
| projects/app/src/pages/account/setting.tsx | Removes SSR props; client i18n hook |
| projects/app/src/pages/account/promotion.tsx | Deletes promotion page |
| projects/app/src/pages/account/model/index.tsx | Removes SSR props; client i18n hook |
| projects/app/src/pages/account/inform.tsx | Removes SSR props; client i18n hook |
| projects/app/src/pages/account/info/index.tsx | Removes SSR props; client i18n hook |
| projects/app/src/pages/account/customDomain/index.tsx | Removes SSR props; client i18n hook |
| projects/app/src/pages/account/bill/index.tsx | Removes SSR props; client i18n hook |
| projects/app/src/pages/account/apikey.tsx | Removes SSR props; relies on table i18n |
| projects/app/src/pages/_app.tsx | Route-split client-only shell + client i18n config |
| projects/app/src/pageComponents/price/utils.ts | Move price strings into price ns |
| projects/app/src/pageComponents/price/Standard.tsx | Move price strings into price ns |
| projects/app/src/pageComponents/price/Points.tsx | Move price strings into price ns |
| projects/app/src/pageComponents/price/FAQ.tsx | Move price strings into price ns |
| projects/app/src/pageComponents/price/ExtraPlan.tsx | Move price strings into price ns |
| projects/app/src/pageComponents/app/detail/Publish/index.tsx | Disable SSR for API tab module |
| projects/app/src/pageComponents/account/usage/UsageTable.tsx | Use client i18n; swap recharge modal wrapper |
| projects/app/src/pageComponents/account/usage/UsageRechargeModal.tsx | Lazy wrapper around recharge modal |
| projects/app/src/pageComponents/account/usage/UsageDetail.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/usage/DashboardChart.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/usage/Dashboard.tsx | Use client i18n hook + recharge wrapper |
| projects/app/src/pageComponents/account/thirdParty/WorkflowVariableModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/thirdParty/OpenAIAccountModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/TeamSelector.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/TransferOwnershipModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/PermissionManage/index.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/OrgManage/OrgInfoModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/OrgManage/index.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/MemberTable.tsx | Use client i18n hook; modal sizing |
| projects/app/src/pageComponents/account/team/Invite/InviteModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/Invite/HandleInviteModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/Invite/CreateInvitationModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/GroupManage/index.tsx | Use client i18n hook; formatting tweak |
| projects/app/src/pageComponents/account/team/GroupManage/GroupTransferOwnerModal.tsx | Use client i18n hook; simplify pagination hook |
| projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/EnterpriseAuth/useEnterpriseAuthFormFlow.ts | Use client i18n hook |
| projects/app/src/pageComponents/account/team/EnterpriseAuth/index.tsx | Use client i18n hook; type TFunction |
| projects/app/src/pageComponents/account/team/EnterpriseAuth/ContactBusinessModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/EditInfoModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/team/context.tsx | Use client i18n hook; trim imports |
| projects/app/src/pageComponents/account/team/Audit/index.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/NotificationDetailsModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/model/ModelDashboard/index.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/model/ModelDashboard/DataTableComponent.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/model/ModelConfigTable.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/model/Log/index.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/model/Channel/ModelTest.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/model/Channel/index.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/model/Channel/EditChannelModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/model/AddModelBox.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/info/UpdatePswModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/info/standardDetailModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/info/RedeemCouponModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/info/DiscountCouponsModal.tsx | Use client i18n hook; move keys to new ns |
| projects/app/src/pageComponents/account/info/ConversionModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/customDomain/domainVerifyModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/customDomain/createModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/bill/InvoiceTable.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/bill/InvoiceHeaderForm.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/bill/BillTable.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/bill/BillDetailModal.tsx | Use client i18n hook; new coupon keys |
| projects/app/src/pageComponents/account/bill/ApplyInvoiceModal.tsx | Use client i18n hook |
| projects/app/src/pageComponents/account/AccountContainer.tsx | Remove promotion tab + use client i18n |
| projects/app/src/global/support/api/userRes.ts | Remove unused import |
| projects/app/src/components/support/wallet/StandardPlanContentList.tsx | Use client i18n hook; minor formatting |
| projects/app/src/components/support/wallet/QRCodePayModal.tsx | Use client i18n hook; cast dynamic keys |
| projects/app/src/components/support/wallet/NotSufficientModal/index.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/ResumeInheritText/index.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/Radio/index.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/PermissionTags/index.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/MemberManager/RoleTags.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/MemberManager/RoleSelect.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/MemberManager/MemberModal.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/MemberManager/context.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/IconText/index.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/DefaultPerList/index.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/ConfigPerModal/index.tsx | Use client i18n hook |
| projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx | Use client i18n hook |
| projects/app/src/components/support/apikey/TagMultiSelect.tsx | Move keys to apikey ns |
| projects/app/src/components/support/apikey/TagManageModal.tsx | Move keys to apikey ns |
| projects/app/src/components/support/apikey/Table.tsx | Move keys to apikey ns |
| projects/app/src/components/Layout/navbarPhone.tsx | Removes promotion nav route |
| projects/app/src/components/Layout/navbar.tsx | Removes promotion nav route |
| projects/app/src/components/core/ai/ModelTable/index.tsx | Use client i18n hook |
| projects/app/src/components/CommunityModal/index.tsx | Use client i18n hook |
| packages/web/vitest.config.ts | Adds Vitest config for @fastgpt/web |
| packages/web/test/i18n/resourceLoaders.test.ts | Tests generated loaders + status tracking |
| packages/web/test/i18n/dynamicImportBackend.test.ts | Tests retry + typed errors |
| packages/web/package.json | Adds @fastgpt/web test script |
| packages/web/i18n/zh-Hant/price.json | Adds price namespace translations |
| packages/web/i18n/zh-Hant/discount_coupon.json | Adds discount_coupon translations |
| packages/web/i18n/zh-Hant/apikey.json | Adds/updates apikey translations |
| packages/web/i18n/zh-Hant/account.json | Removes promotion label |
| packages/web/i18n/zh-Hant/account_usage.json | Adds new usage keys |
| packages/web/i18n/zh-Hant/account_team.json | Adds new audit keys |
| packages/web/i18n/zh-Hant/account_promotion.json | Removes promotion namespace file |
| packages/web/i18n/zh-Hant/account_info.json | Moves coupon-related keys out |
| packages/web/i18n/zh-CN/price.json | Adds price namespace translations |
| packages/web/i18n/zh-CN/discount_coupon.json | Adds discount_coupon translations |
| packages/web/i18n/zh-CN/apikey.json | Adds/updates apikey translations |
| packages/web/i18n/zh-CN/account.json | Removes promotion label |
| packages/web/i18n/zh-CN/account_promotion.json | Removes promotion namespace file |
| packages/web/i18n/zh-CN/account_info.json | Moves coupon-related keys out |
| packages/web/i18n/useClientTranslation.ts | New client i18n translation hook |
| packages/web/i18n/resourceLoaders.ts | Resource loader status + error tracking |
| packages/web/i18n/resourceLoaders.generated.ts | Generated loader map |
| packages/web/i18n/i18next.ts | Updates namespace typings + adds new namespaces |
| packages/web/i18n/en/price.json | Adds price namespace translations |
| packages/web/i18n/en/discount_coupon.json | Adds discount_coupon translations |
| packages/web/i18n/en/apikey.json | Adds/updates apikey translations |
| packages/web/i18n/en/account.json | Removes promotion label |
| packages/web/i18n/en/account_promotion.json | Removes promotion namespace file |
| packages/web/i18n/en/account_info.json | Moves coupon-related keys out |
| packages/web/i18n/dynamicImportBackend.ts | Backend reading via dynamic imports + retry |
| packages/web/i18n/constants.ts | Updates namespace list |
| packages/web/i18n/ClientI18nLoadError.ts | Typed error for client i18n failures |
| packages/web/i18n/ClientI18nGate.tsx | Gate for initial language/common load |
| packages/web/i18n/ClientI18nErrorFallback.tsx | Minimal fallback UI for i18n load failures |
| packages/web/i18n/ClientI18nBoundary.tsx | Suspense + error boundary wrapper |
| packages/web/i18n/clientConfig.ts | next-i18next config for client backend |
| packages/web/hooks/useSafeTranslation.ts | Refactors safe translation wrapper |
| packages/web/context/useSystem.tsx | Centralizes system size readiness |
| packages/service/support/activity/promotion/schema.ts | Removes outdated comment |
| packages/global/support/activity/type.ts | Trims inline comments |
| packages/global/common/system/types/index.ts | Removes promotion FE config flag |
| package.json | Adds i18n loader scripts + web tests |
| document/middleware.ts | Adjusts matcher for favicon path variants |
| document/app/[lang]/layout.tsx | notFound() for unsupported doc locales |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const ns = key.split(':')[0]; | ||
| if (!I18N_NAMESPACES_MAP[ns as any]) { | ||
| return key; | ||
| } | ||
|
|
||
| // @ts-ignore | ||
| return originalT(key, ...args); |
| const [isPc] = useMediaQuery('(min-width: 900px)', { fallback: true }); | ||
| const [isSystemSizeReady, setIsSystemSizeReady] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| setSize(isPc ? 'pc' : 'mobile'); | ||
| setIsSystemSizeReady(true); |
| /** | ||
| * 在使用记录页打开充值弹窗时才加载套餐文案,避免首屏提前请求 user namespace。 | ||
| */ | ||
| const UsageRechargeModalContent = (props: UsageRechargeModalProps) => { | ||
| useClientTranslation(); | ||
|
|
||
| return <RechargeModal {...props} />; | ||
| }; |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1139d32d46
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (this.state.error === undefined) return this.props.children; | ||
| if (!isClientI18nLoadError(this.state.error)) throw this.state.error; | ||
|
|
||
| return <ClientI18nErrorFallback language={this.props.language} error={this.state.error} />; |
There was a problem hiding this comment.
Preserve the previous language when a switch fails
When a user switches languages and any mounted namespace chunk fails to load, useI18nLng.onChangeLng has already persisted the target locale before awaiting changeLanguage; this boundary then replaces the page with a fallback whose only action reloads the same now-persisted failing locale. A transient chunk failure can therefore trap the user on the error screen instead of retaining the working language. Preload the target resources before persisting/switching, or roll back the i18n language and stored locale when this error is caught.
Useful? React with 👍 / 👎.
|
✅ Build Successful - Preview fastgpt Image for this PR: 🕒 Time: 2026-08-14 10:41:42 (UTC+8) |
Summary
Migrate the account and
/priceflows to a stable client-side i18n architecture, while removing the obsolete promotion feature and making language persistence and request propagation resilient when browser storage is restricted.What changed
/path.localStorage, then in-memory state when browser storage is unavailable or blocked.localStorage → memory → navigator.language.x-fastgpt-languageheader for requests when the preference is not available through a Cookie.Cookie → x-fastgpt-language → en.prosubmodule to the promotion-cleanup commit.Why
The account and pricing routes previously depended on mixed SSR/client translation behavior and local fallbacks. That made route transitions, language switching, and restricted-cookie or restricted-storage environments prone to stale or incomplete translations. The new flow centralizes resource readiness and language state, propagates the effective client language to the backend, and keeps the server fallback deterministic.
Verification
pnpm --filter=@fastgpt/web test— 5 files, 29 tests passed.pnpm --filter=@fastgpt/app exec vitest run -c vitest.config.ts test/web/common/api/request.test.ts— 26 tests passed.pnpm --filter=@fastgpt/service exec vitest run -c vitest.config.ts test/common/middle/i18n.test.ts— 4 tests passed.pnpm --filter=@fastgpt/app typecheckpassed.git diff --checkpassed for the changed files.CI notes
The PR has separate Web test coverage jobs in the GitHub and Forgejo workflows. At the time of this update, the existing PR checks were still running for the test and build pipelines;
detect-changes,detect_changes, andtest-webhad already passed.