第26.6章:locale
1. locale 层是什么
locale 层回答那些不属于值本身的问题:哪个字符是千位分隔符、数字怎么分组、
日期格式串展开成什么、货币符号是什么、locale 使用哪些星期名。
fast_io 把这些数据放在单独的 fast_io.i18n 模块里,
因为大多数程序不需要它,而且 locale 数据很大。
这一层不是全局流状态。locale 是一个指向不可变镜像的指针;
你把它显式传给 imbue 或 lc_concat。
两个线程可以同时通过两个不同的 locale 打印。
2. locale 镜像
l10n::lc_locale 是一张扁平的、位置无关的镜像。它存的是偏移,不是绝对指针:
lc_rva<T>—— 相对偏移,和镜像基址相加后解析为指针。lc_scatter<T>—— 相对偏移加长度,解析为basic_io_scatter_t。lc_get_rva、lc_get_scatter、lc_get_all—— 解析这些偏移的访问器。
这就是为什么同一份字节可以从文件映射、嵌入数组、WebAssembly 模块或资源里使用。
没有重定位步骤,也没有指针修复。镜像带 magic 和版本;
lc_verify 在发布之前把整张镜像检查一次。
lc_locale 为每种字符宽度携带一个 section:
all(声明的 codeset,通常是 UTF-8 或 GB18030)、u8all、
u16all 和 u32all。lc_get_all<char_type>
选择与输出字符类型匹配的 section。
3. 加载 locale
l10n::load_l10n(name, flags) 映射 locale blob,验证它,
返回 lc_locale const *。名字是 UTF-8 字符串。
空名字解析操作系统当前的 locale。
auto const *loc{::fast_io::l10n::load_l10n(u8"de_DE.UTF-8")};
loader 维护进程级缓存:先是线程局部映射,然后是互斥量保护下的全局映射。 映射永远存活,所以重新加载只是指针查找,不是重新映射。 loader 相对一个进程生命周期的目录句柄打开文件,不手工拼接路径。
l10n_load_flags::ignore_system_settings 告诉 loader 在解析空名字时
不要查询 OS 用户 locale 数据。在 Windows 上,这避免读取
HKCU\Control Panel\International 注册表值;
正常的 OS 查找仍然会解析空名字。在 POSIX 上,
FAST_IO_L10N_LANG、LC_ALL、LANG 和
FAST_IO_L10N_PATH 仍然是正常机制。
沙箱调用者不必使用 loader。它们可以传入一个已经映射或嵌入的
lc_locale 镜像指针。这就是 freestanding 路径,
也是为什么 API 是 locale 指针类型,而不是 loader 对象。
4. lc_ctx 与 imbue
lc_ctx<char_type> 是格式化钩子使用的运行时视图。
它持有 locale 指针、选中的 section,以及解码后的数字热字段(分隔符和分组)。
lc_load_ctx<char_type>(loc) 构建它。
imbue(loc, out) 包装一个输出流。上下文延迟构建,
只有参数真的需要 locale 格式化时才建立:
import fast_io.i18n;
int main()
{
using namespace ::fast_io::io;
auto const *loc{::fast_io::l10n::load_l10n(u8"de_DE.UTF-8")};
println(::fast_io::imbue(loc, ::fast_io::out()), 1234567);
}
这会按 locale 的分组和分隔符打印数字。没有 locale 敏感表示形式的参数走普通打印路径; 包装不会给它们增加格式化成本。没有分组的 locale 根本不会构建上下文。
5. locale 类别
| Section | 携带什么 |
|---|---|
| identification | locale 名称、编码、标题和相关元数据。 |
| numeric | 小数点、千位分隔符、分组、正负号。 |
| monetary | 货币符号、国际符号、小数和分组规则。 |
| time | d_t_fmt、d_fmt、t_fmt、
t_fmt_ampm、date_fmt、纪元格式、星期名和月份名。 |
| messages | 是/否表达式和消息目录。 |
| paper、telephone、name、address、measurement、keyboard | 其余 POSIX locale section,装在同一张镜像里。 |
lc_time_flag 在 time section 里选择时间格式:
d_t_fmt、d_fmt、t_fmt、
t_fmt_ampm、date_fmt、era_d_t_fmt、
era_d_fmt 和 era_t_fmt。
mnp::d_t_fmt、mnp::d_fmt、mnp::t_fmt
等时间操纵器目前是头文件侧辅助;模块导出 lc_dump,但还没有导出时间操纵器。
6. locale 感知的拼接
lc_concat 使用 locale 把参数格式化进一个新字符串,不需要包装流:
auto s{::fast_io::lc_concat(loc, "total: ", 1234567, " items")};
lc_concatln 追加换行。basic_lc_concat 让你选择目标字符串类型;
basic_lc_general_concat 是通用形式。
没有 locale 敏感表示形式的参数走普通打印路径,所以这个函数不会把 locale 格式化
强加给字符串或字符。
7. 转储 locale
mnp::lc_dump(loc) 按文本、分 section 的形式打印整个 locale。
它是调试和检查工具,不是可重新加载的序列化格式:
println(::fast_io::imbue(loc, ::fast_io::out()), ::fast_io::mnp::lc_dump(loc));
转储由被包装 locale 自己的镜像产生,所以被转储的 locale 不必是当前流上 imbue 的 locale。
原始 lc_locale const * 刻意不可打印;把它包进
lc_dump 才是显式请求。
8. 模块与头文件边界
fast_io.i18n 导出 fast_io 和 locale 核心:
l10n::lc_locale、线格式类型、load_l10n、
lc_ctx、lc_load_ctx、lc_imbuer、
imbue、lc_concat、lc_concatln 和
mnp::lc_dump。loader 编译进模块,所以导入它不需要额外链接
fast_io_i18n 库。
头文件接口是 #include <fast_io_i18n.h>。它暴露同样的核心,
外加头文件侧时间操纵器和 i18n.inc 还没有的辅助。
如果模块缺少某个 locale 辅助,在导出列表增长之前用头文件。
要点
- locale 是一张不可变、位置无关的镜像,用
lc_locale const *寻址。 load_l10n映射并验证 blob,然后缓存指针;沙箱可以直接传镜像。imbue(loc, out)是显式的、按流的绑定;没有全局 locale 状态。lc_ctx延迟构建,携带解码后的数字热字段。lc_concat生成 locale 格式的字符串;mnp::lc_dump检查 locale。- 模块导出 locale 核心和
lc_dump;时间操纵器和部分辅助仍在头文件侧。 - 空 locale 名表示 OS locale;
ignore_system_settings在 Windows 上 抑制用户 locale 查询。