第26.6章:locale

English

1. locale 层是什么

locale 层回答那些不属于值本身的问题:哪个字符是千位分隔符、数字怎么分组、 日期格式串展开成什么、货币符号是什么、locale 使用哪些星期名。 fast_io 把这些数据放在单独的 fast_io.i18n 模块里, 因为大多数程序不需要它,而且 locale 数据很大。

这一层不是全局流状态。locale 是一个指向不可变镜像的指针; 你把它显式传给 imbue 或 lc_concat。 两个线程可以同时通过两个不同的 locale 打印。

2. locale 镜像

l10n::lc_locale 是一张扁平的、位置无关的镜像。它存的是偏移,不是绝对指针:

这就是为什么同一份字节可以从文件映射、嵌入数组、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 辅助,在导出列表增长之前用头文件。

要点