astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
441 lines (440 loc) • 11.8 kB
JavaScript
// @ts-expect-error This is an internal module
import * as config from "astro:config/client";
import { toFallbackType } from "../core/app/common.js";
import { toRoutingStrategy } from "../core/app/entrypoints/index.js";
import { IncorrectStrategyForI18n, InvalidI18nMiddlewareConfiguration } from "../core/errors/errors-data.js";
import { AstroError } from "../core/errors/index.js";
import * as I18nInternals from "../i18n/index.js";
import { normalizeTheLocale as normalizeTheLocaleInternal, pathHasLocale as pathHasLocaleInternal } from "../i18n/path.js";
const { trailingSlash, site, i18n, build } = config;
const { format } = build;
const isBuild = import.meta.env.PROD;
const { defaultLocale, locales, domains, fallback, routing } = i18n;
const base = import.meta.env.BASE_URL;
let strategy = toRoutingStrategy(routing, domains);
let fallbackType = toFallbackType(routing);
const noop = (method) => function() {
throw new AstroError({
...IncorrectStrategyForI18n,
message: IncorrectStrategyForI18n.message(method)
});
};
/**
* @param locale A locale
* @param path An optional path to add after the `locale`.
* @param options Customise the generated path
*
* Returns a _relative_ path with passed locale.
*
* ## Errors
*
* Throws an error if the locale doesn't exist in the list of locales defined in the configuration.
*
* ## Examples
*
* ```js
* import { getRelativeLocaleUrl } from "astro:i18n";
* getRelativeLocaleUrl("es"); // /es
* getRelativeLocaleUrl("es", "getting-started"); // /es/getting-started
* getRelativeLocaleUrl("es_US", "getting-started", { prependWith: "blog" }); // /blog/es-us/getting-started
* getRelativeLocaleUrl("es_US", "getting-started", { prependWith: "blog", normalizeLocale: false }); // /blog/es_US/getting-started
* ```
*/
export const getRelativeLocaleUrl = (locale, path, options) => I18nInternals.getLocaleRelativeUrl({
locale,
path,
base,
trailingSlash,
format,
defaultLocale,
locales,
strategy,
domains,
...options
});
/**
*
* @param locale A locale
* @param path An optional path to add after the `locale`.
* @param options Customise the generated path
*
* Returns an absolute path with the passed locale. The behaviour is subject to change based on `site` configuration.
* If _not_ provided, the function will return a _relative_ URL.
*
* ## Errors
*
* Throws an error if the locale doesn't exist in the list of locales defined in the configuration.
*
* ## Examples
*
* If `site` is `https://example.com`:
*
* ```js
* import { getAbsoluteLocaleUrl } from "astro:i18n";
* getAbsoluteLocaleUrl("es"); // https://example.com/es
* getAbsoluteLocaleUrl("es", "getting-started"); // https://example.com/es/getting-started
* getAbsoluteLocaleUrl("es_US", "getting-started", { prependWith: "blog" }); // https://example.com/blog/es-us/getting-started
* getAbsoluteLocaleUrl("es_US", "getting-started", { prependWith: "blog", normalizeLocale: false }); // https://example.com/blog/es_US/getting-started
* ```
*/
export const getAbsoluteLocaleUrl = (locale, path, options) => I18nInternals.getLocaleAbsoluteUrl({
locale,
path,
base,
trailingSlash,
format,
site,
defaultLocale,
locales,
strategy,
domains,
isBuild,
...options
});
/**
* @param path An optional path to add after the `locale`.
* @param options Customise the generated path
*
* Works like `getRelativeLocaleUrl` but it emits the relative URLs for ALL locales:
*/
export const getRelativeLocaleUrlList = (path, options) => I18nInternals.getLocaleRelativeUrlList({
base,
path,
trailingSlash,
format,
defaultLocale,
locales,
strategy,
domains,
...options
});
/**
* @param path An optional path to add after the `locale`.
* @param options Customise the generated path
*
* Works like `getAbsoluteLocaleUrl` but it emits the absolute URLs for ALL locales:
*/
export const getAbsoluteLocaleUrlList = (path, options) => I18nInternals.getLocaleAbsoluteUrlList({
site,
base,
path,
trailingSlash,
format,
defaultLocale,
locales,
strategy,
domains,
isBuild,
...options
});
/**
* A function that return the `path` associated to a locale (defined as code). It's particularly useful in case you decide
* to use locales that are broken down in paths and codes.
*
* @param locale The code of the locale
* @returns The path associated to the locale
*
* ## Example
*
* ```js
* // astro.config.mjs
*
* export default defineConfig({
* i18n: {
* locales: [
* { codes: ["it", "it-VT"], path: "italiano" },
* "es"
* ]
* }
* })
* ```
*
* ```js
* import { getPathByLocale } from "astro:i18n";
* getPathByLocale("it"); // returns "italiano"
* getPathByLocale("it-VT"); // returns "italiano"
* getPathByLocale("es"); // returns "es"
* ```
*/
export const getPathByLocale = (locale) => I18nInternals.getPathByLocale(locale, locales);
/**
* A function that returns the preferred locale given a certain path. This is particularly useful if you configure a locale using
* `path` and `codes`. When you define multiple `code`, this function will return the first code of the array.
*
* Astro will treat the first code as the one that the user prefers.
*
* @param path The path that maps to a locale
* @returns The path associated to the locale
*
* ## Example
*
* ```js
* // astro.config.mjs
*
* export default defineConfig({
* i18n: {
* locales: [
* { codes: ["it-VT", "it"], path: "italiano" },
* "es"
* ]
* }
* })
* ```
*
* ```js
* import { getLocaleByPath } from "astro:i18n";
* getLocaleByPath("italiano"); // returns "it-VT" because that's the first code configured
* getLocaleByPath("es"); // returns "es"
* ```
*/
export const getLocaleByPath = (path) => I18nInternals.getLocaleByPath(path, locales);
/**
* A function that can be used to check if the current path contains a configured locale.
*
* @param path The path that maps to a locale
* @returns Whether the `path` has the locale
*
* ## Example
*
* Given the following configuration:
*
* ```js
* // astro.config.mjs
*
* export default defineConfig({
* i18n: {
* locales: [
* { codes: ["it-VT", "it"], path: "italiano" },
* "es"
* ]
* }
* })
* ```
*
* Here's some use cases:
*
* ```js
* import { pathHasLocale } from "astro:i18n";
* pathHasLocale("italiano"); // returns `true`
* pathHasLocale("es"); // returns `true`
* pathHasLocale("it-VT"); // returns `false`
* ```
*/
export const pathHasLocale = (path) => pathHasLocaleInternal(path, locales);
/**
*
* This function returns a redirect to the default locale configured in the
*
* @param {APIContext} context The context passed to the middleware
* @param {ValidRedirectStatus?} statusCode An optional status code for the redirect.
*/
export let redirectToDefaultLocale;
if (i18n?.routing === "manual") {
redirectToDefaultLocale = I18nInternals.redirectToDefaultLocale({
base,
trailingSlash,
format,
defaultLocale,
locales,
strategy,
domains,
fallback,
fallbackType
});
} else {
redirectToDefaultLocale = noop("redirectToDefaultLocale");
}
/**
*
* Use this function to return a 404 when:
* - the current path isn't a root. e.g. / or /<base>
* - the URL doesn't contain a locale
*
* When a `Response` is passed, the new `Response` emitted by this function will contain the same headers of the original response.
*
* @param {APIContext} context The context passed to the middleware
* @param {Response?} response An optional `Response` in case you're handling a `Response` coming from the `next` function.
*
*/
export let notFound;
if (i18n?.routing === "manual") {
notFound = I18nInternals.notFound({
base,
trailingSlash,
format,
defaultLocale,
locales,
strategy,
domains,
fallback,
fallbackType
});
} else {
notFound = noop("notFound");
}
/**
* Checks whether the current URL contains a configured locale. Internally, this function will use `APIContext#url.pathname`
*
* @param {APIContext} context The context passed to the middleware
*/
export let requestHasLocale;
if (i18n?.routing === "manual") {
requestHasLocale = I18nInternals.requestHasLocale(locales);
} else {
requestHasLocale = noop("requestHasLocale");
}
/**
* Allows to use the built-in fallback system of Astro
*
* @param {APIContext} context The context passed to the middleware
* @param {Promise<Response>} response An optional `Response` in case you're handling a `Response` coming from the `next` function.
*/
export let redirectToFallback;
if (i18n?.routing === "manual") {
redirectToFallback = I18nInternals.redirectToFallback({
base,
trailingSlash,
format,
defaultLocale,
locales,
strategy,
domains,
fallback,
fallbackType
});
} else {
redirectToFallback = noop("useFallback");
}
/**
* @param {AstroConfig['i18n']['routing']} customOptions
*
* A function that allows you to programmatically create the Astro i18n middleware.
*
* This is useful when you still want to use the default i18n logic, but add only a few exceptions to your website.
*
* ## Examples
*
* ```js
* // middleware.js
* import { middleware } from "astro:i18n";
* import { sequence, defineMiddleware } from "astro:middleware";
*
* const customLogic = defineMiddleware(async (context, next) => {
* const response = await next();
*
* // Custom logic after resolving the response.
* // It's possible to catch the response coming from Astro i18n middleware.
*
* return response;
* });
*
* export const onRequest = sequence(customLogic, middleware({
* prefixDefaultLocale: true,
* redirectToDefaultLocale: false
* }))
*
* ```
*/
export let middleware;
if (i18n?.routing === "manual") {
middleware = (customOptions) => {
if (customOptions.prefixDefaultLocale === false && customOptions.redirectToDefaultLocale === true) {
throw new AstroError(InvalidI18nMiddlewareConfiguration);
}
strategy = toRoutingStrategy(customOptions, {});
fallbackType = toFallbackType(customOptions);
const manifest = {
...i18n,
strategy,
domainLookupTable: {},
fallbackType,
fallback: i18n.fallback,
domains: i18n.domains
};
return I18nInternals.createMiddleware(manifest, base, trailingSlash, format);
};
} else {
middleware = noop("middleware");
}
/**
* Replaces underscores (`_`) with hyphens (`-`) in the given locale before
* returning a lowercase version.
*
* @param {string} locale A locale code to normalize.
* @returns {string} The normalized locale.
*
* ## Example
*
* ```js
* import { normalizeTheLocale } from "astro:i18n";
* normalizeTheLocale("it_VT") // returns `it-vt`
* ```
*/
export const normalizeTheLocale = normalizeTheLocaleInternal;
/**
* Retrieves the configured locale codes for each locale defined in your
* configuration. When multiple codes are associated to a locale, only the
* first one will be added to the array.
*
* @param {Locales} locales The configured locales.
* @returns {string[]} The configured locales codes.
*
* ## Example
*
* Given the following configuration:
*
* ```js
* // astro.config.mjs
*
* export default defineConfig({
* i18n: {
* locales: [
* { codes: ["it-VT", "it"], path: "italiano" },
* "es"
* ]
* }
* })
* ```
*
* Here's a use case:
*
* ```js
* import { i18n } from "astro:config/client";
* import { toCodes } from "astro:i18n";
* toCodes(i18n.locales); // ["it-VT", "es"]
* ```
*/
export const toCodes = I18nInternals.toCodes;
/**
* Retrieves the configured locale paths for each locale defined in your
* configuration.
*
* @param {Locales} locales The configured locales.
* @returns {string[]} The configured locales paths.
*
* ## Example
*
* Given the following configuration:
*
* ```js
* // astro.config.mjs
*
* export default defineConfig({
* i18n: {
* locales: [
* { codes: ["it-VT", "it"], path: "italiano" },
* "es"
* ]
* }
* })
* ```
*
* Here's a use case:
*
* ```js
* import { i18n } from "astro:config/client";
* import { toPaths } from "astro:i18n";
* toPaths(i18n.locales); // ["italiano", "es"]
* ```
*/
export const toPaths = I18nInternals.toPaths;