Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: i18n support #50

Merged
merged 3 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@
},
"homepage": "https://github.com/vuejs/vitepress/tree/master/#readme",
"scripts": {
"dev": "run-p dev-client dev-client-copy dev-node",
"dev": "run-p dev-client dev-client-copy dev-node dev-shared",
"dev-client": "tsc -w -p src/client",
"dev-client-copy": "node scripts/watchAndCopy",
"dev-node": "tsc -w -p src/node",
"build": "rimraf -rf dist && tsc -p src/client && tsc -p src/node && node scripts/copy",
"dev-shared": "tsc -w -p src/shared",
"build": "rimraf -rf dist && tsc -p src/client && tsc -p src/node && tsc -p src/shared && node scripts/copy",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"prepublishOnly": "yarn build && yarn changelog",
"postpublish": "git add CHANGELOG.md && git commit -m 'chore: changelog [ci skip]'"
Expand Down
11 changes: 7 additions & 4 deletions src/client/app/components/Debug.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<template>
<div class="debug" :class="{ open }" @click="open = !open">
<pre>debug</pre>
<pre>$site {{ $site }}</pre>
<pre>$page {{ $page }}</pre>
<pre>$siteByRoute {{ $siteByRoute }}</pre>
<pre>$site {{ $site }}</pre>
</div>
</template>

Expand All @@ -27,8 +28,8 @@ export default {
cursor: pointer;
bottom: 0;
right: 0;
width: 50px;
height: 20px;
width: 80px;
height: 30px;
padding: 5px;
overflow: hidden;
color: #eeeeee;
Expand All @@ -40,13 +41,15 @@ export default {
width: 500px;
height: 100%;
margin-top: 0;
padding: 5px 20px;
padding: 0 0;
overflow: scroll;
}

.debug pre {
font-family: Hack, monospace;
font-size: 13px;
margin: 0;
padding: 5px 10px;
border-bottom: 1px solid #eee;
}
</style>
12 changes: 7 additions & 5 deletions src/client/app/composables/head.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { watchEffect } from 'vue'
import { siteDataRef } from './siteData'
import { watchEffect, Ref } from 'vue'
import { PageDataRef } from './pageData'
import { HeadConfig } from '../../../../types/shared'
import { HeadConfig, SiteData } from '../../../../types/shared'

export function useUpdateHead(pageDataRef: PageDataRef) {
export function useUpdateHead(
pageDataRef: PageDataRef,
siteDataByRouteRef: Ref<SiteData>
) {
const metaTags: HTMLElement[] = Array.from(document.querySelectorAll('meta'))

let isFirstUpdate = true
Expand All @@ -27,7 +29,7 @@ export function useUpdateHead(pageDataRef: PageDataRef) {

watchEffect(() => {
const pageData = pageDataRef.value
const siteData = siteDataRef.value
const siteData = siteDataByRouteRef.value
const pageTitle = pageData && pageData.title
document.title = (pageTitle ? pageTitle + ` | ` : ``) + siteData.title
updateHeadTags([
Expand Down
10 changes: 10 additions & 0 deletions src/client/app/composables/siteDataByRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { computed } from 'vue'
import { resolveSiteDataByRoute } from '/@shared/config'
import { siteDataRef } from './siteData'
import { useRoute } from '../router'

export function useSiteDataByRoute(route = useRoute()) {
return computed(() => {
return resolveSiteDataByRoute(siteDataRef.value, route.path)
})
}
1 change: 1 addition & 0 deletions src/client/app/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './theme'
// composables
export { useSiteData } from './composables/siteData'
export { usePageData } from './composables/pageData'
export { useSiteDataByRoute } from './composables/siteDataByRoute'
export { useRouter, useRoute, Router, Route } from './router'

// components
Expand Down
24 changes: 16 additions & 8 deletions src/client/app/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { createApp as createClientApp, createSSRApp, ref, readonly } from 'vue'
import { createRouter, RouterSymbol } from './router'
import { useUpdateHead } from './composables/head'
import { siteDataRef } from './composables/siteData'
import { pageDataSymbol } from './composables/pageData'
import { Content } from './components/Content'
import Debug from './components/Debug.vue'
import Theme from '/@theme/index'
import { inBrowser, pathToFile } from './utils'
import { useSiteDataByRoute } from './composables/siteDataByRoute'
import { siteDataRef } from './composables/siteData'

const NotFound = Theme.NotFound || (() => '404 Not Found')

Expand All @@ -15,11 +16,6 @@ export function createApp() {
// distinct per-request.
const pageDataRef = ref()

if (inBrowser) {
// dynamically update head tags
useUpdateHead(pageDataRef)
}

if (import.meta.hot) {
// hot reload pageData
import.meta.hot!.on('vitepress:pageData', (data) => {
Expand Down Expand Up @@ -80,20 +76,32 @@ export function createApp() {
process.env.NODE_ENV === 'production' ? () => null : Debug
)

const siteDataByRouteRef = useSiteDataByRoute(router.route)

if (inBrowser) {
// dynamically update head tags
useUpdateHead(pageDataRef, siteDataByRouteRef)
}

Object.defineProperties(app.config.globalProperties, {
$site: {
get() {
return siteDataRef.value
}
},
$siteByRoute: {
get() {
return siteDataByRouteRef.value
}
},
$page: {
get() {
return pageDataRef.value
}
},
$theme: {
get() {
return siteDataRef.value.themeConfig
return siteDataByRouteRef.value.themeConfig
}
}
})
Expand All @@ -102,7 +110,7 @@ export function createApp() {
Theme.enhanceApp({
app,
router,
siteData: siteDataRef
siteData: siteDataByRouteRef
})
}

Expand Down
6 changes: 3 additions & 3 deletions src/client/theme-default/components/NavBar.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { computed } from 'vue'
import { useSiteData } from 'vitepress'
import { useSiteDataByRoute } from 'vitepress'
import NavBarLink from './NavBarLink.vue'
import NavDropdownLink from './NavDropdownLink.vue'

Expand All @@ -14,9 +14,9 @@ export default {
navData:
process.env.NODE_ENV === 'production'
? // navbar items do not change in production
useSiteData().value.themeConfig.nav
useSiteDataByRoute().value.themeConfig.nav
: // use computed in dev for hot reload
computed(() => useSiteData().value.themeConfig.nav)
computed(() => useSiteDataByRoute().value.themeConfig.nav)
}
}
}
4 changes: 2 additions & 2 deletions src/client/theme-default/components/SideBar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useSiteData, usePageData, useRoute } from 'vitepress'
import { usePageData, useRoute, useSiteDataByRoute } from 'vitepress'
import { computed, h, FunctionalComponent, VNode } from 'vue'
import { Header } from '../../../../types/shared'
import { isActive, getPathDirName } from '../utils'
Expand Down Expand Up @@ -31,7 +31,7 @@ export default {

setup() {
const pageData = usePageData()
const siteData = useSiteData()
const siteData = useSiteDataByRoute()
const route = useRoute()

useActiveSidebarLinks()
Expand Down
11 changes: 9 additions & 2 deletions src/client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "../../dist/client",
"outDir": "../../dist",
"module": "esnext",
"lib": ["ESNext", "DOM"],
"types": ["vite"],
"paths": {
"/@app/*": ["app/*"],
"/@theme/*": ["theme-default/*"],
"/@shared/*": ["../shared/*"],
"vitepress": ["app/exports.ts"]
}
},
"include": [".", "../../types/shared.d.ts"]
"include": [
".",
"../../types/shared.d.ts",
],
"exclude": [
"../shared"
]
}
13 changes: 7 additions & 6 deletions src/node/build/render.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'path'
import fs from 'fs-extra'
import { SiteConfig } from '../config'
import { SiteConfig, resolveSiteDataByRoute } from '../config'
import { HeadConfig } from '../../../types/shared'
import { BuildResult } from 'vite'
import { OutputChunk, OutputAsset } from 'rollup'
Expand All @@ -19,6 +19,7 @@ export async function renderPage(
const { createApp } = require(path.join(config.tempDir, 'app.js'))
const { app, router } = createApp()
const routePath = `/${page.replace(/\.md$/, '')}`
const siteData = resolveSiteDataByRoute(config.site, routePath)
router.go(routePath)
// lazy require server-renderer for production build
const content = await require('@vue/server-renderer').renderToString(app)
Expand All @@ -38,7 +39,7 @@ export async function renderPage(
))
const pageData = JSON.parse(__pageData)

const assetPath = `${config.site.base}_assets/`
const assetPath = `${siteData.base}_assets/`
const preloadLinks = [
// resolve imports for index.js + page.md.js and inject script tags for
// them as well so we fetch everything as early as possible without having
Expand All @@ -53,15 +54,15 @@ export async function renderPage(
.join('\n ')

const html = `
<html lang="en-US">
<html lang="${siteData.lang}">
<head>
<title>${pageData.title ? pageData.title + ` | ` : ``}${
config.site.title
siteData.title
}</title>
<meta name="description" content="${config.site.description}">
<meta name="description" content="${siteData.description}">
<link rel="stylesheet" href="${assetPath}${cssChunk.fileName}">
${preloadLinks}
${renderHead(config.site.head)}
${renderHead(siteData.head)}
${renderHead(pageData.frontmatter.head)}
</head>
<body>
Expand Down
9 changes: 7 additions & 2 deletions src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import chalk from 'chalk'
import globby from 'globby'
import { createResolver, APP_PATH } from './resolver'
import { Resolver } from 'vite'
import { SiteData, HeadConfig } from '../../types/shared'
import { SiteData, HeadConfig, LocaleConfig } from '../../types/shared'
export { resolveSiteDataByRoute } from '../shared/config'

const debug = require('debug')('vitepress:config')

export interface UserConfig<ThemeConfig = any> {
lang?: string
base?: string
title?: string
description?: string
head?: HeadConfig[]
themeConfig?: ThemeConfig
locales?: Record<string, LocaleConfig>
// TODO locales support etc.
}

Expand Down Expand Up @@ -70,10 +73,12 @@ export async function resolveSiteData(root: string): Promise<SiteData> {
}

return {
lang: userConfig.lang || 'en-US',
title: userConfig.title || 'VitePress',
description: userConfig.description || 'A VitePress site',
base: userConfig.base ? userConfig.base.replace(/([^/])$/, '$1/') : '/',
head: userConfig.head || [],
themeConfig: userConfig.themeConfig || {}
themeConfig: userConfig.themeConfig || {},
locales: userConfig.locales || {}
}
}
2 changes: 2 additions & 0 deletions src/node/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'path'
import { Resolver } from 'vite'

export const APP_PATH = path.join(__dirname, '../client/app')
export const SHARED_PATH = path.join(__dirname, '../client/shared')

// special virtual file
// we can't directly import '/@siteData' becase
Expand All @@ -19,6 +20,7 @@ export function createResolver(themeDir: string): Resolver {
alias: {
'/@app/': APP_PATH,
'/@theme/': themeDir,
'/@shared/': SHARED_PATH,
vitepress: '/@app/exports.js',
[SITE_DATA_ID]: SITE_DATA_REQUEST_PATH
},
Expand Down
4 changes: 2 additions & 2 deletions src/node/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../dist/node",
"outDir": "../../dist",
"module": "commonjs",
"lib": ["ESNext", "DOM"],
"sourceMap": true
},
"include": [".", "../../types/shared.d.ts"]
"include": [".", "../shared", "../../types/shared.d.ts"]
}
48 changes: 48 additions & 0 deletions src/shared/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { SiteData } from '../../types/shared'

function findMatchRoot(route: string, roots: string[]) {
// first match to the routes with the most deep level.
roots.sort((a, b) => {
const levelDelta = b.split('/').length - a.split('/').length
if (levelDelta !== 0) {
return levelDelta
} else {
return b.length - a.length
}
})

for (const r of roots) {
if (route.startsWith(r)) return r
}
return undefined
}

function resolveLocales<T>(
locales: Record<string, T>,
route: string
): T | undefined {
const localeRoot = findMatchRoot(route, Object.keys(locales))
return localeRoot ? locales[localeRoot] : undefined
}

// this merges the locales data to the main data by the route
export function resolveSiteDataByRoute(siteData: SiteData, route: string) {
const localeData = resolveLocales(siteData.locales || {}, route) || {}
const localeThemeConfig =
resolveLocales<any>(
(siteData.themeConfig && siteData.themeConfig.locales) || {},
route
) || {}

return {
...siteData,
...localeData,
themeConfig: {
...siteData.themeConfig,
...localeThemeConfig,
// clean the locales to reduce the bundle size
locales: {}
},
locales: {}
}
}
13 changes: 13 additions & 0 deletions src/shared/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "../../dist/client/shared",
"module": "esnext",
"lib": ["ESNext", "DOM"],
},
"include": [
".",
"../../types/shared.d.ts",
]
}
Loading