对于一个内容管理系统(CMS)来说,网站需要为不同语言的用户提供本地化的体验,支持多语言已经成为必备功能之一。本文将通过安企CMS的多语言实现为例,深入探讨多语言网站的设计与实现策略。
需求背景
安企CMS自推出以来,已经逐步扩展到多站点功能,用户群体也在不断扩大。在这个过程中,用户们对于多语言功能的呼声越来越高。然而,当前版本的安企CMS虽然支持每个站点设置不同语言,但由于无法实现内容之间的语言切换,这种多语言功能显得不够完善。尤其是在国际化环境中,内容的多语言切换和自动翻译的需求变得尤为迫切。因此,作为开发者,我们决定改进多语言功能,以满足用户需求,并让安企CMS更好地服务于全球市场。
多语言网站的功能调研
在深入研究多语言网站的实现后,我们总结出几种常见的处理方式。每种方式都有其独特的优势和适用场景,以下是几种主要的多语言处理方法:
URL路径分段
通过在URL中添加语言代码路径来区分语言。例如:- 英文版:
example.com/en
- 中文版:
example.com/zh
这种方式直观且有助于SEO优化,因为不同语言的页面通过不同的URL被区分。它还方便用户直接通过URL分享特定语言的页面。
- 英文版:
子域名分离
为不同语言版本创建独立的子域名。例如:- 英文版:
en.example.com
- 中文版:
zh.example.com
使用子域名的好处在于可以清晰区分不同的语言版本,也有利于SEO优化,但这需要在服务器和DNS配置上做更多工作。
- 英文版:
查询参数 + Cookie 持久化
通过查询参数(如?lang=en
)来选择语言,同时结合 Cookie 实现用户的语言偏好持久化。例如:- 英文版:
example.com?lang=en
- 中文版:
example.com?lang=zh
这种方法可以根据用户的浏览器语言自动匹配,并允许用户手动切换语言。不过,URL中包含查询参数的方式在SEO上不如路径分段或子域名方式友好。
- 英文版:
完全独立的多语言站点
为每个语言版本创建完全独立的网站。例如:- 英文版:
example.com
- 中文版:
example.cn
这种方式适用于不同市场的独立运营,尤其适合需要针对不同地区和语言定制内容的场景。不过,维护多个独立站点的成本较高,每个站点都需要单独进行SEO优化和服务器配置。
- 英文版:
目前,安企CMS的多语言功能实际上类似于第四种方式,即通过创建独立站点来实现不同语言的切换。这种方式在小规模使用中效果尚可,但随着内容增长和用户多样化,这种方式的局限性开始显现。
多语言站点处理方式的选择
为了让安企CMS更加灵活,满足不同用户的需求,不能局限于单一的多语言实现方式。我们决定在安企CMS中同时支持上述多种多语言处理方式,并让用户根据自己的实际需求自由选择。这不仅提升了用户体验,也为开发者提供了更广泛的定制化空间。
在设计过程中,我们特别关注以下几个关键点:
- 灵活性:支持多种实现方式,让用户根据需求选择最适合的方案。
- 易用性:通过后台简单的配置操作,即可实现多语言功能的开启与管理。
- SEO 友好性:无论用户选择何种语言处理方式,系统都要确保其对搜索引擎友好,支持
hreflang
标签的正确使用。 - 高效性:在处理多语言内容时,系统应尽可能减少冗余操作,并通过缓存等技术提升性能。
多语言站点的实现方案
基于安企CMS现有的多站点架构,我们的多语言功能在此基础上进行了进一步扩展。以下将详细介绍后台、前台以及模板的实现方案,并通过代码示例展示如何在安企CMS中实现多语言功能。
1. 后台多语言的实现
后台通过 ant-design
提供多语言站点的配置选项,让用户轻松配置不同站点的语言,并允许语言间的关联管理。我们可以在数据库中为每个站点记录其对应的语言信息。
数据库设计示例:
CREATE TABLE websites (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
language VARCHAR(10) -- 站点语言,如 'en', 'zh', 'fr'
);
在后台接口中,使用 iris
框架处理语言配置的 API:
type Website struct {
ID int `json:"id"`
Name string `json:"name"`
Language string `json:"language"` // 站点语言
}
// 更新站点语言的API
func UpdateSiteLanguage(ctx iris.Context) {
var site Website
if err := ctx.ReadJSON(&site); err != nil {
ctx.StatusCode(iris.StatusBadRequest)
ctx.JSON(iris.Map{"error": "Invalid input"})
return
}
// 更新数据库
_, err := db.Exec("UPDATE site SET language = ? WHERE id = ?", site.Language, site.ID)
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.JSON(iris.Map{"error": "Failed to update site language"})
return
}
ctx.JSON(iris.Map{"success": true})
}
通过前端 ant-design
表单组件,管理员可以在后台选择或更新站点语言:
// 语言选择表单
import { Form, Select, Button } from 'antd';
const { Option } = Select;
const LanguageForm = () => {
const onFinish = (values) => {
// 调用API更新语言设置
fetch('/api/site/update-language', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
});
};
return (
<Form onFinish={onFinish}>
<Form.Item name="language" label="选择语言">
<Select>
<Option value="en">English</Option>
<Option value="zh">中文</Option>
<Option value="fr">Français</Option>
</Select>
</Form.Item>
<Button type="primary" htmlType="submit">保存</Button>
</Form>
);
};
2. 前台多语言的实现
在前台,通过 iris
框架实现用户语言的切换与记忆。在用户选择语言时,我们将语言存储在 Cookie 中,以便后续访问时加载相应的语言版本。
中间件:多语言选择
func LanguageMiddleware(ctx iris.Context) {
// 从 Cookie 获取语言信息
lang := ctx.GetCookie("lang")
if lang == "" {
// 默认语言设置为 'en'
lang = "en"
}
// 将语言信息存入上下文,供后续处理使用
ctx.Values().Set("lang", lang)
ctx.Next()
}
用户切换语言时,可以通过 URL 或表单请求触发语言更新:
func SetLanguage(ctx iris.Context) {
lang := ctx.URLParam("lang")
if lang == "" {
lang = "en" // 默认语言
}
// 将语言写入 Cookie,有效期为 7 天
ctx.SetCookie(&http.Cookie{
Name: "lang",
Value: lang,
Path: "/",
MaxAge: 60 * 60 * 24 * 7,
})
// 重定向到主页
ctx.Redirect("/")
}
3. 模板多语言的实现
在前端页面中,安企CMS使用 pongo2
作为模板引擎。模板文件夹中带了 locales 文件夹用于存放不同的语言。
语言包文件示例(以 en.yml
为例):
"welcome": "Welcome"
"contact_us": "Contact Us"
解析模板语言
func(s *DjangoEngine) LoadTplLocales(site *Website) {
// 检查模板是否有多语言
var mapLocales = map[string]struct{}{}
sfs := getFS(site.GetTemplateDir())
rootDirName := getRootDirName(sfs)
err = walk(sfs, "", func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info == nil || info.IsDir() {
return nil
}
// 判断是否有多语言
if strings.HasPrefix(path, "locales") {
pathSplit := strings.Split(path, "/")
if len(pathSplit) > 2 {
mapLocales[pathSplit[1]] = struct{}{}
}
}
return nil
})
if len(mapLocales) > 0 {
var locales = make([]string, 0, len(mapLocales))
for k := range mapLocales {
locales = append(locales, k)
}
tplI18n := i18n.New()
err = tplI18n.LoadFS(sfs, "./locales/*/*.yml", locales...)
if err == nil {
site.TplI18n = tplI18n
}
}
}
制作模板的多语言翻译标签:
package tags
import (
"github.com/flosch/pongo2/v6"
"kandaoni.com/anqicms/provider"
)
// tagTrNode 翻译
type tagTrNode struct {
args []pongo2.IEvaluator
key string
}
func (node *tagTrNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error {
var args []interface{}
for _, value := range node.args {
val, err := value.Evaluate(ctx)
if err != nil {
return err
}
args = append(args, val.Interface())
}
currentSite, _ := ctx.Public["website"].(*provider.Website)
if currentSite == nil || currentSite.DB == nil {
writer.WriteString(node.key)
return nil
}
writer.WriteString(currentSite.TplTr(node.key, args...))
return nil
}
func TagTrParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) {
tagNode := &tagTrNode{
args: []pongo2.IEvaluator{},
}
if arguments.Remaining() > 0 {
arg := arguments.Current()
arguments.Consume()
tagNode.key = arg.Val
}
var args []pongo2.IEvaluator
for arguments.Remaining() > 0 {
valueExpr, err := arguments.ParseExpression()
if err != nil {
return nil, arguments.Error("Can not parse with args.", nil)
}
args = append(args, valueExpr)
}
tagNode.args = args
return tagNode, nil
}
模板文件示例:
<!DOCTYPE html>
<html>
<head>
<title>{% tr 'welcome' %}</title>
</head>
<body>
<h1>{% tr 'welcome' %}</h1>
<a href="/contact">{% tr 'contact_us' %}</a>
</body>
</html>
4. 文档自动同步与翻译
为了解决多语言内容维护繁琐的问题,安企CMS集成了文档自动翻译功能。当管理员更新某一语言版本的内容时,系统会自动将修改同步到其他语言版本,并使用第三方API进行自动翻译。
自动同步和翻译的功能可以大大减少管理员的手动工作量,提升管理效率。
总结
通过支持多种多语言实现方式,安企CMS为用户提供了灵活的多语言站点解决方案。无论是路径分段、子域名还是查询参数,用户都可以根据需求选择适合自己的实现方式。同时,文档自动同步与翻译功能的加入,让国际化内容的维护更加高效。未来,安企CMS将持续优化多语言功能,探索更多创新的多语言实现策略。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。