背景
基础
我使用过多款博客开发框架,Django | Vuepress | Nuxt | Gatsby
等等,在这里暂时不谈Django
和Gatsby
,就我目前深入使用的Vuepress
和Nuxt
(主要是Nuxt-Content
中的Content-Theme-Docs
主题)体验来说,导航的设计方案真地很有必要仔细考虑。
类似的博客站点大多是默认基于文件路径生成路由,例如/zh/home.md
文件一般会生成/zh/home
的路由。此外,也会提供默认语言的设定,因此/zh/home
最后会变成/home
,两者等价。在此基础之上,有些框架还会再加入默认index file
的设定,以vuepress
为例,如果这个文件是index.md
的话,则最后就等价于/
,即主页。
以上是博客框架生成导航栏、侧边栏,显示文章标题等的一些支撑性概念,只有在理解这个的基础之上,才能设计出自己满意的路由与导航、侧边栏展示。
多语言
尽管现在的浏览器,都能很智能地解析中文网址,但我们必须审慎对待这样的一种“特殊待遇”。就拿我曾经使用的vuepress
框架来说,我曾经为了里面的中文路径而苦苦 debug 一整天,后来发现是框架的作者对于路由的处理不够稳健,从而有中文路径不匹配的 bug,这是十分令人恼火的。
此外,不同的浏览器,以及不同的版本,对于非英文网址的转义设定总会有一些区别,在这里,大家都是有能力去处理,但并没有统一的约定去强制开发者遵循,所以中文路径往往并不是第一选择。但对于国人来说,好的中文路径依旧有其诱人的吸引力。以我的实际项目为例,考虑两个路由:/博客/考研专辑
与/blog/postgraduation-exam-album
。也许有翻译不够优雅的问题,但从目标用户角度出发,这个中文路径就十分地友好。
基于此,我们试想一下,如果我们要设计一套多语言系统来适配我们的文章,该怎么做?按照惯例,我们首先有一批中文文档,并准备逐步更新成英文文档。
首先是中英文文件系统的一一映射必须要做,否则当我访问/博客/文章A
时,切换成英文,如果对应的路由/blog/arcticel_A
不存在,则不能满足我们的需求。但值得注意的是,这里的映射,已经不单单是文件系统的映射,还是路由的映射。
我们试想另一种解决方案——纯英文路径。当我访问/blog/arcticel_A
时,切换成英文,对应路由变成/en/blog/arcticel_A
,这种设计也是十分友好的。在这里,我必须指出,大多数人并不在乎网址是中文还是英文,他们更在乎页面的内容是中文还是英文。此外,中文的路径大多数人并不习惯,由此看起来甚至还有点点不专业。
对比下来,我个人觉得第二种解决方案操作起来比较友善,那就是一套文件地址,更换其根目录文件夹名,从而适配多语言系统,这听起来十分 make sense。
解决方案
基于多语言角度的考虑,我最终决定:文件系统的文件夹命名全部英文,方便网址的国际化。(在这里,文件名可以是中文,因为我一般使用typora
写作文档,并且无须提前命名文件,而是直接写完一级标题然后保存就可以自动命名了。这样的话,文件名就是文章的标题名,并且往往是中文优先的。)
这样,假设我们的默认工作目录是/zh/articles/
,我们新建了一个文档为home.md
,则中文路由为/articles/home
。我们"同步复制"该文档到英文目下,得到英文路由为/en/articles/home
。这里的“同步复制”打引号是因为,复制的操作实在还是太麻烦了,因此在设计解决方案的时候,应该要做一些程序化的处理。
路径的问题解决了,(即以英文优先,默认使用文件路径,不同语言子路径相同),接下来就是导航栏的多语言设计了。
由于我们的文件路径用于多语言的匹配,与路由的生成,因此我们需要使用其他的方式生成导航栏的多语言
终极解决方案
设置文档根目录,根目录下存放多个语言目录,以及可以预设一个
settings.js|jaon|yaml
的配置文件。指定默认语言,例如中文,即
zh
,该语言目录下的文档,最好是其他语言文档的超集。遍历文档目录下的文件夹与
.md
文件,生成文档树,得到以下字段:relativePath, dirName, fileName, mdTitle, mdID, mdDate, mdTags, mdDescription, mdAuthor
。其中:relativePath, dirName, fileName
均是文档系统默认的mdTitle
首先从文档的一级标题中获取,如若获取不到,再从frontmatter
中获取,最后等于fileName
mdID
首先从文档的frontmatter
中获取,获取不到则等于relativePath
的hash
值mdDate
首先从文档的frontmatter
中获取,获取不到再尝试从fileName
中解析出日期,fallback 为空mdTags
首先从文档的frontmatter
中获取,获取不到则等于relativePath
上的所有文件夹名集合mdDescription
首先从文档的frontmatter
中获取,获取不到则等于文档中开头的 N 个字,可设置是否包含一级标题mdAuthor
首选从文档的frontmatter
中获取,获取不到则等于默认的作者名,比如南川
,作者名也要配置多语言,在settings
中。
以上,可以得出,文档的
frontmatter
中最好包含以下字段:id
title
date
tags
description
author
方案分析
对于 字典树的方案,并不太靠谱,我当时试想着
递归方案
分析输入
输入是相对路径,通过默认的根路径与相对路径拼接得到绝对路径,通过读取绝对路径下的子文件夹得到进一步的信息
分析输出
输出有两种,字典或者列表。
因为我们对输入的操作,涉及到了子文件夹的遍历,因此容易得到一个列表,所以用列表输出是可行的一种方案。
即:
输入:相对路径,得到 path 字段
处理:基于 path 字段得到一些基本信息
遍历:
输出的衔接
如果输出是一个列表,
{
path: input,
basic: handle(input),
children: get_children(input)
}
如果希望输入是一个空字典,最终返回一个字典,则函数的输出需要是一个字典。
如果希望输入是一个空字典,最终返回一个列表,然后进行拼接构造
亮 | 暗 | |
---|---|---|
登录 | bg-white text-teal-700 | bg-gray-900 text-teal-300 |
未登录 | bg-white text-gray-500 | bg-gray-900 text-gray-500 |