跳到主要内容

翻译

报告缺省是英文 —— 那是 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 的一次单向改写,而且只碰上面列出的那些位置。