前言

  • 为什么要支持多语言,为了辐射更多的用户。即使理论上互联网将世界各地的人联系起来,但是在互联网中,大多数人依然本能的通过母语语言聚在一起。英文和中文是世界使用最多的两种语言,所以首先支持这两种语言。
  • 本篇属于hexo博客搭建指南系列的V1.4,主要介绍了hexo博客 butterfly 主题,支持多语言的切换的配置和优化方案,整个博客搭建指南系列详见:Hexo博客搭建指南:系统化方案与详细构建过程
  • 整体方案
    • hexo 博客,使用butterfly 主题
    • 中文博客、英文博客使用两个独立项目
      • 中文博客:my-hexo-blog-cn;英文博客:my-hexo-blog-en
      • 好处:相互备份;简化管理,减少中文英文之间相互影响或干扰
    • 中文博客和英文博客,以子目录的形式构建,而不是子域名,有利于两个版本博客的搜索权重整合到一起
  • 参考Hexo博客搭建指南:系统化方案与详细构建过程中的整体方案,也可以从全局理解当前博客多语言方案,如下:

整体流程:

整体流程

说明:

  • 整体流程,包括三个阶段:准备、支持多语言配置、支持多余优化,包括四个模块:hexo本地-中文博客、hexo本地-英文博客、Github、Cloudflare
  • 使用 Switch language 切换中文博客和英文博客,增加或去除地址栏中/en/前缀
    • 由于博客只能部署到 cloudflare pages 到根目录,只能使用 cloudflare worker 来实现子目录部署,免费额度100,000次/每天,在切换语言的时候被使用,一般来说足够使用了

准备

搭建并部署中文博客

搭建并部署英文博客

  • 搭建并部署英文博客,总体流程和搭建并部署中文博客一致
  • 注意事项
    • 英文博客,GitHub 仓库名称为:/en/

支持多语言配置

中文博客本地配置

hexo的config文件配置

1
2
3
# 设置网站的基本URL和根目录
url: https://blog.dmindie.com
root: /

_config.butterfly.yml文件配置

  • 导航栏增加切换按钮菜单
1
2
3
4
5
6
7
8
9
10
11
menu: # 导航菜单名称
首页: / || fas fa-home
分类: /categories/ || fas fa-folder-open
标签: /tags/ || fas fa-tags
归档: /archives/ || fas fa-archive
关于: /about/ || fas fa-heart #fas fa-heart;
友链: /link/ || fas -link
# 添加语言切换菜单项
语言||fas fa-language:
简体中文: javascript:switchLanguage('cn')
English: javascript:switchLanguage('en')

加入Switch language逻辑

  • layout.pug(my-hexo-blog-cn/themes/butterfly/layout/includes/layout.pug)增加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
- var globalPageType = getPageType(page, is_home)
- var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : ''
- page.aside = globalPageType === 'archive' ? theme.aside.display.archive: globalPageType === 'category' ? theme.aside.display.category : globalPageType === 'tag' ? theme.aside.display.tag : page.aside
- var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : ''
- var pageType = globalPageType === 'post' ? 'post' : 'page'
- pageType = page.type ? pageType + ' type-' + page.type : pageType

doctype html
html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside)
head
include ./head.pug
body
!=partial('includes/loading/index', {}, {cache: true})

if theme.background
#web_bg(style=getBgPath(theme.background))

!=partial('includes/sidebar', {}, {cache: true})

#body-wrap(class=pageType)
include ./header/index.pug

main#content-inner.layout(class=hideAside)
if body
div!= body
else
block content
if theme.aside.enable && page.aside !== false
include widget/index.pug

- const footerBg = theme.footer_img
- const footer_bg = footerBg ? footerBg === true ? bg_img : getBgPath(footerBg) : ''
footer#footer(style=footer_bg)
!=partial('includes/footer', {}, {cache: true})

include ./rightside.pug
include ./additional-js.pug

//以下为Switch language逻辑
script.
function switchLanguage(targetLang) {
const currentPath = window.location.pathname;
let newPath;

if (targetLang === 'cn') {
// 切换到中文
if (currentPath.startsWith('/en/')) {
// 当前在英文页面,需要移除 /en/ 前缀
newPath = currentPath.substring(3) || '/';

// 检查新路径是否存在
fetch(newPath)
.then(response => {
if (response.ok) {
// 如果页面存在,直接跳转
window.location.href = newPath;
} else if (response.status === 404) {
// 如果页面不存在,跳转到中文首页
window.location.href = '/';
}
})
.catch(() => {
// 网络错误等其他问题交由默认行为处理
console.error('Network error occurred.');
});
} else {
console.log('Already on the Chinese page.');
}
} else if (targetLang === 'en') {
// 切换到英文
if (!currentPath.startsWith('/en/')) {
// 当前在中文页面,需要添加 /en/ 前缀
newPath = '/en' + (currentPath === '/' ? '/' : currentPath);

// 检查新路径是否存在
fetch(newPath)
.then(response => {
if (response.ok) {
// 如果页面存在,直接跳转
window.location.href = newPath;
} else if (response.status === 404) {
// 如果页面不存在,跳转到 no-match 页面
window.location.href = '/en/no-match/';
}
})
.catch(() => {
// 网络错误等其他问题交由默认行为处理
console.error('Network error occurred.');
});
} else {
// 已经在英文页面了,不需要改变
console.log('Already on the English page.');
}
}
}

英文博客本地配置

hexo的config文件配置

1
2
3
# 设置网站的基本URL和根目录
url: https://blog.dmindie.com/en/
root: /en/

_config.butterfly.yml文件配置

1
2
3
4
5
6
7
8
9
10
11
menu: # 导航菜单名称
Home: / || fas fa-home
Categories: /categories/ || fas fa-folder-open
Tags: /tags/ || fas fa-tags
Archives: /archives/ || fas fa-archive
About: /about/ || fas fa-heart #fas fa-heart;
Link: /link/ || fas fa-link
# 添加语言切换菜单项
Language||fas fa-language:
简体中文: javascript:switchLanguage('cn')
English: javascript:switchLanguage('en')

加入Switch language逻辑

  • layout.pug(my-hexo-blog-en/themes/butterfly/layout/includes/layout.pug)增加Switch language逻辑,具体代码与中文博客的代码一致

新增 no-match.md 文件

  • 目的:中文切换为英文,若无多对应内容,则统一转到 no-match,再由下面的 cloudflare worker 统一转到到英文博客首页
  • no-match(my-blog/my-hexo-blog-en/source/no-match.md) 内容如下:
1
2
3
4
5
6
7
8
---
title: No Match Found
date: 2025-01-07 17:25:30
description: "The English version of this page is not available. You will be redirected to the homepage."

---

The English version of this page is not available. You will be redirected to the homepage.

英文博客cloudflare worker 配置

配置 worker 代码

  • 登录到 Cloudflare Dashboard,在“Workers & Pages”部分创建一个新的 Worker。
  • 使用以下代码将 /en/ 的请求代理到英文博客项目:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
addEventListener('fetch', event => {
const url = new URL(event.request.url);

// 检查是否为英文博客请求
if (url.pathname.startsWith('/en')) {
// 替换为英文博客地址
url.hostname = 'my-hexo-blog-en.pages.dev';
url.pathname = url.pathname.replace('/en', '');

event.respondWith(
fetch(url.toString()).then(response => {
// 检查是否为 no-match 页面
if (url.pathname === '/no-match/') {
// 如果是 /no-match/ 页面,跳转到英文首页
return Response.redirect('https://blog.dmindie.com/en/', 302);
}
return response;
})
);
} else {
// 默认处理其他请求(假设为中文博客)
event.respondWith(fetch(event.request));
}
});

绑定 Worker 到主域名

(1)登录到 Cloudflare Dashboard

(2)进入 Workers & Pages

  • 在左侧菜单中,选择 Workers & Pages。
  • 找到您刚刚创建的 Worker(例如用于多语言代理的 Worker)。

(3)配置 worker 的路由规则

  • 点击您创建的 Worker 的名称,进入 Worker 的详细页面。
  • 在页面顶部,选择 Settings(设置)。
  • 在设置页面中,找到 域或路由(Domains & Routes)部分。
  • 点击 Add Route(添加路由)
    • 区域:dmindie.com
    • 路由:blog.dmindie.com/en*
    • 失败模式:失败时自动打开

绑定 Worker 到主域名

部署预览

  • 两个版本的博客修改完之后,部署到 GitHub pages 和 cloudflare
  • 打开浏览器访问两个版本博客,例如:
    • https://blog.dmindie.com/
    • https://blog.dmindie.com/en
  • 经过以上配置,可以实现首页、分类、标签、归档、关于和友链等主要页面自由切换语言,验证结果可以参考本站。

支持多语言优化

支持博文多语言切换

实现步骤

  • notion配置:中文版本的 notion 数据库和英文版本的 notion 数据库,都添加字段:urlname
  • elog 配置:配置同步规则,在elog.config.js中,添加字段:urlname
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module.exports = {
write: {
platform: 'notion',
notion: {
token: process.env.NOTION_TOKEN,
databaseId: process.env.NOTION_DATABASE_ID,
filter: true, // {property: 'status', select: {equals: '已发布'}}
frontMatter: {
enable: true, // 启用 Front Matter
include: ['title', 'categories', 'tags', 'description', 'status', 'cover','urlname','date'], // 包含的字段,deploy和write同时配置并保持一致
},
},
},
deploy: {
platform: 'local',
local: { //为了发布部署的内容
outputDir: './source/_posts/', // 发布到 Hexo 的 posts的目录
filename: 'title',
format: 'matter-markdown',
frontMatter: {
enable: true, // 启用 Front Matter
include: ['title', 'categories', 'tags', 'description', 'status','cover','urlname','date'], // 包含的字段,deploy和write同时配置并保持一致
},
},
},
}

基于 SEO 优化

添加 hreflang 标签

  • 中文博客,head.pug(projects/my-blog/my-hexo-blog-cn/themes/butterfly/layout/includes/head.pug)添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//-添加hreflang标签
//- 定义基础 URL
- const baseUrl = 'https://blog.dmindie.com'
- let currentPath = page.path
//- 处理各种页面的路径
- if (currentPath === 'index.html' || currentPath === '/index.html') {
- currentPath = ''
- } else {
- currentPath = currentPath.replace(/index\.html$/, '').replace(/^\/+|\/+$/g, '')
- }
//- 添加 hreflang 标签
link(rel="alternate" hreflang="zh-CN" href=`${baseUrl}/${currentPath}`)
link(rel="alternate" hreflang="en" href=`${baseUrl}/en/${currentPath}`)
link(rel="alternate" hreflang="x-default" href=`${baseUrl}/${currentPath}`)
  • 英文博客,head.pug(projects/my-blog/my-hexo-blog-en/themes/butterfly/layout/includes/head.pug)添加代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//-添加hreflang标签
//- 定义基础 URL
- const baseUrl = 'https://blog.dmindie.com'
- let currentPath = page.path
//- 处理各种页面的路径
- if (currentPath === 'index.html' || currentPath === '/index.html') {
- currentPath = ''
- } else {
- currentPath = currentPath.replace(/index\.html$/, '').replace(/^\/+|\/+$/g, '')
- }
//- 移除路径中的 en/ 前缀(如果存在)
- currentPath = currentPath.replace(/^en\//, '')
//- 添加 hreflang 标签
link(rel="alternate" hreflang="en" href=`${baseUrl}/en/${currentPath}`)
link(rel="alternate" hreflang="zh-CN" href=`${baseUrl}/${currentPath}`)
link(rel="alternate" hreflang="x-default" href=`${baseUrl}/${currentPath}`)

其他多语言方案

只部署GitHubpages的多语言简化方案

部署方案调整

  • 第一个版本博客,部署到GitHub pages 的根目录中
  • 剩下的 n 个版本,都部署到 GitHub pages 的子目录中
    • 由于 GitHub pages 中,多个版本都是默认部署到子目录,可以完美多语言的子目录的需求
  • 不再部署到 cloudflare pages

加入Switch language逻辑调整

  • hexo的config文件和_config.butterfly.yml文件配置,与以上方案一致
  • 对于中文博客和英文博客项目中,layout.pug(my-hexo-blog-cn/themes/butterfly/layout/includes/layout.pug)和layout.pug(my-hexo-blog-en/themes/butterfly/layout/includes/layout.pug)都增加Switch language逻辑,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
- var globalPageType = getPageType(page, is_home)
- var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : ''
- page.aside = globalPageType === 'archive' ? theme.aside.display.archive: globalPageType === 'category' ? theme.aside.display.category : globalPageType === 'tag' ? theme.aside.display.tag : page.aside
- var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : ''
- var pageType = globalPageType === 'post' ? 'post' : 'page'
- pageType = page.type ? pageType + ' type-' + page.type : pageType

doctype html
html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside)
head
include ./head.pug
body
!=partial('includes/loading/index', {}, {cache: true})

if theme.background
#web_bg(style=getBgPath(theme.background))

!=partial('includes/sidebar', {}, {cache: true})

#body-wrap(class=pageType)
include ./header/index.pug

main#content-inner.layout(class=hideAside)
if body
div!= body
else
block content
if theme.aside.enable && page.aside !== false
include widget/index.pug

- const footerBg = theme.footer_img
- const footer_bg = footerBg ? footerBg === true ? bg_img : getBgPath(footerBg) : ''
footer#footer(style=footer_bg)
!=partial('includes/footer', {}, {cache: true})

include ./rightside.pug
include ./additional-js.pug

//以下为Switch language逻辑
script.
function switchLanguage(targetLang) {
const currentPath = window.location.pathname;
let newPath;

if (targetLang === 'cn') {
// 如果目标是中文
if (currentPath.startsWith('/en/')) {
// 当前在英文页面,需要移除 /en/ 前缀
newPath = currentPath.substring(3) || '/';
} else {
// 已经在中文页面了,不需要改变
newPath = currentPath;
}
} else {
// 如果目标是英文
if (!currentPath.startsWith('/en/')) {
// 当前在中文页面,需要添加 /en/ 前缀
newPath = '/en' + (currentPath === '/' ? '/' : currentPath);
} else {
// 已经在英文页面了,不需要改变
newPath = currentPath;
}
}

// 检查新路径是否存在
fetch(newPath)
.then(response => {
if (response.ok) {
// 如果页面存在,直接跳转
window.location.href = newPath;
} else {
// 如果页面不存在,跳转到对应语言的首页
window.location.href = targetLang === 'cn' ? '/' : '/en/';
}
})
.catch(() => {
// 发生错误时跳转到对应语言的首页
window.location.href = targetLang === 'cn' ? '/' : '/en/';
});
}

结语

通过以上的配置,相信我们都可以搭建一个支持多语言切换的 Hexo 博客。后续,基于这个多语言方案,我会进一步优化,并同步更新本篇文章的方案。

如果您有更好的方案或想法,请随时与我们分享,我们将持续更新这篇文章,确保其内容保持最新和最优。