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
pull/350/head
Eduardo San Martin Morote 4 years ago committed by GitHub
parent 858c0e77a0
commit bc78adb468
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,7 +7,7 @@ const el = ref<HTMLElement | null>(null)
const open = ref(false) const open = ref(false)
watch(open, (value) => { watch(open, (value) => {
if (value === false) { if (!value) {
el.value!.scrollTop = 0 el.value!.scrollTop = 0
} }
}) })

@ -46,9 +46,11 @@ export function initData(route: Route): VitePressData {
frontmatter: computed(() => route.data.frontmatter), frontmatter: computed(() => route.data.frontmatter),
lang: computed(() => site.value.lang), lang: computed(() => site.value.lang),
localePath: computed(() => { localePath: computed(() => {
const { locales, lang } = site.value const { langs, lang } = site.value
const path = Object.keys(locales).find((lp) => locales[lp].lang === lang) const path = Object.keys(langs).find(
return withBase((locales && path) || '/') (langPath) => langs[langPath].lang === lang
)
return withBase(path || '/')
}), }),
title: computed(() => { title: computed(() => {
return route.data.title return route.data.title

@ -32,9 +32,7 @@ const isCustomLayout = computed(() => !!frontmatter.value.customLayout)
const enableHome = computed(() => !!frontmatter.value.home) const enableHome = computed(() => !!frontmatter.value.home)
// automatic multilang check for AlgoliaSearchBox // automatic multilang check for AlgoliaSearchBox
const isMultiLang = computed( const isMultiLang = computed(() => Object.keys(site.value.langs).length > 1)
() => Object.keys(theme.value.locales || {}).length > 0
)
// navbar // navbar
const showNavbar = computed(() => { const showNavbar = computed(() => {

@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { useData } from 'vitepress' import { useData } from 'vitepress'
import { useLocaleLinks } from '../composables/nav' import { useLanguageLinks } from '../composables/nav'
import { useRepo } from '../composables/repo' import { useRepo } from '../composables/repo'
import NavLink from './NavLink.vue' import NavLink from './NavLink.vue'
import NavDropdownLink from './NavDropdownLink.vue' import NavDropdownLink from './NavDropdownLink.vue'
const { theme } = useData() const { theme } = useData()
const localeLinks = useLocaleLinks() const localeLinks = useLanguageLinks()
const repo = useRepo() const repo = useRepo()
const show = computed(() => theme.value.nav || repo.value || localeLinks.value) const show = computed(() => theme.value.nav || repo.value || localeLinks.value)
</script> </script>

@ -1,57 +1,30 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useRoute, useData, inBrowser } from 'vitepress' import { useData, useRoute } from 'vitepress'
import type { DefaultTheme } from '../config' import type { DefaultTheme } from '../config'
export function useLocaleLinks() { export function useLanguageLinks() {
const route = useRoute() const { site, localePath, theme } = useData()
const { site } = useData()
return computed(() => { return computed(() => {
const theme = site.value.themeConfig as DefaultTheme.Config const langs = site.value.langs
const locales = theme.locales const localePaths = Object.keys(langs)
if (!locales) { // one language
if (localePaths.length < 2) {
return null return null
} }
const localeKeys = Object.keys(locales) const route = useRoute()
if (localeKeys.length <= 1) { // intentionally remove the leading slash because each locale has one
return null const currentPath = route.path.replace(localePath.value, '')
}
// 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}`
}
})
const currentLangKey = currentLangBase ? currentLangBase : '/' const candidates = localePaths.map((localePath) => ({
text: langs[localePath].label,
link: `${localePath}${currentPath}`
}))
const selectText = locales[currentLangKey].selectText const selectText = theme.value.selectText || 'Languages'
? locales[currentLangKey].selectText
: 'Languages'
return { return {
text: selectText, text: selectText,

@ -4,7 +4,12 @@ import chalk from 'chalk'
import globby from 'globby' import globby from 'globby'
import { AliasOptions, UserConfig as ViteConfig } from 'vite' import { AliasOptions, UserConfig as ViteConfig } from 'vite'
import { Options as VuePluginOptions } from '@vitejs/plugin-vue' 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 { resolveAliases, APP_PATH, DEFAULT_THEME_PATH } from './alias'
import { MarkdownOptions } from './markdown/markdown' import { MarkdownOptions } from './markdown/markdown'
@ -142,6 +147,7 @@ export async function resolveSiteData(
head: userConfig.head || [], head: userConfig.head || [],
themeConfig: userConfig.themeConfig || {}, themeConfig: userConfig.themeConfig || {},
locales: userConfig.locales || {}, locales: userConfig.locales || {},
langs: createLangDictionary(userConfig),
customData: userConfig.customData || {} customData: userConfig.customData || {}
} }
} }

@ -1,4 +1,4 @@
import { SiteData } from '../../types/shared' import { LocaleConfig, SiteData } from '../../types/shared'
export type { export type {
SiteData, SiteData,
@ -12,7 +12,7 @@ export const EXTERNAL_URL_RE = /^https?:/i
export const inBrowser = typeof window !== 'undefined' 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. // first match to the routes with the most deep level.
roots.sort((a, b) => { roots.sort((a, b) => {
const levelDelta = b.split('/').length - a.split('/').length const levelDelta = b.split('/').length - a.split('/').length
@ -24,9 +24,8 @@ function findMatchRoot(route: string, roots: string[]) {
}) })
for (const r of roots) { for (const r of roots) {
if (route.startsWith(r)) return if (route.startsWith(r)) return r
} }
return undefined
} }
function resolveLocales<T>( function resolveLocales<T>(
@ -37,6 +36,23 @@ function resolveLocales<T>(
return localeRoot ? locales[localeRoot] : undefined return localeRoot ? locales[localeRoot] : undefined
} }
export function createLangDictionary(siteData: {
themeConfig?: any
locales?: Record<string, LocaleConfig>
}) {
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<string, { lang: string; label: string }>)
: {}
}
// this merges the locales data to the main data by the route // this merges the locales data to the main data by the route
export function resolveSiteDataByRoute( export function resolveSiteDataByRoute(
siteData: SiteData, siteData: SiteData,
@ -44,12 +60,11 @@ export function resolveSiteDataByRoute(
): SiteData { ): SiteData {
route = cleanRoute(siteData, route) route = cleanRoute(siteData, route)
const localeData = resolveLocales(siteData.locales || {}, route) || {} const localeData = resolveLocales(siteData.locales || {}, route)
const localeThemeConfig = const localeThemeConfig = resolveLocales<any>(
resolveLocales<any>( siteData.themeConfig.locales || {},
(siteData.themeConfig && siteData.themeConfig.locales) || {}, route
route )
) || {}
return { return {
...siteData, ...siteData,
@ -60,8 +75,10 @@ export function resolveSiteDataByRoute(
// clean the locales to reduce the bundle size // clean the locales to reduce the bundle size
locales: {} locales: {}
}, },
lang: localeThemeConfig.lang || siteData.lang, lang: (localeData || siteData).lang,
locales: {} // clean the locales to reduce the bundle size
locales: {},
langs: createLangDictionary(siteData)
} }
} }

24
types/shared.d.ts vendored

@ -11,12 +11,36 @@ export interface LocaleConfig {
export interface SiteData<ThemeConfig = any> { export interface SiteData<ThemeConfig = any> {
base: string base: string
/**
* Language of the site as it should be set on the `html` element.
* @example `en-US`, `zh-CN`
*/
lang: string lang: string
title: string title: string
description: string description: string
head: HeadConfig[] head: HeadConfig[]
themeConfig: ThemeConfig themeConfig: ThemeConfig
locales: Record<string, LocaleConfig> locales: Record<string, LocaleConfig>
/**
* 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 `<html>` element.
* @example `en-US`, `zh-CN`
*/
lang: string
/**
* Label to display in the language menu.
* @example `English', ``
*/
label: string
}
>
customData: any customData: any
} }

Loading…
Cancel
Save