diff options
author | Chris Swithinbank | 2023-05-03 22:33:11 +0200 |
---|---|---|
committer | GitHub | 2023-05-03 22:33:11 +0200 |
commit | 7b9f27372c18a3737e83954ad303e94f74d62fa0 (patch) | |
tree | 6fc23fe9d3a73e2c01a641d3c9687596101afdd0 | |
parent | 46f06dab8c8a53c3dacde2ed9df5369595b92e2f (diff) | |
download | IT.starlight-7b9f27372c18a3737e83954ad303e94f74d62fa0.tar.gz IT.starlight-7b9f27372c18a3737e83954ad303e94f74d62fa0.tar.bz2 IT.starlight-7b9f27372c18a3737e83954ad303e94f74d62fa0.zip |
Enable fallback content for missing translations (#27)
-rw-r--r-- | docs/src/content/docs/guides/i18n.md | 2 | ||||
-rw-r--r-- | docs/src/content/docs/reference/configuration.md | 12 | ||||
-rw-r--r-- | packages/starbook/components/FallbackContentNotice.astro | 27 | ||||
-rw-r--r-- | packages/starbook/components/HeadSEO.astro | 2 | ||||
-rw-r--r-- | packages/starbook/components/LanguageSelect.astro | 2 | ||||
-rw-r--r-- | packages/starbook/index.astro | 41 | ||||
-rw-r--r-- | packages/starbook/utils/navigation.ts | 100 | ||||
-rw-r--r-- | packages/starbook/utils/routing.ts | 109 | ||||
-rw-r--r-- | packages/starbook/utils/slugs.ts | 57 | ||||
-rw-r--r-- | packages/starbook/utils/user-config.ts | 53 |
10 files changed, 311 insertions, 94 deletions
diff --git a/docs/src/content/docs/guides/i18n.md b/docs/src/content/docs/guides/i18n.md index 28c7cc10..a6f48ffc 100644 --- a/docs/src/content/docs/guides/i18n.md +++ b/docs/src/content/docs/guides/i18n.md @@ -16,6 +16,8 @@ StarBook provides built-in support for multilingual sites. export default defineConfig({ integrations: [ starbook({ + // Set English as the default language for this site. + defaultLocale: 'en', locales: { // English docs in `src/content/docs/en/` en: { diff --git a/docs/src/content/docs/reference/configuration.md b/docs/src/content/docs/reference/configuration.md index fa99e5f7..53ba8746 100644 --- a/docs/src/content/docs/reference/configuration.md +++ b/docs/src/content/docs/reference/configuration.md @@ -114,6 +114,8 @@ import starbook from 'starbook'; export default defineConfig({ integrations: [ starbook({ + // Set English as the default language for this site. + defaultLocale: 'en', locales: { // English docs in `src/content/docs/en/` en: { @@ -177,6 +179,16 @@ starbook({ For example, this allows you to serve `/getting-started/` as an English route and use `/fr/getting-started/` as the equivalent French page. +### `defaultLocale` + +**type:** `string` + +Set the language which is the default for this site. +The value should match one of the keys of your [`locales`](#locales) object. +(If your default language is your [root locale](#root-locale), you can skip this.) + +The default locale will be used to provide fallback content where translations are missing. + ### `social` Optional details about the social media accounts for this site. diff --git a/packages/starbook/components/FallbackContentNotice.astro b/packages/starbook/components/FallbackContentNotice.astro new file mode 100644 index 00000000..e4dc3a4a --- /dev/null +++ b/packages/starbook/components/FallbackContentNotice.astro @@ -0,0 +1,27 @@ +--- +import Icon from './Icon.astro'; +--- + +<p> + <Icon + name={'warning'} + size="1.5em" + color="var(--sb-color-orange-high)" + /><span>This content is not available in your language yet.</span> +</p> + +<style> + p { + border: 1px solid var(--sb-color-orange); + padding: 0.75em 1em; + background-color: var(--sb-color-orange-low); + color: var(--sb-color-orange-high); + width: max-content; + max-width: 100%; + display: flex; + align-items: center; + gap: 0.75em; + font-size: var(--sb-text-body-sm); + line-height: var(--sb-line-height-headings); + } +</style> diff --git a/packages/starbook/components/HeadSEO.astro b/packages/starbook/components/HeadSEO.astro index 2eafbc4f..9ebdde57 100644 --- a/packages/starbook/components/HeadSEO.astro +++ b/packages/starbook/components/HeadSEO.astro @@ -22,7 +22,7 @@ const description = data.description || config.description; <link rel="canonical" href={canonical} /> { canonical && - config.locales && + config.isMultilingual && Object.entries(config.locales).map( ([locale, localeOpts]) => localeOpts && ( diff --git a/packages/starbook/components/LanguageSelect.astro b/packages/starbook/components/LanguageSelect.astro index 04343c1f..65d978e2 100644 --- a/packages/starbook/components/LanguageSelect.astro +++ b/packages/starbook/components/LanguageSelect.astro @@ -16,7 +16,7 @@ function localizedPathname(locale: string | undefined): string { --- { - config.locales && Object.keys(config.locales).length > 1 && ( + config.isMultilingual && ( <starbook-lang-select> <Select icon="translate" diff --git a/packages/starbook/index.astro b/packages/starbook/index.astro index e200e317..4ae47c32 100644 --- a/packages/starbook/index.astro +++ b/packages/starbook/index.astro @@ -1,15 +1,9 @@ --- -import type { GetStaticPathsResult, InferGetStaticPropsType } from 'astro'; -import { getCollection } from 'astro:content'; +import type { InferGetStaticPropsType } from 'astro'; import config from 'virtual:starbook/user-config'; import { getPrevNextLinks, getSidebar } from './utils/navigation'; -import { - slugToDir, - slugToLang, - slugToLocale, - slugToParam, -} from './utils/slugs'; +import { paths } from './utils/routing'; // Built-in CSS styles. import './style/props.css'; @@ -20,6 +14,7 @@ import './style/util.css'; // Components — can override built-in CSS, but not user CSS. import ContentPanel from './components/ContentPanel.astro'; import EditLink from './components/EditLink.astro'; +import FallbackContentNotice from './components/FallbackContentNotice.astro'; import HeadSEO from './components/HeadSEO.astro'; import Header from './components/Header.astro'; import LastUpdated from './components/LastUpdated.astro'; @@ -40,21 +35,13 @@ import './style/asides.css'; import 'virtual:starbook/user-css'; export async function getStaticPaths() { - const docs = await getCollection('docs'); - - return docs.map((doc) => ({ - params: { slug: slugToParam(doc.slug) }, - props: doc, - })) satisfies GetStaticPathsResult; + return paths; } type Props = InferGetStaticPropsType<typeof getStaticPaths>; -const { render, data, slug, id } = Astro.props; -const { Content, headings } = await render(); -const lang = slugToLang(slug); -const locale = slugToLocale(slug); -const dir = slugToDir(slug); +const { dir, entry, entryMeta, isFallback, lang, locale } = Astro.props; +const { Content, headings } = await entry.render(); const sidebar = getSidebar(Astro.url.pathname, locale); const prevNextLinks = getPrevNextLinks(sidebar); --- @@ -63,7 +50,7 @@ const prevNextLinks = getPrevNextLinks(sidebar); <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> - <HeadSEO data={data} lang={lang} /> + <HeadSEO data={entry.data} lang={lang} /> </head> <body> <ThemeProvider /> @@ -81,24 +68,30 @@ const prevNextLinks = getPrevNextLinks(sidebar); config.editLink.baseUrl && ( <> <h2>Contribute</h2> - <EditLink data={data} id={id} /> + <EditLink data={entry.data} id={entry.id} /> </> ) } </RightSidebarPanel> </Fragment> - <main id="starbook__overview" data-pagefind-body> + <main + id="starbook__overview" + data-pagefind-body + lang={entryMeta.lang} + dir={entryMeta.dir} + > <ContentPanel> <h1 style="font-size: var(--sb-text-h1); line-height: var(--sb-line-height-headings); font-weight: 600; color: var(--sb-color-white); margin-top: 1rem;" > - {data.title} + {entry.data.title} </h1> + {isFallback && <FallbackContentNotice />} </ContentPanel> <ContentPanel> <MarkdownContent><Content /></MarkdownContent> <footer> - <LastUpdated id={id} lang={lang} /> + <LastUpdated id={entry.id} lang={lang} /> <PrevNextLinks {...prevNextLinks} dir={dir} /> </footer> </ContentPanel> diff --git a/packages/starbook/utils/navigation.ts b/packages/starbook/utils/navigation.ts index 7c829daf..5c45bde6 100644 --- a/packages/starbook/utils/navigation.ts +++ b/packages/starbook/utils/navigation.ts @@ -1,15 +1,13 @@ -import { CollectionEntry, getCollection } from 'astro:content'; import { basename, dirname } from 'node:path'; -import { slugToPathname } from '../utils/slugs'; import config from 'virtual:starbook/user-config'; +import { slugToPathname } from '../utils/slugs'; +import { Route, getLocaleRoutes, routes } from './routing'; import type { AutoSidebarGroup, SidebarItem, SidebarLinkItem, } from './user-config'; -const allDocs = await getCollection('docs'); - export interface Link { type: 'link'; label: string; @@ -26,13 +24,13 @@ interface Group { export type SidebarEntry = Link | Group; /** - * A representation of the file structure. For each object entry: - * if it's a folder, the key is the directory name, and value is the directory - * content; if it's a doc file, the key is the doc's source file name, and value - * is the collection entry. + * A representation of the route structure. For each object entry: + * if it’s a folder, the key is the directory name, and value is the directory + * content; if it’s a route entry, the key is the last segment of the route, and value + * is the entry’s full slug. */ interface Dir { - [item: string]: Dir | CollectionEntry<'docs'>['id']; + [item: string]: Dir | string; } /** Convert an item in a user’s sidebar config to a sidebar entry. */ @@ -40,18 +38,18 @@ function configItemToEntry( item: SidebarItem, currentPathname: string, locale: string | undefined, - docs: CollectionEntry<'docs'>[] + routes: Route[] ): SidebarEntry { if ('link' in item) { return linkFromConfig(item, locale, currentPathname); } else if ('autogenerate' in item) { - return groupFromAutogenerateConfig(item, locale, docs, currentPathname); + return groupFromAutogenerateConfig(item, locale, routes, currentPathname); } else { return { type: 'group', label: item.label, entries: item.items.map((i) => - configItemToEntry(i, currentPathname, locale, docs) + configItemToEntry(i, currentPathname, locale, routes) ), }; } @@ -61,12 +59,12 @@ function configItemToEntry( function groupFromAutogenerateConfig( item: AutoSidebarGroup, locale: string | undefined, - docs: CollectionEntry<'docs'>[], + routes: Route[], currentPathname: string ): Group { const { directory } = item.autogenerate; const localeDir = locale ? locale + '/' + directory : directory; - const dirDocs = docs.filter((doc) => doc.id.startsWith(localeDir)); + const dirDocs = routes.filter((doc) => doc.slug.startsWith(localeDir)); const tree = treeify(dirDocs, localeDir); return { type: 'group', @@ -113,27 +111,26 @@ function makeLink(href: string, label: string, currentPathname: string): Link { } /** Get the segments leading to a page. */ -function getBreadcrumbs( - id: CollectionEntry<'docs'>['id'], - baseDir: string -): string[] { +function getBreadcrumbs(slug: string, baseDir: string): string[] { // Ensure base directory ends in a trailing slash. if (!baseDir.endsWith('/')) baseDir += '/'; - // Strip base directory from file ID if present. - const relativeId = id.startsWith(baseDir) ? id.replace(baseDir, '') : id; - let dir = dirname(relativeId); + // Strip base directory from slug if present. + const relativeSlug = slug.startsWith(baseDir) + ? slug.replace(baseDir, '') + : slug; + let dir = dirname(relativeSlug); // Return no breadcrumbs for items in the root directory. if (dir === '.') return []; return dir.split('/'); } -/** Turn a flat array of docs into a tree structure. */ -function treeify(docs: CollectionEntry<'docs'>[], baseDir: string): Dir { +/** Turn a flat array of routes into a tree structure. */ +function treeify(routes: Route[], baseDir: string): Dir { const treeRoot: Dir = {}; - docs.forEach((doc) => { - const breadcrumbs = getBreadcrumbs(doc.id, baseDir); + routes.forEach((doc) => { + const breadcrumbs = getBreadcrumbs(doc.slug, baseDir); - // Walk down the file's path to generate the fs structure + // Walk down the route’s path to generate the tree. let currentDir = treeRoot; breadcrumbs.forEach((dir) => { // Create new folder if needed. @@ -141,19 +138,20 @@ function treeify(docs: CollectionEntry<'docs'>[], baseDir: string): Dir { // Go into the subdirectory. currentDir = currentDir[dir] as Dir; }); - // We've walked through the path. Register the file in this directory. - currentDir[basename(doc.id)] = doc.id; + // We’ve walked through the path. Register the route in this directory. + currentDir[basename(doc.slug)] = doc.slug; }); return treeRoot; } /** Create a link entry for a given content collection entry. */ -function linkFromId( - id: CollectionEntry<'docs'>['id'], - currentPathname: string -): Link { - const doc = allDocs.find((doc) => doc.id === id)!; - return makeLink(slugToPathname(doc.slug), doc.data.title, currentPathname); +function linkFromSlug(slug: string, currentPathname: string): Link { + const doc = routes.find((doc) => doc.slug === slug)!; + return makeLink( + slugToPathname(doc.slug), + doc.entry.data.title, + currentPathname + ); } /** Create a group entry for a given content collection directory. */ @@ -164,8 +162,8 @@ function groupFromDir( currentPathname: string, locale: string | undefined ): Group { - const entries = Object.entries(dir).map(([key, dirOrId]) => - dirToItem(dirOrId, `${fullPath}/${key}`, key, currentPathname, locale) + const entries = Object.entries(dir).map(([key, dirOrSlug]) => + dirToItem(dirOrSlug, `${fullPath}/${key}`, key, currentPathname, locale) ); return { type: 'group', @@ -174,17 +172,17 @@ function groupFromDir( }; } -/** Create a sidebar entry for a directory or content ID. */ +/** Create a sidebar entry for a directory or content slug. */ function dirToItem( - dirOrId: Dir[string], + dirOrSlug: Dir[string], fullPath: string, dirName: string, currentPathname: string, locale: string | undefined ): SidebarEntry { - return typeof dirOrId === 'string' - ? linkFromId(dirOrId, currentPathname) - : groupFromDir(dirOrId, fullPath, dirName, currentPathname, locale); + return typeof dirOrSlug === 'string' + ? linkFromSlug(dirOrSlug, currentPathname) + : groupFromDir(dirOrSlug, fullPath, dirName, currentPathname, locale); } /** Create a sidebar entry for a given content directory. */ @@ -193,8 +191,8 @@ function sidebarFromDir( currentPathname: string, locale: string | undefined ) { - return Object.entries(tree).map(([key, dirOrId]) => - dirToItem(dirOrId, key, key, currentPathname, locale) + return Object.entries(tree).map(([key, dirOrSlug]) => + dirToItem(dirOrSlug, key, key, currentPathname, locale) ); } @@ -203,23 +201,13 @@ export function getSidebar( pathname: string, locale: string | undefined ): SidebarEntry[] { - let docs = allDocs; - if (config.locales) { - if (locale && locale in config.locales) { - docs = allDocs.filter((doc) => doc.id.startsWith(locale + '/')); - } else if (config.locales.root) { - const langKeys = Object.keys(config.locales).filter((k) => k !== 'root'); - const isLangDir = new RegExp(`^(${langKeys.join('|')})/`); - docs = allDocs.filter((doc) => !isLangDir.test(doc.id)); - } - } - + const routes = getLocaleRoutes(locale); if (config.sidebar) { return config.sidebar.map((group) => - configItemToEntry(group, pathname, locale, docs) + configItemToEntry(group, pathname, locale, routes) ); } else { - const tree = treeify(docs, locale || ''); + const tree = treeify(routes, locale || ''); return sidebarFromDir(tree, pathname, locale); } } diff --git a/packages/starbook/utils/routing.ts b/packages/starbook/utils/routing.ts new file mode 100644 index 00000000..38154e66 --- /dev/null +++ b/packages/starbook/utils/routing.ts @@ -0,0 +1,109 @@ +import type { GetStaticPathsItem } from 'astro'; +import { CollectionEntry, getCollection } from 'astro:content'; +import config from 'virtual:starbook/user-config'; +import { + LocaleData, + localizedSlug, + slugToLocaleData, + slugToParam, +} from './slugs'; + +export interface Route extends LocaleData { + entry: CollectionEntry<'docs'>; + entryMeta: LocaleData; + slug: string; + isFallback?: true; + [key: string]: unknown; +} + +interface Path extends GetStaticPathsItem { + params: { slug: string | undefined }; + props: Route; +} + +/** All entries in the docs content collection. */ +const docs = await getCollection('docs'); + +function getRoutes(): Route[] { + const routes: Route[] = docs.map((entry) => ({ + entry, + slug: entry.slug, + entryMeta: slugToLocaleData(entry.slug), + ...slugToLocaleData(entry.slug), + })); + + // In multilingual sites, add required fallback routes. + if (config.isMultilingual) { + /** Entries in the docs content collection for the default locale. */ + const defaultLocaleDocs = getLocaleDocs( + config.defaultLocale?.locale === 'root' + ? undefined + : config.defaultLocale?.locale + ); + for (const key in config.locales) { + if (key === config.defaultLocale.locale) continue; + const localeConfig = config.locales[key]; + if (!localeConfig) continue; + const locale = key === 'root' ? undefined : key; + const localeDocs = getLocaleDocs(locale); + for (const fallback of defaultLocaleDocs) { + const slug = localizedSlug(fallback.slug, locale); + const doesNotNeedFallback = localeDocs.some((doc) => doc.slug === slug); + if (doesNotNeedFallback) continue; + routes.push({ + entry: fallback, + slug, + isFallback: true, + lang: localeConfig.lang || 'en', + locale, + dir: localeConfig.dir, + entryMeta: slugToLocaleData(fallback.slug), + }); + } + } + } + + return routes; +} +export const routes = getRoutes(); + +function getPaths(): Path[] { + return routes.map((route) => ({ + params: { slug: slugToParam(route.slug) }, + props: route, + })); +} +export const paths = getPaths(); + +/** + * Get all routes for a specific locale. + * A locale of `undefined` is treated as the “root” locale, if configured. + */ +export function getLocaleRoutes(locale: string | undefined): Route[] { + return filterByLocale(routes, locale); +} + +/** + * Get all entries in the docs content collection for a specific locale. + * A locale of `undefined` is treated as the “root” locale, if configured. + */ +function getLocaleDocs(locale: string | undefined): CollectionEntry<'docs'>[] { + return filterByLocale(docs, locale); +} + +/** Filter an array to find items whose slug matches the passed locale. */ +function filterByLocale<T extends { slug: string }>( + items: T[], + locale: string | undefined +): T[] { + if (config.locales) { + if (locale && locale in config.locales) { + return items.filter((i) => i.slug.startsWith(locale + '/')); + } else if (config.locales.root) { + const langKeys = Object.keys(config.locales).filter((k) => k !== 'root'); + const isLangDir = new RegExp(`^(${langKeys.join('|')})/`); + return items.filter((i) => !isLangDir.test(i.slug)); + } + } + return items; +} diff --git a/packages/starbook/utils/slugs.ts b/packages/starbook/utils/slugs.ts index 2e0c193e..34eef630 100644 --- a/packages/starbook/utils/slugs.ts +++ b/packages/starbook/utils/slugs.ts @@ -1,13 +1,22 @@ import type { CollectionEntry } from 'astro:content'; import config from 'virtual:starbook/user-config'; +export interface LocaleData { + /** Writing direction. */ + dir: 'ltr' | 'rtl'; + /** BCP-47 language tag. */ + lang: string; + /** The base path at which a language is served. `undefined` for root locale slugs. */ + locale: string | undefined; +} + /** * 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 */ -export function slugToLocale( +function slugToLocale( slug: CollectionEntry<'docs'>['slug'] ): string | undefined { const locales = Object.keys(config.locales || {}); @@ -16,12 +25,19 @@ export function slugToLocale( return undefined; } +/** Get locale information for a given slug. */ +export function slugToLocaleData( + slug: CollectionEntry<'docs'>['slug'] +): LocaleData { + const locale = slugToLocale(slug); + return { dir: localeToDir(locale), lang: localeToLang(locale), locale }; +} + /** - * Get the BCP-47 language tag for the locale of a slug. - * @param slug A collection entry slug + * Get the BCP-47 language tag for the given locale. + * @param locale Locale string or `undefined` for the root locale. */ -export function slugToLang(slug: CollectionEntry<'docs'>['slug']): string { - const locale = slugToLocale(slug); +function localeToLang(locale: string | undefined): string { const lang = locale ? config.locales?.[locale]?.lang : config.locales?.root?.lang; @@ -29,13 +45,10 @@ export function slugToLang(slug: CollectionEntry<'docs'>['slug']): string { } /** - * Get the configured writing direction for the locale of a slug. - * @param slug A collection entry slug + * Get the configured writing direction for the given locale. + * @param locale Locale string or `undefined` for the root locale. */ -export function slugToDir( - slug: CollectionEntry<'docs'>['slug'] -): 'ltr' | 'rtl' { - const locale = slugToLocale(slug); +function localeToDir(locale: string | undefined): 'ltr' | 'rtl' { const dir = locale ? config.locales?.[locale]?.dir : config.locales?.root?.dir; @@ -54,3 +67,25 @@ 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: CollectionEntry<'docs'>['slug'], + locale: string | undefined +): string { + const slugLocale = slugToLocale(slug); + if (slugLocale === locale) return slug; + if (slugLocale) { + return slug.replace(slugLocale + '/', locale ? locale + '/' : ''); + } + return locale + '/' + slug; +} diff --git a/packages/starbook/utils/user-config.ts b/packages/starbook/utils/user-config.ts index ed4e514f..6b6064ec 100644 --- a/packages/starbook/utils/user-config.ts +++ b/packages/starbook/utils/user-config.ts @@ -84,7 +84,7 @@ const SidebarGroupSchema: z.ZodType< ManualSidebarGroup | z.infer<typeof AutoSidebarGroupSchema> > = z.union([ManualSidebarGroupSchema, AutoSidebarGroupSchema]); -export const StarbookConfigSchema = z.object({ +const StarbookUserConfigSchema = z.object({ /** Title for your website. Will be used in metadata and as browser tab title. */ title: z .string() @@ -177,6 +177,13 @@ export const StarbookConfigSchema = z.object({ .optional() .describe('Configure locales for internationalization (i18n).'), + /** + * Specify the default language for this site. + * + * The default locale will be used to provide fallback content where translations are missing. + */ + defaultLocale: z.string().optional(), + /** Configure your site’s sidebar navigation items. */ sidebar: SidebarGroupSchema.array().optional(), @@ -195,5 +202,49 @@ export const StarbookConfigSchema = z.object({ customCss: z.string().array().optional().default([]), }); +export const StarbookConfigSchema = StarbookUserConfigSchema.strict().transform( + ({ locales, defaultLocale, ...config }, ctx) => { + if (locales !== undefined && Object.keys(locales).length > 1) { + // This is a multilingual site (more than one locale configured). + // Make sure we can find the default locale and if not, help the user set it. + // We treat the root locale as the default if present and no explicit default is set. + const defaultLocaleConfig = locales[defaultLocale || 'root']; + + if (!defaultLocaleConfig) { + const availableLocales = Object.keys(locales) + .map((l) => `"${l}"`) + .join(', '); + ctx.addIssue({ + code: 'custom', + message: + 'Could not determine the default locale. ' + + 'Please make sure `defaultLocale` in your StarBook config is one of ' + + availableLocales, + }); + return z.NEVER; + } + + return { + ...config, + /** Flag indicating if this site has multiple locales set up. */ + isMultilingual: true, + /** Full locale object for this site’s default language. */ + defaultLocale: { ...defaultLocaleConfig, locale: defaultLocale }, + locales, + } as const; + } + + // This is a monolingual site, so things are pretty simple. + return { + ...config, + /** Flag indicating if this site has multiple locales set up. */ + isMultilingual: false, + /** Full locale object for this site’s default language. */ + defaultLocale: undefined, + locales: undefined, + } as const; + } +); + export type StarbookConfig = z.infer<typeof StarbookConfigSchema>; export type StarBookUserConfig = z.input<typeof StarbookConfigSchema>; |