Skip to content

Commit

Permalink
Docs(locales): add chinese locale support (#2772)
Browse files Browse the repository at this point in the history
  • Loading branch information
a76yyyy authored Jul 14, 2023
1 parent 24186a4 commit c7e34bd
Show file tree
Hide file tree
Showing 33 changed files with 2,636 additions and 97 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ blank_issues_enabled: false

contact_links:
- name: (中文)阅读 Wiki
url: https://dreamacro.github.io/clash/
url: https://dreamacro.github.io/clash/zh_CN/
about: 如果你是新手,或者想要了解 Clash 的更多信息,请阅读我们撰写的官方 Wiki
- name: (English) Read our Wiki page
url: https://dreamacro.github.io/clash/
Expand Down
121 changes: 27 additions & 94 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,9 @@
import { defineConfig } from 'vitepress'
import directoryTree from 'directory-tree'
import fs from 'fs'
import metadataParser from 'markdown-yaml-metadata-parser'

function getMetadataFromDoc(path: string): { sidebarTitle?: string, sidebarOrder?: number } {
const fileContents = fs.readFileSync(path, 'utf8')

return metadataParser(fileContents).metadata
}

function generateSidebarChapter(chapterDirName: string): any {
const chapterPath = `./${chapterDirName}`
const tree = directoryTree(chapterPath)

if (!tree || !tree.children) {
console.error(tree)
throw new Error(`Could not genereate sidebar: invalid chapter at ${chapterPath}`)
}

let items: { sidebarOrder: number, text: string, link: string }[] = []

// Look into files in the chapter
for (const doc of tree.children) {
// make sure it's a .md file
if (doc.children || !doc.name.endsWith('.md'))
continue

const { sidebarOrder, sidebarTitle } = getMetadataFromDoc(doc.path)

if (!sidebarOrder)
throw new Error('Cannot find sidebarOrder in doc metadata: ' + doc.path)

if (!sidebarTitle)
throw new Error('Cannot find sidebarTitle in doc metadata: ' + doc.path)

items.push({
sidebarOrder,
text: sidebarTitle,
link: "/" + doc.path
})
}

items = items.sort((a, b) => a.sidebarOrder - b.sidebarOrder)

// remove dash and capitalize first character of each word as chapter title
const text = chapterDirName.split('-').join(' ').replace(/\b\w/g, l => l.toUpperCase())

return {
text,
collapsed: false,
items,
}
}

const chapters = [
'introduction',
'configuration',
'premium',
'runtime',
'advanced-usages',
].map(generateSidebarChapter)

// Override index page link
chapters[0]['items'][0]['link'] = '/'
import locales from './locales'

// https://vitepress.dev/reference/site-config
export default defineConfig({
title: 'Clash',
description: 'Rule-based Tunnel',

base: '/clash/',

Expand All @@ -78,37 +14,34 @@ export default defineConfig({
],
],

themeConfig: {
outline: 'deep',

search: {
provider: 'local'
},
locales: locales.locales,

editLink: {
pattern: 'https://github.com/Dreamacro/clash/edit/master/docs/:path',
text: 'Edit this page on GitHub'
},
lastUpdated: true,

logo: '/logo.png',
themeConfig: {
search: {
provider: 'local',
options: {
locales: {
zh_CN: {
translations: {
button: {
buttonText: '搜索文档',
buttonAriaLabel: '搜索文档'
},
modal: {
noResultsText: '无法找到相关结果',
resetButtonTitle: '清除查询条件',
footer: {
selectText: '选择',
navigateText: '切换'
}
}
}
}
},

// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Configuration', link: '/configuration/configuration-reference' },
{
text: 'Download',
items: [
{ text: 'Open-source Edition', link: 'https://github.com/Dreamacro/clash/releases/' },
{ text: 'Premium Edition', link: 'https://github.com/Dreamacro/clash/releases/tag/premium' },
]
}
],

socialLinks: [
{ icon: 'github', link: 'https://github.com/Dreamacro/clash' },
],

sidebar: chapters
}
}
},
})
60 changes: 60 additions & 0 deletions docs/.vitepress/locales/en_US.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createRequire } from 'module'
import { defineConfig } from 'vitepress'
import { generateSidebarChapter } from './side_bar.js'

const require = createRequire(import.meta.url)

const chapters = generateSidebarChapter('en_US', new Map([
['introduction', 'Introduction'],
['configuration', 'Configuration'],
['premium', 'Premium'],
['runtime', 'Runtime'],
['advanced-usages', 'Advanced Usages'],
]))

export default defineConfig({
lang: 'en-US',

description: 'A rule-based tunnel in Go.',

themeConfig: {
nav: nav(),

logo: '/logo.png',

lastUpdatedText: 'Last updated at',

sidebar: chapters,

socialLinks: [
{ icon: 'github', link: 'https://github.com/Dreamacro/clash' },
],

editLink: {
pattern: 'https://github.com/Dreamacro/clash/edit/master/docs/:path',
text: 'Edit this page on GitHub'
},

outline: {
level: 'deep',
label: 'On this page',
},

}
})

function nav() {
return [
{ text: 'Home', link: '/' },
{ text: 'Configuration', link: '/configuration/configuration-reference' },
{
text: 'Download',
items: [
{ text: 'Open-source Edition', link: 'https://github.com/Dreamacro/clash/releases/' },
{ text: 'Premium Edition', link: 'https://github.com/Dreamacro/clash/releases/tag/premium' },
]
}
]
}


20 changes: 20 additions & 0 deletions docs/.vitepress/locales/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineConfig } from 'vitepress'
import en_US from './en_US'
import zh_CN from './zh_CN'

export default defineConfig({
locales: {
root: {
label: 'English',
lang: en_US.lang,
themeConfig: en_US.themeConfig,
description: en_US.description
},
zh_CN: {
label: '简体中文',
lang: zh_CN.lang,
themeConfig: zh_CN.themeConfig,
description: zh_CN.description
}
}
})
78 changes: 78 additions & 0 deletions docs/.vitepress/locales/side_bar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import directoryTree from 'directory-tree'
import fs from 'fs'
import metadataParser from 'markdown-yaml-metadata-parser'

function getMetadataFromDoc(path: string): { sidebarTitle?: string, sidebarOrder?: number } {
const fileContents = fs.readFileSync(path, 'utf8')

return metadataParser(fileContents).metadata
}

export function generateSidebarChapter(locale:string, chapterDirName: Map<string,string>): any[] {
if (chapterDirName.size < 1) {
console.error(chapterDirName)
throw new Error(`Could not genereate sidebar: chapterDirName is empty`)
}

var chapterPath = ''
var sidebar: any[] = []

for (const chapterDirKey of chapterDirName.keys()) {
if (locale !== 'en_US') {
chapterPath = `./${locale}/${chapterDirKey}`
} else {
chapterPath = `./${chapterDirKey}`
}

const tree = directoryTree(chapterPath)

if (!tree || !tree.children) {
console.error(tree)
throw new Error(`Could not genereate sidebar: invalid chapter at ${chapterPath}`)
}

let items: { sidebarOrder: number, text: string, link: string }[] = []

// Look into files in the chapter
for (const doc of tree.children) {
// make sure it's a .md file
if (doc.children || !doc.name.endsWith('.md'))
continue

const { sidebarOrder, sidebarTitle } = getMetadataFromDoc(doc.path)

if (!sidebarOrder)
throw new Error('Cannot find sidebarOrder in doc metadata: ' + doc.path)

if (!sidebarTitle)
throw new Error('Cannot find sidebarTitle in doc metadata: ' + doc.path)

if (chapterDirKey === 'introduction' && doc.name === '_dummy-index.md') {
// Override index page link
items.push({
sidebarOrder,
text: sidebarTitle,
link: '/' + (locale === 'en_US' ? '' : locale + '/')
})
} else {
items.push({
sidebarOrder,
text: sidebarTitle,
link: "/" + doc.path
})
}
}

items = items.sort((a, b) => a.sidebarOrder - b.sidebarOrder)

// remove dash and capitalize first character of each word as chapter title
const text = chapterDirName.get(chapterDirKey) || chapterDirKey.split('-').join(' ').replace(/\b\w/g, l => l.toUpperCase())
sidebar.push({
text,
collapsed: false,
items,
})
}

return sidebar
}
60 changes: 60 additions & 0 deletions docs/.vitepress/locales/zh_CN.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createRequire } from 'module'
import { defineConfig } from 'vitepress'
import { generateSidebarChapter } from './side_bar.js'

const require = createRequire(import.meta.url)

const chapters = generateSidebarChapter('zh_CN', new Map([
['introduction', '简介'],
['configuration', '配置'],
['premium', 'Premium 版本'],
['runtime', '运行时'],
['advanced-usages', '高级用法'],
]))

export default defineConfig({
lang: 'zh-CN',

description: '基于规则的 Go 网络隧道. ',

themeConfig: {
nav: nav(),

logo: '/logo.png',

lastUpdatedText: '最后更新于',

sidebar: chapters,

socialLinks: [
{ icon: 'github', link: 'https://github.com/Dreamacro/clash' },
],

editLink: {
pattern: 'https://github.com/Dreamacro/clash/edit/master/docs/:path',
text: '在 GitHub 中编辑此页面'
},

docFooter: { prev: '上一篇', next: '下一篇' },

outline: {
level: 'deep',
label: '页面导航',
},

}
})

function nav() {
return [
{ text: '主页', link: '/zh_CN/' },
{ text: '配置', link: '/zh_CN/configuration/configuration-reference' },
{
text: '下载',
items: [
{ text: 'GitHub 开源版', link: 'https://github.com/Dreamacro/clash/releases/' },
{ text: 'Premium 版本', link: 'https://github.com/Dreamacro/clash/releases/tag/premium' },
]
}
]
}
2 changes: 1 addition & 1 deletion docs/configuration/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Inbound is the component that listens on the local end. It works by opening a lo

### Outbound

Outbound is the component that connects to the remote end. Depending on the configuration, it can be a specific network interface, a proxy server, or a [proxy group](#proxy-groups).
Outbound is the component that connects to the remote end. Depending on the configuration, it can be a specific network interface, a proxy server, or a [proxy group](./outbound#proxy-groups).

## Rule-based Routing

Expand Down
2 changes: 1 addition & 1 deletion docs/configuration/outbound.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ The request to the same eTLD+1 will be dialed with the same proxy.

### select

The first server is by default used when Clash starts up. Users can choose the server to use with the RESTful API. In this mode, you can hardcode servers in the config or use [Proxy Providers](/configuration/outbound#proxy-providers).
The first server is by default used when Clash starts up. Users can choose the server to use with the RESTful API. In this mode, you can hardcode servers in the config or use [Proxy Providers](#proxy-providers).

Either way, sometimes you might as well just route packets with a direct connection. In this case, you can use the `DIRECT` outbound.

Expand Down
Loading

0 comments on commit c7e34bd

Please sign in to comment.