@vkuttyp/docus 5.4.6

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 (78) hide show
  1. package/README.md +116 -0
  2. package/app/app.config.ts +33 -0
  3. package/app/app.vue +79 -0
  4. package/app/components/IconMenuToggle.vue +83 -0
  5. package/app/components/LanguageSelect.vue +83 -0
  6. package/app/components/OgImage/OgImageDocs.vue +78 -0
  7. package/app/components/OgImage/OgImageLanding.vue +75 -0
  8. package/app/components/app/AppFooter.vue +11 -0
  9. package/app/components/app/AppFooterLeft.vue +5 -0
  10. package/app/components/app/AppFooterRight.vue +30 -0
  11. package/app/components/app/AppHeader.vue +82 -0
  12. package/app/components/app/AppHeaderBody.vue +13 -0
  13. package/app/components/app/AppHeaderCTA.vue +3 -0
  14. package/app/components/app/AppHeaderCenter.vue +10 -0
  15. package/app/components/app/AppHeaderLogo.vue +16 -0
  16. package/app/components/docs/DocsAsideLeftBody.vue +12 -0
  17. package/app/components/docs/DocsAsideLeftTop.vue +3 -0
  18. package/app/components/docs/DocsAsideRightBottom.vue +18 -0
  19. package/app/components/docs/DocsPageHeaderLinks.vue +96 -0
  20. package/app/composables/useDocusI18n.ts +35 -0
  21. package/app/error.vue +79 -0
  22. package/app/layouts/default.vue +5 -0
  23. package/app/layouts/docs.vue +15 -0
  24. package/app/pages/[[lang]]/[...slug].vue +145 -0
  25. package/app/plugins/i18n.ts +40 -0
  26. package/app/templates/landing.vue +44 -0
  27. package/app/types/index.d.ts +42 -0
  28. package/app/utils/navigation.ts +7 -0
  29. package/app/utils/prerender.ts +12 -0
  30. package/content.config.ts +67 -0
  31. package/i18n/locales/ar.json +22 -0
  32. package/i18n/locales/be.json +23 -0
  33. package/i18n/locales/bg.json +22 -0
  34. package/i18n/locales/bn.json +22 -0
  35. package/i18n/locales/ca.json +22 -0
  36. package/i18n/locales/ckb.json +18 -0
  37. package/i18n/locales/cs.json +22 -0
  38. package/i18n/locales/da.json +22 -0
  39. package/i18n/locales/de.json +22 -0
  40. package/i18n/locales/el.json +22 -0
  41. package/i18n/locales/en.json +22 -0
  42. package/i18n/locales/es.json +22 -0
  43. package/i18n/locales/et.json +22 -0
  44. package/i18n/locales/fi.json +22 -0
  45. package/i18n/locales/fr.json +22 -0
  46. package/i18n/locales/he.json +22 -0
  47. package/i18n/locales/hi.json +22 -0
  48. package/i18n/locales/hy.json +22 -0
  49. package/i18n/locales/it.json +22 -0
  50. package/i18n/locales/ja.json +22 -0
  51. package/i18n/locales/kk.json +22 -0
  52. package/i18n/locales/km.json +22 -0
  53. package/i18n/locales/ko.json +22 -0
  54. package/i18n/locales/ky.json +22 -0
  55. package/i18n/locales/lb.json +22 -0
  56. package/i18n/locales/ms.json +22 -0
  57. package/i18n/locales/nb.json +22 -0
  58. package/i18n/locales/nl.json +22 -0
  59. package/i18n/locales/pl.json +23 -0
  60. package/i18n/locales/ro.json +22 -0
  61. package/i18n/locales/ru.json +23 -0
  62. package/i18n/locales/sl.json +22 -0
  63. package/i18n/locales/sv.json +22 -0
  64. package/i18n/locales/uk.json +22 -0
  65. package/i18n/locales/ur.json +22 -0
  66. package/i18n/locales/vi.json +22 -0
  67. package/modules/config.ts +116 -0
  68. package/modules/css.ts +32 -0
  69. package/modules/routing.ts +41 -0
  70. package/nuxt.config.ts +84 -0
  71. package/nuxt.schema.ts +218 -0
  72. package/package.json +55 -0
  73. package/server/mcp/tools/get-page.ts +60 -0
  74. package/server/mcp/tools/list-pages.ts +60 -0
  75. package/server/routes/raw/[...slug].md.get.ts +45 -0
  76. package/server/utils/content.ts +37 -0
  77. package/utils/git.ts +110 -0
  78. package/utils/meta.ts +29 -0
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <div />
3
+ </template>
@@ -0,0 +1,10 @@
1
+ <template>
2
+ <UContentSearchButton
3
+ :collapsed="false"
4
+ class="w-full"
5
+ variant="soft"
6
+ :ui="{
7
+ leadingIcon: 'size-4 mr-1',
8
+ }"
9
+ />
10
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ const appConfig = useAppConfig()
3
+ </script>
4
+
5
+ <template>
6
+ <UColorModeImage
7
+ v-if="appConfig.header?.logo?.dark || appConfig.header?.logo?.light"
8
+ :light="appConfig.header?.logo?.light || appConfig.header?.logo?.dark"
9
+ :dark="appConfig.header?.logo?.dark || appConfig.header?.logo?.light"
10
+ :alt="appConfig.header?.logo?.alt || appConfig.header?.title"
11
+ class="h-6 w-auto shrink-0"
12
+ />
13
+ <span v-else>
14
+ {{ appConfig.header?.title || '{appConfig.header.title}' }}
15
+ </span>
16
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import type { ContentNavigationItem } from '@nuxt/content'
3
+
4
+ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
5
+ </script>
6
+
7
+ <template>
8
+ <UContentNavigation
9
+ highlight
10
+ :navigation="navigation"
11
+ />
12
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <div />
3
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ const appConfig = useAppConfig()
3
+ const { t } = useDocusI18n()
4
+ </script>
5
+
6
+ <template>
7
+ <div
8
+ v-if="appConfig.toc?.bottom?.links?.length"
9
+ class="hidden lg:block space-y-6"
10
+ >
11
+ <USeparator type="dashed" />
12
+
13
+ <UPageLinks
14
+ :title="appConfig.toc?.bottom?.title || t('docs.links')"
15
+ :links="appConfig.toc?.bottom?.links"
16
+ />
17
+ </div>
18
+ </template>
@@ -0,0 +1,96 @@
1
+ <script setup lang="ts">
2
+ import { useClipboard } from '@vueuse/core'
3
+ import { useRuntimeConfig } from '#imports'
4
+
5
+ const route = useRoute()
6
+ const toast = useToast()
7
+ const appBaseURL = useRuntimeConfig().app?.baseURL || '/'
8
+
9
+ const { copy, copied } = useClipboard()
10
+ const { t } = useDocusI18n()
11
+
12
+ const markdownLink = computed(() => `${window?.location?.origin}${appBaseURL}raw${route.path}.md`)
13
+ const items = [
14
+ [{
15
+ label: t('docs.copy.link'),
16
+ icon: 'i-lucide-link',
17
+ onSelect() {
18
+ copy(markdownLink.value)
19
+ },
20
+ },
21
+ {
22
+ label: t('docs.copy.view'),
23
+ icon: 'i-simple-icons:markdown',
24
+ target: '_blank',
25
+ to: markdownLink.value,
26
+ },
27
+ {
28
+ label: t('docs.copy.gpt'),
29
+ icon: 'i-simple-icons:openai',
30
+ target: '_blank',
31
+ to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
32
+ },
33
+ {
34
+ label: t('docs.copy.claude'),
35
+ icon: 'i-simple-icons:anthropic',
36
+ target: '_blank',
37
+ to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
38
+ }],
39
+ [
40
+ {
41
+ label: 'Copy MCP Server URL',
42
+ icon: 'i-lucide-link',
43
+ onSelect() {
44
+ copy(`${window?.location?.origin}${appBaseURL}mcp`)
45
+ toast.add({
46
+ title: 'Copied to clipboard',
47
+ icon: 'i-lucide-check-circle',
48
+ })
49
+ },
50
+ },
51
+ {
52
+ label: 'Add MCP Server',
53
+ icon: 'i-simple-icons:cursor',
54
+ target: '_blank',
55
+ to: `/mcp/deeplink`,
56
+ },
57
+ ],
58
+ ]
59
+
60
+ async function copyPage() {
61
+ const page = await $fetch<string>(`/raw${route.path}.md`)
62
+ copy(page)
63
+ }
64
+ </script>
65
+
66
+ <template>
67
+ <UFieldGroup size="sm">
68
+ <UButton
69
+ :label="t('docs.copy.page')"
70
+ :icon="copied ? 'i-lucide-check' : 'i-lucide-copy'"
71
+ color="neutral"
72
+ variant="soft"
73
+ :ui="{
74
+ leadingIcon: 'text-neutral size-3.5',
75
+ }"
76
+ @click="copyPage"
77
+ />
78
+
79
+ <UDropdownMenu
80
+ size="sm"
81
+ :items="items"
82
+ :content="{
83
+ align: 'end',
84
+ side: 'bottom',
85
+ sideOffset: 8,
86
+ }"
87
+ >
88
+ <UButton
89
+ icon="i-lucide-chevron-down"
90
+ color="neutral"
91
+ variant="soft"
92
+ class="border-l border-muted"
93
+ />
94
+ </UDropdownMenu>
95
+ </UFieldGroup>
96
+ </template>
@@ -0,0 +1,35 @@
1
+ import type { LocaleObject } from '@nuxtjs/i18n'
2
+
3
+ export const useDocusI18n = () => {
4
+ const config = useRuntimeConfig().public
5
+ const isEnabled = ref(!!config.i18n)
6
+
7
+ if (!isEnabled.value) {
8
+ const locale = useNuxtApp().$locale as string
9
+ const localeMessages = useNuxtApp().$localeMessages as Record<string, unknown>
10
+
11
+ return {
12
+ isEnabled,
13
+ locale: ref(locale),
14
+ locales: [],
15
+ localePath: (path: string) => path,
16
+ switchLocalePath: () => {},
17
+ t: (key: string): string => {
18
+ const path = key.split('.')
19
+ return path.reduce((acc: unknown, curr) => (acc as Record<string, unknown>)?.[curr], localeMessages) as string
20
+ },
21
+ }
22
+ }
23
+
24
+ const { locale, t } = useI18n()
25
+ const filteredLocales = (config.docus as { filteredLocales: LocaleObject<string>[] })?.filteredLocales || []
26
+
27
+ return {
28
+ isEnabled,
29
+ locale,
30
+ locales: filteredLocales,
31
+ t,
32
+ localePath: useLocalePath(),
33
+ switchLocalePath: useSwitchLocalePath(),
34
+ }
35
+ }
package/app/error.vue ADDED
@@ -0,0 +1,79 @@
1
+ <script setup lang="ts">
2
+ import type { NuxtError } from '#app'
3
+ import type { ContentNavigationItem, PageCollections } from '@nuxt/content'
4
+ import * as nuxtUiLocales from '@nuxt/ui/locale'
5
+
6
+ const props = defineProps<{
7
+ error: NuxtError
8
+ }>()
9
+
10
+ const { locale, locales, isEnabled, t, switchLocalePath } = useDocusI18n()
11
+
12
+ const nuxtUiLocale = computed(() => nuxtUiLocales[locale.value as keyof typeof nuxtUiLocales] || nuxtUiLocales.en)
13
+ const lang = computed(() => nuxtUiLocale.value.code)
14
+ const dir = computed(() => nuxtUiLocale.value.dir)
15
+
16
+ useHead({
17
+ htmlAttrs: {
18
+ lang,
19
+ dir,
20
+ },
21
+ })
22
+
23
+ const localizedError = computed(() => {
24
+ return {
25
+ ...props.error,
26
+ statusMessage: t('common.error.title'),
27
+ message: t('common.error.description'),
28
+ }
29
+ })
30
+
31
+ useSeoMeta({
32
+ title: () => t('common.error.title'),
33
+ description: () => t('common.error.description'),
34
+ })
35
+
36
+ if (isEnabled.value) {
37
+ const route = useRoute()
38
+ const defaultLocale = useRuntimeConfig().public.i18n.defaultLocale!
39
+ onMounted(() => {
40
+ const currentLocale = route.path.split('/')[1]
41
+ if (!locales.some(locale => locale.code === currentLocale)) {
42
+ return navigateTo(switchLocalePath(defaultLocale) as string)
43
+ }
44
+ })
45
+ }
46
+
47
+ const collectionName = computed(() => isEnabled.value ? `docs_${locale.value}` : 'docs')
48
+
49
+ const { data: navigation } = await useAsyncData(`navigation_${collectionName.value}`, () => queryCollectionNavigation(collectionName.value as keyof PageCollections), {
50
+ transform: (data: ContentNavigationItem[]) => {
51
+ const rootResult = data.find(item => item.path === '/docs')?.children || data || []
52
+
53
+ return rootResult.find(item => item.path === `/${locale.value}`)?.children || rootResult
54
+ },
55
+ watch: [locale],
56
+ })
57
+ const { data: files } = useLazyAsyncData(`search_${collectionName.value}`, () => queryCollectionSearchSections(collectionName.value as keyof PageCollections), {
58
+ server: false,
59
+ })
60
+
61
+ provide('navigation', navigation)
62
+ </script>
63
+
64
+ <template>
65
+ <UApp :locale="nuxtUiLocale">
66
+ <AppHeader />
67
+
68
+ <UError :error="localizedError" />
69
+
70
+ <AppFooter />
71
+
72
+ <ClientOnly>
73
+ <LazyUContentSearch
74
+ :files="files"
75
+ :navigation="navigation"
76
+ />
77
+ </ClientOnly>
78
+ </UApp>
79
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <UMain>
3
+ <slot />
4
+ </UMain>
5
+ </template>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <UMain>
3
+ <UContainer>
4
+ <UPage>
5
+ <template #left>
6
+ <UPageAside>
7
+ <DocsAsideLeftTop />
8
+ <DocsAsideLeftBody />
9
+ </UPageAside>
10
+ </template>
11
+ <slot />
12
+ </UPage>
13
+ </UContainer>
14
+ </UMain>
15
+ </template>
@@ -0,0 +1,145 @@
1
+ <script setup lang="ts">
2
+ import { kebabCase } from 'scule'
3
+ import type { ContentNavigationItem, Collections, DocsCollectionItem } from '@nuxt/content'
4
+ import { findPageHeadline } from '@nuxt/content/utils'
5
+ import { addPrerenderPath } from '../../utils/prerender'
6
+
7
+ definePageMeta({
8
+ layout: 'docs',
9
+ })
10
+
11
+ const route = useRoute()
12
+ const { locale, isEnabled, t } = useDocusI18n()
13
+ const appConfig = useAppConfig()
14
+ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
15
+
16
+ const collectionName = computed(() => isEnabled.value ? `docs_${locale.value}` : 'docs')
17
+
18
+ const [{ data: page }, { data: surround }] = await Promise.all([
19
+ useAsyncData(kebabCase(route.path), () => queryCollection(collectionName.value as keyof Collections).path(route.path).first() as Promise<DocsCollectionItem>),
20
+ useAsyncData(`${kebabCase(route.path)}-surround`, () => {
21
+ return queryCollectionItemSurroundings(collectionName.value as keyof Collections, route.path, {
22
+ fields: ['description'],
23
+ })
24
+ }),
25
+ ])
26
+
27
+ if (!page.value) {
28
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
29
+ }
30
+
31
+ // Add the page path to the prerender list
32
+ addPrerenderPath(`/raw${route.path}.md`)
33
+
34
+ const title = page.value.seo?.title || page.value.title
35
+ const description = page.value.seo?.description || page.value.description
36
+
37
+ useSeoMeta({
38
+ title,
39
+ ogTitle: title,
40
+ description,
41
+ ogDescription: description,
42
+ })
43
+
44
+ const headline = ref(findPageHeadline(navigation?.value, page.value?.path))
45
+ watch(() => navigation?.value, () => {
46
+ headline.value = findPageHeadline(navigation?.value, page.value?.path) || headline.value
47
+ })
48
+
49
+ defineOgImageComponent('Docs', {
50
+ headline: headline.value,
51
+ })
52
+
53
+ const github = computed(() => appConfig.github ? appConfig.github : null)
54
+
55
+ const editLink = computed(() => {
56
+ if (!github.value) {
57
+ return
58
+ }
59
+
60
+ return [
61
+ github.value.url,
62
+ 'edit',
63
+ github.value.branch,
64
+ github.value.rootDir,
65
+ 'content',
66
+ `${page.value?.stem}.${page.value?.extension}`,
67
+ ].filter(Boolean).join('/')
68
+ })
69
+ </script>
70
+
71
+ <template>
72
+ <UPage v-if="page">
73
+ <UPageHeader
74
+ :title="page.title"
75
+ :description="page.description"
76
+ :headline="headline"
77
+ :ui="{
78
+ wrapper: 'flex-row items-center flex-wrap justify-between',
79
+ }"
80
+ >
81
+ <template #links>
82
+ <UButton
83
+ v-for="(link, index) in (page as DocsCollectionItem).links"
84
+ :key="index"
85
+ size="sm"
86
+ v-bind="link"
87
+ />
88
+
89
+ <DocsPageHeaderLinks />
90
+ </template>
91
+ </UPageHeader>
92
+
93
+ <UPageBody>
94
+ <ContentRenderer
95
+ v-if="page"
96
+ :value="page"
97
+ />
98
+
99
+ <USeparator>
100
+ <div
101
+ v-if="github"
102
+ class="flex items-center gap-2 text-sm text-muted"
103
+ >
104
+ <UButton
105
+ variant="link"
106
+ color="neutral"
107
+ :to="editLink"
108
+ target="_blank"
109
+ icon="i-lucide-pen"
110
+ :ui="{ leadingIcon: 'size-4' }"
111
+ >
112
+ {{ t('docs.edit') }}
113
+ </UButton>
114
+ <span>{{ t('common.or') }}</span>
115
+ <UButton
116
+ variant="link"
117
+ color="neutral"
118
+ :to="`${github.url}/issues/new/choose`"
119
+ target="_blank"
120
+ icon="i-lucide-alert-circle"
121
+ :ui="{ leadingIcon: 'size-4' }"
122
+ >
123
+ {{ t('docs.report') }}
124
+ </UButton>
125
+ </div>
126
+ </USeparator>
127
+ <UContentSurround :surround="surround" />
128
+ </UPageBody>
129
+
130
+ <template
131
+ v-if="page?.body?.toc?.links?.length"
132
+ #right
133
+ >
134
+ <UContentToc
135
+ highlight
136
+ :title="appConfig.toc?.title || t('docs.toc')"
137
+ :links="page.body?.toc?.links"
138
+ >
139
+ <template #bottom>
140
+ <DocsAsideRightBottom />
141
+ </template>
142
+ </UContentToc>
143
+ </template>
144
+ </UPage>
145
+ </template>
@@ -0,0 +1,40 @@
1
+ import en from '../../i18n/locales/en.json'
2
+ import type { RouteLocationNormalized } from 'vue-router'
3
+
4
+ export default defineNuxtPlugin(async () => {
5
+ const nuxtApp = useNuxtApp()
6
+
7
+ const i18nConfig = nuxtApp.$config.public.i18n
8
+
9
+ // If i18n is not enabled, fetch and provide the configured locale in app config
10
+ if (!i18nConfig) {
11
+ let locale = 'en'
12
+ let resolvedMessages: Record<string, unknown> = en
13
+
14
+ const appConfig = useAppConfig()
15
+ const configuredLocale = appConfig.docus.locale
16
+ if (configuredLocale !== 'en') {
17
+ const localeMessages = await import(`../../i18n/locales/${configuredLocale}.json`)
18
+ if (!localeMessages) {
19
+ console.warn(`[Docus] Missing locale file for "${configuredLocale}". Falling back to "en".`)
20
+ }
21
+ else {
22
+ locale = configuredLocale
23
+ resolvedMessages = localeMessages
24
+ }
25
+ }
26
+
27
+ nuxtApp.provide('locale', locale)
28
+ nuxtApp.provide('localeMessages', resolvedMessages)
29
+
30
+ return
31
+ }
32
+
33
+ addRouteMiddleware((to: RouteLocationNormalized) => {
34
+ if (to.path === '/') {
35
+ const cookieLocale = useCookie('i18n_redirected').value || i18nConfig.defaultLocale || 'en'
36
+
37
+ return navigateTo(`/${cookieLocale}`)
38
+ }
39
+ })
40
+ })
@@ -0,0 +1,44 @@
1
+ <script setup lang="ts">
2
+ import type { Collections } from '@nuxt/content'
3
+
4
+ const route = useRoute()
5
+ const { locale, isEnabled } = useDocusI18n()
6
+
7
+ // Dynamic collection name based on i18n status
8
+ const collectionName = computed(() => isEnabled.value ? `landing_${locale.value}` : 'landing')
9
+
10
+ const { data: page } = await useAsyncData(collectionName.value, () => queryCollection(collectionName.value as keyof Collections).path(route.path).first())
11
+ if (!page.value) {
12
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
13
+ }
14
+
15
+ const title = page.value.seo?.title || page.value.title
16
+ const description = page.value.seo?.description || page.value.description
17
+
18
+ useSeoMeta({
19
+ title,
20
+ description,
21
+ ogTitle: title,
22
+ ogDescription: description,
23
+ })
24
+
25
+ if (page.value?.seo?.ogImage) {
26
+ useSeoMeta({
27
+ ogImage: page.value.seo.ogImage,
28
+ twitterImage: page.value.seo.ogImage,
29
+ })
30
+ }
31
+ else {
32
+ defineOgImageComponent('Landing', {
33
+ title,
34
+ description,
35
+ })
36
+ }
37
+ </script>
38
+
39
+ <template>
40
+ <ContentRenderer
41
+ v-if="page"
42
+ :value="page"
43
+ />
44
+ </template>
@@ -0,0 +1,42 @@
1
+ declare module 'nuxt/schema' {
2
+ interface AppConfig {
3
+ docus: {
4
+ locale: string
5
+ }
6
+ seo: {
7
+ titleTemplate: string
8
+ title: string
9
+ description: string
10
+ }
11
+ header: {
12
+ title: string
13
+ logo: {
14
+ light: string
15
+ dark: string
16
+ alt: string
17
+ }
18
+ }
19
+ socials: Record<string, string>
20
+ toc: {
21
+ title: string
22
+ bottom: {
23
+ title: string
24
+ links: {
25
+ icon: string
26
+ label: string
27
+ to: string
28
+ target: string
29
+ }[]
30
+ }
31
+ }
32
+ github: {
33
+ owner: string
34
+ name: string
35
+ url: string
36
+ branch: string
37
+ rootDir?: string
38
+ } | false
39
+ }
40
+ }
41
+
42
+ export {}
@@ -0,0 +1,7 @@
1
+ import type { ContentNavigationItem } from '@nuxt/content'
2
+
3
+ export const flattenNavigation = (items?: ContentNavigationItem[]): ContentNavigationItem[] => items?.flatMap(
4
+ item => item.children
5
+ ? flattenNavigation(item.children)
6
+ : [item],
7
+ ) || []
@@ -0,0 +1,12 @@
1
+ export const addPrerenderPath = (path: string) => {
2
+ const event = useRequestEvent()
3
+ if (event) {
4
+ event.node.res.setHeader(
5
+ 'x-nitro-prerender',
6
+ [
7
+ event.node.res.getHeader('x-nitro-prerender'),
8
+ path,
9
+ ].filter(Boolean).join(','),
10
+ )
11
+ }
12
+ }
@@ -0,0 +1,67 @@
1
+ import type { DefinedCollection } from '@nuxt/content'
2
+ import { defineContentConfig, defineCollection, z } from '@nuxt/content'
3
+ import { useNuxt } from '@nuxt/kit'
4
+ import { joinURL } from 'ufo'
5
+
6
+ const { options } = useNuxt()
7
+ const cwd = joinURL(options.rootDir, 'content')
8
+ const locales = options.i18n?.locales
9
+
10
+ const createDocsSchema = () => z.object({
11
+ links: z.array(z.object({
12
+ label: z.string(),
13
+ icon: z.string(),
14
+ to: z.string(),
15
+ target: z.string().optional(),
16
+ })).optional(),
17
+ })
18
+
19
+ let collections: Record<string, DefinedCollection>
20
+
21
+ if (locales && Array.isArray(locales)) {
22
+ collections = {}
23
+ for (const locale of locales) {
24
+ const code = typeof locale === 'string' ? locale : locale.code
25
+
26
+ collections[`landing_${code}`] = defineCollection({
27
+ type: 'page',
28
+ source: {
29
+ cwd,
30
+ include: `${code}/index.md`,
31
+ },
32
+ })
33
+
34
+ collections[`docs_${code}`] = defineCollection({
35
+ type: 'page',
36
+ source: {
37
+ cwd,
38
+ include: `${code}/**/*`,
39
+ prefix: `/${code}`,
40
+ exclude: [`${code}/index.md`],
41
+ },
42
+ schema: createDocsSchema(),
43
+ })
44
+ }
45
+ }
46
+ else {
47
+ collections = {
48
+ landing: defineCollection({
49
+ type: 'page',
50
+ source: {
51
+ cwd,
52
+ include: 'index.md',
53
+ },
54
+ }),
55
+ docs: defineCollection({
56
+ type: 'page',
57
+ source: {
58
+ cwd,
59
+ include: '**',
60
+ exclude: ['index.md'],
61
+ },
62
+ schema: createDocsSchema(),
63
+ }),
64
+ }
65
+ }
66
+
67
+ export default defineContentConfig({ collections })
@@ -0,0 +1,22 @@
1
+ {
2
+ "common": {
3
+ "or": "أو",
4
+ "error": {
5
+ "title": "الصفحة غير موجودة",
6
+ "description": "نأسف، لكن الصفحة التي تبحث عنها غير موجودة."
7
+ }
8
+ },
9
+ "docs": {
10
+ "copy": {
11
+ "page": "نسخ الصفحة",
12
+ "link": "نسخ صفحة Markdown",
13
+ "view": "عرض كـ Markdown",
14
+ "gpt": "فتح في ChatGPT",
15
+ "claude": "فتح في Claude"
16
+ },
17
+ "links": "المجتمع",
18
+ "toc": "في هذه الصفحة",
19
+ "report": "الإبلاغ عن مشكلة",
20
+ "edit": "تحرير هذه الصفحة"
21
+ }
22
+ }