From a4373d8b7b0f65807c49569da31f3bbe4febbd01 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Thu, 5 Mar 2026 10:45:16 +0800 Subject: [PATCH] chore: i18n hmr support, fix hmr for app context (#32997) --- web/app/(commonLayout)/layout.tsx | 2 +- web/app/account/(commonLayout)/layout.tsx | 2 +- web/app/account/oauth/authorize/layout.tsx | 4 +- .../model-provider-page/index.tsx | 10 +- ...p-context.tsx => app-context-provider.tsx} | 81 ++--------- web/context/app-context.ts | 73 ++++++++++ web/eslint-suppressions.json | 42 ------ web/package.json | 3 +- web/pnpm-lock.yaml | 132 +++++++++++++++--- web/vite.config.ts | 90 ++++++++++++ 10 files changed, 291 insertions(+), 148 deletions(-) rename web/context/{app-context.tsx => app-context-provider.tsx} (74%) create mode 100644 web/context/app-context.ts diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index abd5dd96fd..bd067fde6a 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -8,7 +8,7 @@ import GotoAnything from '@/app/components/goto-anything' import Header from '@/app/components/header' import HeaderWrapper from '@/app/components/header/header-wrapper' import ReadmePanel from '@/app/components/plugins/readme-panel' -import { AppContextProvider } from '@/context/app-context' +import { AppContextProvider } from '@/context/app-context-provider' import { EventEmitterContextProvider } from '@/context/event-emitter' import { ModalContextProvider } from '@/context/modal-context' import { ProviderContextProvider } from '@/context/provider-context' diff --git a/web/app/account/(commonLayout)/layout.tsx b/web/app/account/(commonLayout)/layout.tsx index e4125015d9..47fb47b02b 100644 --- a/web/app/account/(commonLayout)/layout.tsx +++ b/web/app/account/(commonLayout)/layout.tsx @@ -4,7 +4,7 @@ import { AppInitializer } from '@/app/components/app-initializer' import AmplitudeProvider from '@/app/components/base/amplitude' import GA, { GaType } from '@/app/components/base/ga' import HeaderWrapper from '@/app/components/header/header-wrapper' -import { AppContextProvider } from '@/context/app-context' +import { AppContextProvider } from '@/context/app-context-provider' import { EventEmitterContextProvider } from '@/context/event-emitter' import { ModalContextProvider } from '@/context/modal-context' import { ProviderContextProvider } from '@/context/provider-context' diff --git a/web/app/account/oauth/authorize/layout.tsx b/web/app/account/oauth/authorize/layout.tsx index 189971b16f..7f6b270b45 100644 --- a/web/app/account/oauth/authorize/layout.tsx +++ b/web/app/account/oauth/authorize/layout.tsx @@ -2,7 +2,7 @@ import Loading from '@/app/components/base/loading' import Header from '@/app/signin/_header' -import { AppContextProvider } from '@/context/app-context' +import { AppContextProvider } from '@/context/app-context-provider' import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' import { useIsLogin } from '@/service/use-common' @@ -38,7 +38,7 @@ export default function SignInLayout({ children }: any) { {systemFeatures.branding.enabled === false && ( -
+
© {' '} {new Date().getFullYear()} diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx index 7606bbc04f..4fb1b770e1 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/index.tsx @@ -99,7 +99,7 @@ const ModelProviderPage = ({ searchText }: Props) => { return (
-
{t('modelProvider.models', { ns: 'common' })}
+
{t('modelProvider.models', { ns: 'common' })}
{ > {defaultModelNotConfigured &&
} {defaultModelNotConfigured && ( -
+
{t('modelProvider.notConfigured', { ns: 'common' })}
@@ -129,8 +129,8 @@ const ModelProviderPage = ({ searchText }: Props) => {
-
{t('modelProvider.emptyProviderTitle', { ns: 'common' })}
-
{t('modelProvider.emptyProviderTip', { ns: 'common' })}
+
{t('modelProvider.emptyProviderTitle', { ns: 'common' })}
+
{t('modelProvider.emptyProviderTip', { ns: 'common' })}
)} {!!filteredConfiguredProviders?.length && ( @@ -145,7 +145,7 @@ const ModelProviderPage = ({ searchText }: Props) => { )} {!!filteredNotConfiguredProviders?.length && ( <> -
{t('modelProvider.toBeConfigured', { ns: 'common' })}
+
{t('modelProvider.toBeConfigured', { ns: 'common' })}
{filteredNotConfiguredProviders?.map(provider => ( ({ - userProfile: userProfilePlaceholder, - currentWorkspace: initialWorkspaceInfo, - isCurrentWorkspaceManager: false, - isCurrentWorkspaceOwner: false, - isCurrentWorkspaceEditor: false, - isCurrentWorkspaceDatasetOperator: false, - mutateUserProfile: noop, - mutateCurrentWorkspace: noop, - langGeniusVersionInfo: initialLangGeniusVersionInfo, - useSelector, - isLoadingCurrentWorkspace: false, - isValidatingCurrentWorkspace: false, -}) - -export function useSelector(selector: (value: AppContextValue) => T): T { - return useContextSelector(AppContext, selector) -} - export type AppContextProviderProps = { children: ReactNode } @@ -170,7 +109,7 @@ export const AppContextProvider: FC = ({ children }) => // Report user and workspace info to Amplitude when loaded if (userProfile?.id) { setUserId(userProfile.email) - const properties: Record = { + const properties: Record = { email: userProfile.email, name: userProfile.name, has_password: userProfile.is_password_set, @@ -213,7 +152,3 @@ export const AppContextProvider: FC = ({ children }) => ) } - -export const useAppContext = () => useContext(AppContext) - -export default AppContext diff --git a/web/context/app-context.ts b/web/context/app-context.ts new file mode 100644 index 0000000000..298e213e7d --- /dev/null +++ b/web/context/app-context.ts @@ -0,0 +1,73 @@ +'use client' + +import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' +import { noop } from 'es-toolkit/function' +import { createContext, useContext, useContextSelector } from 'use-context-selector' + +export type AppContextValue = { + userProfile: UserProfileResponse + mutateUserProfile: VoidFunction + currentWorkspace: ICurrentWorkspace + isCurrentWorkspaceManager: boolean + isCurrentWorkspaceOwner: boolean + isCurrentWorkspaceEditor: boolean + isCurrentWorkspaceDatasetOperator: boolean + mutateCurrentWorkspace: VoidFunction + langGeniusVersionInfo: LangGeniusVersionResponse + useSelector: typeof useSelector + isLoadingCurrentWorkspace: boolean + isValidatingCurrentWorkspace: boolean +} + +export const userProfilePlaceholder = { + id: '', + name: '', + email: '', + avatar: '', + avatar_url: '', + is_password_set: false, +} + +export const initialLangGeniusVersionInfo = { + current_env: '', + current_version: '', + latest_version: '', + release_date: '', + release_notes: '', + version: '', + can_auto_update: false, +} + +export const initialWorkspaceInfo: ICurrentWorkspace = { + id: '', + name: '', + plan: '', + status: '', + created_at: 0, + role: 'normal', + providers: [], + trial_credits: 200, + trial_credits_used: 0, + next_credit_reset_date: 0, +} + +export const AppContext = createContext({ + userProfile: userProfilePlaceholder, + currentWorkspace: initialWorkspaceInfo, + isCurrentWorkspaceManager: false, + isCurrentWorkspaceOwner: false, + isCurrentWorkspaceEditor: false, + isCurrentWorkspaceDatasetOperator: false, + mutateUserProfile: noop, + mutateCurrentWorkspace: noop, + langGeniusVersionInfo: initialLangGeniusVersionInfo, + useSelector, + isLoadingCurrentWorkspace: false, + isValidatingCurrentWorkspace: false, +}) + +export function useSelector(selector: (value: AppContextValue) => T): T { + return useContextSelector(AppContext, selector) +} + +export const useAppContext = () => useContext(AppContext) diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 00c817eac9..0880084320 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -301,9 +301,6 @@ } }, "app/account/oauth/authorize/layout.tsx": { - "tailwindcss/enforce-consistent-class-order": { - "count": 1 - }, "ts/no-explicit-any": { "count": 1 } @@ -4697,11 +4694,6 @@ "count": 3 } }, - "app/components/header/account-setting/model-provider-page/index.tsx": { - "tailwindcss/enforce-consistent-class-order": { - "count": 5 - } - }, "app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 3 @@ -6472,11 +6464,6 @@ "count": 2 } }, - "app/components/workflow/__tests__/trigger-status-sync.test.tsx": { - "ts/no-explicit-any": { - "count": 2 - } - }, "app/components/workflow/block-selector/all-start-blocks.tsx": { "react-hooks-extra/no-direct-set-state-in-use-effect": { "count": 1 @@ -8488,11 +8475,6 @@ "count": 5 } }, - "app/components/workflow/nodes/tool/__tests__/output-schema-utils.test.ts": { - "ts/no-explicit-any": { - "count": 1 - } - }, "app/components/workflow/nodes/tool/components/copy-id.tsx": { "no-restricted-imports": { "count": 1 @@ -8617,11 +8599,6 @@ "count": 1 } }, - "app/components/workflow/nodes/trigger-plugin/utils/__tests__/form-helpers.test.ts": { - "ts/no-explicit-any": { - "count": 2 - } - }, "app/components/workflow/nodes/trigger-plugin/utils/form-helpers.ts": { "ts/no-explicit-any": { "count": 7 @@ -9594,14 +9571,6 @@ "count": 5 } }, - "context/app-context.tsx": { - "react-refresh/only-export-components": { - "count": 2 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, "context/datasets-context.tsx": { "react-refresh/only-export-components": { "count": 1 @@ -9762,17 +9731,6 @@ "count": 1 } }, - "lib/utils.ts": { - "import/consistent-type-specifier-style": { - "count": 1 - }, - "perfectionist/sort-named-imports": { - "count": 1 - }, - "style/quotes": { - "count": 2 - } - }, "models/common.ts": { "ts/no-explicit-any": { "count": 3 diff --git a/web/package.json b/web/package.json index 5527dcaa37..0633a499b4 100644 --- a/web/package.json +++ b/web/package.json @@ -243,8 +243,9 @@ "tsx": "4.21.0", "typescript": "5.9.3", "uglify-js": "3.19.3", - "vinext": "https://pkg.pr.new/hyoban/vinext@a30ba79", + "vinext": "https://pkg.pr.new/hyoban/vinext@556a6d6", "vite": "8.0.0-beta.16", + "vite-plugin-inspect": "11.3.3", "vite-tsconfig-paths": "6.1.1", "vitest": "4.0.18", "vitest-canvas-mock": "1.1.3" diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 1c5347dd2e..3620dbaae2 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -596,11 +596,14 @@ importers: specifier: 3.19.3 version: 3.19.3 vinext: - specifier: https://pkg.pr.new/hyoban/vinext@a30ba79 - version: https://pkg.pr.new/hyoban/vinext@a30ba79(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.93.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.104.1(esbuild@0.27.2)(uglify-js@3.19.3)) + specifier: https://pkg.pr.new/hyoban/vinext@556a6d6 + version: https://pkg.pr.new/hyoban/vinext@556a6d6(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.93.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.104.1(esbuild@0.27.2)(uglify-js@3.19.3)) vite: specifier: 8.0.0-beta.16 version: 8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite-plugin-inspect: + specifier: 11.3.3 + version: 11.3.3(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) vite-tsconfig-paths: specifier: 6.1.1 version: 6.1.1(typescript@5.9.3)(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) @@ -2175,20 +2178,13 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} - '@pivanov/utils@0.0.2': - resolution: {integrity: sha512-q9CN0bFWxWgMY5hVVYyBgez1jGiLBa6I+LkG37ycylPhFvEGOOeaADGtUSu46CaZasPnlY8fCdVJZmrgKb1EPA==} - peerDependencies: - react: '>=18' - react-dom: '>=18' - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@preact/signals-core@1.12.2': resolution: {integrity: sha512-5Yf8h1Ke3SMHr15xl630KtwPTW4sYDFkkxS0vQ8UiQLWwZQnrF9IKaVG1mN5VcJz52EcWs2acsc/Npjha/7ysA==} @@ -3874,6 +3870,9 @@ packages: birecord@0.1.1: resolution: {integrity: sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==} + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -4580,6 +4579,9 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -6109,6 +6111,10 @@ packages: moo-color@1.0.3: resolution: {integrity: sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -6231,6 +6237,9 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -6354,6 +6363,9 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + periscopic@4.0.2: resolution: {integrity: sha512-sqpQDUy8vgB7ycLkendSKS6HnVz1Rneoc3Rc+ZBUCe2pbqlVuCC5vF52l0NJ1aiMg/r1qfYF9/myz8CZeI2rjA==} @@ -6982,6 +6994,10 @@ packages: simple-swizzle@0.2.4: resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -7289,6 +7305,10 @@ packages: resolution: {integrity: sha512-A5F0cM6+mDleacLIEUkmfpkBbnHJFV1d2rprHU2MXNk7mlxHq2zGojA+SRvQD1RoMo9gqjZPWEaKG4v1BQ48lw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tough-cookie@6.0.0: resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} @@ -7436,6 +7456,10 @@ packages: unpic@4.2.2: resolution: {integrity: sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==} + unplugin-utils@0.3.1: + resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} + engines: {node: '>=20.19.0'} + unplugin@2.1.0: resolution: {integrity: sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ==} engines: {node: '>=18.12.0'} @@ -7542,8 +7566,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vinext@https://pkg.pr.new/hyoban/vinext@a30ba79: - resolution: {integrity: sha512-yx/gCneOli5eGTrLUq6/M7A6DGQs14qOJW/Xp9RN6sTI0mErKyWWjO5E7FZT98BJbqH5xzI5nk8EOCLF3bojkA==, tarball: https://pkg.pr.new/hyoban/vinext@a30ba79} + vinext@https://pkg.pr.new/hyoban/vinext@556a6d6: + resolution: {integrity: sha512-Sz8RkTDsY6cnGrevlQi4nXgahu8okEGsdKY5m31d/L9tXo35bNETMHfVee5gaI2UKZS9LMcffWaTOxxINUgogQ==, tarball: https://pkg.pr.new/hyoban/vinext@556a6d6} version: 0.0.5 engines: {node: '>=22'} hasBin: true @@ -7552,12 +7576,32 @@ packages: react-dom: '>=19.2.0' vite: ^7.0.0 + vite-dev-rpc@1.1.0: + resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 + + vite-hot-client@2.1.0: + resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + vite-plugin-commonjs@0.10.4: resolution: {integrity: sha512-eWQuvQKCcx0QYB5e5xfxBNjQKyrjEWZIR9UOkOV6JAgxVhtbZvCOF+FNC2ZijBJ3U3Px04ZMMyyMyFBVWIJ5+g==} vite-plugin-dynamic-import@1.6.0: resolution: {integrity: sha512-TM0sz70wfzTIo9YCxVFwS8OA9lNREsh+0vMHGSkWDTZ7bgd1Yjs5RV8EgB634l/91IsXJReg0xtmuQqP0mf+rg==} + vite-plugin-inspect@11.3.3: + resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': '*' + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + vite-plugin-storybook-nextjs@3.2.2: resolution: {integrity: sha512-ZJXCrhi9mW4jEJTKhJ5sUtpBe84mylU40me2aMuLSgIJo4gE/Rc559hZvMYLFTWta1gX7Rm8Co5EEHakPct+wA==} peerDependencies: @@ -9707,16 +9751,10 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.6 optional: true - '@pivanov/utils@0.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - '@pkgjs/parseargs@0.11.0': - optional: true - '@pkgr/core@0.2.9': {} + '@polka/url@1.0.0-next.29': {} + '@preact/signals-core@1.12.2': {} '@preact/signals@1.3.2(preact@10.28.2)': @@ -11533,6 +11571,8 @@ snapshots: birecord@0.1.1: {} + birpc@2.9.0: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -12237,6 +12277,8 @@ snapshots: environment@1.1.0: {} + error-stack-parser-es@1.0.5: {} + es-module-lexer@1.7.0: {} es-module-lexer@2.0.0: {} @@ -14300,6 +14342,8 @@ snapshots: dependencies: color-name: 1.1.4 + mrmime@2.0.1: {} + ms@2.1.3: {} mz@2.7.0: @@ -14396,6 +14440,8 @@ snapshots: obug@2.1.1: {} + ohash@2.0.11: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -14549,6 +14595,8 @@ snapshots: pend@1.2.0: {} + perfect-debounce@2.1.0: {} + periscopic@4.0.2: dependencies: '@types/estree': 1.0.8 @@ -15381,6 +15429,12 @@ snapshots: dependencies: is-arrayish: 0.3.4 + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sisteransi@1.0.5: {} size-sensor@1.0.3: {} @@ -15696,6 +15750,8 @@ snapshots: dependencies: eslint-visitor-keys: 5.0.0 + totalist@3.0.1: {} + tough-cookie@6.0.0: dependencies: tldts: 7.0.17 @@ -15840,6 +15896,11 @@ snapshots: unpic@4.2.2: {} + unplugin-utils@0.3.1: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + unplugin@2.1.0: dependencies: acorn: 8.16.0 @@ -15933,7 +15994,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinext@https://pkg.pr.new/hyoban/vinext@a30ba79(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.93.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.104.1(esbuild@0.27.2)(uglify-js@3.19.3)): + vinext@https://pkg.pr.new/hyoban/vinext@556a6d6(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.93.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.104.1(esbuild@0.27.2)(uglify-js@3.19.3)): dependencies: '@unpic/react': 1.0.2(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.93.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@vercel/og': 0.8.6 @@ -15952,6 +16013,16 @@ snapshots: - typescript - webpack + vite-dev-rpc@1.1.0(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + dependencies: + birpc: 2.9.0 + vite: 8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite-hot-client: 2.1.0(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + + vite-hot-client@2.1.0(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + dependencies: + vite: 8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite-plugin-commonjs@0.10.4: dependencies: acorn: 8.16.0 @@ -15965,6 +16036,21 @@ snapshots: fast-glob: 3.3.3 magic-string: 0.30.21 + vite-plugin-inspect@11.3.3(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + dependencies: + ansis: 4.2.0 + debug: 4.4.3 + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.2.0 + perfect-debounce: 2.1.0 + sirv: 3.0.2 + unplugin-utils: 0.3.1 + vite: 8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite-dev-rpc: 1.1.0(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + transitivePeerDependencies: + - supports-color + vite-plugin-storybook-nextjs@3.2.2(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.93.2))(storybook@10.2.13(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0-beta.16(@types/node@24.10.12)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.93.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@next/env': 16.0.0 diff --git a/web/vite.config.ts b/web/vite.config.ts index 152df913f8..b07e7ea7be 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -6,6 +6,7 @@ import react from '@vitejs/plugin-react' import { codeInspectorPlugin } from 'code-inspector-plugin' import vinext from 'vinext' import { defineConfig } from 'vite' +import Inspect from 'vite-plugin-inspect' import tsconfigPaths from 'vite-tsconfig-paths' const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -70,6 +71,93 @@ const createForceInspectorClientInjectionPlugin = (): Plugin => { } } +function customI18nHmrPlugin(): Plugin { + const injectTarget = inspectorInjectTarget + const i18nHmrClientMarker = 'custom-i18n-hmr-client' + const i18nHmrClientSnippet = `/* ${i18nHmrClientMarker} */ +if (import.meta.hot) { + const getI18nUpdateTarget = (file) => { + const match = file.match(/[/\\\\]i18n[/\\\\]([^/\\\\]+)[/\\\\]([^/\\\\]+)\\.json$/) + if (!match) + return null + const [, locale, namespaceFile] = match + return { locale, namespaceFile } + } + + import.meta.hot.on('i18n-update', async ({ file, content }) => { + const target = getI18nUpdateTarget(file) + if (!target) + return + + const [{ getI18n }, { camelCase }] = await Promise.all([ + import('react-i18next'), + import('es-toolkit/string'), + ]) + + const i18n = getI18n() + if (!i18n) + return + if (target.locale !== i18n.language) + return + + let resources + try { + resources = JSON.parse(content) + } + catch { + return + } + + const namespace = camelCase(target.namespaceFile) + i18n.addResourceBundle(target.locale, namespace, resources, true, true) + i18n.emit('languageChanged', i18n.language) + }) +} +` + + const injectI18nHmrClient = (code: string) => { + if (code.includes(i18nHmrClientMarker)) + return code + + const useClientMatch = code.match(/(['"])use client\1;?\s*\n/) + if (!useClientMatch) + return `${i18nHmrClientSnippet}\n${code}` + + const insertAt = (useClientMatch.index ?? 0) + useClientMatch[0].length + return `${code.slice(0, insertAt)}\n${i18nHmrClientSnippet}\n${code.slice(insertAt)}` + } + + return { + name: 'custom-i18n-hmr', + apply: 'serve', + handleHotUpdate({ file, server }) { + if (file.endsWith('.json') && file.includes('/i18n/')) { + server.ws.send({ + type: 'custom', + event: 'i18n-update', + data: { + file, + content: fs.readFileSync(file, 'utf-8'), + }, + }) + + // return empty array to prevent the default HMR + return [] + } + }, + transform(code, id) { + const cleanId = normalizeInspectorModuleId(id) + if (cleanId !== injectTarget) + return null + + const nextCode = injectI18nHmrClient(code) + if (nextCode === code) + return null + return { code: nextCode, map: null } + }, + } +} + export default defineConfig(({ mode }) => { const isTest = mode === 'test' @@ -89,10 +177,12 @@ export default defineConfig(({ mode }) => { } as Plugin, ] : [ + Inspect(), createCodeInspectorPlugin(), createForceInspectorClientInjectionPlugin(), react(), vinext(), + customI18nHmrPlugin(), ], resolve: { alias: {