# 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 页面。

## 完整配置

```caddyfile
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
    }
}
```

## 逐层解析

### 根路径语言跳转

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

    redir * /en/ 302
}
```

访问 `www.example.com/`（根路径）时：

- 检查 `Accept-Language` 请求头是否以 `zh` 开头（包括 `zh-CN`、`zh-TW`、`zh-HK` 等）
- 中文用户 → `/zh-cn/`（302 临时重定向）
- 其他语言用户 → `/en/`（默认回退）

### 子路径语言探测

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

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

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

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

```caddyfile
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 错误处理

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

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

## 参考链接

- [Caddy redir 指令](https://caddyserver.com/docs/caddyfile/directives/redir)
- [Caddy file Matchers](https://caddyserver.com/docs/caddyfile/matchers#file)
- [Caddy header_regexp Matchers](https://caddyserver.com/docs/caddyfile/matchers#header-regexp)
- [Hugo 多语言模式](https://gohugo.io/content-management/multilingual/)


---

> 作者: Nite  
> URL: https://www.nite07.com/zh-cn/posts/caddy-hugo-multilingual/  

