From bc78adb468bce8ce2d4e2543423adacc9351cf51 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Fri, 20 Aug 2021 12:08:06 +0200 Subject: [PATCH] fix(i18n): fix locales reading, add site.langs (#353) It works with the same config as Vuepress 1: https://vuepress.vuejs.org/guide/i18n.html#site-level-i18n-config We could probably adapt the `label` property like Vuepress 2 https://v2.vuepress.vuejs.org/guide/i18n.html#site-i18n-config in a next release --- src/client/app/components/Debug.vue | 2 +- src/client/app/data.ts | 8 ++- src/client/theme-default/Layout.vue | 4 +- .../theme-default/components/NavLinks.vue | 4 +- src/client/theme-default/composables/nav.ts | 57 +++++-------------- src/node/config.ts | 8 ++- src/shared/shared.ts | 41 +++++++++---- types/shared.d.ts | 24 ++++++++ 8 files changed, 84 insertions(+), 64 deletions(-) diff --git a/src/client/app/components/Debug.vue b/src/client/app/components/Debug.vue index ce805cf3c210..617895dbf659 100644 --- a/src/client/app/components/Debug.vue +++ b/src/client/app/components/Debug.vue @@ -7,7 +7,7 @@ const el = ref(null) const open = ref(false) watch(open, (value) => { - if (value === false) { + if (!value) { el.value!.scrollTop = 0 } }) diff --git a/src/client/app/data.ts b/src/client/app/data.ts index 143b3f5a7d07..09da9662b7dd 100644 --- a/src/client/app/data.ts +++ b/src/client/app/data.ts @@ -46,9 +46,11 @@ export function initData(route: Route): VitePressData { frontmatter: computed(() => route.data.frontmatter), lang: computed(() => site.value.lang), localePath: computed(() => { - const { locales, lang } = site.value - const path = Object.keys(locales).find((lp) => locales[lp].lang === lang) - return withBase((locales && path) || '/') + const { langs, lang } = site.value + const path = Object.keys(langs).find( + (langPath) => langs[langPath].lang === lang + ) + return withBase(path || '/') }), title: computed(() => { return route.data.title diff --git a/src/client/theme-default/Layout.vue b/src/client/theme-default/Layout.vue index c7803adc86d2..d7da603e6afb 100644 --- a/src/client/theme-default/Layout.vue +++ b/src/client/theme-default/Layout.vue @@ -32,9 +32,7 @@ const isCustomLayout = computed(() => !!frontmatter.value.customLayout) const enableHome = computed(() => !!frontmatter.value.home) // automatic multilang check for AlgoliaSearchBox -const isMultiLang = computed( - () => Object.keys(theme.value.locales || {}).length > 0 -) +const isMultiLang = computed(() => Object.keys(site.value.langs).length > 1) // navbar const showNavbar = computed(() => { diff --git a/src/client/theme-default/components/NavLinks.vue b/src/client/theme-default/components/NavLinks.vue index aaa622a1079d..bc221596d56d 100644 --- a/src/client/theme-default/components/NavLinks.vue +++ b/src/client/theme-default/components/NavLinks.vue @@ -1,13 +1,13 @@ diff --git a/src/client/theme-default/composables/nav.ts b/src/client/theme-default/composables/nav.ts index 06f8bae67f02..7c93572184a7 100644 --- a/src/client/theme-default/composables/nav.ts +++ b/src/client/theme-default/composables/nav.ts @@ -1,57 +1,30 @@ import { computed } from 'vue' -import { useRoute, useData, inBrowser } from 'vitepress' +import { useData, useRoute } from 'vitepress' import type { DefaultTheme } from '../config' -export function useLocaleLinks() { - const route = useRoute() - const { site } = useData() +export function useLanguageLinks() { + const { site, localePath, theme } = useData() return computed(() => { - const theme = site.value.themeConfig as DefaultTheme.Config - const locales = theme.locales + const langs = site.value.langs + const localePaths = Object.keys(langs) - if (!locales) { + // one language + if (localePaths.length < 2) { return null } - const localeKeys = Object.keys(locales) + const route = useRoute() - if (localeKeys.length <= 1) { - return null - } - - // handle site base - const siteBase = inBrowser ? site.value.base : '/' - - const siteBaseWithoutSuffix = siteBase.endsWith('/') - ? siteBase.slice(0, -1) - : siteBase - - // remove site base in browser env - const routerPath = route.path.slice(siteBaseWithoutSuffix.length) - - const currentLangBase = localeKeys.find((key) => { - return key === '/' ? false : routerPath.startsWith(key) - }) - - const currentContentPath = currentLangBase - ? routerPath.substring(currentLangBase.length - 1) - : routerPath - - const candidates = localeKeys.map((v) => { - const localePath = v.endsWith('/') ? v.slice(0, -1) : v - - return { - text: locales[v].label, - link: `${localePath}${currentContentPath}` - } - }) + // intentionally remove the leading slash because each locale has one + const currentPath = route.path.replace(localePath.value, '') - const currentLangKey = currentLangBase ? currentLangBase : '/' + const candidates = localePaths.map((localePath) => ({ + text: langs[localePath].label, + link: `${localePath}${currentPath}` + })) - const selectText = locales[currentLangKey].selectText - ? locales[currentLangKey].selectText - : 'Languages' + const selectText = theme.value.selectText || 'Languages' return { text: selectText, diff --git a/src/node/config.ts b/src/node/config.ts index 4f8ebdd78cae..a7b052b579b1 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -4,7 +4,12 @@ import chalk from 'chalk' import globby from 'globby' import { AliasOptions, UserConfig as ViteConfig } from 'vite' import { Options as VuePluginOptions } from '@vitejs/plugin-vue' -import { SiteData, HeadConfig, LocaleConfig } from './shared' +import { + SiteData, + HeadConfig, + LocaleConfig, + createLangDictionary +} from './shared' import { resolveAliases, APP_PATH, DEFAULT_THEME_PATH } from './alias' import { MarkdownOptions } from './markdown/markdown' @@ -142,6 +147,7 @@ export async function resolveSiteData( head: userConfig.head || [], themeConfig: userConfig.themeConfig || {}, locales: userConfig.locales || {}, + langs: createLangDictionary(userConfig), customData: userConfig.customData || {} } } diff --git a/src/shared/shared.ts b/src/shared/shared.ts index 302fa31284ba..bdfe89f64716 100644 --- a/src/shared/shared.ts +++ b/src/shared/shared.ts @@ -1,4 +1,4 @@ -import { SiteData } from '../../types/shared' +import { LocaleConfig, SiteData } from '../../types/shared' export type { SiteData, @@ -12,7 +12,7 @@ export const EXTERNAL_URL_RE = /^https?:/i export const inBrowser = typeof window !== 'undefined' -function findMatchRoot(route: string, roots: string[]) { +function findMatchRoot(route: string, roots: string[]): string | undefined { // first match to the routes with the most deep level. roots.sort((a, b) => { const levelDelta = b.split('/').length - a.split('/').length @@ -24,9 +24,8 @@ function findMatchRoot(route: string, roots: string[]) { }) for (const r of roots) { - if (route.startsWith(r)) return + if (route.startsWith(r)) return r } - return undefined } function resolveLocales( @@ -37,6 +36,23 @@ function resolveLocales( return localeRoot ? locales[localeRoot] : undefined } +export function createLangDictionary(siteData: { + themeConfig?: any + locales?: Record +}) { + const { locales } = siteData.themeConfig + const siteLocales = siteData.locales + return locales && siteLocales + ? Object.keys(locales).reduce((langs, path) => { + langs[path] = { + label: locales![path].label, + lang: siteLocales[path].lang + } + return langs + }, {} as Record) + : {} +} + // this merges the locales data to the main data by the route export function resolveSiteDataByRoute( siteData: SiteData, @@ -44,12 +60,11 @@ export function resolveSiteDataByRoute( ): SiteData { route = cleanRoute(siteData, route) - const localeData = resolveLocales(siteData.locales || {}, route) || {} - const localeThemeConfig = - resolveLocales( - (siteData.themeConfig && siteData.themeConfig.locales) || {}, - route - ) || {} + const localeData = resolveLocales(siteData.locales || {}, route) + const localeThemeConfig = resolveLocales( + siteData.themeConfig.locales || {}, + route + ) return { ...siteData, @@ -60,8 +75,10 @@ export function resolveSiteDataByRoute( // clean the locales to reduce the bundle size locales: {} }, - lang: localeThemeConfig.lang || siteData.lang, - locales: {} + lang: (localeData || siteData).lang, + // clean the locales to reduce the bundle size + locales: {}, + langs: createLangDictionary(siteData) } } diff --git a/types/shared.d.ts b/types/shared.d.ts index 1f14a6a53bec..822061e6f608 100644 --- a/types/shared.d.ts +++ b/types/shared.d.ts @@ -11,12 +11,36 @@ export interface LocaleConfig { export interface SiteData { base: string + /** + * Language of the site as it should be set on the `html` element. + * @example `en-US`, `zh-CN` + */ lang: string title: string description: string head: HeadConfig[] themeConfig: ThemeConfig locales: Record + /** + * Available locales for the site when it has defined `locales` in its + * `themeConfig`. This object is otherwise empty. Keys are paths like `/` or + * `/zh/`. + */ + langs: Record< + string, + { + /** + * Lang attribute as set on the `` element. + * @example `en-US`, `zh-CN` + */ + lang: string + /** + * Label to display in the language menu. + * @example `English', `简体中文` + */ + label: string + } + > customData: any }