Skip to content

Commit

Permalink
feat(i18n): fallback system in SSR (#8850)
Browse files Browse the repository at this point in the history
* feat(i18n): fallback system in SSG

* feat(i18n): fallback system in SSR
  • Loading branch information
ematipico committed Oct 20, 2023
1 parent 907bd0f commit 81abf24
Show file tree
Hide file tree
Showing 16 changed files with 214 additions and 33 deletions.
17 changes: 15 additions & 2 deletions packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
import { matchRoute } from '../routing/match.js';
import { EndpointNotFoundError, SSRRoutePipeline } from './ssrPipeline.js';
import type { RouteInfo } from './types.js';
import { createI18nMiddleware } from '../../i18n/middleware.js';
import { sequence } from '../middleware/index.js';
export { deserializeManifest } from './common.js';

const clientLocalsSymbol = Symbol.for('astro.locals');
Expand Down Expand Up @@ -164,8 +166,19 @@ export class App {
);
let response;
try {
if (mod.onRequest) {
this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
let i18nMiddleware = createI18nMiddleware(this.#manifest.i18n);
if (i18nMiddleware) {
if (mod.onRequest) {
this.#pipeline.setMiddlewareFunction(
sequence(i18nMiddleware, mod.onRequest as MiddlewareEndpointHandler)
);
} else {
this.#pipeline.setMiddlewareFunction(i18nMiddleware);
}
} else {
if (mod.onRequest) {
this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
}
}
response = await this.#pipeline.renderRoute(renderContext, pageModule);
} catch (err: any) {
Expand Down
13 changes: 8 additions & 5 deletions packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ export type SSRManifest = {
componentMetadata: SSRResult['componentMetadata'];
pageModule?: SinglePageBuiltModule;
pageMap?: Map<ComponentPath, ImportComponentInstance>;
i18n: SSRManifestI18n | undefined;
};

export type SSRManifestI18n = {
fallback?: Record<string, string[]>;
fallbackControl?: 'none' | 'redirect' | 'render';
locales: string[];
defaultLocale: string;
};

export type SerializedSSRManifest = Omit<
Expand All @@ -60,8 +68,3 @@ export type SerializedSSRManifest = Omit<
componentMetadata: [string, SSRComponentMetadata][];
clientDirectives: [string, string][];
};

export type AdapterCreateExports<T = any> = (
manifest: SSRManifest,
args?: T
) => Record<string, any>;
13 changes: 12 additions & 1 deletion packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import { getTimeStat, shouldAppendForwardSlash } from './util.js';
import { createI18nMiddleware } from '../../i18n/middleware.js';
import { sequence } from '../middleware/index.js';
import { routeIsFallback } from '../redirects/helpers.js';
import type { SSRManifestI18n } from '../app/types.js';

function createEntryURL(filePath: string, outFolder: URL) {
return new URL('./' + filePath + `?time=${Date.now()}`, outFolder);
Expand Down Expand Up @@ -296,7 +297,7 @@ async function generatePage(

const pageModulePromise = ssrEntry.page;
const onRequest = ssrEntry.onRequest;
const i18nMiddleware = createI18nMiddleware(config, logger);
const i18nMiddleware = createI18nMiddleware(pipeline.getManifest().i18n);
if (config.experimental.i18n && i18nMiddleware) {
if (onRequest) {
pipeline.setMiddlewareFunction(
Expand Down Expand Up @@ -640,6 +641,15 @@ export function createBuildManifest(
internals: BuildInternals,
renderers: SSRLoadedRenderer[]
): SSRManifest {
let i18nManifest: SSRManifestI18n | undefined = undefined;
if (settings.config.experimental.i18n) {
i18nManifest = {
fallback: settings.config.experimental.i18n.fallback,
fallbackControl: settings.config.experimental.i18n.fallbackControl,
defaultLocale: settings.config.experimental.i18n.defaultLocale,
locales: settings.config.experimental.i18n.locales,
};
}
return {
assets: new Set(),
entryModules: Object.fromEntries(internals.entrySpecifierToBundleMap.entries()),
Expand All @@ -654,5 +664,6 @@ export function createBuildManifest(
? new URL(settings.config.base, settings.config.site).toString()
: settings.config.site,
componentMetadata: internals.componentMetadata,
i18n: i18nManifest,
};
}
17 changes: 13 additions & 4 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { type Plugin as VitePlugin } from 'vite';
import { runHookBuildSsr } from '../../../integrations/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
import type { SerializedRouteInfo, SerializedSSRManifest } from '../../app/types.js';
import type { SSRManifestI18n } from '../../app/types.js';
import { joinPaths, prependForwardSlash } from '../../path.js';
import { serializeRouteData } from '../../routing/index.js';
import { addRollupInput } from '../add-rollup-input.js';
import { getOutFile, getOutFolder } from '../common.js';
import { cssOrder, mergeInlineCss, type BuildInternals } from '../internal.js';
import { type BuildInternals, cssOrder, mergeInlineCss } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import type { StaticBuildOptions } from '../types.js';

Expand Down Expand Up @@ -237,8 +238,17 @@ function buildManifest(
// Set this to an empty string so that the runtime knows not to try and load this.
entryModules[BEFORE_HYDRATION_SCRIPT_ID] = '';
}
let i18nManifest: SSRManifestI18n | undefined = undefined;
if (settings.config.experimental.i18n) {
i18nManifest = {
fallback: settings.config.experimental.i18n.fallback,
fallbackControl: settings.config.experimental.i18n.fallbackControl,
locales: settings.config.experimental.i18n.locales,
defaultLocale: settings.config.experimental.i18n.defaultLocale,
};
}

const ssrManifest: SerializedSSRManifest = {
return {
adapterName: opts.settings.adapter?.name ?? '',
routes,
site: settings.config.site,
Expand All @@ -250,7 +260,6 @@ function buildManifest(
clientDirectives: Array.from(settings.clientDirectives),
entryModules,
assets: staticFiles.map(prefixAssetPath),
i18n: i18nManifest,
};

return ssrManifest;
}
2 changes: 1 addition & 1 deletion packages/astro/src/core/build/plugins/plugin-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function vitePluginMiddleware(
let resolvedMiddlewareId: string;
return {
name: '@astro/plugin-middleware',

enforce: 'post',
options(options) {
return addRollupInput(options, [MIDDLEWARE_MODULE_ID]);
},
Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/core/build/static-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ async function ssrBuild(
const routes = Object.values(allPages)
.flat()
.map((pageData) => pageData.route);

const { lastVitePlugins, vitePlugins } = container.runBeforeHook('ssr', input);

const viteBuildConfig: vite.InlineConfig = {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/build/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function shouldAppendForwardSlash(
}

export function i18nHasFallback(config: AstroConfig): boolean {
if (config.experimental.i18n) {
if (config.experimental.i18n && config.experimental.i18n.fallback) {
// we have some fallback and the control is not none
return (
Object.keys(config.experimental.i18n.fallback).length > 0 &&
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,10 @@ export const AstroConfigSchema = z.object({
.object({
defaultLocale: z.string(),
locales: z.string().array(),
fallback: z.record(z.string(), z.string().array()).optional().default({}),
fallback: z.record(z.string(), z.string().array()).optional(),
detectBrowserLanguage: z.boolean().optional().default(false),
// TODO: properly add default when the feature goes of experimental
fallbackControl: z.enum(['none', 'redirect', 'render']).optional().default('none'),
fallbackControl: z.enum(['none', 'redirect', 'render']).optional(),
})
.optional()
.superRefine((i18n, ctx) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export async function createVite(
vitePluginSSRManifest(),
astroAssetsPlugin({ settings, logger, mode }),
astroTransitions(),
!!settings.config.experimental.i18n && astroInternalization({ settings, logger }),
!!settings.config.experimental.i18n && astroInternalization({ settings }),
],
publicDir: fileURLToPath(settings.config.publicDir),
root: fileURLToPath(settings.config.root),
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/core/routing/manifest/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ export function createRouteManifest(
routes.push(routeData);
});

if (settings.config.experimental.i18n) {
if (settings.config.experimental.i18n && settings.config.experimental.i18n.fallback) {
let fallback = Object.entries(settings.config.experimental.i18n.fallback);
if (fallback.length > 0) {
for (const [fallbackLocale, fallbackLocaleList] of fallback) {
Expand Down Expand Up @@ -523,6 +523,7 @@ export function createRouteManifest(
...fallbackToRoute,
pathname,
route,
segments,
pattern: getPattern(segments, config),
type: 'fallback',
});
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AstroError } from '../core/errors/index.js';
import { MissingLocale } from '../core/errors/errors-data.js';
import type { AstroConfig } from '../@types/astro.js';
import { shouldAppendForwardSlash } from '../core/build/util.js';
import type { AstroConfig } from '../@types/astro.js';

type GetI18nBaseUrl = {
locale: string;
Expand Down
13 changes: 5 additions & 8 deletions packages/astro/src/i18n/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import type { AstroConfig, MiddlewareEndpointHandler } from '../@types/astro.js';
import type { Logger } from '../core/logger/core.js';
import type { MiddlewareEndpointHandler } from '../@types/astro.js';
import type { SSRManifest } from '../@types/astro.js';

export function createI18nMiddleware(
config: Readonly<AstroConfig>,
logger: Logger
i18n: SSRManifest['i18n']
): MiddlewareEndpointHandler | undefined {
const i18n = config.experimental?.i18n;
if (!i18n) {
return undefined;
}
const fallbackKeys = Object.keys(i18n.fallback);
const locales = i18n.locales;

logger.debug('i18n', 'Successfully created middleware');
return async (context, next) => {
if (fallbackKeys.length <= 0) {
if (!i18n.fallback) {
return next();
}

const response = await next();
if (i18n.fallbackControl === 'redirect' && response instanceof Response) {
const fallbackKeys = i18n.fallback ? Object.keys(i18n.fallback) : [];
const url = context.url;
const separators = url.pathname.split('/');

Expand Down
3 changes: 1 addition & 2 deletions packages/astro/src/i18n/vite-plugin-i18n.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import * as vite from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import type { Logger } from '../core/logger/core.js';

const virtualModuleId = 'astro:i18n';
const resolvedVirtualModuleId = '\0' + virtualModuleId;

type AstroInternalization = {
settings: AstroSettings;
logger: Logger;
};

export default function astroInternalization({ settings }: AstroInternalization): vite.Plugin {
return {
name: 'astro:i18n',
enforce: 'pre',
async resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
Expand Down
11 changes: 11 additions & 0 deletions packages/astro/src/vite-plugin-astro-server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { baseMiddleware } from './base.js';
import { createController } from './controller.js';
import DevPipeline from './devPipeline.js';
import { handleRequest } from './request.js';
import type { SSRManifestI18n } from '../core/app/types.js';

export interface AstroPluginOptions {
settings: AstroSettings;
Expand Down Expand Up @@ -85,6 +86,15 @@ export default function createVitePluginAstroServer({
* @param renderers
*/
export function createDevelopmentManifest(settings: AstroSettings): SSRManifest {
let i18nManifest: SSRManifestI18n | undefined = undefined;
if (settings.config.experimental.i18n) {
i18nManifest = {
fallback: settings.config.experimental.i18n.fallback,
fallbackControl: settings.config.experimental.i18n.fallbackControl,
defaultLocale: settings.config.experimental.i18n.defaultLocale,
locales: settings.config.experimental.i18n.locales,
};
}
return {
compressHTML: settings.config.compressHTML,
assets: new Set(),
Expand All @@ -99,5 +109,6 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
? new URL(settings.config.base, settings.config.site).toString()
: settings.config.site,
componentMetadata: new Map(),
i18n: i18nManifest,
};
}
2 changes: 1 addition & 1 deletion packages/astro/src/vite-plugin-astro-server/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export async function handleRoute({

const onRequest = middleware?.onRequest as MiddlewareEndpointHandler | undefined;
if (config.experimental.i18n) {
const i18Middleware = createI18nMiddleware(config, logger);
const i18Middleware = createI18nMiddleware(config.experimental.i18n);

if (i18Middleware) {
if (onRequest) {
Expand Down
Loading

0 comments on commit 81abf24

Please sign in to comment.