diff --git a/packages/runtime/plugin-i18n/src/runtime/context.tsx b/packages/runtime/plugin-i18n/src/runtime/context.tsx index 185ecb93923e..185d523fe201 100644 --- a/packages/runtime/plugin-i18n/src/runtime/context.tsx +++ b/packages/runtime/plugin-i18n/src/runtime/context.tsx @@ -138,11 +138,6 @@ export const useModernI18n = ( // Update i18n instance await i18nInstance.changeLanguage(newLang); - // Update context language state - if (updateLanguage) { - updateLanguage(newLang); - } - // Update URL if locale detection is enabled, we're in browser, and router is available if ( enableLocaleDetection && @@ -174,6 +169,8 @@ export const useModernI18n = ( // Use history API to navigate without page reload window.history.pushState(null, '', newUrl); + } else if (updateLanguage) { + updateLanguage(newLang); } } catch (error) { console.error('Failed to change language:', error); diff --git a/packages/runtime/plugin-i18n/src/runtime/utils.ts b/packages/runtime/plugin-i18n/src/runtime/utils.ts index 84738c5ebff5..eeea0273867b 100644 --- a/packages/runtime/plugin-i18n/src/runtime/utils.ts +++ b/packages/runtime/plugin-i18n/src/runtime/utils.ts @@ -1,7 +1,12 @@ +import { getGlobalBasename } from '@modern-js/runtime/context'; import { MAIN_ENTRY_NAME } from '@modern-js/utils/universal/constants'; -export const getEntryPath = (entryName: string = MAIN_ENTRY_NAME) => { - return entryName === MAIN_ENTRY_NAME ? '' : `/${entryName}`; +export const getEntryPath = (entryName?: string): string => { + const basename = getGlobalBasename(); + if (basename) { + return basename === '/' ? '' : basename; + } + return ''; }; /** * Helper function to get language from current pathname diff --git a/packages/runtime/plugin-runtime/src/cli/code.ts b/packages/runtime/plugin-runtime/src/cli/code.ts index 50321af82ab0..328cf080beaf 100644 --- a/packages/runtime/plugin-runtime/src/cli/code.ts +++ b/packages/runtime/plugin-runtime/src/cli/code.ts @@ -188,6 +188,8 @@ export const generateCode = async ( // runtime-global-context.js let contextCode = ''; if (!config.server.rsc || entrypoint.nestedRoutesEntry) { + const route = serverRoutes.find(r => r.entryName === entryName); + const basename = route?.urlPath || '/'; contextCode = template.runtimeGlobalContext({ entryName, srcDirectory, @@ -195,6 +197,7 @@ export const generateCode = async ( metaName, entry, customEntry, + basename, }); } else { const AppProxyPath = path.join( diff --git a/packages/runtime/plugin-runtime/src/cli/template.ts b/packages/runtime/plugin-runtime/src/cli/template.ts index 21bd8cd43f55..49a8437163ad 100644 --- a/packages/runtime/plugin-runtime/src/cli/template.ts +++ b/packages/runtime/plugin-runtime/src/cli/template.ts @@ -267,6 +267,7 @@ export const runtimeGlobalContext = ({ metaName, entry, customEntry, + basename, }: { entryName: string; srcDirectory: string; @@ -274,6 +275,7 @@ export const runtimeGlobalContext = ({ metaName: string; entry: string; customEntry?: boolean; + basename?: string; }) => { return `import { setGlobalContext } from '@${metaName}/runtime/context' @@ -289,9 +291,11 @@ import App from '${ }'; const entryName = '${entryName}'; +const basename = '${basename || '/'}'; setGlobalContext({ entryName, App, + basename, });`; }; diff --git a/packages/runtime/plugin-runtime/src/core/context/index.ts b/packages/runtime/plugin-runtime/src/core/context/index.ts index b28ca4f38dd8..23e61232e972 100644 --- a/packages/runtime/plugin-runtime/src/core/context/index.ts +++ b/packages/runtime/plugin-runtime/src/core/context/index.ts @@ -30,6 +30,10 @@ interface GlobalContext { * page router _app.tsx export layout app */ layoutApp?: React.ComponentType; + /** + * Entry basename for routing + */ + basename?: string; internalRuntimeContext?: InternalRuntimeContext; /** @@ -64,6 +68,7 @@ export function setGlobalContext( globalContext.routes = context.routes; globalContext.appInit = context.appInit; globalContext.layoutApp = context.layoutApp; + globalContext.basename = context.basename; globalContext.RSCRoot = context.RSCRoot; globalContext.isRscClient = context.isRscClient; globalContext.enableRsc = context.enableRsc; @@ -98,3 +103,7 @@ export function getGlobalRoutes(): undefined | (NestedRoute | PageRoute)[] { export function getGlobalLayoutApp() { return globalContext.layoutApp; } + +export function getGlobalBasename() { + return globalContext.basename; +} diff --git a/packages/runtime/plugin-runtime/src/router/cli/code/templates.ts b/packages/runtime/plugin-runtime/src/router/cli/code/templates.ts index 5076054687d8..ec465f4d3c42 100644 --- a/packages/runtime/plugin-runtime/src/router/cli/code/templates.ts +++ b/packages/runtime/plugin-runtime/src/router/cli/code/templates.ts @@ -558,6 +558,7 @@ export const runtimeGlobalContext = async ({ internalSrcAlias, globalApp, rscType = false, + basename, }: { entryName: string; metaName: string; @@ -566,6 +567,7 @@ export const runtimeGlobalContext = async ({ internalSrcAlias: string; globalApp?: string | false; rscType?: 'server' | 'client' | false; + basename?: string; }) => { const imports = [ `import { setGlobalContext } from '@${metaName}/runtime/context';`, @@ -619,11 +621,13 @@ export const runtimeGlobalContext = async ({ import { routes } from './routes'; const entryName = '${entryName}'; + const basename = '${basename || '/'}'; setGlobalContext({ entryName, layoutApp, routes, appInit, + basename, isRscClient: true, enableRsc: true, }); @@ -634,11 +638,13 @@ export const runtimeGlobalContext = async ({ import { routes } from './routes'; const entryName = '${entryName}'; + const basename = '${basename || '/'}'; setGlobalContext({ entryName, layoutApp, routes, appInit, + basename, enableRsc: ${enableRsc}, }); `; diff --git a/packages/runtime/plugin-runtime/src/router/cli/handler.ts b/packages/runtime/plugin-runtime/src/router/cli/handler.ts index 6e5ca4b221ab..690781699747 100644 --- a/packages/runtime/plugin-runtime/src/router/cli/handler.ts +++ b/packages/runtime/plugin-runtime/src/router/cli/handler.ts @@ -34,6 +34,10 @@ export async function handleGeneratorEntryCode( await Promise.all( entrypoints.map(async entrypoint => { if (entrypoint.nestedRoutesEntry || entrypoint.pageRoutesEntry) { + const route = appContext.serverRoutes.find( + r => r.entryName === entrypoint.entryName, + ); + const basename = route?.urlPath || '/'; generatorRegisterCode( internalDirectory, entrypoint.entryName, @@ -45,6 +49,7 @@ export async function handleGeneratorEntryCode( internalSrcAlias: appContext.internalSrcAlias, globalApp: entrypoint.fileSystemRoutes?.globalApp, rscType: enableRsc ? 'client' : undefined, + basename, }), ); if (enableRsc) { @@ -59,6 +64,7 @@ export async function handleGeneratorEntryCode( internalSrcAlias: appContext.internalSrcAlias, globalApp: entrypoint.fileSystemRoutes?.globalApp, rscType: 'server', + basename, }), ); } diff --git a/packages/solutions/app-tools/src/builder/shared/builderPlugins/adapterHtml.ts b/packages/solutions/app-tools/src/builder/shared/builderPlugins/adapterHtml.ts index 14ac99e0fe23..7611f87de096 100644 --- a/packages/solutions/app-tools/src/builder/shared/builderPlugins/adapterHtml.ts +++ b/packages/solutions/app-tools/src/builder/shared/builderPlugins/adapterHtml.ts @@ -6,12 +6,11 @@ import type { RsbuildPlugin, RspackChain, } from '@rsbuild/core'; -import type { HtmlUserConfig } from '../../../types/config/html'; import { BottomTemplatePlugin } from '../bundlerPlugins'; import type { BuilderOptions } from '../types'; const createVirtualModule = (content: string) => - `data:text/javascript,${content}`; + `data:text/javascript;charset=utf-8,${encodeURIComponent(content)}`; export const builderPluginAdapterHtml = ( options: BuilderOptions, diff --git a/packages/solutions/app-tools/src/config/default.ts b/packages/solutions/app-tools/src/config/default.ts index e427c90073b2..e6a38a7348ad 100644 --- a/packages/solutions/app-tools/src/config/default.ts +++ b/packages/solutions/app-tools/src/config/default.ts @@ -1,4 +1,4 @@ -import { MAIN_ENTRY_NAME } from '@modern-js/utils'; +import { DEFAULT_ENTRY_NAME, MAIN_ENTRY_NAME } from '@modern-js/utils'; import type { AppUserConfig } from '../types'; import type { AppToolsContext } from '../types/plugin'; import { getAutoInjectEnv } from '../utils/env'; @@ -37,7 +37,7 @@ export function createDefaultConfig( alias: Record; } = { entries: undefined, - mainEntryName: MAIN_ENTRY_NAME, + mainEntryName: DEFAULT_ENTRY_NAME, enableAsyncEntry: false, disableDefaultEntries: false, entriesDir: './src', diff --git a/packages/solutions/app-tools/src/plugins/analyze/getServerRoutes.ts b/packages/solutions/app-tools/src/plugins/analyze/getServerRoutes.ts index ea8635ec89d5..3599bea4bbd0 100644 --- a/packages/solutions/app-tools/src/plugins/analyze/getServerRoutes.ts +++ b/packages/solutions/app-tools/src/plugins/analyze/getServerRoutes.ts @@ -129,6 +129,7 @@ const collectHtmlRoutes = ( server: { baseUrl, routes, ssr, ssrByEntries, rsc }, deploy, } = config; + const { packageName } = appContext; const workerSSR = deploy?.worker?.ssr; diff --git a/packages/toolkit/utils/src/cli/constants.ts b/packages/toolkit/utils/src/cli/constants.ts index 43f8c9529efe..ff55552c47d7 100644 --- a/packages/toolkit/utils/src/cli/constants.ts +++ b/packages/toolkit/utils/src/cli/constants.ts @@ -3,6 +3,7 @@ import type { InternalPlugins } from '@modern-js/types'; export { NESTED_ROUTE_SPEC_FILE, MAIN_ENTRY_NAME, + DEFAULT_ENTRY_NAME, ROUTE_SPEC_FILE, SERVER_BUNDLE_DIRECTORY, SERVER_RENDER_FUNCTION_NAME, diff --git a/packages/toolkit/utils/src/universal/constants.ts b/packages/toolkit/utils/src/universal/constants.ts index 55f9d25befcd..43a98b35f179 100644 --- a/packages/toolkit/utils/src/universal/constants.ts +++ b/packages/toolkit/utils/src/universal/constants.ts @@ -38,6 +38,11 @@ export const NESTED_ROUTE_SPEC_FILE = 'nestedRoutes.json'; */ export const MAIN_ENTRY_NAME = 'main'; +/** + * default entry name + */ +export const DEFAULT_ENTRY_NAME = 'index'; + /** * server side bundles directory, which relative to dist. */ diff --git a/tests/integration/i18n/app-csr/modern.config.ts b/tests/integration/i18n/app-csr/modern.config.ts index 7dfa00a45c88..932a4c31580d 100644 --- a/tests/integration/i18n/app-csr/modern.config.ts +++ b/tests/integration/i18n/app-csr/modern.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ languages: ['zh', 'en'], fallbackLanguage: 'en', localeDetectionByEntry: { - main: { + index: { enable: false, }, },