@astrojs/starlight
Version:
Build beautiful, high-performance documentation websites with Astro
121 lines (111 loc) • 4.65 kB
text/typescript
import config from 'virtual:starlight/user-config';
import { slugToLocale as getLocaleFromSlug } from '../integrations/shared/slugToLocale';
import { BuiltInDefaultLocale } from './i18n';
import { stripTrailingSlash } from './path';
import type { LocaleData } from './routing/types';
/**
* Get the “locale” of a slug. This is the base path at which a language is served.
* For example, if French docs are in `src/content/docs/french/`, the locale is `french`.
* Root locale slugs will return `undefined`.
* @param slug A collection entry slug
*/
function slugToLocale(slug: string): string | undefined {
return getLocaleFromSlug(slug, config);
}
/** Get locale information for a given slug. */
export function slugToLocaleData(slug: string): LocaleData {
const locale = slugToLocale(slug);
return { dir: localeToDir(locale), lang: localeToLang(locale), locale };
}
/**
* Get the BCP-47 language tag for the given locale.
* @param locale Locale string or `undefined` for the root locale.
*/
export function localeToLang(locale: string | undefined): string {
const lang = locale ? config.locales?.[locale]?.lang : config.locales?.root?.lang;
const defaultLang = config.defaultLocale?.lang || config.defaultLocale?.locale;
return lang || defaultLang || BuiltInDefaultLocale.lang;
}
/**
* Get the configured writing direction for the given locale.
* @param locale Locale string or `undefined` for the root locale.
*/
function localeToDir(locale: string | undefined): 'ltr' | 'rtl' {
const dir = locale ? config.locales?.[locale]?.dir : config.locales?.root?.dir;
return dir || config.defaultLocale.dir;
}
/**
* Convert a content collection slug to a param as expected by Astro’s router.
* This utility handles stripping `index` from file names and matches
* [Astro’s param sanitization logic](https://github.com/withastro/astro/blob/687d25365a41ff8a9e6da155d3527f841abb70dd/packages/astro/src/core/routing/manifest/generator.ts#L4-L18)
* by normalizing strings to their canonical representations.
* @param slug Content collection slug
* @returns Param compatible with Astro’s router
*/
export function slugToParam(slug: string): string | undefined {
return slug === 'index' || slug === '' || slug === '/'
? undefined
: (slug.endsWith('/index') ? slug.slice(0, -6) : slug).normalize();
}
export function slugToPathname(slug: string): string {
const param = slugToParam(slug);
return param ? '/' + param + '/' : '/';
}
/**
* Convert a slug to a different locale.
* For example, passing a slug of `en/home` and a locale of `fr` results in `fr/home`.
* An undefined locale is treated as the root locale, resulting in `home`
* @param slug A collection entry slug
* @param locale The target locale
* @example
* localizedSlug('en/home', 'fr') // => 'fr/home'
* localizedSlug('en/home', undefined) // => 'home'
*/
export function localizedSlug(slug: string, locale: string | undefined): string {
const slugLocale = slugToLocale(slug);
if (slugLocale === locale) return slug;
locale = locale || '';
if (slugLocale === slug) return locale;
if (slugLocale) {
return stripTrailingSlash(slug.replace(slugLocale + '/', locale ? locale + '/' : ''));
}
return slug ? locale + '/' + slug : locale;
}
/**
* Convert a legacy collection entry ID or filePath relative to the collection root to a different
* locale.
* For example, passing an ID of `en/home.md` and a locale of `fr` results in `fr/home.md`.
* An undefined locale is treated as the root locale, resulting in `home.md`.
* @param id A collection entry ID
* @param locale The target locale
* @example
* localizedSlug('en/home.md', 'fr') // => 'fr/home.md'
* localizedSlug('en/home.md', undefined) // => 'home.md'
*/
export function localizedId(id: string, locale: string | undefined): string {
const idLocale = slugToLocale(id);
if (idLocale) {
return id.replace(idLocale + '/', locale ? locale + '/' : '');
} else if (locale) {
return locale + '/' + id;
} else {
return id;
}
}
/** Extract the slug from a URL. */
export function urlToSlug(url: URL): string {
let pathname = url.pathname;
const base = stripTrailingSlash(import.meta.env.BASE_URL);
if (pathname.startsWith(base)) pathname = pathname.replace(base, '');
const segments = pathname.split('/');
const htmlExt = '.html';
if (segments.at(-1) === 'index.html') {
// Remove trailing `index.html`.
segments.pop();
} else if (segments.at(-1)?.endsWith(htmlExt)) {
// Remove trailing `.html`.
const last = segments.pop();
if (last) segments.push(last.slice(0, -1 * htmlExt.length));
}
return segments.filter(Boolean).join('/');
}