From c8d9b08d762d915930217277c9138bf24a8b98f2 Mon Sep 17 00:00:00 2001 From: Fuma Nama Date: Wed, 25 Dec 2024 09:55:13 +0800 Subject: [PATCH 1/8] CLI: support Next.js 15 i18n auto-config --- .changeset/cool-kids-enjoy.md | 5 +++++ packages/cli/src/utils/i18n/transform-root-layout.ts | 6 ++++-- packages/cli/test/fixture/layout.out | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 .changeset/cool-kids-enjoy.md diff --git a/.changeset/cool-kids-enjoy.md b/.changeset/cool-kids-enjoy.md new file mode 100644 index 000000000..29311b927 --- /dev/null +++ b/.changeset/cool-kids-enjoy.md @@ -0,0 +1,5 @@ +--- +'@fumadocs/cli': patch +--- + +support Next.js 15 i18n auto-config diff --git a/packages/cli/src/utils/i18n/transform-root-layout.ts b/packages/cli/src/utils/i18n/transform-root-layout.ts index 736ad743b..118b7d7ce 100644 --- a/packages/cli/src/utils/i18n/transform-root-layout.ts +++ b/packages/cli/src/utils/i18n/transform-root-layout.ts @@ -46,7 +46,7 @@ export function runTransform(sourceFile: SourceFile): void { .join('\n'); parent.setBodyText( - ` ${inner.trim()} @@ -64,8 +64,10 @@ export function runTransform(sourceFile: SourceFile): void { const func = sourceFile .getDescendantsOfKind(SyntaxKind.FunctionDeclaration) .find((v) => v.isDefaultExport()); + func?.toggleModifier('async', true); + const param = func?.getParameters().at(0); - param?.setType(`{ params: { lang: string }, children: ReactNode }`); + param?.setType(`{ params: Promise<{ lang: string }>, children: ReactNode }`); param?.set({ name: `{ params, children }`, }); diff --git a/packages/cli/test/fixture/layout.out b/packages/cli/test/fixture/layout.out index 951b12a3b..5675456bd 100644 --- a/packages/cli/test/fixture/layout.out +++ b/packages/cli/test/fixture/layout.out @@ -8,11 +8,11 @@ const inter = Inter({ subsets: ['latin'], }); -export default function Layout({ params, children }: { params: { lang: string }, children: ReactNode }) { +export default async function Layout({ params, children }: { params: Promise<{ lang: string }>, children: ReactNode }) { return ( - {children} From 96b400e4f119ceeead59541b645b6c65dd7d5680 Mon Sep 17 00:00:00 2001 From: Fuma Nama <76240755+fuma-nama@users.noreply.github.com> Date: Wed, 25 Dec 2024 10:31:33 +0800 Subject: [PATCH 2/8] Version Packages (#1185) --- .changeset/cool-kids-enjoy.md | 5 ----- packages/cli/CHANGELOG.md | 6 ++++++ packages/cli/package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/cool-kids-enjoy.md diff --git a/.changeset/cool-kids-enjoy.md b/.changeset/cool-kids-enjoy.md deleted file mode 100644 index 29311b927..000000000 --- a/.changeset/cool-kids-enjoy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@fumadocs/cli': patch ---- - -support Next.js 15 i18n auto-config diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index be5383c1a..442c04523 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # fumadocs +## 0.0.5 + +### Patch Changes + +- c8d9b08: support Next.js 15 i18n auto-config + ## 0.0.4 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 54efcc690..76b6cc529 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@fumadocs/cli", - "version": "0.0.4", + "version": "0.0.5", "description": "The CLI tool for Fumadocs", "keywords": [ "NextJs", From 969da268f1de030006e470e8f5cd73f2fd63f182 Mon Sep 17 00:00:00 2001 From: Fuma Nama Date: Fri, 27 Dec 2024 16:47:53 +0800 Subject: [PATCH 3/8] Improve i18n examples --- .changeset/rare-garlics-hammer.md | 7 + .../content/docs/ui/internationalization.mdx | 137 +++++++++++------- examples/i18n/app/api/search/route.ts | 2 +- packages/cli/src/plugins/i18n.ts | 4 +- packages/core/src/search/client/static.ts | 15 +- packages/core/src/search/create-db-simple.ts | 44 ------ .../core/src/search/{ => orama}/_stemmers.ts | 0 .../core/src/search/{ => orama}/create-db.ts | 39 ++++- .../src/search/{ => orama}/create-endpoint.ts | 0 .../search/{ => orama}/create-from-source.ts | 2 +- .../{i18n-api.ts => orama/create-i18n.ts} | 4 +- .../src/search/{ => orama}/search/advanced.ts | 5 +- .../src/search/{ => orama}/search/simple.ts | 11 +- packages/core/src/search/server.ts | 20 ++- .../src/components/dialog/search-algolia.tsx | 8 +- .../src/components/dialog/search-default.tsx | 8 +- .../ui/src/components/dialog/search-orama.tsx | 8 +- packages/ui/src/components/dialog/search.tsx | 67 +++++++++ .../ui/src/components/dialog/tag-list.tsx | 68 --------- packages/ui/src/contexts/i18n.tsx | 26 ++-- packages/ui/src/i18n.tsx | 39 ++--- 21 files changed, 283 insertions(+), 231 deletions(-) create mode 100644 .changeset/rare-garlics-hammer.md delete mode 100644 packages/core/src/search/create-db-simple.ts rename packages/core/src/search/{ => orama}/_stemmers.ts (100%) rename packages/core/src/search/{ => orama}/create-db.ts (68%) rename packages/core/src/search/{ => orama}/create-endpoint.ts (100%) rename packages/core/src/search/{ => orama}/create-from-source.ts (96%) rename packages/core/src/search/{i18n-api.ts => orama/create-i18n.ts} (96%) rename packages/core/src/search/{ => orama}/search/advanced.ts (94%) rename packages/core/src/search/{ => orama}/search/simple.ts (72%) delete mode 100644 packages/ui/src/components/dialog/tag-list.tsx diff --git a/.changeset/rare-garlics-hammer.md b/.changeset/rare-garlics-hammer.md new file mode 100644 index 000000000..c61705ae7 --- /dev/null +++ b/.changeset/rare-garlics-hammer.md @@ -0,0 +1,7 @@ +--- +'fumadocs-core': patch +'@fumadocs/cli': patch +'fumadocs-ui': patch +--- + +Improve i18n api diff --git a/apps/docs/content/docs/ui/internationalization.mdx b/apps/docs/content/docs/ui/internationalization.mdx index 195e1e328..891c76ffb 100644 --- a/apps/docs/content/docs/ui/internationalization.mdx +++ b/apps/docs/content/docs/ui/internationalization.mdx @@ -49,24 +49,6 @@ export const source = loader({ }); ``` -Update the usages to your source to include a locale code: - -```ts -import { source } from '@/lib/source'; - -// get page tree -source.pageTree[params.lang]; - -// get page -source.getPage(params.slug, params.lang); - -// get pages -source.getPages(params.lang); -``` - -Note that without providing a locale code, it uses your default locale instead. -You can see [Source API](/docs/headless/source-api) for other usages. - ### Middleware Create a middleware that redirects users to appropriate locale. @@ -83,28 +65,57 @@ Create a middleware that redirects users to appropriate locale. See [Middleware](/docs/headless/internationalization#middleware) for customisable options. -### Root Layout +### Routing Create a dynamic route `/app/[lang]`, and move all special files from `/app` to the folder. -A `I18nProvider` is needed for localization. Wrap the root provider inside your I18n provider. +A `I18nProvider` is needed for localization. +Wrap the root provider inside your I18n provider, and provide available languages & translations to it. + +Note that only English translations are provided by default. ```tsx import { RootProvider } from 'fumadocs-ui/provider'; -import { I18nProvider } from 'fumadocs-ui/i18n'; +import { I18nProvider, type Translations } from 'fumadocs-ui/i18n'; + +const cn: Translations = { + search: 'Translated Content', + // other props +}; -export default function RootLayout({ +// available languages that will be displayed on UI +// make sure `locale` is consistent with your i18n config +const locales = [ + { + name: 'English', + locale: 'en', + }, + { + name: 'Chinese', + locale: 'cn', + }, +]; + +export default async function RootLayout({ params, children, }: { - params: { lang: string }; + params: Promise<{ lang: string }>; children: React.ReactNode; }) { return ( - + - + {children} @@ -113,47 +124,61 @@ export default function RootLayout({ } ``` -### Writing Documents +### Source -see [Page Conventions](/docs/ui/page-conventions#internationalization) to learn how to organize your documents. +Update the usages to your source to include a locale code: -### Search +```ts +import { source } from '@/lib/source'; -Configure i18n on your search solution. +// get page tree +source.pageTree[params.lang]; -You don't need further changes if you're using the `createFromSource` shortcut. +// get page +source.getPage(params.slug, params.lang); -For the built-in Orama search, see [Search I18n](/docs/headless/search/orama#internationalization). +// get pages +source.getPages(params.lang); +``` -### Adding Translations +like: -We only provide English translation by default, you have to pass your translations to the provider. +```tsx title="app/[lang]/layout.tsx" +import { source } from '@/lib/source'; +import { DocsLayout } from 'fumadocs-ui/docs'; +import type { ReactNode } from 'react'; -```tsx -import { I18nProvider } from 'fumadocs-ui/i18n'; - -; +export default async function Layout({ + params, + children, +}: { + params: Promise<{ lang: string }>; + children: ReactNode; +}) { + const pageTree = source.pageTree[(await params).lang]; + + return {children}; +} ``` +Note that without providing a locale code, it uses your default locale instead. +You can see [Source API](/docs/headless/source-api) for other usages. + +### Writing Documents + +see [Page Conventions](/docs/ui/page-conventions#internationalization) to learn how to organize your documents. + +### Search + +Configure i18n on your search solution. + +- Built-in Search (Orama): + - For `createFromSource` and most languages, no further changes are needed. + - For special languages like Chinese & Japanese, they require additional config. + See [Orama I18n](/docs/headless/search/orama#internationalization) guide. +- Cloud Solutions (e.g. Algolia): + - They usually have official support for multilingual. + ### Add Language Switch To allow users changing their language, enable `i18n` on your layouts. diff --git a/examples/i18n/app/api/search/route.ts b/examples/i18n/app/api/search/route.ts index e38b20464..a15d0f483 100644 --- a/examples/i18n/app/api/search/route.ts +++ b/examples/i18n/app/api/search/route.ts @@ -4,7 +4,7 @@ import { createFromSource } from 'fumadocs-core/search/server'; import { createTokenizer } from '@orama/tokenizers/mandarin'; import { stopwords } from '@orama/stopwords/mandarin'; -export const { GET, search } = createFromSource(source, undefined, { +export const { GET } = createFromSource(source, undefined, { localeMap: { // the prop name should be its locale code in your i18n config, (e.g. `cn`) cn: { diff --git a/packages/cli/src/plugins/i18n.ts b/packages/cli/src/plugins/i18n.ts index 19a859e36..3b37e5307 100644 --- a/packages/cli/src/plugins/i18n.ts +++ b/packages/cli/src/plugins/i18n.ts @@ -27,10 +27,10 @@ export const i18nPlugin: Plugin = { type: 'code', title: 'page.tsx', code: ` -export default function Page({ +export default async function Page({ params, }: { - ${picocolors.underline(picocolors.bold('params: { lang: string; slug?: string[] };'))} + ${picocolors.underline(picocolors.bold('params: Promise<{ lang: string; slug?: string[] }>'))} }) `.trim(), }, diff --git a/packages/core/src/search/client/static.ts b/packages/core/src/search/client/static.ts index 3addb9017..0c2b09988 100644 --- a/packages/core/src/search/client/static.ts +++ b/packages/core/src/search/client/static.ts @@ -6,10 +6,12 @@ import { type RawData, } from '@orama/orama'; import { type SortedResult } from '@/server'; -import { searchSimple } from '@/search/search/simple'; -import { searchAdvanced } from '@/search/search/advanced'; -import { type advancedSchema } from '@/search/create-db'; -import { type schema } from '@/search/create-db-simple'; +import { searchSimple } from '@/search/orama/search/simple'; +import { searchAdvanced } from '@/search/orama/search/advanced'; +import { + type advancedSchema, + type simpleSchema, +} from '@/search/orama/create-db'; export interface StaticOptions { /** @@ -86,7 +88,10 @@ export function createStaticClient({ if (!cached) return []; if (cached.type === 'simple') - return searchSimple(cached as unknown as Orama, query); + return searchSimple( + cached as unknown as Orama, + query, + ); return searchAdvanced( cached.db as Orama, diff --git a/packages/core/src/search/create-db-simple.ts b/packages/core/src/search/create-db-simple.ts deleted file mode 100644 index ef8e484a1..000000000 --- a/packages/core/src/search/create-db-simple.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - create, - insertMultiple, - type Orama, - type TypedDocument, -} from '@orama/orama'; -import { type SimpleOptions } from '@/search/server'; - -export type SimpleDocument = TypedDocument>; -export const schema = { - url: 'string', - title: 'string', - description: 'string', - content: 'string', - keywords: 'string', -} as const; - -export async function createDBSimple({ - indexes, - tokenizer, - ...rest -}: SimpleOptions): Promise> { - const items = typeof indexes === 'function' ? await indexes() : indexes; - const db = (await create({ - schema, - components: { - tokenizer, - }, - ...rest, - })) as unknown as Orama; - - await insertMultiple( - db, - items.map((page) => ({ - title: page.title, - description: page.description, - url: page.url, - content: page.content, - keywords: page.keywords, - })), - ); - - return db; -} diff --git a/packages/core/src/search/_stemmers.ts b/packages/core/src/search/orama/_stemmers.ts similarity index 100% rename from packages/core/src/search/_stemmers.ts rename to packages/core/src/search/orama/_stemmers.ts diff --git a/packages/core/src/search/create-db.ts b/packages/core/src/search/orama/create-db.ts similarity index 68% rename from packages/core/src/search/create-db.ts rename to packages/core/src/search/orama/create-db.ts index a7a598b3f..bc78097b0 100644 --- a/packages/core/src/search/create-db.ts +++ b/packages/core/src/search/orama/create-db.ts @@ -5,7 +5,7 @@ import { type PartialSchemaDeep, type TypedDocument, } from '@orama/orama'; -import { type AdvancedOptions } from '@/search/server'; +import { type AdvancedOptions, type SimpleOptions } from '@/search/server'; export type AdvancedDocument = TypedDocument>; export const advancedSchema = { @@ -85,3 +85,40 @@ export async function createDB({ await insertMultiple(db, mapTo); return db; } + +export type SimpleDocument = TypedDocument>; +export const simpleSchema = { + url: 'string', + title: 'string', + description: 'string', + content: 'string', + keywords: 'string', +} as const; + +export async function createDBSimple({ + indexes, + tokenizer, + ...rest +}: SimpleOptions): Promise> { + const items = typeof indexes === 'function' ? await indexes() : indexes; + const db = (await create({ + schema: simpleSchema, + components: { + tokenizer, + }, + ...rest, + })) as unknown as Orama; + + await insertMultiple( + db, + items.map((page) => ({ + title: page.title, + description: page.description, + url: page.url, + content: page.content, + keywords: page.keywords, + })), + ); + + return db; +} diff --git a/packages/core/src/search/create-endpoint.ts b/packages/core/src/search/orama/create-endpoint.ts similarity index 100% rename from packages/core/src/search/create-endpoint.ts rename to packages/core/src/search/orama/create-endpoint.ts diff --git a/packages/core/src/search/create-from-source.ts b/packages/core/src/search/orama/create-from-source.ts similarity index 96% rename from packages/core/src/search/create-from-source.ts rename to packages/core/src/search/orama/create-from-source.ts index 3d59edde3..85773b446 100644 --- a/packages/core/src/search/create-from-source.ts +++ b/packages/core/src/search/orama/create-from-source.ts @@ -12,7 +12,7 @@ import { type Page, } from '@/source'; import { type StructuredData } from '@/mdx-plugins'; -import { type LocaleMap } from '@/search/i18n-api'; +import { type LocaleMap } from '@/search/orama/create-i18n'; function pageToIndex(page: Page): AdvancedIndex { if (!('structuredData' in page.data)) { diff --git a/packages/core/src/search/i18n-api.ts b/packages/core/src/search/orama/create-i18n.ts similarity index 96% rename from packages/core/src/search/i18n-api.ts rename to packages/core/src/search/orama/create-i18n.ts index 734daa52d..896a33358 100644 --- a/packages/core/src/search/i18n-api.ts +++ b/packages/core/src/search/orama/create-i18n.ts @@ -10,9 +10,9 @@ import { type SearchServer, type SimpleOptions, } from '@/search/server'; -import { createEndpoint } from '@/search/create-endpoint'; +import { createEndpoint } from '@/search/orama/create-endpoint'; import { type I18nConfig } from '@/i18n'; -import { STEMMERS } from '@/search/_stemmers'; +import { STEMMERS } from '@/search/orama/_stemmers'; export type LocaleMap = Record; diff --git a/packages/core/src/search/search/advanced.ts b/packages/core/src/search/orama/search/advanced.ts similarity index 94% rename from packages/core/src/search/search/advanced.ts rename to packages/core/src/search/orama/search/advanced.ts index 6bfe7326d..2b8622264 100644 --- a/packages/core/src/search/search/advanced.ts +++ b/packages/core/src/search/orama/search/advanced.ts @@ -1,5 +1,8 @@ import { getByID, type Orama, search, type SearchParams } from '@orama/orama'; -import { type AdvancedDocument, type advancedSchema } from '@/search/create-db'; +import { + type AdvancedDocument, + type advancedSchema, +} from '@/search/orama/create-db'; import { removeUndefined } from '@/utils/remove-undefined'; import type { SortedResult } from '@/server'; diff --git a/packages/core/src/search/search/simple.ts b/packages/core/src/search/orama/search/simple.ts similarity index 72% rename from packages/core/src/search/search/simple.ts rename to packages/core/src/search/orama/search/simple.ts index 7e97be0d9..a9c565057 100644 --- a/packages/core/src/search/search/simple.ts +++ b/packages/core/src/search/orama/search/simple.ts @@ -1,11 +1,16 @@ import { type Orama, search, type SearchParams } from '@orama/orama'; import type { SortedResult } from '@/server'; -import { type schema, type SimpleDocument } from '@/search/create-db-simple'; +import { + type simpleSchema, + type SimpleDocument, +} from '@/search/orama/create-db'; export async function searchSimple( - db: Orama, + db: Orama, query: string, - params: Partial, SimpleDocument>> = {}, + params: Partial< + SearchParams, SimpleDocument> + > = {}, ): Promise { const result = await search(db, { term: query, diff --git a/packages/core/src/search/server.ts b/packages/core/src/search/server.ts index ce5cddc28..ab9968ef4 100644 --- a/packages/core/src/search/server.ts +++ b/packages/core/src/search/server.ts @@ -2,19 +2,17 @@ import { type Orama, type SearchParams, save, create } from '@orama/orama'; import { type NextRequest } from 'next/server'; import type { StructuredData } from '@/mdx-plugins/remark-structure'; import type { SortedResult } from '@/server/types'; -import { createEndpoint } from '@/search/create-endpoint'; +import { createEndpoint } from '@/search/orama/create-endpoint'; import { type AdvancedDocument, type advancedSchema, createDB, -} from '@/search/create-db'; -import { searchSimple } from '@/search/search/simple'; -import { searchAdvanced } from '@/search/search/advanced'; -import { createDBSimple, - type schema, type SimpleDocument, -} from './create-db-simple'; + simpleSchema, +} from '@/search/orama/create-db'; +import { searchSimple } from '@/search/orama/search/simple'; +import { searchAdvanced } from '@/search/orama/search/advanced'; export interface SearchServer { search: ( @@ -57,7 +55,7 @@ export interface SimpleOptions extends SharedOptions { /** * Customise search options on server */ - search?: Partial, SimpleDocument>>; + search?: Partial, SimpleDocument>>; } export interface AdvancedOptions extends SharedOptions { @@ -133,7 +131,7 @@ export function initAdvancedSearch(options: AdvancedOptions): SearchServer { async export() { return { type: 'advanced', - ...save(await get), + ...(await save(await get)), }; }, async search(query, searchOptions) { @@ -144,5 +142,5 @@ export function initAdvancedSearch(options: AdvancedOptions): SearchServer { }; } -export { createFromSource } from './create-from-source'; -export { createI18nSearchAPI } from './i18n-api'; +export { createFromSource } from './orama/create-from-source'; +export { createI18nSearchAPI } from './orama/create-i18n'; diff --git a/packages/ui/src/components/dialog/search-algolia.tsx b/packages/ui/src/components/dialog/search-algolia.tsx index 108532b8a..babe04808 100644 --- a/packages/ui/src/components/dialog/search-algolia.tsx +++ b/packages/ui/src/components/dialog/search-algolia.tsx @@ -6,8 +6,12 @@ import { } from 'fumadocs-core/search/client'; import { type ReactNode, useState } from 'react'; import { useOnChange } from 'fumadocs-core/utils/use-on-change'; -import { SearchDialog, type SharedProps } from './search'; -import { type TagItem, TagsList } from './tag-list'; +import { + SearchDialog, + type SharedProps, + TagsList, + type TagItem, +} from './search'; export interface AlgoliaSearchDialogProps extends SharedProps { index: AlgoliaOptions['index']; diff --git a/packages/ui/src/components/dialog/search-default.tsx b/packages/ui/src/components/dialog/search-default.tsx index f6732e346..bc3d3e691 100644 --- a/packages/ui/src/components/dialog/search-default.tsx +++ b/packages/ui/src/components/dialog/search-default.tsx @@ -4,8 +4,12 @@ import { useDocsSearch } from 'fumadocs-core/search/client'; import { type ReactNode, useState } from 'react'; import { useOnChange } from 'fumadocs-core/utils/use-on-change'; import { useI18n } from '@/contexts/i18n'; -import { SearchDialog, type SharedProps } from './search'; -import { type TagItem, TagsList } from './tag-list'; +import { + SearchDialog, + type SharedProps, + type TagItem, + TagsList, +} from './search'; export interface DefaultSearchDialogProps extends SharedProps { /** diff --git a/packages/ui/src/components/dialog/search-orama.tsx b/packages/ui/src/components/dialog/search-orama.tsx index 12de7c689..7f17fcda5 100644 --- a/packages/ui/src/components/dialog/search-orama.tsx +++ b/packages/ui/src/components/dialog/search-orama.tsx @@ -6,8 +6,12 @@ import { } from 'fumadocs-core/search/client'; import { type ReactNode, useState } from 'react'; import { useOnChange } from 'fumadocs-core/utils/use-on-change'; -import { SearchDialog, type SharedProps } from './search'; -import { type TagItem, TagsList } from './tag-list'; +import { + SearchDialog, + type SharedProps, + type TagItem, + TagsList, +} from './search'; export interface OramaSearchDialogProps extends SharedProps { client: OramaCloudOptions['client']; diff --git a/packages/ui/src/components/dialog/search.tsx b/packages/ui/src/components/dialog/search.tsx index 53252befa..e45e9ea2a 100644 --- a/packages/ui/src/components/dialog/search.tsx +++ b/packages/ui/src/components/dialog/search.tsx @@ -10,6 +10,7 @@ import { useRef, type ButtonHTMLAttributes, useCallback, + type HTMLAttributes, } from 'react'; import { useI18n } from '@/contexts/i18n'; import { cn } from '@/utils/cn'; @@ -23,6 +24,7 @@ import { DialogTitle, } from '@radix-ui/react-dialog'; import type { SortedResult } from 'fumadocs-core/server'; +import { cva } from 'class-variance-authority'; export type SearchLink = [name: string, href: string]; @@ -287,3 +289,68 @@ function CommandItem({ ); } + +export interface TagItem { + name: string; + value: string | undefined; + + props?: HTMLAttributes; +} + +export interface TagsListProps extends HTMLAttributes { + tag?: string; + onTagChange: (tag: string | undefined) => void; + allowClear?: boolean; + + items: TagItem[]; +} + +const itemVariants = cva( + 'rounded-md border px-2 py-0.5 text-xs font-medium text-fd-muted-foreground transition-colors', + { + variants: { + active: { + true: 'bg-fd-accent text-fd-accent-foreground', + }, + }, + }, +); + +export function TagsList({ + tag, + onTagChange, + items, + allowClear, + ...props +}: TagsListProps) { + return ( +
+ {items.map((item) => ( + + ))} + {props.children} +
+ ); +} diff --git a/packages/ui/src/components/dialog/tag-list.tsx b/packages/ui/src/components/dialog/tag-list.tsx deleted file mode 100644 index 59078b913..000000000 --- a/packages/ui/src/components/dialog/tag-list.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import type { HTMLAttributes } from 'react'; -import { cva } from 'class-variance-authority'; -import { cn } from '@/utils/cn'; - -export interface TagItem { - name: string; - value: string | undefined; - - props?: HTMLAttributes; -} - -export interface TagsListProps extends HTMLAttributes { - tag?: string; - onTagChange: (tag: string | undefined) => void; - allowClear?: boolean; - - items: TagItem[]; -} - -const itemVariants = cva( - 'rounded-md border px-2 py-0.5 text-xs font-medium text-fd-muted-foreground transition-colors', - { - variants: { - active: { - true: 'bg-fd-accent text-fd-accent-foreground', - }, - }, - }, -); - -export function TagsList({ - tag, - onTagChange, - items, - allowClear, - ...props -}: TagsListProps) { - return ( -
- {items.map((item) => ( - - ))} - {props.children} -
- ); -} diff --git a/packages/ui/src/contexts/i18n.tsx b/packages/ui/src/contexts/i18n.tsx index bce100392..71ba9d0ea 100644 --- a/packages/ui/src/contexts/i18n.tsx +++ b/packages/ui/src/contexts/i18n.tsx @@ -28,19 +28,21 @@ interface I18nContextType { locales?: LocaleItem[]; } +export const defaultTranslations: Translations = { + search: 'Search', + searchNoResult: 'No results found', + toc: 'On this page', + tocNoHeadings: 'No Headings', + lastUpdate: 'Last updated on', + chooseLanguage: 'Choose a language', + nextPage: 'Next', + previousPage: 'Previous', + chooseTheme: 'Theme', + editOnGithub: 'Edit on GitHub', +}; + export const I18nContext = createContext({ - text: { - search: 'Search', - searchNoResult: 'No results found', - toc: 'On this page', - tocNoHeadings: 'No Headings', - lastUpdate: 'Last updated on', - chooseLanguage: 'Choose a language', - nextPage: 'Next', - previousPage: 'Previous', - chooseTheme: 'Theme', - editOnGithub: 'Edit on GitHub', - }, + text: defaultTranslations, }); export function I18nLabel(props: { label: keyof Translations }): string { diff --git a/packages/ui/src/i18n.tsx b/packages/ui/src/i18n.tsx index 0fb7b12e1..ac8b70f11 100644 --- a/packages/ui/src/i18n.tsx +++ b/packages/ui/src/i18n.tsx @@ -1,11 +1,12 @@ 'use client'; -import { useCallback, type ReactNode } from 'react'; +import { useCallback, type ReactNode, useRef } from 'react'; import { useRouter, usePathname } from 'next/navigation'; import { useI18n, type Translations, I18nContext, + defaultTranslations, type LocaleItem, } from './contexts/i18n'; @@ -40,24 +41,26 @@ export function I18nProvider({ }: I18nProviderProps) { const context = useI18n(); const router = useRouter(); - const segments = usePathname() - .split('/') - .filter((v) => v.length > 0); + const pathname = usePathname(); - const onChange = useCallback( - (v: string) => { - // If locale prefix hidden - if (segments[0] !== locale) { - segments.unshift(v); - } else { - segments[0] = v; - } + const onChangeCallback = (locale: string) => { + const segments = pathname.split('/').filter((v) => v.length > 0); - router.push(`/${segments.join('/')}`); - router.refresh(); - }, - [locale, segments, router], - ); + // If locale prefix hidden + if (segments[0] !== locale) { + segments.unshift(locale); + } else { + segments[0] = locale; + } + + router.push(`/${segments.join('/')}`); + router.refresh(); + }; + + const onChangeRef = useRef(onChangeCallback); + onChangeRef.current = onChangeCallback; + + const onChange = useCallback((v: string) => onChangeRef.current(v), []); return ( Date: Fri, 27 Dec 2024 17:04:20 +0800 Subject: [PATCH 4/8] UI: improve warnings --- packages/ui/src/layouts/docs.tsx | 4 ++-- packages/ui/src/layouts/docs/shared.tsx | 15 +++++++++++++++ packages/ui/src/layouts/notebook.tsx | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/layouts/docs.tsx b/packages/ui/src/layouts/docs.tsx index 81f5fc0ff..a058976cf 100644 --- a/packages/ui/src/layouts/docs.tsx +++ b/packages/ui/src/layouts/docs.tsx @@ -35,13 +35,13 @@ import { } from '@/components/layout/search-toggle'; import { SearchOnly } from '@/contexts/search'; import { + checkPageTree, getSidebarTabsFromOptions, layoutVariables, SidebarLinkItem, type SidebarOptions, } from '@/layouts/docs/shared'; import { type PageStyles, StylesProvider } from '@/contexts/layout'; -import { notFound } from 'next/navigation'; export interface DocsLayoutProps extends BaseLayoutProps { tree: PageTree.Root; @@ -71,9 +71,9 @@ export function DocsLayout({ i18n = false, ...props }: DocsLayoutProps): ReactNode { + checkPageTree(props.tree); const links = getLinks(props.links ?? [], props.githubUrl); const Aside = collapsible ? CollapsibleSidebar : Sidebar; - if (props.tree === undefined) notFound(); const tabs = getSidebarTabsFromOptions(tabOptions, props.tree) ?? []; const variables = cn( diff --git a/packages/ui/src/layouts/docs/shared.tsx b/packages/ui/src/layouts/docs/shared.tsx index 6bacbdba1..061043623 100644 --- a/packages/ui/src/layouts/docs/shared.tsx +++ b/packages/ui/src/layouts/docs/shared.tsx @@ -12,6 +12,7 @@ import { buttonVariants } from '@/components/ui/button'; import type { PageTree } from 'fumadocs-core/server'; import type { FC, ReactNode } from 'react'; import type { Option } from '@/components/layout/root-toggle'; +import { notFound } from 'next/navigation'; export const layoutVariables = { '--fd-layout-offset': 'max(calc(50vw - var(--fd-layout-width) / 2), 0px)', @@ -99,6 +100,20 @@ export function SidebarLinkItem({ item }: { item: LinkItemType }) { ); } +export function checkPageTree(passed: unknown) { + if (!passed) notFound(); + if ( + typeof passed === 'object' && + 'children' in passed && + Array.isArray(passed) + ) + return; + + throw new Error( + 'You passed an invalid page tree to ``. Check your usage in layout.tsx if you have enabled i18n.', + ); +} + export function getSidebarTabsFromOptions( options: SidebarOptions['tabs'], tree: PageTree.Root, diff --git a/packages/ui/src/layouts/notebook.tsx b/packages/ui/src/layouts/notebook.tsx index d4f7160f5..a2e507676 100644 --- a/packages/ui/src/layouts/notebook.tsx +++ b/packages/ui/src/layouts/notebook.tsx @@ -13,7 +13,6 @@ import { SidebarViewport, SidebarPageTree, } from '@/layouts/docs/sidebar'; -import { notFound } from 'next/navigation'; import { RootToggle } from '@/components/layout/root-toggle'; import { TreeContextProvider } from '@/contexts/tree'; import { NavProvider, Title } from '@/components/layout/nav'; @@ -35,6 +34,7 @@ import { PopoverTrigger, } from '@/components/ui/popover'; import { + checkPageTree, getSidebarTabsFromOptions, layoutVariables, SidebarLinkItem, @@ -65,9 +65,9 @@ export function DocsLayout({ i18n = false, ...props }: DocsLayoutProps): ReactNode { + checkPageTree(props.tree); const links = getLinks(props.links ?? [], props.githubUrl); const Aside = sidebarCollapsible ? CollapsibleSidebar : Sidebar; - if (props.tree === undefined) notFound(); const tabs = getSidebarTabsFromOptions(tabOptions, props.tree) ?? []; const variables = cn( From 6e2e2bc919626ae3e60d91980d8346698463142c Mon Sep 17 00:00:00 2001 From: Fuma Nama <76240755+fuma-nama@users.noreply.github.com> Date: Fri, 27 Dec 2024 17:07:34 +0800 Subject: [PATCH 5/8] Version Packages (#1189) --- .changeset/rare-garlics-hammer.md | 7 ------- packages/cli/CHANGELOG.md | 6 ++++++ packages/cli/package.json | 2 +- packages/core/CHANGELOG.md | 6 ++++++ packages/core/package.json | 2 +- packages/create-app/CHANGELOG.md | 2 ++ packages/create-app/package.json | 2 +- packages/openapi/CHANGELOG.md | 8 ++++++++ packages/openapi/package.json | 2 +- packages/ui/CHANGELOG.md | 8 ++++++++ packages/ui/package.json | 2 +- 11 files changed, 35 insertions(+), 12 deletions(-) delete mode 100644 .changeset/rare-garlics-hammer.md diff --git a/.changeset/rare-garlics-hammer.md b/.changeset/rare-garlics-hammer.md deleted file mode 100644 index c61705ae7..000000000 --- a/.changeset/rare-garlics-hammer.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'fumadocs-core': patch -'@fumadocs/cli': patch -'fumadocs-ui': patch ---- - -Improve i18n api diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 442c04523..309c8f7f3 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # fumadocs +## 0.0.6 + +### Patch Changes + +- 969da26: Improve i18n api + ## 0.0.5 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 76b6cc529..2131775bc 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@fumadocs/cli", - "version": "0.0.5", + "version": "0.0.6", "description": "The CLI tool for Fumadocs", "keywords": [ "NextJs", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 67b011767..4406a5b83 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # next-docs-zeta +## 14.6.5 + +### Patch Changes + +- 969da26: Improve i18n api + ## 14.6.4 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index a3ffdf7b7..8e7f7496d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-core", - "version": "14.6.4", + "version": "14.6.5", "description": "The library for building a documentation website in Next.js", "keywords": [ "NextJs", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index f7126de85..974faf843 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,7 @@ # create-next-docs-app +## 14.6.5 + ## 14.6.4 ## 14.6.3 diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 8e8ef1f5a..4e78f7ed7 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "create-fumadocs-app", - "version": "14.6.4", + "version": "14.6.5", "description": "Create a new documentation site with Fumadocs", "keywords": [ "NextJs", diff --git a/packages/openapi/CHANGELOG.md b/packages/openapi/CHANGELOG.md index f5698ed10..42d93fd2e 100644 --- a/packages/openapi/CHANGELOG.md +++ b/packages/openapi/CHANGELOG.md @@ -1,5 +1,13 @@ # @fuma-docs/openapi +## 5.10.3 + +### Patch Changes + +- Updated dependencies [969da26] + - fumadocs-core@14.6.5 + - fumadocs-ui@14.6.5 + ## 5.10.2 ### Patch Changes diff --git a/packages/openapi/package.json b/packages/openapi/package.json index c25350037..6d739865f 100644 --- a/packages/openapi/package.json +++ b/packages/openapi/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-openapi", - "version": "5.10.2", + "version": "5.10.3", "description": "Generate MDX docs for your OpenAPI spec", "keywords": [ "NextJs", diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index c2723ecc4..2cb153aa3 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,13 @@ # next-docs-ui +## 14.6.5 + +### Patch Changes + +- 969da26: Improve i18n api +- Updated dependencies [969da26] + - fumadocs-core@14.6.5 + ## 14.6.4 ### Patch Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index 7a5cf8eeb..daeecac09 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-ui", - "version": "14.6.4", + "version": "14.6.5", "description": "The framework for building a documentation website in Next.js", "keywords": [ "NextJs", From 6a0bfe5a6107b444f46cc68d870c6874a0532f2d Mon Sep 17 00:00:00 2001 From: Fuma Nama <76240755+fuma-nama@users.noreply.github.com> Date: Fri, 27 Dec 2024 19:00:48 +0800 Subject: [PATCH 6/8] Update shared.tsx --- packages/ui/src/layouts/docs/shared.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/layouts/docs/shared.tsx b/packages/ui/src/layouts/docs/shared.tsx index 061043623..3ccb7b5aa 100644 --- a/packages/ui/src/layouts/docs/shared.tsx +++ b/packages/ui/src/layouts/docs/shared.tsx @@ -105,7 +105,7 @@ export function checkPageTree(passed: unknown) { if ( typeof passed === 'object' && 'children' in passed && - Array.isArray(passed) + Array.isArray(passed.children) ) return; From 9c930ea2944c04c295542ce50d41c95eabce0ffe Mon Sep 17 00:00:00 2001 From: Fuma Nama <76240755+fuma-nama@users.noreply.github.com> Date: Fri, 27 Dec 2024 19:02:52 +0800 Subject: [PATCH 7/8] Add changesets --- .changeset/rare-garlics-hammer.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changeset/rare-garlics-hammer.md diff --git a/.changeset/rare-garlics-hammer.md b/.changeset/rare-garlics-hammer.md new file mode 100644 index 000000000..b8c3cd225 --- /dev/null +++ b/.changeset/rare-garlics-hammer.md @@ -0,0 +1,4 @@ +--- +'fumadocs-ui': patch +--- +fix runtime error \ No newline at end of file From d0da3c836cf16a85187f652221cd27bdb7d40dcf Mon Sep 17 00:00:00 2001 From: Fuma Nama <76240755+fuma-nama@users.noreply.github.com> Date: Fri, 27 Dec 2024 19:07:17 +0800 Subject: [PATCH 8/8] Version Packages (#1191) --- .changeset/rare-garlics-hammer.md | 4 ---- packages/core/CHANGELOG.md | 2 ++ packages/core/package.json | 2 +- packages/create-app/CHANGELOG.md | 2 ++ packages/create-app/package.json | 2 +- packages/openapi/CHANGELOG.md | 8 ++++++++ packages/openapi/package.json | 2 +- packages/ui/CHANGELOG.md | 7 +++++++ packages/ui/package.json | 2 +- 9 files changed, 23 insertions(+), 8 deletions(-) delete mode 100644 .changeset/rare-garlics-hammer.md diff --git a/.changeset/rare-garlics-hammer.md b/.changeset/rare-garlics-hammer.md deleted file mode 100644 index b8c3cd225..000000000 --- a/.changeset/rare-garlics-hammer.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -'fumadocs-ui': patch ---- -fix runtime error \ No newline at end of file diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 4406a5b83..4590856c3 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,7 @@ # next-docs-zeta +## 14.6.6 + ## 14.6.5 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 8e7f7496d..b54384be3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-core", - "version": "14.6.5", + "version": "14.6.6", "description": "The library for building a documentation website in Next.js", "keywords": [ "NextJs", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 974faf843..77ff7fc9b 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,7 @@ # create-next-docs-app +## 14.6.6 + ## 14.6.5 ## 14.6.4 diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 4e78f7ed7..85aefa496 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "create-fumadocs-app", - "version": "14.6.5", + "version": "14.6.6", "description": "Create a new documentation site with Fumadocs", "keywords": [ "NextJs", diff --git a/packages/openapi/CHANGELOG.md b/packages/openapi/CHANGELOG.md index 42d93fd2e..02fc6bf22 100644 --- a/packages/openapi/CHANGELOG.md +++ b/packages/openapi/CHANGELOG.md @@ -1,5 +1,13 @@ # @fuma-docs/openapi +## 5.10.4 + +### Patch Changes + +- Updated dependencies [9c930ea] + - fumadocs-ui@14.6.6 + - fumadocs-core@14.6.6 + ## 5.10.3 ### Patch Changes diff --git a/packages/openapi/package.json b/packages/openapi/package.json index 6d739865f..40ad2a3b1 100644 --- a/packages/openapi/package.json +++ b/packages/openapi/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-openapi", - "version": "5.10.3", + "version": "5.10.4", "description": "Generate MDX docs for your OpenAPI spec", "keywords": [ "NextJs", diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 2cb153aa3..f3ccda611 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,12 @@ # next-docs-ui +## 14.6.6 + +### Patch Changes + +- 9c930ea: fix runtime error + - fumadocs-core@14.6.6 + ## 14.6.5 ### Patch Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index daeecac09..405e961d1 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-ui", - "version": "14.6.5", + "version": "14.6.6", "description": "The framework for building a documentation website in Next.js", "keywords": [ "NextJs",