翻译
报告缺省是英文 —— 那是 quantstats-rs 渲染出来的。写上 lang 配置项,报告自己说的那些固定文本
(分节标题、指标名、月份、图表标题、图例)就会换成另一种语言,而且每一条都带一句简短说明,浏览器会从它所在
的那个元素上浮出:
WITH prices AS (
SELECT * FROM read_csv('https://shijianjs.github.io/duckfn-quantstats/demo/prices.csv')
WHERE symbol IN ('GOOGL', 'SPX')
)
SELECT (r).symbol AS symbol, (r).html AS html
FROM (
SELECT unnest(qs_html_reports_by_prices(
symbol, date, price,
{'benchmark': ['SPX'],
'lang': 'zh-CN',
'title': symbol,
'strategy_title': symbol}::qs_html_report_options)) AS r
FROM prices
);
把鼠标停在上面那份报告的任意一个指标名、图表标题或月份上,浏览器会用同一种语言显示它的简要说明。
lang 配置项
| 取值 | 结果 |
|---|---|
| 不写(缺省) | 什么都不做 —— 报告里一个字符都不变 |
'en' | 英文原文原样保留,额外补上英文说明 |
| 任何其它语言 | 文本换成该语言,并补上该语言的说明 |
字段叫 lang 而不是 language(language 是 DuckDB 的关键字)。要注意结构体字面量里写错的键会被
DuckDB 静默忽略 —— 比如旁边有个合法的 'title' 时,{'language': 'zh-CN'} 只会出一份没翻译的报告,
不会报错,所以务必写成 'lang'。
取值必须是当前翻译表里有条目的语言(下面那六种内置语言,加上你用 qs_set_translation 加进去的)。
没有条目的取值会报错,而不是悄悄出一份没翻译的报告:
WITH prices AS (
SELECT * FROM read_csv('https://shijianjs.github.io/duckfn-quantstats/demo/prices.csv')
WHERE symbol = 'GOOGL'
)
SELECT unnest(qs_html_reports_by_prices(symbol, date, price,
{'lang': 'english'}::qs_html_report_options))
FROM prices;
该写哪个标签,用 qs_list_translations() 一查就知道。
哪些内容会被翻译
一百出头处位置 —— 报告自己说的话全在内,调用方给的东西一个也不碰:
- 文档
<title>、三个分节标题,以及Benchmark is … / Generated by …那一行; - 每一行的指标名(
Sharpe、Max Drawdown、CAGR﹪……)与Metric这一列表头; - 年度收益表与回撤表的表头(
Year、Started、Days……); - 每一张图的标题(
Cumulative Returns vs Benchmark、Rolling Sharpe (6-Months)……); - SVG 里的那些标签:图例、
Mean参考线、分布图的五个分组名,以及月度热量图的十二个月份列; - 标题栏里的日期区间,它是重新排版而不是逐词替换:英文报告写的是
5 Jan, 2021 - 21 Sep, 2026, 中文与日文得到的是2021年1月5日 - 2026年9月21日。
有意不翻译的:来自你的数据或配置的一切。symbol、title、strategy_title、benchmark_title
都是你的,所以把 title 设成 'GOOGL 日报' 的报告就原样显示那串字;只有库的缺省值
'Strategy Tearsheet' 会以英文露出来。想要什么语言,就自己把标题写成那个语言。
说明(浮出提示)
每一条翻译都带一句说明,报告通过文本所在的 DOM 元素把它显示出来 —— 用的是浏览器自己的浮出提示,没有 JavaScript、也没有运行时 i18n,报告依然是一个自包含的静态文件:
- HTML 元素(指标单元格、标题、图表标题):文本外面包一层
<span title="…">; - SVG 标签(图例、月份):插一个标准的
<title>子元素,SVG 不会渲染它; - 文档
<title>:只换文本 —— 浏览器标签页没有地方可以浮出。
说明里可以用 emoji(📊、📉、🎯),扫一眼就能对上。
内置语言
扩展自带 en、zh-CN、ja、de、fr、es 六种,每种都覆盖全部位置。
它们只是起点,不是固定集合:这张表活在当前 DuckDB 进程里,qs_set_translation 就地改写它。不做持久化,
所以重新加载扩展(或者重启 DuckDB)就恢复内置数据 —— 改得太多时,这也是回到原样的路。
查看这张表
SELECT * FROM qs_list_translations() WHERE lang = 'de' AND key LIKE 'plot.%';
lang 就是配置里要写的那个语言标签,key 是报告里某一处位置的名字,label 是那一处要显示的文字,
description 是浮出的说明。四个名字都不是 SQL 关键字,所以都能裸写(SELECT lang, label FROM …)。
改写
qs_set_translation(lang, entries) 收一个 {key, label, description} 列表,返回布尔值。它只作用于
当前进程:
SELECT qs_set_translation('ja', [
{'key': 'metric.max_drawdown', 'label': '最大下落', 'description': '高値からの最大の落ち込み 📉'}
]);
SELECT label, description FROM qs_list_translations()
WHERE lang = 'ja' AND key = 'metric.max_drawdown';
改写只有三条规则:
| 你写的 | 结果 |
|---|---|
给了 label(写不写 description 都行) | 该 key 的文字被替换;不写 description 就保留原有说明 |
label 是 NULL 或 '' | 该 key 被删除 —— 没有文字、没有说明,也不回退到内置数据 |
整个列表是 NULL | 整个语言被删除 |
返回值告诉你这张表是不是真的变了,所以删一个本来就没有的 key 会得到一个无害的 false:
SELECT qs_set_translation('nl', [{'key': 'month.feb'}]::qs_translation_entry[]) AS nothing_changed;
struct 字面量要写满三个字段才对得上签名;想少写就用 ::qs_translation_entry 显式 cast(列表写
::qs_translation_entry[]),缺的字段会变成 NULL。目录里没有的 key 会报错 —— key 对应报告里一个真实的
DOM 位置,凭空造一个永远不会生效。
加一个语言就是同一次调用;而且只加一部分是有用的:没填的位置保持原样(英文、不带说明),报告其余部分照常 翻译。
SELECT qs_set_translation('nl', [
{'key': 'metric.sharpe', 'label': 'Sharpe-ratio', 'description': 'Overrendement per eenheid volatiliteit ⚖️'},
{'key': 'metric.max_drawdown', 'label': 'Max drawdown', 'description': 'Diepste daling van piek naar dal 📉'}
]);
SELECT lang, key, label FROM qs_list_translations() WHERE lang = 'nl' ORDER BY key;
再把它删掉就是列表传 NULL,布尔值告诉你它本来在不在:
SELECT qs_set_translation('nl', NULL) AS deleted_whole_language;
SELECT count(*) AS rows_left FROM qs_list_translations() WHERE lang = 'nl';
翻译发生在什么时候
发生在报告渲染完之后、写文件与开浏览器之前,所以磁盘上的文件、返回行里的 html 与你看到的页面是同一份
内容 —— 见输出与浏览器。它是对 HTML 的一次单向改写,而且只碰上面列出的那些位置。