feat(docs): zh-Hans for docs

This commit is contained in:
Neko Ayaka
2025-07-15 15:53:43 +08:00
parent e2adbafa97
commit cb0d767af2
151 changed files with 1383 additions and 208 deletions
+6 -4
View File
@@ -1,17 +1,19 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useEditLink } from '../composables/edit-link'
import { discord, github, sponsor } from '../meta'
const editLink = useEditLink()
const { t } = useI18n()
const links = computed(() => [
{ label: editLink.value.text, url: editLink.value.url, icon: 'lucide:pencil-line' },
{ label: 'Star on GitHub', url: github, icon: 'lucide:star' },
{ label: 'Chat on Discord', url: discord, icon: 'lucide:messages-square' },
{ label: 'Support the project', url: sponsor, icon: 'lucide:hand-heart' },
{ label: t('docs.theme.doc.community.star-github.title'), url: github, icon: 'lucide:star' },
{ label: t('docs.theme.doc.community.discord.title'), url: discord, icon: 'lucide:messages-square' },
{ label: t('docs.theme.doc.community.support.title'), url: sponsor, icon: 'lucide:hand-heart' },
])
</script>
@@ -19,7 +21,7 @@ const links = computed(() => [
<div>
<div class="pt-4">
<div class="mb-2 text-sm font-bold">
Community
{{ t('docs.theme.doc.community.title') }}
</div>
<div class="mt-4 space-y-3 lg:space-y-2">
+4 -2
View File
@@ -2,6 +2,7 @@
import { Icon } from '@iconify/vue'
import { useData } from 'vitepress'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import DocFooterLastUpdated from './DocFooterLastUpdated.vue'
@@ -9,6 +10,7 @@ import { useEditLink } from '../composables/edit-link'
import { usePrevNext } from '../composables/prev-next'
const { theme, page, frontmatter } = useData()
const { t } = useI18n()
const editLink = useEditLink()
const control = usePrevNext()
@@ -78,7 +80,7 @@ const showFooter = computed(
>
<span
class="text-muted-foreground group-hover:text-foreground text-xs transition-all duration-200 ease-in-out"
v-html="theme.docFooter?.prev || 'Previous page'"
v-html="theme.docFooter?.prev || t('docs.theme.doc.previous-page.title')"
/>
<p class="mt-2 inline-flex items-center gap-1">
<Icon icon="lucide:arrow-left" />
@@ -97,7 +99,7 @@ const showFooter = computed(
>
<span
class="text-muted-foreground group-hover:text-foreground text-xs transition-all duration-200 ease-in-out"
v-html="theme.docFooter?.next || 'Next page'"
v-html="theme.docFooter?.next || t('docs.theme.doc.next-page.title')"
/>
<p class="mt-2 inline-flex items-center gap-1">
+3 -1
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { onContentUpdated, useData } from 'vitepress'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import DocOutlineItem from '../components/DocOutlineItem.vue'
@@ -11,6 +12,7 @@ defineProps<{
}>()
const { page, frontmatter, theme } = useData()
const { t } = useI18n()
onContentUpdated(() => {
getHeaders(frontmatter.value.outline ?? theme.value.outline)
@@ -39,7 +41,7 @@ useActiveAnchor(container, marker)
class="trunc mb-2 text-sm font-bold"
role="heading"
>
On this page
{{ t('docs.theme.doc.outline.title') }}
</div>
<div class="border-muted border-l font-sans">
+12 -4
View File
@@ -6,6 +6,7 @@ import { useScroll } from '@vueuse/core'
import { DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger } from 'reka-ui'
import { useData, useRoute } from 'vitepress'
import { computed, ref, toRefs, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import DocSidebarItem from '../components/DocSidebarItem.vue'
@@ -13,6 +14,7 @@ import { flatten } from '../functions/flatten'
const { path } = toRefs(useRoute())
const { page, theme } = useData()
const { t } = useI18n()
const isSidebarOpen = ref(false)
const sidebar = computed(() => (theme.value.sidebar as (DefaultTheme.SidebarItem & { icon?: string })[]))
@@ -26,6 +28,12 @@ const sectionTabs = computed(() => sidebar.value
.filter(i => !!i?.link),
)
function isCharacterPage(link?: string) {
if (!link)
return false
return link.includes('/characters') || link.includes('/characters/')
}
const { arrivedState } = useScroll(globalThis.window)
const { top } = toRefs(arrivedState)
@@ -42,7 +50,7 @@ watch(path, () => {
<div class="h-full items-center justify-between hidden md:flex">
<div class="h-full flex items-center">
<a
v-for="tab in sectionTabs.filter(i => i.label !== 'Characters')"
v-for="tab in sectionTabs.filter(i => !isCharacterPage(i.link))"
:key="tab.label"
:href="tab.link"
:class="{ '!after:bg-primary !text-foreground': `/${page.relativePath}`.includes(tab.link?.split('/').slice(0, -1).join('/') || '') }"
@@ -60,7 +68,7 @@ watch(path, () => {
<div class="h-full flex items-center">
<a
v-for="tab in sectionTabs.filter(i => i.label === 'Characters')"
v-for="tab in sectionTabs.filter(i => isCharacterPage(i.link))"
:key="tab.label"
:href="tab.link"
:class="{ '!after:bg-primary !text-foreground': page.relativePath.includes(tab.label?.toLowerCase() ?? '') }"
@@ -93,7 +101,7 @@ watch(path, () => {
<DialogPortal>
<DialogOverlay class="data-[state=open]:animate-fadeIn data-[state=closed]:animate-fadeOut fixed inset-0 z-50 bg-black/80" />
<DialogContent class="bg-background border-muted data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left data-[state=open]:animate-enterFromLeft data-[state=closed]:animate-exitToLeft fixed inset-y-0 left-0 z-50 h-full w-3/4 gap-4 border-r pr-0 shadow-lg transition ease-in-out sm:max-w-sm">
<DialogContent class="border-muted bg-background data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left data-[state=open]:animate-enterFromLeft data-[state=closed]:animate-exitToLeft fixed inset-y-0 left-0 z-50 h-full w-3/4 gap-4 border-r pr-0 shadow-lg transition ease-in-out sm:max-w-sm">
<DialogTitle class="sr-only">
Sidebar menu
</DialogTitle>
@@ -156,7 +164,7 @@ watch(path, () => {
icon="lucide:scan-face"
class="text-lg"
/>
Characters
{{ t('docs.theme.pages.characters.title') }}
</a>
</div>
</div>
+10 -7
View File
@@ -15,11 +15,13 @@ defineProps<{
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger class="text-muted-foreground hover:text-foreground data-[state=open]:text-foreground mx-0 h-full inline-flex items-center justify-between px-2 py-2 text-nowrap text-sm font-semibold md:mx-3 md:px-0">
<span>{{ label }}</span>
<Icon
icon="lucide:chevron-down"
class="ml-1 text-lg"
/>
<slot name="trigger">
<span>{{ label }}</span>
<Icon
icon="lucide:chevron-down"
class="ml-1 text-lg"
/>
</slot>
</DropdownMenuTrigger>
<DropdownMenuPortal>
@@ -34,7 +36,7 @@ defineProps<{
:initial="{ opacity: 0, y: -10 }"
:animate="{ opacity: 1, y: 0 }"
:exit="{ opacity: 0, y: -10 }"
class="border-muted z-10 border rounded-lg bg-white/30 p-2 shadow-sm backdrop-blur-md dark:bg-neutral-800/30"
class="border-muted z-10 border rounded-lg bg-white/60 p-2 shadow-sm backdrop-blur-md transition-shadow duration-200 ease-in-out dark:bg-neutral-800/30 focus:shadow-lg hover:shadow-lg"
>
<DropdownMenuItem
v-for="item in items"
@@ -46,10 +48,11 @@ defineProps<{
<a
v-if="item.link"
:href="item.link"
target="_blank"
:target="item.link.startsWith('http') || item.link.startsWith('https') ? '_blank' : undefined"
>
<span>{{ item.text }}</span>
<Icon
v-if="item.link.startsWith('http') || item.link.startsWith('https')"
icon="lucide:arrow-up-right"
class="ml-2 text-base"
/>
+7 -3
View File
@@ -1,5 +1,9 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import homeCover from '../assets/home-cover-2025-07-15.png'
const { t } = useI18n()
</script>
<template>
@@ -13,20 +17,20 @@ import homeCover from '../assets/home-cover-2025-07-15.png'
</div>
</div>
<div class="relative max-w-prose text-center text-slate-900 dark:text-white">
A container of souls of AI waifu / virtual characters to bring them into our worlds.
{{ t('docs.theme.home.subtitle') }}
</div>
<div class="relative z-10 w-full flex justify-center gap-4">
<a
href="https://github.com/moeru-ai/airi/releases/latest"
class="rounded-lg bg-primary/60 px-4 py-2 text-slate-100 font-extrabold outline-none backdrop-blur-md dark:bg-primary/30 dark:text-cyan-200 focus:outline-none"
>
Download
{{ t('docs.theme.home.download.title') }}
</a>
<a
href="./overview/"
class="rounded-lg bg-white/60 px-4 py-2 text-slate-900 font-extrabold outline-none backdrop-blur-md dark:bg-white/20 dark:text-slate-100 focus:outline-none"
>
Get Started
{{ t('docs.theme.home.get-started.title') }}
</a>
</div>
</div>
+27 -19
View File
@@ -3,12 +3,16 @@ import { Icon } from '@iconify/vue'
import { DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuRoot, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Separator } from 'reka-ui'
import { useData, useRoute } from 'vitepress'
import { ref, toRefs, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import DropdownMenu from '../components/DropdownMenu.vue'
import ThemeToggle from '../components/ThemeToggle.vue'
import DropdownMenu from './DropdownMenu.vue'
import NavbarLanguage from './NavbarLanguage.vue'
import NavbarLanguageSubMenu from './NavbarLanguageSubMenu.vue'
import ThemeToggle from './ThemeToggle.vue'
const { path } = toRefs(useRoute())
const { theme } = useData()
const { t } = useI18n()
const isPopoverOpen = ref(false)
@@ -40,10 +44,13 @@ watch(path, () => {
</template>
<Separator
class="bg-muted mx-4 h-4 w-px"
class="bg-muted ml-2 mr-2 h-4 w-px"
decorative
orientation="vertical"
/>
<NavbarLanguage />
<ThemeToggle />
<Separator
@@ -68,10 +75,7 @@ watch(path, () => {
<div class="lg:hidden">
<DropdownMenuRoot v-model:open="isPopoverOpen">
<DropdownMenuTrigger class="rounded-lg p-2">
<Icon
icon="lucide:ellipsis"
class="text-lg"
/>
<Icon icon="lucide:ellipsis" class="text-lg" />
</DropdownMenuTrigger>
<DropdownMenuPortal>
@@ -79,7 +83,7 @@ watch(path, () => {
side="bottom"
:side-offset="5"
align="end"
class="bg-card will-change-[transform,opacity] border-muted data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl p-2 shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_green7]"
class="bg-card will-change-[transform,opacity] border-muted data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl p-2 shadow-sm focus:shadow-lg"
>
<nav class="flex flex-col">
<template
@@ -99,14 +103,13 @@ watch(path, () => {
<DropdownMenuSub v-else-if="nav.items">
<DropdownMenuSubTrigger class="text-muted-foreground h-full w-full inline-flex items-center justify-between rounded p-2 text-sm font-semibold hover:bg-primary/10 hover:text-primary">
<span>{{ nav.text }}</span>
<Icon
icon="lucide:chevron-down"
class="ml-1 text-lg"
/>
<Icon icon="lucide:chevron-down" class="ml-1 text-lg" />
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent class="bg-card border-muted will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl p-2 shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_green7]">
<DropdownMenuSubContent
class="border-muted will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl bg-white/60 p-2 shadow-sm backdrop-blur-md transition-shadow duration-200 ease-in-out dark:bg-neutral-800/30 focus:shadow-lg"
>
<DropdownMenuItem
v-for="item in nav.items"
:key="item.text"
@@ -119,11 +122,7 @@ watch(path, () => {
class="w-full flex items-center justify-between"
>
<span>{{ item.text }}</span>
<Icon
icon="lucide:arrow-up-right"
class="ml-2 text-base"
/>
<Icon icon="lucide:arrow-up-right" class="ml-2 text-base" />
</a>
<div v-else>
{{ item.text }}
@@ -144,7 +143,7 @@ watch(path, () => {
<label
for="theme-toggle"
class="text-muted-foreground font-semibold"
>Appearance</label>
>{{ t('docs.theme.navbar.appearance.title') }}</label>
<DropdownMenuItem
as-child
@select.prevent
@@ -153,6 +152,15 @@ watch(path, () => {
</DropdownMenuItem>
</div>
<div class="mt-2 flex items-center justify-between text-sm">
<DropdownMenuItem
as-child
@select.prevent
>
<NavbarLanguageSubMenu hide-icon />
</DropdownMenuItem>
</div>
<Separator
class="bg-muted my-2 h-px w-full"
decorative
@@ -0,0 +1,46 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { useData, useRoute } from 'vitepress'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import DropdownMenu from './DropdownMenu.vue'
const props = defineProps<{
hideIcon?: boolean
}>()
const { site, lang } = useData()
const route = useRoute()
const { t } = useI18n()
const languages = computed(() => {
const replacedLink = (targetLang: string) => {
return route.path.replace(`/${lang.value}/`, `/${targetLang}/`)
}
return Object.values(site.value.locales).map(locale => ({
text: locale.label,
link: replacedLink(locale.lang!),
}))
})
</script>
<template>
<DropdownMenu
:label="t('docs.theme.navbar.language.title')"
:items="languages"
>
<template #trigger>
<Icon
v-if="!props.hideIcon"
icon="lucide:globe"
class="text-lg"
/>
<Icon
icon="lucide:chevron-down"
class="ml-1 text-lg"
/>
</template>
</DropdownMenu>
</template>
@@ -0,0 +1,53 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { DropdownMenuItem, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from 'reka-ui'
import { useData, useRoute } from 'vitepress'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
const { site, lang } = useData()
const route = useRoute()
const { t } = useI18n()
const languages = computed(() => {
const replacedLink = (targetLang: string) => {
return route.path.replace(`/${lang.value}/`, `/${targetLang}/`)
}
return Object.values(site.value.locales).map(locale => ({
text: locale.label,
link: replacedLink(locale.lang!),
}))
})
</script>
<template>
<DropdownMenuSub>
<DropdownMenuSubTrigger class="text-muted-foreground h-full w-full inline-flex items-center justify-between rounded p-2 text-sm font-semibold hover:bg-primary/10 hover:text-primary">
<span>{{ t('docs.theme.navbar.language.title') }}</span>
<Icon icon="lucide:chevron-down" class="ml-1 text-lg" />
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent class="border-muted bg-card will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade z-10 w-[180px] border rounded-xl p-2 shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_green7]">
<DropdownMenuItem
v-for="item in languages"
:key="item.text"
class="text-muted-foreground h-full w-full inline-flex items-center rounded p-2 text-sm font-semibold hover:bg-primary/10 hover:text-primary"
>
<a
v-if="item.link"
:href="item.link"
target="_blank"
class="w-full flex items-center justify-between"
>
<span>{{ item.text }}</span>
</a>
<div v-else>
{{ item.text }}
</div>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</template>
@@ -12,6 +12,7 @@ import { computedAsync, debouncedWatch } from '@vueuse/core'
import { DialogClose, ListboxContent, ListboxFilter, ListboxItem, ListboxRoot } from 'reka-ui'
import { useData } from 'vitepress'
import { markRaw, nextTick, onMounted, ref, shallowRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { LRUCache } from '../functions/cache'
@@ -20,6 +21,7 @@ const emits = defineEmits<{
}>()
const { localeIndex } = useData()
const { t } = useI18n()
const filterText = ref('')
const enableNoResults = ref(false)
@@ -141,7 +143,7 @@ function formMarkRegex(terms: Set<string>) {
<ListboxFilter
v-model="filterText"
class="placeholder:text-muted-foreground h-12 w-full flex-1 bg-transparent text-sm outline-none md:h-14 focus:outline-none"
placeholder="Search documentation"
:placeholder="t('docs.theme.search.placeholder')"
auto-focus
/>
<DialogClose>
+3 -1
View File
@@ -4,11 +4,13 @@ import { useMagicKeys, whenever } from '@vueuse/core'
import { AnimatePresence, Motion } from 'motion-v'
import { DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger } from 'reka-ui'
import { defineAsyncComponent, ref } from 'vue'
import { useI18n } from 'vue-i18n'
const SearchCommandBox = defineAsyncComponent(() => import('./SearchCommandBox.vue'))
const open = ref(false)
const { meta_k } = useMagicKeys()
const { t } = useI18n()
whenever(meta_k, (n) => {
if (n)
@@ -26,7 +28,7 @@ function handleClose() {
<DialogRoot v-model:open="open">
<DialogTrigger class="border-muted hover:bg-muted text-muted-foreground text-md md:bg-card flex items-center rounded-lg px-3 py-[7px] transition-colors duration-200 ease-in-out space-x-2 md:border md:text-sm">
<Icon icon="lucide:search" />
<span class="w-24 text-left hidden lg:w-40 md:inline-flex">Search</span>
<span class="w-24 text-left hidden lg:w-40 md:inline-flex">{{ t('docs.theme.search.title') }}</span>
<span class="text-xs hidden md:inline-flex">
<kbd> K</kbd>
</span>
+2 -8
View File
@@ -25,14 +25,8 @@ watchPostEffect(() => {
<SwitchThumb
class="text-muted-foreground border-muted bg-background my-auto h-5 w-5 flex translate-x-0.5 items-center justify-center border rounded-full text-xs will-change-transform data-[state=checked]:translate-x-5 !transition-transform"
>
<Icon
v-if="isDark"
icon="lucide:moon-star"
/>
<Icon
v-else
icon="lucide:sun"
/>
<Icon v-if="isDark" icon="lucide:moon-star" />
<Icon v-else icon="lucide:sun" />
</SwitchThumb>
</SwitchRoot>
</template>
+3 -1
View File
@@ -1,11 +1,13 @@
import { useData } from 'vitepress'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
export function useEditLink() {
const { theme, page } = useData()
const { t } = useI18n()
return computed(() => {
const { text = 'Edit this page', pattern = '' } = theme.value.editLink || {}
const { text = t('docs.theme.doc.community.edit.title'), pattern = '' } = theme.value.editLink || {}
let url: string
if (typeof pattern === 'function') {
url = pattern(page.value)
+111 -44
View File
@@ -1,10 +1,11 @@
import type { DefaultTheme } from 'vitepress'
import { posix } from 'node:path'
import { join, posix, resolve } from 'node:path'
import { env } from 'node:process'
import anchor from 'markdown-it-anchor'
import unocss from 'unocss/vite'
import yaml from 'unplugin-yaml/vite'
import { footnote } from '@mdit/plugin-footnote'
import { tasklist } from '@mdit/plugin-tasklist'
@@ -65,55 +66,115 @@ export default defineConfig({
base: env.BASE_URL || '/',
lastUpdated: true,
sitemap: { hostname: ogUrl },
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Docs', link: withBase('/overview/') },
{ text: 'Blog', link: withBase('/blog/') },
{
text: `v${version}`,
items: [
{ text: 'Release Notes ', link: releases },
],
},
],
outline: {
level: 'deep',
},
logo: '/favicon.svg',
sidebar: [
{
text: 'Overview',
icon: 'lucide:rocket',
items: [
{ text: 'Introduction', link: withBase('/overview/') },
{ text: 'About AI Vtuber', link: withBase('/overview/about-ai-vtuber') },
{ text: 'About Neuro-sama', link: withBase('/overview/about-neuro-sama') },
],
},
{
text: 'Guides',
icon: 'lucide:book-open',
items: [
{ text: 'Contributing', link: withBase('/guides/contributing/') },
locales: {
'root': {
label: 'English',
lang: 'en',
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Docs', link: withBase('/en/overview/') },
{ text: 'Blog', link: withBase('/en/blog/') },
{
text: 'Design Guidelines',
link: withBase('/guides/design-guidelines/'),
text: `v${version}`,
items: [
{ text: 'Resources', link: withBase('/guides/contributing/design-guidelines/resources') },
{ text: 'Tools', link: withBase('/guides/contributing/design-guidelines/tools') },
{ text: 'Release Notes ', link: releases },
],
},
],
},
{
text: 'Characters',
icon: 'lucide:scan-face',
link: withBase('/characters/'),
},
] as (DefaultTheme.SidebarItem & { icon?: string })[],
outline: {
level: 'deep',
},
logo: '/favicon.svg',
sidebar: [
{
text: 'Overview',
icon: 'lucide:rocket',
items: [
{ text: 'Introduction', link: withBase('/en/overview/') },
{ text: 'About AI VTuber', link: withBase('/en/overview/about-ai-vtuber') },
{ text: 'About Neuro-sama', link: withBase('/en/overview/about-neuro-sama') },
],
},
{
text: 'Guides',
icon: 'lucide:book-open',
items: [
{ text: 'Contributing', link: withBase('/en/guides/contributing/') },
{
text: 'Design Guidelines',
link: withBase('/en/guides/design-guidelines/'),
items: [
{ text: 'Resources', link: withBase('/en/guides/contributing/design-guidelines/resources') },
{ text: 'Tools', link: withBase('/en/guides/contributing/design-guidelines/tools') },
],
},
],
},
{
text: 'Characters',
icon: 'lucide:scan-face',
link: withBase('/en/characters/'),
},
] as (DefaultTheme.SidebarItem & { icon?: string })[],
},
},
'zh-Hans': {
label: '简体中文',
lang: 'zh-Hans',
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: '文档', link: withBase('/zh-Hans/overview/') },
{ text: '博客 / 开发日志', link: withBase('/zh-Hans/blog/') },
{
text: `v${version}`,
items: [
{ text: '发布说明 ', link: releases },
],
},
],
outline: {
level: 'deep',
},
logo: '/favicon.svg',
sidebar: [
{
text: '概览',
icon: 'lucide:rocket',
items: [
{ text: '介绍', link: withBase('/zh-Hans/overview/') },
{ text: '有关 AI VTuber', link: withBase('/zh-Hans/overview/about-ai-vtuber') },
{ text: '有关 Neuro-sama', link: withBase('/zh-Hans/overview/about-neuro-sama') },
],
},
{
text: '指南',
icon: 'lucide:book-open',
items: [
{ text: '贡献指南', link: withBase('/zh-Hans/guides/contributing/') },
{
text: '设计指引',
link: withBase('/zh-Hans/guides/design-guidelines/'),
items: [
{ text: '参考资源', link: withBase('/zh-Hans/guides/contributing/design-guidelines/resources') },
{ text: '工具', link: withBase('/zh-Hans/guides/contributing/design-guidelines/tools') },
],
},
],
},
{
text: '角色',
icon: 'lucide:scan-face',
link: withBase('/zh-Hans/characters/'),
},
] as (DefaultTheme.SidebarItem & { icon?: string })[],
},
},
},
themeConfig: {
socialLinks: [
{ icon: 'discord', link: discord },
{ icon: 'github', link: github },
@@ -177,8 +238,14 @@ export default defineConfig({
pageData.frontmatter.sidebar = pageData.frontmatter.layout !== 'showcase'
},
vite: {
resolve: {
alias: {
'@proj-airi/i18n': resolve(join(import.meta.dirname, '..', '..', 'packages', 'i18n', 'src')),
},
},
plugins: [
unocss(),
yaml(),
],
css: {
postcss: {
+8 -7
View File
@@ -2,25 +2,26 @@
import { useScroll } from '@vueuse/core'
import { TooltipProvider } from 'reka-ui'
import { useData } from 'vitepress'
import { computed, toRefs } from 'vue'
import { computed, toRefs, watch } from 'vue'
import { useI18n } from 'vue-i18n'
// import HomePageDemo from '../components/HomePageDemo.vue'
import Home from '../components/Home.vue'
import Navbar from '../components/Navbar.vue'
import SearchTrigger from '../components/SearchTrigger.vue'
import Docs from './Docs.vue'
import Showcase from './Showcase.vue'
const { site, theme, frontmatter } = useData<{
logo: string
}>()
const { site, theme, frontmatter, lang } = useData<{ logo: string }>()
const { locale } = useI18n()
const { arrivedState } = useScroll(globalThis.window)
const { top } = toRefs(arrivedState)
const logo = computed(() => theme.value.logo)
const title = computed(() => site.value.title)
const layout = computed(() => frontmatter.value.layout)
const isHome = computed(() => layout.value === 'home')
const { arrivedState } = useScroll(globalThis.window)
const { top } = toRefs(arrivedState)
watch(lang, () => locale.value = lang.value, { immediate: true })
</script>
<template>
+13 -1
View File
@@ -1,5 +1,9 @@
import type { Theme } from 'vitepress'
import messages from '@proj-airi/i18n/locales'
import { createI18n } from 'vue-i18n'
import Layout from '../custom/Layout.vue'
import '@unocss/reset/tailwind.css'
@@ -8,6 +12,14 @@ import './style.css'
export default {
Layout,
enhanceApp(_ctx) {
enhanceApp({ app }) {
const i18n = createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'en',
messages,
})
app.use(i18n)
},
} satisfies Theme
+1 -1
View File
@@ -2,7 +2,7 @@
--background: 0 0% 100%;
--foreground: 207 10% 3.9%;
--card: 207 8% 97%;
--card: 207 4% 100%;
--card-foreground: 0 10% 3.9%;
--popover: 0 0% 100%;
-12
View File
@@ -1,12 +0,0 @@
---
sidebar: false
outline: false
community: false
---
<script setup>
import { data } from '../../.vitepress/theme/blog.data'
import BlogPosts from '../../.vitepress/components/BlogPosts.vue'
</script>
<BlogPosts :data="data" />

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 181 KiB

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 222 KiB

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before

Width:  |  Height:  |  Size: 1019 KiB

After

Width:  |  Height:  |  Size: 1019 KiB

Before

Width:  |  Height:  |  Size: 588 KiB

After

Width:  |  Height:  |  Size: 588 KiB

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 410 KiB

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Before

Width:  |  Height:  |  Size: 334 KiB

After

Width:  |  Height:  |  Size: 334 KiB

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 244 KiB

Before

Width:  |  Height:  |  Size: 393 KiB

After

Width:  |  Height:  |  Size: 393 KiB

Before

Width:  |  Height:  |  Size: 219 KiB

After

Width:  |  Height:  |  Size: 219 KiB

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Before

Width:  |  Height:  |  Size: 677 KiB

After

Width:  |  Height:  |  Size: 677 KiB

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 378 KiB

Before

Width:  |  Height:  |  Size: 373 KiB

After

Width:  |  Height:  |  Size: 373 KiB

Before

Width:  |  Height:  |  Size: 14 MiB

After

Width:  |  Height:  |  Size: 14 MiB

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Some files were not shown because too many files have changed in this diff Show More