Compare commits
4
Commits
cb9382ade4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16b3141e09 | ||
|
|
85ee9dbdbf | ||
|
|
d99c5d1dc5 | ||
|
|
b8089e8a7e |
No files matched your search
+20
-15
@@ -9,26 +9,27 @@ This document describes the compatibility of Gitea Mail Templates with different
|
||||
Select the recommended template release for the Gitea version you run. `[PASS]` identifies a combination recorded as compatible; `[PENDING]` means verification is incomplete.
|
||||
|
||||
> [!NOTE]
|
||||
> Historical entries reflect the project's existing compatibility assessments. They do not mean that every patch version is retested for each release.
|
||||
> This project generally aligns template version numbers with Gitea version numbers. However, not every Gitea release introduces mail template changes, so the compatibility matrix recommends template versions based on compatibility assessments; their version numbers may differ from the Gitea version you run. Historical entries reflect existing assessments and do not imply that every listed Gitea version is retested for each template release.
|
||||
|
||||
| Gitea version | Recommended template release | Status | Notes |
|
||||
|---|---|---|---|
|
||||
| 28.1.0 | — | [PENDING] | Compatibility verification pending |
|
||||
| 28.0.0 | **v28.0.0** | [PASS] | Gitea replaced the mail function `FileSize` with `FormatByteSize` |
|
||||
| 1.27.3 | **v1.27.3** | [PASS] | Pull request push notification commit links fixed in every theme |
|
||||
| 1.27.2 | **v1.27.3** | [PASS] | The matching v1.27.2 template release has push notification defects in Bloom, Ember and Heritage |
|
||||
| 1.27.1 | **v1.27.3** | [PASS] | Older v1.0.x templates use obsolete push commit fields |
|
||||
| 28.2.0 | [**v28.1.0**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.1.0) | [PASS] | Mail templates, template function registrations and mail translation keys are unchanged from 28.1.0 |
|
||||
| 28.1.0 | [**v28.1.0**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.1.0) | [PASS] | - |
|
||||
| 28.0.0 | [**v28.1.0**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.1.0) | [PASS] | Gitea replaced the mail function `FileSize` with `FormatByteSize` |
|
||||
| 1.27.3 | [**v1.27.3**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) | [PASS] | Pull request push notification commit links fixed in every theme |
|
||||
| 1.27.2 | [**v1.27.3**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) | [PASS] | The matching v1.27.2 template release has push notification defects in Bloom, Ember and Heritage |
|
||||
| 1.27.1 | [**v1.27.3**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) | [PASS] | Older v1.0.x templates use obsolete push commit fields |
|
||||
| 1.27.0 | — | [PENDING] | Gitea uses a new commit data structure; v1.27.3 may be compatible but has not been verified against this version |
|
||||
| 1.26.4 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.3 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.2 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.1 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.0 | **v1.0.1** | [PASS] | — |
|
||||
| 1.25.5 | **v1.0.1** | [PASS] | — |
|
||||
| 1.25.0 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.4 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.26.3 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.26.2 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.26.1 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.26.0 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.25.5 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.25.0 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
|
||||
> [!NOTE]
|
||||
> Gitea 1.24.x and earlier use a different mail template layout and are `[UNSUPPORTED]`; Gitea Mail Templates no longer provides separate support for them. The v1.0.0 template tag has been superseded by v1.0.1 and is no longer recommended separately.
|
||||
> Gitea 1.24.x and earlier use a different mail template layout and are `[UNSUPPORTED]`; Gitea Mail Templates no longer provides separate support for them.
|
||||
|
||||
## Gitea Version History — Mail Template Impact
|
||||
|
||||
@@ -36,7 +37,8 @@ This table describes changes in **Gitea**, not fixes in this template repository
|
||||
|
||||
| Gitea | Release date | Mail template changes | Impact |
|
||||
|---|---|---|---|
|
||||
| **28.1.0** | 2026-10-06 | [PENDING] | [PENDING] |
|
||||
| **28.2.0** | 2026-10-18 | No template interface changes | Custom-watch notification recipient rules and incoming reply token validation changed; existing templates are unaffected |
|
||||
| **28.1.0** | 2026-10-06 | None | None |
|
||||
| **28.0.0** | 2026-09-29 | Mail function `FileSize` renamed to `FormatByteSize` ([PR](https://github.com/go-gitea/gitea/pull/39083)) | **Breaking change:** old release attachment templates fail |
|
||||
| | | Introduced `templates/mail/base/` ([PR](https://github.com/go-gitea/gitea/pull/38396)) | **Breaking change:** older Gitea versions cannot directly use the new mail templates |
|
||||
| **1.27.3** | 2026-08-29 | None | None |
|
||||
@@ -54,6 +56,9 @@ This table describes changes in **Gitea**, not fixes in this template repository
|
||||
|
||||
## Template Variable Reference
|
||||
|
||||
> [!NOTE]
|
||||
> Mail templates, context construction, template functions and translation keys were checked against the [Gitea 28.1.0 to 28.2.0 source diff](https://github.com/go-gitea/gitea/compare/v28.1.0...v28.2.0). Version 28.2.0 changes [email notification rules for custom watches](https://github.com/go-gitea/gitea/pull/39837) and [incoming email token validation](https://github.com/go-gitea/gitea/pull/39841), without changing the variables, functions or translation keys required by existing mail templates. Source build inputs are locked to v28.2.0; the published v28.1.0 templates remain usable.
|
||||
|
||||
Official templates define the context for each mail type. For specific mail templates, functions and variables used during development, refer to the official mail template documentation [1](#notes).
|
||||
|
||||
## Translation Keys
|
||||
|
||||
+6
-3
@@ -69,10 +69,10 @@ The root lock file records the Gitea tag, its commit and file SHA-256 hashes. `v
|
||||
|
||||
#### Updating the Official Version
|
||||
|
||||
Use `sync` only when reviewing a new official version. For example, to explicitly synchronize to `v28.0.0`:
|
||||
Use `sync` only when reviewing a new official version. For example, to explicitly synchronize to `v28.2.0`:
|
||||
|
||||
```bash
|
||||
go run . upstream sync --tag v28.0.0
|
||||
go run . upstream sync --tag v28.2.0
|
||||
go run . upstream verify
|
||||
go test ./...
|
||||
go run . preview all
|
||||
@@ -198,6 +198,9 @@ go run . preview all
|
||||
|
||||
Default output includes `preview/rendered.js` and `preview/rendered/<locale>.js`. Open [preview/index.html](preview/index.html) in a browser; no server is required. See the [README](README.md#controls-and-keyboard-shortcuts) for controls and shortcuts.
|
||||
|
||||
> [!NOTE]
|
||||
> Native language names are generated for the actual preview locales from CLDR data in `golang.org/x/text/language/display` and written to the manifest as `language_names`. The preview interface reads this data directly, without maintaining a language-name table.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Each run overwrites the preview manifest at the output location instead of merging with previously selected themes. For example, after `preview aurora`, the manifest contains only Aurora. Run `preview all` to restore every theme.
|
||||
|
||||
@@ -344,7 +347,7 @@ Use concise commit messages. The following prefixes are available as a reference
|
||||
Release tags must match the locked Gitea tag. Version labels, badges and compatibility records are maintained manually; changing official inputs does not trigger automatic version maintenance. Retain historical release assets and check documentation and compatibility records before tagging. After validation and generation of all previews, you can package manually from the repository root:
|
||||
|
||||
```bash
|
||||
python .github/scripts/package_release.py --version v28.0.0 --output build/release-review
|
||||
python .github/scripts/package_release.py --version v28.2.0 --output build/release-review
|
||||
```
|
||||
|
||||
Replace the example version with the tag in the root lock file and use an output directory without release archives of the same name. The script checks source and artifact hashes, packages themes, previews, documentation, the official license and provenance, and excludes stale theme builds.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
Gitea Mail Template offers a range of email template styles for self-hosted [Gitea](https://about.gitea.com) instances.
|
||||
|
||||
> Latest release: [v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
> Latest release: [v28.1.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
|
||||
The repository includes several themes for Gitea emails, suitable for most deployment scenarios and use cases. Before installing, consult the [compatibility matrix](COMPATIBILITY.md#compatibility-matrix) for your Gitea version and choose the corresponding release archive.
|
||||
|
||||
@@ -126,9 +126,11 @@ Open [http://127.0.0.1:3456](http://127.0.0.1:3456). The server watches theme fi
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **Latest tested:** Gitea 28.0.0
|
||||
- **Latest release:** [v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
- **Upstream Gitea 28.1.0:** [PENDING]
|
||||
- **Latest tested:** Gitea 28.2.0
|
||||
- **Source build baseline:** Gitea 28.2.0
|
||||
- **Latest release:** [v28.1.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
|
||||
Gitea 28.2.0 introduces no mail template interface changes, so the published v28.1.0 templates remain compatible. Source builds use the locked v28.2.0 official inputs; see the [compatibility records](COMPATIBILITY.md).
|
||||
|
||||
## Project Directory Structure
|
||||
|
||||
|
||||
+22
-17
@@ -9,26 +9,27 @@
|
||||
根据所运行的 Gitea 版本选择对应推荐的模板版本。`[PASS]` 表示已记录为兼容的组合,`[PENDING]` 表示尚未完成验证。
|
||||
|
||||
> [!NOTE]
|
||||
> 历史条目仅反映项目已有的兼容性评估,不代表每次发布都重新测试了每一个补丁版本。
|
||||
> 本项目主要采用模板版本号与 Gitea 版本号同步的管理方式。但并非每次 Gitea 发布都会引入邮件模板变更,因此兼容性表中的推荐版本依据兼容性评估确定,不一定与所运行的 Gitea 版本同号。历史条目反映已有评估结果,不代表每次模板发布都会重新测试所有列出的 Gitea 版本。
|
||||
|
||||
| Gitea 版本 | 推荐模板版本 | 状态 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 28.1.0 | — | [PENDING] | 等待兼容性验证 |
|
||||
| 28.0.0 | **v28.0.0** | [PASS] | Gitea 将邮件函数 `FileSize` 替换为 `FormatByteSize` |
|
||||
| 1.27.3 | **v1.27.3** | [PASS] | 全部主题已修复拉取请求推送通知的提交链接 |
|
||||
| 1.27.2 | **v1.27.3** | [PASS] | 同号 v1.27.2 模板的 Bloom、Ember、Heritage 存在推送通知缺陷 |
|
||||
| 1.27.1 | **v1.27.3** | [PASS] | 较早 v1.0.x 模板使用过时的推送提交字段 |
|
||||
| 28.2.0 | [**v28.1.0**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.1.0) | [PASS] | 邮件模板、模板函数注册及邮件翻译键与 28.1.0 一致 |
|
||||
| 28.1.0 | [**v28.1.0**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.1.0) | [PASS] | - |
|
||||
| 28.0.0 | [**v28.1.0**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.1.0) | [PASS] | Gitea 将邮件函数 `FileSize` 替换为 `FormatByteSize` |
|
||||
| 1.27.3 | [**v1.27.3**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) | [PASS] | 全部主题已修复拉取请求推送通知的提交链接 |
|
||||
| 1.27.2 | [**v1.27.3**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) | [PASS] | 同号 v1.27.2 模板的 Bloom、Ember、Heritage 存在推送通知缺陷 |
|
||||
| 1.27.1 | [**v1.27.3**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.27.3) | [PASS] | 较早 v1.0.x 模板使用过时的推送提交字段 |
|
||||
| 1.27.0 | — | [PENDING] | Gitea 使用了新的提交数据结构;v1.27.3 可能适用,但未在此版本上验证 |
|
||||
| 1.26.4 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.3 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.2 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.1 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.0 | **v1.0.1** | [PASS] | — |
|
||||
| 1.25.5 | **v1.0.1** | [PASS] | — |
|
||||
| 1.25.0 | **v1.0.1** | [PASS] | — |
|
||||
| 1.26.4 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.26.3 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.26.2 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.26.1 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.26.0 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.25.5 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
| 1.25.0 | [**v1.0.1**](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v1.0.1) | [PASS] | — |
|
||||
|
||||
> [!NOTE]
|
||||
> Gitea 1.24.x 及更早版本使用不同的邮件模板布局,属于 `[UNSUPPORTED]`,Gitea Mail Templates 不再另外支持。v1.0.0 模板标签已由 v1.0.1 替代,不再单独推荐。
|
||||
> Gitea 1.24.x 及更早版本使用不同的邮件模板布局,属于 `[UNSUPPORTED]`,Gitea Mail Templates 不再另外支持。
|
||||
|
||||
## Gitea 历版本对邮件模板影响
|
||||
|
||||
@@ -36,9 +37,10 @@
|
||||
|
||||
| Gitea | 发布日期 | 邮件模板变化 | 影响 |
|
||||
|---|---|---|---|
|
||||
| **28.1.0** | 2026-10-06 | [PENDING] | [PENDING] |
|
||||
| **28.0.0** | 2026-09-29 | 邮件函数 `FileSize` 改为 `FormatByteSize` ([PR](https://github.com/go-gitea/gitea/pull/#39083)) | **不兼容变更:** 旧的发布附件模板失效 |
|
||||
|||引入 templates/mail/base/ ([PR](https://github.com/go-gitea/gitea/pull/#38396))| **不兼容变更:** 旧版本 Gitea 无法直接使用新版本邮件模板 |
|
||||
| **28.2.0** | 2026-10-18 | 无模板接口变更 | 自定义关注的通知收件规则及入站回复令牌校验调整,不影响现有模板 |
|
||||
| **28.1.0** | 2026-10-06 | 无 | 无 |
|
||||
| **28.0.0** | 2026-09-29 | 邮件函数 `FileSize` 改为 `FormatByteSize` ([PR](https://github.com/go-gitea/gitea/pull/39083)) | **不兼容变更:** 旧的发布附件模板失效 |
|
||||
|||引入 templates/mail/base/ ([PR](https://github.com/go-gitea/gitea/pull/38396))| **不兼容变更:** 旧版本 Gitea 无法直接使用新版本邮件模板 |
|
||||
| **1.27.3** | 2026-08-29 | 无 | 无 |
|
||||
| **1.27.2** | 2026-08-14 | 无 | 无 |
|
||||
| **1.27.1** | 2026-07-27 | 内置推送模板改用 `.UserCommit.GitCommit`([PR](https://github.com/go-gitea/gitea/pull/38467)) | **不兼容变更:存在既有未修复缺陷** 旧的自定义拉取请求推送模板可能失效 |
|
||||
@@ -54,6 +56,9 @@
|
||||
|
||||
## 模板变量参考
|
||||
|
||||
> [!NOTE]
|
||||
> 已对照 [Gitea 28.1.0 与 28.2.0 的源码差异](https://github.com/go-gitea/gitea/compare/v28.1.0...v28.2.0)检查邮件模板、上下文构造、模板函数和翻译键。28.2.0 调整了[自定义关注的邮件通知规则](https://github.com/go-gitea/gitea/pull/39837)和[入站邮件令牌校验](https://github.com/go-gitea/gitea/pull/39841),未改变现有邮件模板所需的变量、函数或翻译键。源码构建输入锁定为 v28.2.0,已发布的 v28.1.0 模板仍可使用。
|
||||
|
||||
各类邮件上下文由官方模板定义。开发时具体的邮件模板和函数和变量请参考官方邮件模板文档 [1](#注释)。
|
||||
|
||||
## 翻译键
|
||||
|
||||
@@ -69,10 +69,10 @@ go run . upstream verify --root "C:\Projects\GiteaMailTemplates"
|
||||
|
||||
#### 更新官方版本
|
||||
|
||||
只有需要评审新的官方版本时才使用 `sync`。例如,明确同步到 `v28.0.0`:
|
||||
只有需要评审新的官方版本时才使用 `sync`。例如,明确同步到 `v28.2.0`:
|
||||
|
||||
```bash
|
||||
go run . upstream sync --tag v28.0.0
|
||||
go run . upstream sync --tag v28.2.0
|
||||
go run . upstream verify
|
||||
go test ./...
|
||||
go run . preview all
|
||||
@@ -198,6 +198,9 @@ go run . preview all
|
||||
|
||||
默认输出包括 `preview/rendered.js` 和 `preview/rendered/<语言>.js`。在浏览器中打开 [preview/index.html](../preview/index.html) 即可查看,无需启动服务器。操作和快捷键见 [README](README.zh-CN.md#控制和快捷键)。
|
||||
|
||||
> [!NOTE]
|
||||
> 语言名称由 `golang.org/x/text/language/display` 的 CLDR 数据按实际预览语言生成,并写入预览清单的 `language_names`。预览界面直接读取清单,无需维护语言名称表。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 每次生成会覆盖输出位置的预览清单,不与先前选择的主题合并。例如,执行 `preview aurora` 后,该清单只包含 Aurora;需要恢复全部主题时执行 `preview all`。
|
||||
|
||||
@@ -344,7 +347,7 @@ python -B -m unittest discover -s .github/scripts -p 'test_*.py'
|
||||
发行标签必须与锁定的 Gitea 标签一致。版本标记、徽章和兼容性记录由维护者手动更新;修改官方输入不会自动触发版本维护。历史发行附件应保留,文档和兼容性记录应在打标签前检查。完成验证并生成全部预览后,可在仓库根目录手动打包:
|
||||
|
||||
```bash
|
||||
python .github/scripts/package_release.py --version v28.0.0 --output build/release-review
|
||||
python .github/scripts/package_release.py --version v28.2.0 --output build/release-review
|
||||
```
|
||||
|
||||
将示例版本替换为根锁文件中的标签,并使用没有同名发行压缩包的输出目录。脚本检查源码与产物的校验值,打包主题、预览、文档、官方许可证和来源信息,排除残留的旧主题构建。
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
Gitea Mail Template 为自托管的 [Gitea](https://about.gitea.com) 提供多种可选、适合的邮件模板风格。
|
||||
|
||||
> 最新发布版:[v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
> 最新发布版:[v28.1.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
|
||||
仓库包含数种主题的 Gitea 邮件模板,适合大多数的部署场景与应用范围。安装前,请根据 Gitea 版本查阅[兼容性表](./COMPATIBILITY.zh-CN.md#兼容性表),选择对应的发行包。
|
||||
|
||||
@@ -126,9 +126,11 @@ cd ..
|
||||
|
||||
## 兼容性
|
||||
|
||||
- **最新测试:** Gitea 28.0.0
|
||||
- **最新发布版:** [v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
- **上游 Gitea 28.1.0:** [PENDING]
|
||||
- **最新测试:** Gitea 28.2.0
|
||||
- **源码构建基线:** Gitea 28.2.0
|
||||
- **最新发布版:** [v28.1.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)
|
||||
|
||||
Gitea 28.2.0 未引入邮件模板接口变更,已发布的 v28.1.0 模板仍兼容;源码构建使用锁定的 v28.2.0 官方输入,详见[兼容性记录](COMPATIBILITY.zh-CN.md)。
|
||||
|
||||
## 项目目录结构
|
||||
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"repository": "https://github.com/go-gitea/gitea",
|
||||
"tag": "v28.0.0",
|
||||
"commit": "15b8a5805adf57c5189602008d38cccfd3c795e0",
|
||||
"tag": "v28.2.0",
|
||||
"commit": "829b49c1ae96015e345f9d2a0eb1ea7acd5f31e1",
|
||||
"files": {
|
||||
"LICENSE": "ed2f10a9d78b8c6c9ef33f1420d0eb266981891caf2f15d630553f02dc60d3ae",
|
||||
"modules/templates/mail.go": "fb8204b79f700f88c7c3f0da16e50c3d678a2216e62e98f712805ed1d42ab3a9",
|
||||
@@ -12,7 +12,7 @@
|
||||
"options/locale/locale_cs-CZ.json": "816c4a0cfc6112c8cc9a63e63b3de0bc623819a23d2f2bbb800b09c45d23da66",
|
||||
"options/locale/locale_de-DE.json": "593ec4e3652c0faf9ecb5d5b27a649c1c0a933030ecdfbf4dc810ea4ad69bfb8",
|
||||
"options/locale/locale_el-GR.json": "dd03e6e083d6286d8eb2045a37bcf5c578fc339ab87a72a7397e01733078d89e",
|
||||
"options/locale/locale_en-US.json": "bb58779f10b2cd0ee94f71297b2231a621d4d30f2a56a2b223f3c586f635f592",
|
||||
"options/locale/locale_en-US.json": "0821c7e631d62e8d80a49a119ba0fb4430f27e4da37a3c943b3cd0faee43e163",
|
||||
"options/locale/locale_es-ES.json": "cbb1cd7bc4709a189b8b189d26a83a06fed0c32e0b5ac5602370fb0f03a39d04",
|
||||
"options/locale/locale_fa-IR.json": "ad85f5829335844883608ed5ed09dee5bc5f4800cda7ef9277200aa4a7f35f32",
|
||||
"options/locale/locale_fi-FI.json": "3b811b0ee794548399bcb64a887711a26cc0c26e04dfd0305b0846742d065952",
|
||||
|
||||
+1
-27
@@ -482,34 +482,8 @@ function fillSelect(id, entries, selected) {
|
||||
select.value=selected;
|
||||
}
|
||||
|
||||
// Stable native names also cover browsers with incomplete ICU locale data.
|
||||
const LOCALE_NAMES = {
|
||||
'cs-CZ':'čeština (Česko)', 'de-DE':'Deutsch (Deutschland)',
|
||||
'el-GR':'Ελληνικά (Ελλάδα)', 'en-US':'English (United States)',
|
||||
'es-ES':'español (España)', 'fa-IR':'فارسی (ایران)',
|
||||
'fi-FI':'suomi (Suomi)', 'fr-FR':'français (France)',
|
||||
'ga-IE':'Gaeilge (Éire)', 'hu-HU':'magyar (Magyarország)',
|
||||
'id-ID':'Indonesia (Indonesia)', 'is-IS':'íslenska (Ísland)',
|
||||
'it-IT':'italiano (Italia)', 'ja-JP':'日本語 (日本)',
|
||||
'ko-KR':'한국어 (대한민국)', 'lv-LV':'latviešu (Latvija)',
|
||||
'nl-NL':'Nederlands (Nederland)', 'pl-PL':'polski (Polska)',
|
||||
'pt-BR':'português (Brasil)', 'pt-PT':'português (Portugal)',
|
||||
'ru-RU':'русский (Россия)', 'si-LK':'සිංහල (ශ්රී ලංකාව)',
|
||||
'sk-SK':'slovenčina (Slovensko)',
|
||||
'sv-SE':'svenska (Sverige)', 'tr-TR':'Türkçe (Türkiye)',
|
||||
'uk-UA':'українська (Україна)', 'zh-CN':'中文 (简体,中国)',
|
||||
'zh-TW':'中文 (繁體,台灣)'
|
||||
};
|
||||
|
||||
function localeLabel(locale) {
|
||||
let name = LOCALE_NAMES[locale];
|
||||
if (!name) {
|
||||
try {
|
||||
if (Intl.DisplayNames.supportedLocalesOf([locale]).length) {
|
||||
name = new Intl.DisplayNames([locale], {type:'language', fallback:'none'}).of(locale);
|
||||
}
|
||||
} catch (_) { /* Unknown languages remain selectable by their locale code. */ }
|
||||
}
|
||||
const name = manifest?.language_names?.[locale];
|
||||
return name && name !== locale ? locale + ' — ' + name : locale;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,7 @@ func UpstreamCommand() *cli.Command {
|
||||
return err
|
||||
}
|
||||
logging.Info("Upstream", "%s: %d mail files, %d languages", s.Lock.Tag, len(s.Templates), len(s.Locales))
|
||||
for _, lang := range s.Languages() {
|
||||
if keys := missing[lang]; len(keys) > 0 {
|
||||
logging.Warn("Locale", "%s -> en-US: %v", lang, keys)
|
||||
}
|
||||
}
|
||||
logging.WarnMissingTranslations(missing)
|
||||
return nil
|
||||
}},
|
||||
}}
|
||||
|
||||
@@ -5,6 +5,7 @@ go 1.24.0
|
||||
require (
|
||||
github.com/urfave/cli/v2 v2.27.7
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/text v0.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -8,3 +8,5 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGC
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
@@ -0,0 +1,43 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WarnMissingTranslations groups missing keys without wrapping long key lists.
|
||||
func WarnMissingTranslations(missing map[string][]string) {
|
||||
if message := missingTranslations(missing); message != "" {
|
||||
Warn("Locale", "%s", message)
|
||||
}
|
||||
}
|
||||
|
||||
func missingTranslations(missing map[string][]string) string {
|
||||
languages := make([]string, 0, len(missing))
|
||||
width := len("Language")
|
||||
for lang, keys := range missing {
|
||||
if len(keys) > 0 {
|
||||
languages = append(languages, lang)
|
||||
width = max(width, len(lang))
|
||||
}
|
||||
}
|
||||
if len(languages) == 0 {
|
||||
return ""
|
||||
}
|
||||
sort.Strings(languages)
|
||||
var message strings.Builder
|
||||
fmt.Fprintf(&message, "Missing translation keys:\n%-*s %-8s %s\n", width, "Language", "Fallback", "Key")
|
||||
for _, lang := range languages {
|
||||
keys := append([]string(nil), missing[lang]...)
|
||||
sort.Strings(keys)
|
||||
for i, key := range keys {
|
||||
label, fallback := "", ""
|
||||
if i == 0 {
|
||||
label, fallback = lang, "en-US"
|
||||
}
|
||||
fmt.Fprintf(&message, "%-*s %-8s %s\n", width, label, fallback, key)
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(message.String(), "\n")
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMissingTranslations(t *testing.T) {
|
||||
missing := map[string][]string{
|
||||
"fr-FR": {"mail.second", "mail.first"},
|
||||
"en-US": nil,
|
||||
"de-DE": {"mail.first"},
|
||||
}
|
||||
want := "Missing translation keys:\n" +
|
||||
"Language Fallback Key\n" +
|
||||
"de-DE en-US mail.first\n" +
|
||||
"fr-FR en-US mail.first\n" +
|
||||
" mail.second"
|
||||
message := missingTranslations(missing)
|
||||
if message != want {
|
||||
t.Fatalf("got %q, want %q", message, want)
|
||||
}
|
||||
if !reflect.DeepEqual(missing["fr-FR"], []string{"mail.second", "mail.first"}) {
|
||||
t.Fatal("report mutated input keys")
|
||||
}
|
||||
var output bytes.Buffer
|
||||
write(&output, true, "W", "Locale", message)
|
||||
for _, line := range strings.Split(strings.TrimSuffix(output.String(), "\n"), "\n") {
|
||||
if !strings.HasPrefix(line, "\033[33m[W]\033[0m [Locale] ") {
|
||||
t.Fatalf("missing warning prefix: %q", line)
|
||||
}
|
||||
}
|
||||
if missingTranslations(nil) != "" || missingTranslations(map[string][]string{"en-US": nil}) != "" {
|
||||
t.Fatal("empty report produced a warning")
|
||||
}
|
||||
}
|
||||
+14
-13
@@ -20,16 +20,17 @@ import (
|
||||
)
|
||||
|
||||
type PreviewResult struct {
|
||||
Rendered map[string]map[string]map[string]string
|
||||
Registry map[string]map[string]string
|
||||
Params map[string]map[string]string
|
||||
Languages []string
|
||||
Themes []string
|
||||
Upstream string
|
||||
Commit string
|
||||
Logo []byte
|
||||
Fallback map[string][]string
|
||||
Warnings map[string]map[string][]string
|
||||
Rendered map[string]map[string]map[string]string
|
||||
Registry map[string]map[string]string
|
||||
Params map[string]map[string]string
|
||||
Languages []string
|
||||
LanguageNames map[string]string
|
||||
Themes []string
|
||||
Upstream string
|
||||
Commit string
|
||||
Logo []byte
|
||||
Fallback map[string][]string
|
||||
Warnings map[string]map[string][]string
|
||||
}
|
||||
|
||||
func DiscoverThemes(dir string) ([]string, error) { return builder.Discover(dir) }
|
||||
@@ -92,6 +93,7 @@ func RenderAll(themesDir string, cfg *config.TemplatesConfig, filter map[string]
|
||||
return nil, err
|
||||
}
|
||||
result := &PreviewResult{Rendered: map[string]map[string]map[string]string{}, Registry: config.Registry(cfg), Params: map[string]map[string]string{}, Languages: s.Languages(), Upstream: s.Lock.Tag, Fallback: missing, Warnings: map[string]map[string][]string{}}
|
||||
result.LanguageNames = languageNames(result.Languages)
|
||||
for id, t := range cfg.Templates {
|
||||
result.Params[id] = config.FlattenParams(t.Params)
|
||||
}
|
||||
@@ -236,6 +238,7 @@ func WriteRenderedJS(result *PreviewResult, output string) error {
|
||||
return err
|
||||
}
|
||||
manifest := map[string]any{"schema": 1, "upstream": result.Upstream, "languages": result.Languages, "themes": result.Themes, "registry": result.Registry, "params": result.Params, "fallback": result.Fallback, "warnings": result.Warnings}
|
||||
manifest["language_names"] = result.LanguageNames
|
||||
manifest["logo"] = "data:image/png;base64," + base64.StdEncoding.EncodeToString(result.Logo)
|
||||
manifest["upstream_commit"] = result.Commit
|
||||
data, err := json.Marshal(manifest)
|
||||
@@ -267,10 +270,8 @@ func PrintDetailedSummary(result *PreviewResult, _ string, _ *config.TemplatesCo
|
||||
logging.Warn("Upstream", "%s", strings.TrimPrefix(warning, "[UPSTREAM-WARN] "))
|
||||
}
|
||||
}
|
||||
if keys := result.Fallback[lang]; len(keys) > 0 {
|
||||
logging.Warn("Locale", "%s -> en-US: %s", lang, strings.Join(keys, ", "))
|
||||
}
|
||||
}
|
||||
logging.WarnMissingTranslations(result.Fallback)
|
||||
}
|
||||
|
||||
func markSafeHTML(data map[string]any) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package preview
|
||||
|
||||
import (
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/language/display"
|
||||
)
|
||||
|
||||
// languageNames derives native names from CLDR for the generated preview locales.
|
||||
func languageNames(locales []string) map[string]string {
|
||||
names := make(map[string]string, len(locales))
|
||||
for _, locale := range locales {
|
||||
name := locale
|
||||
if tag, err := language.Parse(locale); err == nil {
|
||||
if native := display.Self.Name(tag); native != "" {
|
||||
name = native
|
||||
}
|
||||
}
|
||||
names[locale] = name
|
||||
}
|
||||
return names
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package preview
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gitea-mail-templates/tools/upstream"
|
||||
)
|
||||
|
||||
func TestNativeLanguageNames(t *testing.T) {
|
||||
snapshot, err := upstream.LoadPinned(filepath.Join("..", ".."))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
locales := snapshot.Languages()
|
||||
names := languageNames(locales)
|
||||
if len(names) != len(locales) {
|
||||
t.Fatal("language names do not match preview locales")
|
||||
}
|
||||
for _, locale := range locales {
|
||||
if names[locale] == "" || names[locale] == locale {
|
||||
t.Errorf("missing native name for %s", locale)
|
||||
}
|
||||
}
|
||||
for locale, want := range map[string]string{"ga-IE": "Gaeilge", "is-IS": "íslenska"} {
|
||||
if names[locale] != want {
|
||||
t.Errorf("%s: got %q, want %q", locale, names[locale], want)
|
||||
}
|
||||
}
|
||||
if got := languageNames([]string{"!invalid"})["!invalid"]; got != "!invalid" {
|
||||
t.Fatalf("invalid locale fallback: %q", got)
|
||||
}
|
||||
}
|
||||
@@ -294,6 +294,7 @@ func TestSubjectSplitAndMissingFixture(t *testing.T) {
|
||||
|
||||
func TestPreviewBundles(t *testing.T) {
|
||||
result := &PreviewResult{Rendered: map[string]map[string]map[string]string{"en-US": {"test": {"activate": "<body>hello</body>"}}, "zh-CN": {"test": {"activate": "<body>你好</body>"}}}, Languages: []string{"en-US", "zh-CN"}, Themes: []string{"test"}, Registry: map[string]map[string]string{}, Upstream: "v28.0.0"}
|
||||
result.LanguageNames = languageNames(result.Languages)
|
||||
output := filepath.Join(t.TempDir(), "rendered.js")
|
||||
if err := WriteRenderedJS(result, output); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -305,6 +306,9 @@ func TestPreviewBundles(t *testing.T) {
|
||||
if bytes.Contains(manifest, []byte("<body>")) {
|
||||
t.Fatal("manifest embeds mail payload")
|
||||
}
|
||||
if !bytes.Contains(manifest, []byte(`"language_names":`)) || !bytes.Contains(manifest, []byte(fmt.Sprintf("%q:%q", "en-US", result.LanguageNames["en-US"]))) {
|
||||
t.Fatal("manifest does not include generated native language names")
|
||||
}
|
||||
for _, lang := range result.Languages {
|
||||
data, err := os.ReadFile(filepath.Join(filepath.Dir(output), "rendered", lang+".js"))
|
||||
if err != nil {
|
||||
|
||||
@@ -11,7 +11,12 @@ import (
|
||||
// TrN keys. Dynamic translation keys require an explicit adapter review.
|
||||
func ReferencedKeys(s *Snapshot) ([]string, error) {
|
||||
fns := template.FuncMap{}
|
||||
for _, name := range []string{"AppUrl", "AppName", "AppDomain", "DotEscape", "QueryEscape", "ShortSha", "HTMLFormat", "PathEscapeSegments", "FormatByteSize", "Dict", "dict", "StringUtils", "SliceUtils", "JsonUtils", "QueryBuild", "PathEscape", "SanitizeHTML"} {
|
||||
for _, name := range []string{
|
||||
"AppUrl", "AppName", "AppDomain", "DotEscape", "QueryEscape",
|
||||
"ShortSha", "HTMLFormat", "PathEscapeSegments", "FormatByteSize",
|
||||
"Dict", "dict", "StringUtils", "SliceUtils", "JsonUtils", "QueryBuild",
|
||||
"PathEscape", "SanitizeHTML"} {
|
||||
|
||||
fns[name] = func(...any) string { return "" }
|
||||
}
|
||||
keys := map[string]bool{}
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestSnapshotAndKeys(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.Lock.Tag != "v28.0.0" || len(s.Templates) != 13 || len(s.Locales) != 28 {
|
||||
if s.Lock.Tag != "v28.2.0" || len(s.Templates) != 13 || len(s.Locales) != 28 {
|
||||
t.Fatalf("unexpected baseline %s", s.Lock.Tag)
|
||||
}
|
||||
keys, err := ReferencedKeys(s)
|
||||
@@ -32,7 +32,7 @@ func TestSnapshotAndKeys(t *testing.T) {
|
||||
if ValidateKeys(s) == nil {
|
||||
t.Fatal("missing English key accepted")
|
||||
}
|
||||
if !validTag("v28.0.0") || validTag("v1.27.3") || validTag("v28.1.0-rc1") {
|
||||
if !validTag("v28.0.0") || !validTag("v28.2.0") || validTag("v1.27.3") || validTag("v28.2.0-rc1") {
|
||||
t.Fatal("tag policy")
|
||||
}
|
||||
for _, p := range []string{"../x", "/x", "C:/x", "mail\\x"} {
|
||||
|
||||
Reference in new issue
Block a user