astro 3.5.3 → 3.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/components/Code.astro +6 -54
  2. package/components/ViewTransitions.astro +5 -2
  3. package/config.d.ts +2 -1
  4. package/dist/@types/astro.d.ts +3 -1
  5. package/dist/assets/services/noop.js +1 -0
  6. package/dist/core/app/index.js +7 -2
  7. package/dist/core/app/types.d.ts +1 -0
  8. package/dist/core/build/generate.js +5 -2
  9. package/dist/core/build/plugins/plugin-manifest.js +1 -0
  10. package/dist/core/build/plugins/plugin-prerender.js +4 -1
  11. package/dist/core/constants.js +1 -1
  12. package/dist/core/dev/dev.js +1 -1
  13. package/dist/core/errors/dev/vite.js +1 -1
  14. package/dist/core/logger/core.d.ts +1 -1
  15. package/dist/core/logger/core.js +3 -3
  16. package/dist/core/messages.js +2 -2
  17. package/dist/core/pipeline.d.ts +6 -0
  18. package/dist/core/pipeline.js +13 -0
  19. package/dist/core/preview/vite-plugin-astro-preview.js +2 -1
  20. package/dist/core/shiki.d.ts +2 -8
  21. package/dist/core/shiki.js +5 -24
  22. package/dist/i18n/middleware.d.ts +6 -1
  23. package/dist/i18n/middleware.js +21 -6
  24. package/dist/runtime/client/dev-overlay/entrypoint.js +155 -6
  25. package/dist/runtime/client/dev-overlay/overlay.d.ts +4 -0
  26. package/dist/runtime/client/dev-overlay/overlay.js +29 -11
  27. package/dist/runtime/client/dev-overlay/plugins/astro.js +9 -36
  28. package/dist/runtime/client/dev-overlay/plugins/settings.d.ts +8 -0
  29. package/dist/runtime/client/dev-overlay/plugins/settings.js +93 -0
  30. package/dist/runtime/client/dev-overlay/plugins/utils/window.d.ts +3 -0
  31. package/dist/runtime/client/dev-overlay/plugins/utils/window.js +45 -0
  32. package/dist/runtime/client/dev-overlay/settings.d.ts +12 -0
  33. package/dist/runtime/client/dev-overlay/settings.js +26 -0
  34. package/dist/runtime/client/dev-overlay/ui-library/icons.d.ts +2 -0
  35. package/dist/runtime/client/dev-overlay/ui-library/icons.js +3 -1
  36. package/dist/runtime/client/dev-overlay/ui-library/toggle.d.ts +8 -0
  37. package/dist/runtime/client/dev-overlay/ui-library/toggle.js +57 -0
  38. package/dist/runtime/client/dev-overlay/ui-library/window.js +36 -7
  39. package/dist/transitions/router.js +1 -1
  40. package/dist/vite-plugin-astro-server/plugin.js +1 -0
  41. package/dist/vite-plugin-astro-server/route.js +7 -2
  42. package/package.json +4 -4
@@ -10,8 +10,7 @@ import type {
10
10
  ThemeRegistration,
11
11
  ThemeRegistrationRaw,
12
12
  } from 'shikiji';
13
- import { visit } from 'unist-util-visit';
14
- import { getCachedHighlighter, replaceCssVariables } from '../dist/core/shiki.js';
13
+ import { getCachedHighlighter } from '../dist/core/shiki.js';
15
14
 
16
15
  interface Props {
17
16
  /** The code to highlight. Required. */
@@ -94,60 +93,13 @@ if (typeof lang === 'object') {
94
93
 
95
94
  const highlighter = await getCachedHighlighter({
96
95
  langs: [lang],
97
- themes: Object.values(experimentalThemes).length ? Object.values(experimentalThemes) : [theme],
96
+ theme,
97
+ experimentalThemes,
98
+ wrap,
98
99
  });
99
100
 
100
- const themeOptions = Object.values(experimentalThemes).length
101
- ? { themes: experimentalThemes }
102
- : { theme };
103
- const html = highlighter.codeToHtml(code, {
104
- lang: typeof lang === 'string' ? lang : lang.name,
105
- ...themeOptions,
106
- transforms: {
107
- pre(node) {
108
- // Swap to `code` tag if inline
109
- if (inline) {
110
- node.tagName = 'code';
111
- }
112
-
113
- // Cast to string as shikiji will always pass them as strings instead of any other types
114
- const classValue = (node.properties.class as string) ?? '';
115
- const styleValue = (node.properties.style as string) ?? '';
116
-
117
- // Replace "shiki" class naming with "astro-code"
118
- node.properties.class = classValue.replace(/shiki/g, 'astro-code');
119
-
120
- // Handle code wrapping
121
- // if wrap=null, do nothing.
122
- if (wrap === false) {
123
- node.properties.style = styleValue + '; overflow-x: auto;';
124
- } else if (wrap === true) {
125
- node.properties.style =
126
- styleValue + '; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;';
127
- }
128
- },
129
- code(node) {
130
- if (inline) {
131
- return node.children[0] as typeof node;
132
- }
133
- },
134
- root(node) {
135
- if (Object.values(experimentalThemes).length) {
136
- return;
137
- }
138
-
139
- // theme.id for shiki -> shikiji compat
140
- const themeName = typeof theme === 'string' ? theme : theme.name;
141
- if (themeName === 'css-variables') {
142
- // Replace special color tokens to CSS variables
143
- visit(node as any, 'element', (child) => {
144
- if (child.properties?.style) {
145
- child.properties.style = replaceCssVariables(child.properties.style);
146
- }
147
- });
148
- }
149
- },
150
- },
101
+ const html = highlighter.highlight(code, typeof lang === 'string' ? lang : lang.name, {
102
+ inline,
151
103
  });
152
104
  ---
153
105
 
@@ -88,11 +88,14 @@ const { fallback = 'animate', handleForms } = Astro.props;
88
88
  }
89
89
 
90
90
  const form = el as HTMLFormElement;
91
+ const submitter = ev.submitter;
91
92
  const formData = new FormData(form);
92
93
  // Use the form action, if defined, otherwise fallback to current path.
93
- let action = form.action ?? location.pathname;
94
+ let action = submitter?.getAttribute('formaction') ?? form.action ?? location.pathname;
95
+ const method = submitter?.getAttribute('formmethod') ?? form.method;
96
+
94
97
  const options: Options = {};
95
- if (form.method === 'get') {
98
+ if (method === 'get') {
96
99
  const params = new URLSearchParams(formData as any);
97
100
  const url = new URL(action);
98
101
  url.search = params.toString();
package/config.d.ts CHANGED
@@ -16,12 +16,12 @@ export function getViteConfig(config: ViteUserConfig): ViteUserConfigFn;
16
16
 
17
17
  /**
18
18
  * Return the configuration needed to use the Sharp-based image service
19
- * See: https://docs.astro.build/en/guides/assets/#using-sharp
20
19
  */
21
20
  export function sharpImageService(): ImageServiceConfig;
22
21
 
23
22
  /**
24
23
  * Return the configuration needed to use the Squoosh-based image service
24
+ * See: https://docs.astro.build/en/guides/images/#configure-squoosh
25
25
  */
26
26
  export function squooshImageService(): ImageServiceConfig;
27
27
 
@@ -29,5 +29,6 @@ export function squooshImageService(): ImageServiceConfig;
29
29
  * Return the configuration needed to use the passthrough image service. This image services does not perform
30
30
  * any image transformations, and is mainly useful when your platform does not support other image services, or you are
31
31
  * not using Astro's built-in image processing.
32
+ * See: https://docs.astro.build/en/guides/images/#configure-no-op-passthrough-service
32
33
  */
33
34
  export function passthroughImageService(): ImageServiceConfig;
@@ -19,6 +19,7 @@ import type { AstroIntegrationLogger, Logger, LoggerLevel } from '../core/logger
19
19
  import type { AstroDevOverlay, DevOverlayCanvas } from '../runtime/client/dev-overlay/overlay.js';
20
20
  import type { DevOverlayHighlight } from '../runtime/client/dev-overlay/ui-library/highlight.js';
21
21
  import type { Icon } from '../runtime/client/dev-overlay/ui-library/icons.js';
22
+ import type { DevOverlayToggle } from '../runtime/client/dev-overlay/ui-library/toggle.js';
22
23
  import type { DevOverlayTooltip } from '../runtime/client/dev-overlay/ui-library/tooltip.js';
23
24
  import type { DevOverlayWindow } from '../runtime/client/dev-overlay/ui-library/window.js';
24
25
  import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server/index.js';
@@ -1393,7 +1394,7 @@ export interface AstroUserConfig {
1393
1394
  * The following example configures your content fallback strategy to redirect unavailable pages in `/pt-br/` to their `es` version, and unavailable pages in `/fr/` to their `en` version. Unavailable `/es/` pages will return a 404.
1394
1395
  *
1395
1396
  * ```js
1396
- * export defualt defineConfig({
1397
+ * export default defineConfig({
1397
1398
  * experimental: {
1398
1399
  * i18n: {
1399
1400
  * defaultLocale: "en",
@@ -2299,5 +2300,6 @@ declare global {
2299
2300
  'astro-dev-overlay-plugin-canvas': DevOverlayCanvas;
2300
2301
  'astro-dev-overlay-tooltip': DevOverlayTooltip;
2301
2302
  'astro-dev-overlay-highlight': DevOverlayHighlight;
2303
+ 'astro-dev-overlay-toggle': DevOverlayToggle;
2302
2304
  }
2303
2305
  }
@@ -1,5 +1,6 @@
1
1
  import { baseService } from "./service.js";
2
2
  const noopService = {
3
+ propertiesToHash: ["src"],
3
4
  validateOptions: baseService.validateOptions,
4
5
  getURL: baseService.getURL,
5
6
  parseURL: baseService.parseURL,
@@ -1,4 +1,4 @@
1
- import { createI18nMiddleware } from "../../i18n/middleware.js";
1
+ import { createI18nMiddleware, i18nPipelineHook } from "../../i18n/middleware.js";
2
2
  import { getSetCookiesFromResponse } from "../cookies/index.js";
3
3
  import { consoleLogDestination } from "../logger/console.js";
4
4
  import { AstroIntegrationLogger, Logger } from "../logger/core.js";
@@ -130,7 +130,11 @@ class App {
130
130
  );
131
131
  let response;
132
132
  try {
133
- let i18nMiddleware = createI18nMiddleware(this.#manifest.i18n, this.#manifest.base);
133
+ let i18nMiddleware = createI18nMiddleware(
134
+ this.#manifest.i18n,
135
+ this.#manifest.base,
136
+ this.#manifest.trailingSlash
137
+ );
134
138
  if (i18nMiddleware) {
135
139
  if (mod.onRequest) {
136
140
  this.#pipeline.setMiddlewareFunction(
@@ -139,6 +143,7 @@ class App {
139
143
  } else {
140
144
  this.#pipeline.setMiddlewareFunction(i18nMiddleware);
141
145
  }
146
+ this.#pipeline.onBeforeRenderRoute(i18nPipelineHook);
142
147
  } else {
143
148
  if (mod.onRequest) {
144
149
  this.#pipeline.setMiddlewareFunction(mod.onRequest);
@@ -30,6 +30,7 @@ export type SSRManifest = {
30
30
  routes: RouteInfo[];
31
31
  site?: string;
32
32
  base: string;
33
+ trailingSlash: 'always' | 'never' | 'ignore';
33
34
  compressHTML: boolean;
34
35
  assetsPrefix?: string;
35
36
  renderers: SSRLoadedRenderer[];
@@ -17,7 +17,7 @@ import {
17
17
  removeLeadingForwardSlash,
18
18
  removeTrailingForwardSlash
19
19
  } from "../../core/path.js";
20
- import { createI18nMiddleware } from "../../i18n/middleware.js";
20
+ import { createI18nMiddleware, i18nPipelineHook } from "../../i18n/middleware.js";
21
21
  import { runHookBuildGenerated } from "../../integrations/index.js";
22
22
  import { getOutputDirectory, isServerLikeOutput } from "../../prerender/utils.js";
23
23
  import { PAGE_SCRIPT_ID } from "../../vite-plugin-scripts/index.js";
@@ -205,7 +205,8 @@ async function generatePage(pageData, ssrEntry, builtPaths, pipeline) {
205
205
  const onRequest = ssrEntry.onRequest;
206
206
  const i18nMiddleware = createI18nMiddleware(
207
207
  pipeline.getManifest().i18n,
208
- pipeline.getManifest().base
208
+ pipeline.getManifest().base,
209
+ pipeline.getManifest().trailingSlash
209
210
  );
210
211
  if (config.experimental.i18n && i18nMiddleware) {
211
212
  if (onRequest) {
@@ -215,6 +216,7 @@ async function generatePage(pageData, ssrEntry, builtPaths, pipeline) {
215
216
  } else {
216
217
  pipeline.setMiddlewareFunction(i18nMiddleware);
217
218
  }
219
+ pipeline.onBeforeRenderRoute(i18nPipelineHook);
218
220
  } else if (onRequest) {
219
221
  pipeline.setMiddlewareFunction(onRequest);
220
222
  }
@@ -468,6 +470,7 @@ function createBuildManifest(settings, internals, renderers) {
468
470
  };
469
471
  }
470
472
  return {
473
+ trailingSlash: settings.config.trailingSlash,
471
474
  assets: /* @__PURE__ */ new Set(),
472
475
  entryModules: Object.fromEntries(internals.entrySpecifierToBundleMap.entries()),
473
476
  routes: [],
@@ -196,6 +196,7 @@ function buildManifest(opts, internals, staticFiles) {
196
196
  routes,
197
197
  site: settings.config.site,
198
198
  base: settings.config.base,
199
+ trailingSlash: settings.config.trailingSlash,
199
200
  compressHTML: settings.config.compressHTML,
200
201
  assetsPrefix: settings.config.build.assetsPrefix,
201
202
  componentMetadata: Array.from(internals.componentMetadata),
@@ -6,7 +6,10 @@ function vitePluginPrerender(opts, internals) {
6
6
  name: "astro:rollup-plugin-prerender",
7
7
  outputOptions(outputOptions) {
8
8
  extendManualChunks(outputOptions, {
9
- before(id, meta) {
9
+ after(id, meta) {
10
+ if (id.includes("astro/dist/runtime")) {
11
+ return "astro";
12
+ }
10
13
  const pageInfo = internals.pagesByViteID.get(id);
11
14
  if (pageInfo) {
12
15
  if (getPrerenderMetadata(meta.getModuleInfo(id))) {
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "3.5.3";
1
+ const ASTRO_VERSION = "3.5.5";
2
2
  const SUPPORTED_MARKDOWN_FILE_EXTENSIONS = [
3
3
  ".markdown",
4
4
  ".mdown",
@@ -20,7 +20,7 @@ async function dev(inlineConfig) {
20
20
  base: restart.container.settings.config.base
21
21
  })
22
22
  );
23
- const currentVersion = "3.5.3";
23
+ const currentVersion = "3.5.5";
24
24
  if (currentVersion.includes("-")) {
25
25
  logger.warn(null, msg.prerelease({ currentVersion }));
26
26
  }
@@ -1,7 +1,7 @@
1
+ import { replaceCssVariables } from "@astrojs/markdown-remark";
1
2
  import * as fs from "node:fs";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import { codeToHtml } from "shikiji";
4
- import { replaceCssVariables } from "../../shiki.js";
5
5
  import { FailedToLoadModuleSSR, InvalidGlob, MdxIntegrationMissingError } from "../errors-data.js";
6
6
  import { AstroError } from "../errors.js";
7
7
  import { createSafeError } from "../utils.js";
@@ -33,7 +33,7 @@ export declare class Logger {
33
33
  info(label: string | null, message: string): void;
34
34
  warn(label: string | null, message: string): void;
35
35
  error(label: string | null, message: string): void;
36
- debug(label: string | null, message: string, ...args: any[]): void;
36
+ debug(label: string | null, ...messages: any[]): void;
37
37
  level(): LoggerLevel;
38
38
  forkIntegrationLogger(label: string): AstroIntegrationLogger;
39
39
  }
@@ -89,8 +89,8 @@ class Logger {
89
89
  error(label, message) {
90
90
  error(this.options, label, message);
91
91
  }
92
- debug(label, message, ...args) {
93
- debug(this.options, label, message, args);
92
+ debug(label, ...messages) {
93
+ debug(label, ...messages);
94
94
  }
95
95
  level() {
96
96
  return this.options.level;
@@ -122,7 +122,7 @@ class AstroIntegrationLogger {
122
122
  error(this.options, this.label, message);
123
123
  }
124
124
  debug(message) {
125
- debug(this.options, this.label, message);
125
+ debug(this.label, message);
126
126
  }
127
127
  }
128
128
  export {
@@ -50,7 +50,7 @@ function serverStart({
50
50
  base,
51
51
  isRestart = false
52
52
  }) {
53
- const version = "3.5.3";
53
+ const version = "3.5.5";
54
54
  const localPrefix = `${dim("\u2503")} Local `;
55
55
  const networkPrefix = `${dim("\u2503")} Network `;
56
56
  const emptyPrefix = " ".repeat(11);
@@ -235,7 +235,7 @@ function printHelp({
235
235
  message.push(
236
236
  linebreak(),
237
237
  ` ${bgGreen(black(` ${commandName} `))} ${green(
238
- `v${"3.5.3"}`
238
+ `v${"3.5.5"}`
239
239
  )} ${headline}`
240
240
  );
241
241
  }
@@ -1,6 +1,7 @@
1
1
  import type { ComponentInstance, MiddlewareEndpointHandler } from '../@types/astro.js';
2
2
  import { type Environment, type RenderContext } from './render/index.js';
3
3
  type EndpointResultHandler = (originalRequest: Request, result: Response) => Promise<Response> | Response;
4
+ export type PipelineHookFunction = (ctx: RenderContext, mod: ComponentInstance | undefined) => void;
4
5
  /**
5
6
  * This is the basic class of a pipeline.
6
7
  *
@@ -37,5 +38,10 @@ export declare class Pipeline {
37
38
  * The main function of the pipeline. Use this function to render any route known to Astro;
38
39
  */
39
40
  renderRoute(renderContext: RenderContext, componentInstance: ComponentInstance | undefined): Promise<Response>;
41
+ /**
42
+ * Store a function that will be called before starting the rendering phase.
43
+ * @param fn
44
+ */
45
+ onBeforeRenderRoute(fn: PipelineHookFunction): void;
40
46
  }
41
47
  export {};
@@ -5,6 +5,9 @@ import {} from "./render/index.js";
5
5
  class Pipeline {
6
6
  env;
7
7
  #onRequest;
8
+ #hooks = {
9
+ before: []
10
+ };
8
11
  /**
9
12
  * The handler accepts the *original* `Request` and result returned by the endpoint.
10
13
  * It must return a `Response`.
@@ -49,6 +52,9 @@ class Pipeline {
49
52
  * The main function of the pipeline. Use this function to render any route known to Astro;
50
53
  */
51
54
  async renderRoute(renderContext, componentInstance) {
55
+ for (const hook of this.#hooks.before) {
56
+ hook(renderContext, componentInstance);
57
+ }
52
58
  const result = await this.#tryRenderRoute(
53
59
  renderContext,
54
60
  this.env,
@@ -125,6 +131,13 @@ class Pipeline {
125
131
  throw new Error(`Couldn't find route of type [${renderContext.route.type}]`);
126
132
  }
127
133
  }
134
+ /**
135
+ * Store a function that will be called before starting the rendering phase.
136
+ * @param fn
137
+ */
138
+ onBeforeRenderRoute(fn) {
139
+ this.#hooks.before.push(fn);
140
+ }
128
141
  }
129
142
  export {
130
143
  Pipeline
@@ -17,7 +17,8 @@ function vitePluginAstroPreview(settings) {
17
17
  res.end(subpathNotUsedTemplate(base, req.url));
18
18
  return;
19
19
  }
20
- const pathname = stripBase(req.url, base);
20
+ const strippedPathname = stripBase(req.url, base);
21
+ const pathname = new URL(strippedPathname, "https://a.b").pathname;
21
22
  const isRoot = pathname === "/";
22
23
  if (!isRoot) {
23
24
  const hasTrailingSlash = pathname.endsWith("/");
@@ -1,8 +1,2 @@
1
- import { getHighlighter, type Highlighter } from 'shikiji';
2
- type HighlighterOptions = NonNullable<Parameters<typeof getHighlighter>[0]>;
3
- /**
4
- * shiki -> shikiji compat as we need to manually replace it
5
- */
6
- export declare function replaceCssVariables(str: string): string;
7
- export declare function getCachedHighlighter(opts: HighlighterOptions): Promise<Highlighter>;
8
- export {};
1
+ import { type ShikiConfig, type ShikiHighlighter } from '@astrojs/markdown-remark';
2
+ export declare function getCachedHighlighter(opts: ShikiConfig): Promise<ShikiHighlighter>;
@@ -1,35 +1,16 @@
1
- import { getHighlighter } from "shikiji";
2
- const ASTRO_COLOR_REPLACEMENTS = {
3
- "#000001": "var(--astro-code-color-text)",
4
- "#000002": "var(--astro-code-color-background)",
5
- "#000004": "var(--astro-code-token-constant)",
6
- "#000005": "var(--astro-code-token-string)",
7
- "#000006": "var(--astro-code-token-comment)",
8
- "#000007": "var(--astro-code-token-keyword)",
9
- "#000008": "var(--astro-code-token-parameter)",
10
- "#000009": "var(--astro-code-token-function)",
11
- "#000010": "var(--astro-code-token-string-expression)",
12
- "#000011": "var(--astro-code-token-punctuation)",
13
- "#000012": "var(--astro-code-token-link)"
14
- };
15
- const COLOR_REPLACEMENT_REGEX = new RegExp(
16
- `(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join("|")})`,
17
- "g"
18
- );
1
+ import {
2
+ createShikiHighlighter
3
+ } from "@astrojs/markdown-remark";
19
4
  const cachedHighlighters = /* @__PURE__ */ new Map();
20
- function replaceCssVariables(str) {
21
- return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match);
22
- }
23
5
  function getCachedHighlighter(opts) {
24
6
  const key = JSON.stringify(opts, Object.keys(opts).sort());
25
7
  if (cachedHighlighters.has(key)) {
26
8
  return cachedHighlighters.get(key);
27
9
  }
28
- const highlighter = getHighlighter(opts);
10
+ const highlighter = createShikiHighlighter(opts);
29
11
  cachedHighlighters.set(key, highlighter);
30
12
  return highlighter;
31
13
  }
32
14
  export {
33
- getCachedHighlighter,
34
- replaceCssVariables
15
+ getCachedHighlighter
35
16
  };
@@ -1,2 +1,7 @@
1
1
  import type { MiddlewareEndpointHandler, SSRManifest } from '../@types/astro.js';
2
- export declare function createI18nMiddleware(i18n: SSRManifest['i18n'], base: SSRManifest['base']): MiddlewareEndpointHandler | undefined;
2
+ import type { PipelineHookFunction } from '../core/pipeline.js';
3
+ export declare function createI18nMiddleware(i18n: SSRManifest['i18n'], base: SSRManifest['base'], trailingSlash: SSRManifest['trailingSlash']): MiddlewareEndpointHandler | undefined;
4
+ /**
5
+ * This pipeline hook attaches a `RouteData` object to the `Request`
6
+ */
7
+ export declare const i18nPipelineHook: PipelineHookFunction;
@@ -1,4 +1,5 @@
1
- import { joinPaths } from "@astrojs/internal-helpers/path";
1
+ import { appendForwardSlash, joinPaths } from "@astrojs/internal-helpers/path";
2
+ const routeDataSymbol = Symbol.for("astro.routeData");
2
3
  function checkIsLocaleFree(pathname, locales) {
3
4
  for (const locale of locales) {
4
5
  if (pathname.includes(`/${locale}`)) {
@@ -7,7 +8,7 @@ function checkIsLocaleFree(pathname, locales) {
7
8
  }
8
9
  return true;
9
10
  }
10
- function createI18nMiddleware(i18n, base) {
11
+ function createI18nMiddleware(i18n, base, trailingSlash) {
11
12
  if (!i18n) {
12
13
  return void 0;
13
14
  }
@@ -15,8 +16,14 @@ function createI18nMiddleware(i18n, base) {
15
16
  if (!i18n) {
16
17
  return await next();
17
18
  }
18
- const { locales, defaultLocale, fallback } = i18n;
19
+ const routeData = Reflect.get(context.request, routeDataSymbol);
20
+ if (routeData) {
21
+ if (routeData.type !== "page" && routeData.type !== "fallback") {
22
+ return await next();
23
+ }
24
+ }
19
25
  const url = context.url;
26
+ const { locales, defaultLocale, fallback } = i18n;
20
27
  const response = await next();
21
28
  if (response instanceof Response) {
22
29
  const separators = url.pathname.split("/");
@@ -30,8 +37,12 @@ function createI18nMiddleware(i18n, base) {
30
37
  headers: response.headers
31
38
  });
32
39
  } else if (i18n.routingStrategy === "prefix-always") {
33
- if (url.pathname === base || url.pathname === base + "/") {
34
- return context.redirect(`${joinPaths(base, i18n.defaultLocale)}`);
40
+ if (url.pathname === base + "/" || url.pathname === base) {
41
+ if (trailingSlash === "always") {
42
+ return context.redirect(`${appendForwardSlash(joinPaths(base, i18n.defaultLocale))}`);
43
+ } else {
44
+ return context.redirect(`${joinPaths(base, i18n.defaultLocale)}`);
45
+ }
35
46
  } else if (isLocaleFree) {
36
47
  return new Response(null, {
37
48
  status: 404,
@@ -57,6 +68,10 @@ function createI18nMiddleware(i18n, base) {
57
68
  return response;
58
69
  };
59
70
  }
71
+ const i18nPipelineHook = (ctx) => {
72
+ Reflect.set(ctx.request, routeDataSymbol, ctx.route);
73
+ };
60
74
  export {
61
- createI18nMiddleware
75
+ createI18nMiddleware,
76
+ i18nPipelineHook
62
77
  };