diff --git a/docs/.vuepress/notes/zh/theme-config.ts b/docs/.vuepress/notes/zh/theme-config.ts index 13e1793c..c0bbb750 100644 --- a/docs/.vuepress/notes/zh/theme-config.ts +++ b/docs/.vuepress/notes/zh/theme-config.ts @@ -9,8 +9,8 @@ export const themeConfig = defineNoteConfig({ collapsed: false, items: [ '配置说明', - '多语言配置', '主题配置', + '多语言配置', '导航栏配置', 'notes配置', '侧边栏配置', diff --git a/docs/notes/theme/config/主题配置.md b/docs/notes/theme/config/主题配置.md index b833a9c4..8e99d288 100644 --- a/docs/notes/theme/config/主题配置.md +++ b/docs/notes/theme/config/主题配置.md @@ -344,9 +344,9 @@ interface LastUpdatedOptions { - 默认值: `{}` - 详情: 多语言配置 -多语言配置,参考 [此文档](./多语言配置.md) +不同语言的文本配置,参考 [此文档](./多语言配置.md) -多语言配置支持以下 [Locale](#locale-配置) 所有配置选项 +多语言配置支持以下 [Locale](#locale-配置) 所有配置选项以控制不同语言下的主题行为。 ## Locale 配置 @@ -385,16 +385,6 @@ interface LastUpdatedOptions { 此选项注入一个内联脚本,从本地存储恢复用户设置。这确保在呈现页面之前应用 `[data-theme="dark"]` 以避免闪烁。 -### appearanceText - -- 类型: `string` -- 默认值: `'Appearance'` -- 详情: 导航栏中的主题切换按钮的文本。 - -### avatar - -弃用,请使用 [profile](#profile)。 - ### profile - 类型: `ProfileOptions` @@ -740,66 +730,6 @@ interface SidebarItem { 详情请参考 [公告板](../guide/功能/公告板.md) -### selectLanguageName - -- 类型: `string` -- 默认值: `''` -- 详情: - - Locale 的语言名称。 - - 该配置项 **仅能在主题配置的 [locales](#locales) 的内部生效** 。它将被用作 locale 的语言名称,展示在 _选择语言菜单_ 内。 - -### selectLanguageText - -- 类型: `string` -- 默认值: `''` -- 详情: - - _选择语言菜单_ 的文本。 - - 如果你在站点配置中设置了多个 [locales](#locales) ,那么 _选择语言菜单_ 就会显示在导航栏中仓库按钮的旁边。 - -### selectLanguageAriaLabel - -- 类型: `string` -- 默认值: `''` -- 详情: - - _选择语言菜单 的 `aria-label` 属性。_ - - 它主要是为了站点的可访问性 (a11y) 。 - -### sidebarMenuLabel - -- 类型: `string` -- 默认值: `'Menu'` -- 详情: - - 移动设备下的导航栏中 菜单选项的文本。 - -### returnToTopLabel - -- 类型: `string` -- 默认值: `'return to top'` -- 详情: - - 移动设备下的导航栏中返回顶部的文本。 - -### outlineLabel - -- 类型: `string` -- 默认值: `'On this page'` -- 详情: - - 移动设备下的导航栏中大纲标题的文本 - -### editLinkText - -- 类型: `string` -- 默认值: `'Edit this page'` -- 详情: 编辑链接文本 - ### editLinkPattern - 类型: `string` @@ -808,36 +738,6 @@ interface SidebarItem { 示例: `':repo/edit/:branch/:path'` -### latestUpdatedText - -- 类型: `string` -- 默认值: `'Latest Updated'` -- 详情: 最近更新时间 的文本 - -### contributorsText - -- 类型: `string` -- 默认值: `'Contributors'` -- 详情: 贡献者的文本 - -### changelogText - -- 类型: `string` -- 默认值: `'Changelog'` -- 详情: 变更记录的文本 - -### changelogOnText - -- 类型: `string` -- 默认值: `'On'` -- 详情: 单次变更记录的时间文本 - -### changelogButtonText - -- 类型: `string` -- 默认值: `'View All Changelog'` -- 详情: 变更记录的按钮文本 - ### copyright - 类型: `boolean | CopyrightLicense | CopyrightOptions` @@ -846,66 +746,18 @@ interface SidebarItem { 详情请参考 [版权所有](../guide/功能/文章版权所有.md) -### copyrightText - -- 类型: `string` -- 默认值: `'Copyright'` -- 详情: 版权所有的文本 - -### copyrightAuthorText - -- 类型: `string` -- 默认值: `'Copyright Ownership:'` -- 详情: 版权所有者的文本 - -### copyrightCreationOriginalText - -- 类型: `string` -- 默认值: `'This article link:'` -- 详情: 本文链接的文本 - -### copyrightCreationTranslateText - -- 类型: `string` -- 默认值: `'This article translated from:'` -- 详情: 本文翻译的文本 - -### copyrightCreationReprintText - -- 类型: `string` -- 默认值: `'This article reprint from:'` -- 详情: 本文转载的文本 - -### copyrightLicenseText - -- 类型: `string` -- 默认值: `'License under:'` -- 详情: 版权许可证的文本 - ### prevPage - 类型: `boolean` - 默认值: `true` - 详情: 是否显示上一页 -### prevPageLabel - -- 类型: `string` -- 默认值: `'Previous Page'` -- 详情: 上一页的文本 - ### nextPage - 类型: `boolean` - 默认值: `true` - 详情: 是否显示下一页 -### nextPageLabel - -- 类型: `string` -- 默认值: `'Next Page'` -- 详情: 下一页的文本 - ### createTime - 类型: `boolean | 'only-blog'` @@ -915,19 +767,3 @@ interface SidebarItem { - `false` - 不显示 - `'only-blog'` - 只显示在博客文章页面 - `true` - 显示在所有文章页面 - -### notFound - -- 类型: `NotFound | undefined` -- 默认值: `undefined` -- 详情: 404 页面配置 - -```ts -interface NotFound { - code?: string - title?: string - quote?: string - linkLabel?: string - linkText?: string -} -``` diff --git a/docs/notes/theme/config/多语言配置.md b/docs/notes/theme/config/多语言配置.md index 8c8e35ec..51d73505 100644 --- a/docs/notes/theme/config/多语言配置.md +++ b/docs/notes/theme/config/多语言配置.md @@ -1,155 +1,279 @@ --- -title: 多语言 +title: 多语言配置 author: pengzhanbo createTime: 2024/03/02 10:07:15 permalink: /config/locales/ --- -## 设置语言 重要 +这些选项用于配置与语言相关的文本。 -你需要为每个语言设置 `lang` 选项。即使你只在使用单个语言,你也必须在 `.vuepress/config.{js,ts}` 中设置 `lang`。 +如果你的站点是以非内置语言支持以外的其他语言提供服务的,你应该为每个语言设置这些选项来提供翻译。 -::: tip 为什么要这样做? -要提供正确的语言环境文本,主题需要知道根文件夹以及每个多语言文件夹正在使用哪种语言。 -::: +## 内置语言支持 + +主题内置了以下语言支持,包括: + +- 简体中文 (`zh-CN`) - `/zh/` +- 繁体中文 (`zh-TW`) - `/zh-tw/` +- 英语 (`en-US`) - `/en/` +- 法语 (`fr-FR`) - `/fr/` +- 德语 (`de-DE`) - `/de/` +- 俄语 (`ru-RU`) - `/ru/` +- 日语 (`ja-JP`) - `/ja/` + +## 配置 + +您应该将配置写入到 `theme.locales` 中。 + +您可以在 `.vuepress/config.ts` ,或者在 `.vuepress/plume.config.ts` 中进行配置: ::: code-tabs -@tab 单语言 -```ts -import { defineUserConfig } from 'vuepress' - -export default defineUserConfig({ - // 设置正在使用的语言 - lang: 'zh-CN', -}) -``` - -@tab 多语言 - -```ts -import { defineUserConfig } from 'vuepress' - -export default defineUserConfig({ - locales: { - '/': { - // 设置正在使用的语言 - lang: 'zh-CN', - }, - '/en/': { - // 设置正在使用的语言 - lang: 'en-US', - }, - }, -}) -``` - -::: - -## 多语言配置 - -`locales` 是一个对象,其键为每个语言的路径前缀,值为该语言的配置,可以包含 `title`, `description`, `lang` 等。 - -你应当为每个语言设置 `lang` 选项,以便主题和插件能够正确的处理它们。 - -如果站点和主题配置中的 `locales` 对象只包含 "/" 一个键,则主题不会显示语言切换菜单。当你通过 `locales` 设置多个键,即存在多个语言的时候,我们会在导航栏显示语言切换菜单。 - -## 语言适配 - -主题默认适配了以下语言 - -- 简体中文 (zh-CN) -- 英文(美国) (en-US) - -::: tip - -如果您希望支持更多语言,欢迎通过 -[PR](https://github.com/pengzhanbo/vuepress-theme-plume/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aopen) 在 主题仓库的 `/theme/src/node/locales` 目录中按照相同的方式添加语言。 - -::: - -## 为每个语言设置主题选项 - -与站点配置和 `@vuepress/theme-default` 的主题配置相同,`vuepress-theme-plume` 也支持你在主题选项中设置 locale 选项,并为每种语言设置不同的配置。 - -::: code-tabs @tab .vuepress/config.ts -```ts :no-line-numbers +```ts import { defineUserConfig } from 'vuepress' import { plumeTheme } from 'vuepress-theme-plume' export default defineUserConfig({ - locales: { - '/': { - lang: 'en-US', - }, - '/zh/': { - lang: 'zh-CN', - }, - }, - theme: plumeTheme({ - // 通用配置 - // ... locales: { - '/': { - // 英文配置 - // ... - }, - '/zh/': { - // 中文配置 - // ... - }, - }, - }), -}) -``` - -::: - -**使用主题配置文件:** - -::: code-tabs -@tab .vuepress/config.ts - -```ts -import { defineUserConfig } from 'vuepress' -import { plumeTheme } from 'vuepress-theme-plume' - -export default defineUserConfig({ - locales: { - '/': { - lang: 'en-US', - }, - '/zh/': { - lang: 'zh-CN', - }, - }, - - theme: plumeTheme(), + // 非内置语言的语言代码 + '/xxx/': { + // 语言配置 + } + } + }) }) ``` @tab .vuepress/plume.config.ts ```ts -import { defineThemeConfig } from 'vuepress-theme-plume' +import { definePlumeConfig } from 'vuepress-theme-plume' -export default defineThemeConfig({ - // 通用配置 - // ... +export default definePlumeConfig({ locales: { - '/': { - // 英文配置 - // ... - }, - '/zh/': { - // 中文配置 - // ... - }, - }, + // 非内置语言的语言代码 + '/xxx/': { + // 语言配置 + } + } }) ``` ::: + +### appearanceText + +- 类型: `string` +- 默认值: `'Appearance'` +- 详情: 导航栏中的主题切换按钮的文本。 + +### selectLanguageName + +- 类型: `string` +- 默认值: `''` +- 详情: + + Locale 的语言名称。 + + 该配置项 **仅能在主题配置的 [locales](./主题配置.md#locales) 的内部生效** 。它将被用作 locale 的语言名称,展示在 _选择语言菜单_ 内。 + +### selectLanguageText + +- 类型: `string` +- 默认值: `''` +- 详情: + + _选择语言菜单_ 的文本。 + + 如果你在站点配置中设置了多个 [locales](./主题配置.md#locales) ,那么 _选择语言菜单_ 就会显示在导航栏中仓库按钮的旁边。 + +### selectLanguageAriaLabel + +- 类型: `string` +- 默认值: `''` +- 详情: + + _选择语言菜单 的 `aria-label` 属性。_ + + 它主要是为了站点的可访问性 (a11y) 。 + +### homeText + +- 类型: `string` +- 默认值: `'Home'` +- 详情: 主页链接的文本。 + + - 主题默认导航栏中的首页链接的文本。 + - 面包屑导航中的首页链接的文本。 + +### blogText + +- 类型: `string` +- 默认值: `'Blog'` +- 详情: 博客链接的文本。 + + - 主题默认导航栏中的博客链接的文本。 + - 面包屑导航中的博客链接的文本。 + +### tagText + +- 类型: `string` +- 默认值: `'Tags'` +- 详情: 标签链接的文本。 + + - 主题默认导航栏中的标签链接的文本。 + - 博客页中的标签链接的文本。 + - 博客标签页中的标题。 + +### categoryText + +- 类型: `string` +- 默认值: `'Categories'` +- 详情: 分类链接的文本。 + + - 主题默认导航栏中的分类链接的文本。 + - 博客页中的分类链接的文本。 + - 博客分类页中的标题。 + +### archiveText + +- 类型: `string` +- 默认值: `'Archives'` +- 详情: 归档链接的文本。 + + - 主题默认导航栏中的归档链接的文本。 + - 博客页中的归档链接的文本。 + - 博客归档页中的标题。 + +### archiveTotalText + +- 类型: `string` +- 默认值: `'{count} articles'` +- 详情: 归档页中的总文章数的文本。 + +### sidebarMenuLabel + +- 类型: `string` +- 默认值: `'Menu'` +- 详情: + + 移动设备下的导航栏中 菜单选项的文本。 + +### returnToTopLabel + +- 类型: `string` +- 默认值: `'return to top'` +- 详情: + + 移动设备下的导航栏中返回顶部的文本。 + +### outlineLabel + +- 类型: `string` +- 默认值: `'On this page'` +- 详情: + + 移动设备下的导航栏中大纲标题的文本 + +### editLinkText + +- 类型: `string` +- 默认值: `'Edit this page'` +- 详情: 编辑链接文本 + +### latestUpdatedText + +- 类型: `string` +- 默认值: `'Latest Updated'` +- 详情: 最近更新时间 的文本 + +### contributorsText + +- 类型: `string` +- 默认值: `'Contributors'` +- 详情: 贡献者的文本 + +### changelogText + +- 类型: `string` +- 默认值: `'Changelog'` +- 详情: 变更记录的文本 + +### changelogOnText + +- 类型: `string` +- 默认值: `'On'` +- 详情: 单次变更记录的时间文本 + +### changelogButtonText + +- 类型: `string` +- 默认值: `'View All Changelog'` +- 详情: 变更记录的按钮文本 + +### copyrightText + +- 类型: `string` +- 默认值: `'Copyright'` +- 详情: 版权所有的文本 + +### copyrightAuthorText + +- 类型: `string` +- 默认值: `'Copyright Ownership:'` +- 详情: 版权所有者的文本 + +### copyrightCreationOriginalText + +- 类型: `string` +- 默认值: `'This article link:'` +- 详情: 本文链接的文本 + +### copyrightCreationTranslateText + +- 类型: `string` +- 默认值: `'This article translated from:'` +- 详情: 本文翻译的文本 + +### copyrightCreationReprintText + +- 类型: `string` +- 默认值: `'This article reprint from:'` +- 详情: 本文转载的文本 + +### copyrightLicenseText + +- 类型: `string` +- 默认值: `'License under:'` +- 详情: 版权许可证的文本 + +### prevPageLabel + +- 类型: `string` +- 默认值: `'Previous Page'` +- 详情: 上一页的文本 + +### nextPageLabel + +- 类型: `string` +- 默认值: `'Next Page'` +- 详情: 下一页的文本 + +### notFound + +- 类型: `NotFound | undefined` +- 默认值: `undefined` +- 详情: 404 页面配置 + +```ts +interface NotFound { + code?: string + title?: string + quote?: string + linkLabel?: string + linkText?: string +} +``` diff --git a/docs/notes/theme/guide/国际化.md b/docs/notes/theme/guide/国际化.md index e5eb709b..420234e7 100644 --- a/docs/notes/theme/guide/国际化.md +++ b/docs/notes/theme/guide/国际化.md @@ -20,10 +20,10 @@ tags: ::: file-tree - docs - - en + - **en** \# 英文目录 - foo.md - README.md \# 英文首页 - - fr + - **fr** \# 法文目录 - foo.md - README.md \# 法文首页 - foo.md @@ -32,19 +32,49 @@ tags: 其中,`docs/en/` 用于保存 **英文** 文档,`docs/fr/` 保存 **法文** 文档,而 **中文** 则直接保存在 `docs/` 下。 -::: important +::: important 不同语言下的目录结构与文件名保持一致 在不同的语言目录下,请尽量保持文件名和目录名的一致。这有助于在 切换语言时,主题能够正确的导航到 文章 的不同的语言版本。 ::: ## vuepress 配置 -接下来,在 `.vuepress/config.ts` 中配置: +### 默认语言 + +在 `.vuepress/config.ts` 中,声明默认的语言: ::: code-tabs @tab .vuepress/config.ts -```js +```ts +import { defineUserConfig } from 'vuepress' + +export default defineUserConfig({ + // 声明默认的语言 + lang: 'zh-CN', // [!code ++] + // 多语言下支持的各种语言 locales + locales: { + '/': { lang: 'zh-CN', title: '博客' }, // 默认语言 为 简体中文 + } +}) +``` + +::: + +### 添加其他语言 + +你需要为每个语言设置 `lang` 选项。即使你只在使用单个语言,你也必须在 `.vuepress/config.{js,ts}` 中设置 `lang`。 + +::: tip 为什么要这样做? +要提供正确的语言环境文本,主题需要知道根文件夹以及每个多语言文件夹正在使用哪种语言。 +::: + +在 `.vuepress/config.ts` 中配置: + +::: code-tabs +@tab .vuepress/config.ts + +```ts import { defineUserConfig } from 'vuepress' export default defineUserConfig({ @@ -54,15 +84,24 @@ export default defineUserConfig({ locales: { // 配置 不同 path 下的语言 '/': { lang: 'zh-CN', title: '博客' }, // 默认语言 为 简体中文 - '/en/': { lang: 'en-US', title: 'Blog' }, // 英文 - '/fr/': { lang: 'fr-FR', title: 'Le blog' }, // 法语 + '/en/': { lang: 'en-US', title: 'Blog' }, // 英文 // [!code ++] + '/fr/': { lang: 'fr-FR', title: 'Le blog' }, // 法语 // [!code ++] } }) ``` ::: -`locales` 中的 `key` 对应着 `docs` 目录下的语言路径,同时,`key` 也将作为 不同语言的页面访问链接的前缀。 +**`locales` 配置中的 `key` 作为 `localPath`, 对应着 `docs` 目录下的语言路径,应该保证它们具有相同的命名。** + +**同时,`key` (`localPath`) 也将作为 不同语言的页面访问链接的前缀。** + +::: important 语言代码 +`locales` 配置中的 `key` 即 `localePath` 需要符合 [ISO 639](https://zh.wikipedia.org/wiki/ISO_639-1) 规范, +对于非默认语言的,如 `英语` 应该为 `/en/`。 + +在 `locales[localePath]` 中, `lang` 为当前语言的 **语言代码**,请使用标准的 [BCP47](https://www.ietf.org/rfc/bcp/bcp47.txt) 语言代码。如, `英语` 应该为 `en-US`,(表示英语(美国))。 +::: ## 主题配置 @@ -87,7 +126,7 @@ export default defineUserConfig({ theme: plumeTheme({ // 主题内的多语言配置 locales: { - '/': { + '/': { // [!code hl] // 当前语言显示在导航栏多语言下拉菜单的文本 selectLanguageName: '简体中文', navbar: [ @@ -95,14 +134,14 @@ export default defineUserConfig({ { text: '博客', link: '/blog/' }, ] }, - '/en/': { + '/en/': { // [!code hl] selectLanguageName: 'English', navbar: [ { text: 'Home', link: '/en/' }, { text: 'Blog', link: '/en/blog/' }, ] }, - '/fr/': { + '/fr/': { // [!code hl] selectLanguageName: 'Français', navbar: [ { text: 'Accueil', link: '/fr/' }, @@ -116,4 +155,11 @@ export default defineUserConfig({ ::: -更多 `locales` 配置请查看 [主题配置 > Locales 配置](../config/主题配置.md#locale-配置) +主题 `theme.locales` 配置中的 `key` 应与 `vuepress` 配置中的 `locales` 配置中的 `key` 一致。 + +您应该为 `theme.locales[localePath]` 配置 `selectLanguageName` 用于在导航栏多语言下拉菜单中显示当前语言的名称。 + +更多 `locales` 配置请查看 + +- [主题配置 > Locales 配置](../config/主题配置.md#locale-配置) - 配置主题在不同语言下的行为。 +- [主题配置 > 多语言配置](../config/多语言配置.md) - 配置与语言相关的文本。 diff --git a/theme/src/client/components/VPDocBreadcrumbs.vue b/theme/src/client/components/VPDocBreadcrumbs.vue index a20a7089..3b489be2 100644 --- a/theme/src/client/components/VPDocBreadcrumbs.vue +++ b/theme/src/client/components/VPDocBreadcrumbs.vue @@ -15,9 +15,9 @@ interface Breadcrumb { current?: boolean } -const { page, theme } = useData<'post'>() +const { page, blog } = useData<'post'>() const { isBlogPost } = useBlogPageData() -const { home, blog, categories } = useInternalLink() +const { home, blog: blogLink, categories } = useInternalLink() const sidebar = useSidebarData() const hasBreadcrumb = computed(() => { @@ -32,9 +32,8 @@ const breadcrumbList = computed(() => { const list: Breadcrumb[] = [{ text: home.value.text, link: home.value.link }] if (isBlogPost.value) { - const blogConf = theme.value.blog || {} - if (blogConf.postList ?? true) - list.push({ text: blog.value.text, link: blog.value.link }) + if (blog.value.postList ?? true) + list.push({ text: blogLink.value.text, link: blogLink.value.link }) const categoryList = page.value.categoryList ?? [] for (const category of categoryList) { diff --git a/theme/src/client/components/VPDocMeta.vue b/theme/src/client/components/VPDocMeta.vue index 4be3831e..4bc5baf6 100644 --- a/theme/src/client/components/VPDocMeta.vue +++ b/theme/src/client/components/VPDocMeta.vue @@ -5,7 +5,7 @@ import { useReadingTimeLocale } from '@vuepress/plugin-reading-time/client' import { computed } from 'vue' import { useBlogPageData, useData, useInternalLink, useTagColors } from '../composables/index.js' -const { page, frontmatter: matter, theme } = useData<'post'>() +const { page, frontmatter: matter, theme, blog } = useData<'post'>() const colors = useTagColors() const readingTime = useReadingTimeLocale() const { tags: tagsLink } = useInternalLink() @@ -22,8 +22,7 @@ const createTime = computed(() => { }) const tags = computed(() => { - const blog = theme.value.blog || {} - const tagTheme = blog.tagsTheme ?? 'colored' + const tagTheme = blog.value.tagsTheme ?? 'colored' if (matter.value.tags) { return matter.value.tags.slice(0, 4).map(tag => ({ name: tag, diff --git a/theme/src/client/composables/blog-archives.ts b/theme/src/client/composables/blog-archives.ts index 5bbf766c..9cb05ffe 100644 --- a/theme/src/client/composables/blog-archives.ts +++ b/theme/src/client/composables/blog-archives.ts @@ -1,18 +1,19 @@ import type { PlumeThemeBlogPostItem } from '../../shared/index.js' import { computed } from 'vue' -import { useRouteLocale } from 'vuepress/client' import { useLocalePostList } from './blog-data.js' -import { getPresetLocaleData } from './preset-locales.js' +import { useData } from './data.js' +import { useThemeData } from './theme-data.js' export type ShortPostItem = Pick export function useArchives() { - const locale = useRouteLocale() + const themeData = useThemeData() const list = useLocalePostList() + const { theme } = useData() const archives = computed(() => { const archives: { title: string, label: string, list: ShortPostItem[] }[] = [] - const countLocale = getPresetLocaleData(locale.value, 'archiveTotal') + const countLocale = theme.value.archiveTotalText || themeData.value.archiveTotalText list.value.forEach((item) => { const createTime = item.createTime?.split(/\s|T/)[0] || '' @@ -30,7 +31,7 @@ export function useArchives() { }) archives.forEach((item) => { - item.label = countLocale.replace('{count}', item.list.length.toString()) + item.label = countLocale?.replace('{count}', item.list.length.toString()) || '' }) return archives diff --git a/theme/src/client/composables/internal-link.ts b/theme/src/client/composables/internal-link.ts index 0e5329f0..27e4912b 100644 --- a/theme/src/client/composables/internal-link.ts +++ b/theme/src/client/composables/internal-link.ts @@ -2,7 +2,7 @@ import type { PresetLocale } from '../../shared/index.js' import { computed } from 'vue' import { useRouteLocale } from 'vuepress/client' import { useData } from './data.js' -import { getPresetLocaleData } from './preset-locales.js' +import { useThemeData } from './theme-data.js' export interface InternalLink { text: string @@ -10,13 +10,14 @@ export interface InternalLink { } export function useInternalLink() { - const { blog } = useData() + const { blog, theme } = useData() + const themeData = useThemeData() const routeLocale = useRouteLocale() function resolveLink(name: keyof PresetLocale, link: string): InternalLink { return { link: (routeLocale.value + link).replace(/\/+/g, '/'), - text: getPresetLocaleData(routeLocale.value, name), + text: theme.value[`${name}Text`] || themeData.value[`${name}Text`], } } diff --git a/theme/src/node/config/resolveLocaleOptions.ts b/theme/src/node/config/resolveLocaleOptions.ts index 0762a18d..fc14cf87 100644 --- a/theme/src/node/config/resolveLocaleOptions.ts +++ b/theme/src/node/config/resolveLocaleOptions.ts @@ -1,6 +1,7 @@ import type { App } from 'vuepress' import type { PlumeThemeData, PlumeThemeLocaleOptions } from '../../shared/index.js' -import { entries, fromEntries, getLocaleConfig } from '@vuepress/helper' +import { hasOwn, uniq } from '@pengzhanbo/utils' +import { entries, fromEntries, getLocaleConfig, isPlainObject } from '@vuepress/helper' import { LOCALE_OPTIONS } from '../locales/index.js' import { THEME_NAME } from '../utils/index.js' @@ -41,8 +42,7 @@ const FALLBACK_OPTIONS: PlumeThemeData = { export function resolveLocaleOptions(app: App, { locales, ...options }: PlumeThemeLocaleOptions): PlumeThemeLocaleOptions { const resolvedOptions: PlumeThemeLocaleOptions = { - ...FALLBACK_OPTIONS, - ...options, + ...mergeLocaleOptions(FALLBACK_OPTIONS, options), locales: getLocaleConfig({ app, name: THEME_NAME, @@ -53,10 +53,31 @@ export function resolveLocaleOptions(app: App, { locales, ...options }: PlumeThe ...locales, }).map(([locale, opt]) => [ locale, - { ...options, ...opt }, + mergeLocaleOptions(options, opt), ]), ), }), } return resolvedOptions } + +function mergeLocaleOptions(target: PlumeThemeData, source: PlumeThemeData): PlumeThemeData { + const res: PlumeThemeData = {} + const keys = uniq([...Object.keys(target), ...Object.keys(source)]) as (keyof PlumeThemeData)[] + for (const key of keys) { + if (hasOwn(source, key)) { + const value = source[key] + const targetValue = target[key] + if (isPlainObject(targetValue) && isPlainObject(value)) { + res[key] = Object.assign({}, targetValue, value) as any + } + else { + res[key] = value as any + } + } + else { + res[key] = target[key] + } + } + return res +} diff --git a/theme/src/node/config/resolveThemeData.ts b/theme/src/node/config/resolveThemeData.ts index 1f9fa685..32e68692 100644 --- a/theme/src/node/config/resolveThemeData.ts +++ b/theme/src/node/config/resolveThemeData.ts @@ -1,7 +1,6 @@ import type { App } from 'vuepress' import type { NavItem, PlumeThemeLocaleOptions } from '../../shared/index.js' -import { entries, getRootLangPath, isBoolean, isPlainObject } from '@vuepress/helper' -import { PRESET_LOCALES } from '../locales/index.js' +import { entries, isBoolean, isPlainObject } from '@vuepress/helper' import { withBase } from '../utils/index.js' const EXCLUDE_LIST = ['locales', 'sidebar', 'navbar', 'notes', 'sidebar', 'article', 'avatar'] @@ -10,7 +9,6 @@ const EXCLUDE_LOCALE_LIST = [...EXCLUDE_LIST, 'blog', 'appearance'] export function resolveThemeData(app: App, options: PlumeThemeLocaleOptions): PlumeThemeLocaleOptions { const themeData: PlumeThemeLocaleOptions = { locales: {} } - const root = getRootLangPath(app) entries(options).forEach(([key, value]) => { if (!EXCLUDE_LIST.includes(key)) @@ -61,27 +59,26 @@ export function resolveThemeData(app: App, options: PlumeThemeLocaleOptions): Pl // home | blog | tags | archives if (opt.navbar !== false && (!opt.navbar || opt.navbar.length === 0)) { // fallback navbar option - const localePath = locale === '/' ? root : locale const navbar: NavItem[] = [{ - text: PRESET_LOCALES[localePath].home, + text: opt.homeText || options.homeText || 'Home', link: locale, }] if (options.blog !== false) { const blog = options.blog || {} const blogLink = blog.link || '/blog/' navbar.push({ - text: PRESET_LOCALES[localePath].blog, + text: opt.blogText || options.blogText || 'Blog', link: withBase(blogLink, locale), }) if (blog.tags !== false) { navbar.push({ - text: PRESET_LOCALES[localePath].tag, + text: opt.tagText || options.tagText || 'Tags', link: withBase(blog.tagsLink || `${blogLink}/tags/`, locale), }) } if (blog.archives !== false) { navbar.push({ - text: PRESET_LOCALES[localePath].archive, + text: opt.archiveText || options.archiveText || 'Archives', link: withBase(blog.archivesLink || `${blogLink}/archives/`, locale), }) } diff --git a/theme/src/node/index.ts b/theme/src/node/index.ts index a37691df..df8b9d04 100644 --- a/theme/src/node/index.ts +++ b/theme/src/node/index.ts @@ -2,6 +2,7 @@ import { plumeTheme } from './theme.js' export * from '../shared/index.js' export * from './defineConfig.js' +export * from './types.js' export { plumeTheme } diff --git a/theme/src/node/locales/de.ts b/theme/src/node/locales/de.ts index 49d10a65..1f4b2248 100644 --- a/theme/src/node/locales/de.ts +++ b/theme/src/node/locales/de.ts @@ -37,6 +37,13 @@ export const deLocale: PlumeThemeLocaleData = { linkText: 'Zur Startseite', }, + homeText: 'Startseite', + blogText: 'Blog', + tagText: 'Tag', + archiveText: 'Archiv', + categoryText: 'Kategorie', + archiveTotalText: '{count} Beiträge', + encryptButtonText: 'Bestätigen', encryptPlaceholder: 'Bitte Passwort eingeben', encryptGlobalText: 'Diese Website ist nur mit Passwort zugänglich', @@ -49,12 +56,6 @@ export const deLocale: PlumeThemeLocaleData = { } export const dePresetLocale: PresetLocale = { - 'home': 'Startseite', - 'blog': 'Blog', - 'tag': 'Tag', - 'archive': 'Archiv', - 'category': 'Kategorie', - 'archiveTotal': '{count} Beiträge', // ------ copyright license ------ 'CC0': 'CC0 1.0 Universell', diff --git a/theme/src/node/locales/en.ts b/theme/src/node/locales/en.ts index fdeaacd4..664d772a 100644 --- a/theme/src/node/locales/en.ts +++ b/theme/src/node/locales/en.ts @@ -28,6 +28,13 @@ export const enLocale: PlumeThemeLocaleData = { encryptGlobalText: 'Only password can access this site', encryptPageText: 'Only password can access this page', + homeText: 'Home', + blogText: 'Blog', + tagText: 'Tags', + archiveText: 'Archives', + categoryText: 'Categories', + archiveTotalText: '{count} articles', + footer: { message: 'Powered by VuePress & vuepress-theme-plume', @@ -35,13 +42,6 @@ export const enLocale: PlumeThemeLocaleData = { } export const enPresetLocale: PresetLocale = { - 'home': 'Home', - 'blog': 'Blog', - 'tag': 'Tags', - 'archive': 'Archives', - 'category': 'Categories', - 'archiveTotal': '{count} articles', - // ------ copyright license ------ 'CC0': 'CC0 1.0 Universal', 'CC-BY-4.0': 'Attribution 4.0 International', diff --git a/theme/src/node/locales/fr.ts b/theme/src/node/locales/fr.ts index 14835eb5..6317f4cd 100644 --- a/theme/src/node/locales/fr.ts +++ b/theme/src/node/locales/fr.ts @@ -37,6 +37,13 @@ export const frLocale: PlumeThemeLocaleData = { linkText: 'Retour à l\'accueil', }, + homeText: 'Accueil', + blogText: 'Blog', + tagText: 'Étiquette', + archiveText: 'Archives', + categoryText: 'Catégorie', + archiveTotalText: '{count} articles', + encryptButtonText: 'Confirmer', encryptPlaceholder: 'Veuillez entrer le mot de passe', encryptGlobalText: 'Ce site n\'est accessible qu\'avec un mot de passe', @@ -49,13 +56,6 @@ export const frLocale: PlumeThemeLocaleData = { } export const frPresetLocale: PresetLocale = { - 'home': 'Accueil', - 'blog': 'Blog', - 'tag': 'Étiquette', - 'archive': 'Archives', - 'category': 'Catégorie', - 'archiveTotal': '{count} articles', - // ------ copyright license ------ 'CC0': 'CC0 1.0 Universel', 'CC-BY-4.0': 'Attribution 4.0 International', diff --git a/theme/src/node/locales/ja.ts b/theme/src/node/locales/ja.ts index d74b4d82..78a2f9f2 100644 --- a/theme/src/node/locales/ja.ts +++ b/theme/src/node/locales/ja.ts @@ -37,6 +37,13 @@ export const jaLocale: PlumeThemeLocaleData = { linkText: 'ホームに戻る', }, + homeText: 'ホーム', + blogText: 'ブログ', + tagText: 'タグ', + archiveText: 'アーカイブ', + categoryText: 'カテゴリー', + archiveTotalText: '{count} 件', + encryptButtonText: '確認', encryptPlaceholder: 'パスワードを入力してください', encryptGlobalText: 'このサイトはパスワードでのみアクセス可能です', @@ -49,13 +56,6 @@ export const jaLocale: PlumeThemeLocaleData = { } export const jaPresetLocale: PresetLocale = { - 'home': 'ホーム', - 'blog': 'ブログ', - 'tag': 'タグ', - 'archive': 'アーカイブ', - 'category': 'カテゴリー', - 'archiveTotal': '{count} 件', - // ------ copyright license ------ 'CC0': 'CC0 1.0 パブリックドメイン', 'CC-BY-4.0': '表示 4.0 国際', diff --git a/theme/src/node/locales/ru.ts b/theme/src/node/locales/ru.ts index 04d956b7..72856e64 100644 --- a/theme/src/node/locales/ru.ts +++ b/theme/src/node/locales/ru.ts @@ -37,6 +37,13 @@ export const ruLocale: PlumeThemeLocaleData = { linkText: 'Вернуться на главную', }, + homeText: 'Главная', + blogText: 'Блог', + tagText: 'Теги', + archiveText: 'Архив', + categoryText: 'Категории', + archiveTotalText: '{count} статей', + encryptButtonText: 'Подтвердить', encryptPlaceholder: 'Введите пароль', encryptGlobalText: 'Доступ к сайту только по паролю', @@ -49,13 +56,6 @@ export const ruLocale: PlumeThemeLocaleData = { } export const ruPresetLocale: PresetLocale = { - 'home': 'Главная', - 'blog': 'Блог', - 'tag': 'Теги', - 'archive': 'Архив', - 'category': 'Категории', - 'archiveTotal': '{count} статей', - // ------ copyright license ------ 'CC0': 'CC0 1.0 Универсальная', 'CC-BY-4.0': 'Атрибуция 4.0 Международный', diff --git a/theme/src/node/locales/zh-tw.ts b/theme/src/node/locales/zh-tw.ts index 540686af..007a5e84 100644 --- a/theme/src/node/locales/zh-tw.ts +++ b/theme/src/node/locales/zh-tw.ts @@ -37,6 +37,13 @@ export const zhTwLocale: PlumeThemeLocaleData = { linkText: '返回首頁', }, + homeText: '首頁', + blogText: '博客', + tagText: '標籤', + archiveText: '歸檔', + categoryText: '分類', + archiveTotalText: '{count} 篇', + encryptButtonText: '確認', encryptPlaceholder: '請輸入密碼', encryptGlobalText: '本站只允許密碼訪問', @@ -49,13 +56,6 @@ export const zhTwLocale: PlumeThemeLocaleData = { } export const zhTwPresetLocale: PresetLocale = { - 'home': '首頁', - 'blog': '博客', - 'tag': '標籤', - 'archive': '歸檔', - 'category': '分類', - 'archiveTotal': '{count} 篇', - // ------ copyright license ------ 'CC0': 'CC0 1.0 通用', 'CC-BY-4.0': '署名 4.0 國際', diff --git a/theme/src/node/locales/zh.ts b/theme/src/node/locales/zh.ts index c5e7b343..6083b3a2 100644 --- a/theme/src/node/locales/zh.ts +++ b/theme/src/node/locales/zh.ts @@ -36,6 +36,13 @@ export const zhLocale: PlumeThemeLocaleData = { linkText: '返回首页', }, + homeText: '首页', + blogText: '博客', + tagText: '标签', + archiveText: '归档', + categoryText: '分类', + archiveTotalText: '{count} 篇', + encryptButtonText: '确认', encryptPlaceholder: '请输入密码', encryptGlobalText: '本站只允许密码访问', @@ -48,13 +55,6 @@ export const zhLocale: PlumeThemeLocaleData = { } export const zhPresetLocale: PresetLocale = { - 'home': '首页', - 'blog': '博客', - 'tag': '标签', - 'archive': '归档', - 'category': '分类', - 'archiveTotal': '{count} 篇', - // ------ copyright license ------ 'CC0': 'CC0 1.0 通用', 'CC-BY-4.0': '署名 4.0 国际', diff --git a/theme/src/node/pages/createPages.ts b/theme/src/node/pages/createPages.ts index eb9c189b..4bc61c87 100644 --- a/theme/src/node/pages/createPages.ts +++ b/theme/src/node/pages/createPages.ts @@ -1,8 +1,7 @@ import type { App, Page } from 'vuepress/core' import type { PlumeThemeLocaleOptions } from '../../shared/index.js' -import { getRootLang, getRootLangPath } from '@vuepress/helper' +import { getRootLang } from '@vuepress/helper' import { createPage } from 'vuepress/core' -import { PRESET_LOCALES } from '../locales/index.js' import { withBase } from '../utils/index.js' export async function createPages(app: App, localeOption: PlumeThemeLocaleOptions) { @@ -11,26 +10,20 @@ export async function createPages(app: App, localeOption: PlumeThemeLocaleOption const pageList: Promise[] = [] const locales = localeOption.locales || {} - const rootPath = getRootLangPath(app) const rootLang = getRootLang(app) const blog = localeOption.blog || {} const link = blog.link || '/blog/' - const getTitle = (locale: string, key: string) => { - const opt = PRESET_LOCALES[locale] || PRESET_LOCALES[rootPath] || {} - return opt[key] || '' - } - for (const localePath of Object.keys(locales)) { const lang = app.siteData.locales?.[localePath]?.lang || rootLang - const locale = localePath === '/' ? rootPath : localePath + const opt = locales[localePath] // 添加 博客页面 if (blog.postList !== false) { pageList.push(createPage(app, { path: withBase(link, localePath), - frontmatter: { lang, _pageLayout: 'blog', title: getTitle(locale, 'blog') }, + frontmatter: { lang, _pageLayout: 'blog', title: opt.blogText || localeOption.blogText || 'Blog' }, })) } @@ -38,7 +31,7 @@ export async function createPages(app: App, localeOption: PlumeThemeLocaleOption if (blog.tags !== false) { pageList.push(createPage(app, { path: withBase(blog.tagsLink || `${link}/tags/`, localePath), - frontmatter: { lang, _pageLayout: 'blog-tags', title: getTitle(locale, 'tag') }, + frontmatter: { lang, _pageLayout: 'blog-tags', title: opt.tagText || localeOption.tagText || 'Tags' }, })) } @@ -46,7 +39,7 @@ export async function createPages(app: App, localeOption: PlumeThemeLocaleOption if (blog.archives !== false) { pageList.push(createPage(app, { path: withBase(blog.archivesLink || `${link}/archives/`, localePath), - frontmatter: { lang, _pageLayout: 'blog-archives', title: getTitle(locale, 'archive') }, + frontmatter: { lang, _pageLayout: 'blog-archives', title: opt.archiveText || localeOption.archiveText || 'Archives' }, })) } @@ -54,7 +47,7 @@ export async function createPages(app: App, localeOption: PlumeThemeLocaleOption if (blog.categories !== false) { pageList.push(createPage(app, { path: withBase(blog.categoriesLink || `${link}/categories/`, localePath), - frontmatter: { lang, _pageLayout: 'blog-categories', title: getTitle(locale, 'category') }, + frontmatter: { lang, _pageLayout: 'blog-categories', title: opt.categoryText || localeOption.categoryText || 'Categories' }, })) } } diff --git a/theme/src/shared/base.ts b/theme/src/shared/base.ts index 5ab6f4bd..85988967 100644 --- a/theme/src/shared/base.ts +++ b/theme/src/shared/base.ts @@ -50,14 +50,7 @@ export type SocialLinkIconUnion = export type SocialLinkIcon = SocialLinkIconUnion | { svg: string, name?: string } -export interface PresetLocale extends Record { - home: string - blog: string - tag: string - archive: string - category: string - archiveTotal: string -} +export interface PresetLocale extends Record {} export type KnownCopyrightLicense = | 'CC-BY-4.0' diff --git a/theme/src/shared/options/locale.ts b/theme/src/shared/options/locale.ts index 40a7978f..e9a33841 100644 --- a/theme/src/shared/options/locale.ts +++ b/theme/src/shared/options/locale.ts @@ -299,6 +299,32 @@ export interface PlumeThemeLocaleData extends LocaleData { linkText?: string } + /** + * 首页文本,用于默认生成的导航栏、面包屑导航中 + */ + homeText?: string + /** + * 博客文本,用于默认生成的导航栏、面包屑导航中 + */ + blogText?: string + /** + * 标签文本,用于默认生成的导航栏、博客标签页中 + */ + tagText?: string + /** + * 归档文本,用于默认生成的导航栏、博客归档页中 + */ + archiveText?: string + /** + * 分类文本,用于默认生成的导航栏、博客分类页中 + */ + categoryText?: string + /** + * 博客总数文本,用于默认生成的导航栏、博客归档页中 + * @default '{count} articles'' + */ + archiveTotalText?: string + /** * 全站加密时的提示 */