Caddy + Hugo 实现基于 Accept-Language 的多语言智能跳转

目录

需求背景

我的个人博客(基于 Hugo + FixIt 主题)支持中英文双语。目录结构如下:

content/
├── posts/
│   ├── podman.zh-CN.md
│   ├── podman.en.md
│   ├── custom-caddy-build.zh-CN.md
│   └── custom-caddy-build.en.md
└── ...

Hugo 构建后,生成独立的语言目录:

/www/blog/
├── zh-cn/
│   ├── posts/
│   │   └── custom-caddy-build/
│   └── 404.html
├── en/
│   ├── posts/
│   │   └── custom-caddy-build/
│   └── 404.html
└── index.html (根路径重定向)

根路径 / 通常是空的。访问者到达 www.example.com 时,我希望 Caddy 根据浏览器语言偏好自动把他送到正确的语言版本。如果目标语言的文章不存在,降级到另一种语言。如果都不存在,显示默认语言的 404 页面。

完整配置

www.example.com {
    root * /www/blog

    # 根路径语言跳转
    route / {
        @chinese header_regexp Accept-Language ^zh
        redir @chinese /zh-cn/ 302

        redir * /en/ 302
    }

    # 非根路径语言探测
    @needs_localization not path / /en/* /zh-cn/*

    route @needs_localization {
        @zh_target_exists {
            header_regexp Accept-Language ^zh
            file {root}/zh-cn{uri} {root}/zh-cn{uri}/index.html
        }
        redir @zh_target_exists /zh-cn{uri} 301

        @en_target_exists {
            file {root}/en{uri} {root}/en{uri}/index.html
        }
        redir @en_target_exists /en{uri} 301
    }

    file_server

    handle_errors 404 {
        rewrite * {root}/en/404.html
        file_server
    }
}

逐层解析

根路径语言跳转

route / {
    @chinese header_regexp Accept-Language ^zh
    redir @chinese /zh-cn/ 302

    redir * /en/ 302
}

访问 www.example.com/(根路径)时:

  • 检查 Accept-Language 请求头是否以 zh 开头(包括 zh-CNzh-TWzh-HK 等)
  • 中文用户 → /zh-cn/(302 临时重定向)
  • 其他语言用户 → /en/(默认回退)

子路径语言探测

@needs_localization not path / /en/* /zh-cn/*

这个命名匹配器定义了什么路径需要语言探测。排除了三种情况:

  • /:根路径已在上一节处理
  • /en/*:已经是英文路径
  • /zh-cn/*:已经是中文路径

也就是说,当用户访问 /posts/custom-caddy-build/ 这样一个没有语言前缀的中性路径时,触发探测。

route @needs_localization {
    @zh_target_exists {
        header_regexp Accept-Language ^zh
        file {root}/zh-cn{uri} {root}/zh-cn{uri}/index.html
    }
    redir @zh_target_exists /zh-cn{uri} 301

    @en_target_exists {
        file {root}/en{uri} {root}/en{uri}/index.html
    }
    redir @en_target_exists /en{uri} 301
}

探测逻辑:

  1. 首先检查 中文条件:用户语言是中文 ^zh 中文版的路径存在(file 指令探测文件)
  2. 如果中文版存在 → 301 重定向到 /zh-cn{uri}
  3. 然后检查 英文条件:英文版路径是否存在
  4. 如果英文版存在 → 301 重定向到 /en{uri}
  5. 如果都不存在 → 继续执行后面的 file_server(会命中 404)

file 指令会尝试两个模式:{uri} 是类似目录的访问方式(Hugo 生成的目录结构),{uri}/index.html 是作为文件的访问方式。任何一个存在就认为语言版本可用。

404 错误处理

handle_errors 404 {
    rewrite * {root}/en/404.html
    file_server
}

如果用户访问了一个不存在的路径(无论哪个语言版本),统一显示英文的 404 页面。当然也可以做得更精细——根据 Accept-Language 显示不同语言的 404,但我觉得一个简单的 404 页面不值得增加复杂度。

总结

这套用 Caddy 实现的多语言跳转方案,完全在反向代理层完成。配置的核心是 header_regexp + file 指令的组合探测,配合 301/302 跳转实现语言降级。与 Hugo 的静态站点结构配合得很好——Hugo 负责生成带语言前缀的目录,Caddy 负责把用户送到正确的目录。

参考链接

添加评论