深入解析 @directus/format-title:把 camelCase、下划线与普通句子统一规范化为 Title Case 的字符串格式化工具
【免费下载链接】directusThe flexible backend for all your projects 🐰 Turn your DB into a headless CMS, admin panels, or apps with a custom UI, instant APIs, auth & more.项目地址: https://gitcode.com/GitHub_Trending/di/directus
@directus/format-title 是 Directus 开源仓库中独立发布的字符串格式化子包,其核心使命是把 camelCase、PascalCase、snake_case 乃至普通英文句子统一转换为符合出版规范的 Title Case(标题大小写)。本文将从该包的 README 出发,结合 核心实现、词表常量 与 测试用例,讲清它的安装方式、调用签名、默认分隔符规则,以及底层那套"专有大小写词 + 首尾词 + 短词小写"的 APA 风格判定管线,帮助你在自己的数据展示、字段名美化或 UI 文案场景中直接用上这套能力。
包定位:解决"机器命名 → 人类可读标题"的最后一公里
在真实业务中,数据库字段往往叫snowWhiteAndTheSevenDwarfs、NewcastleUponTyne、apple_releases_new_ipad这类便于代码引用的标识符,而展示给用户时却希望看到 "Snow White and the Seven Dwarfs" 这样自然、规范的标题。@directus/format-title 就是为此设计的定制格式化器(custom formatter)。
按 README 的定位,它默认可以把四种输入风格转换为 Title Case:
- camelCase(如
snowWhiteAndTheSevenDwarfs); - PascalCase(如
NewcastleUponTyne); - underscore/snake_case(如
brighton_on_sea); - 以及"常规句子"(regular sentences)。
转换遵循美国心理学会发布的 Title Case 规范 明确援引):主要词汇使用大写,而定冠词、连词与介词除非出现在标题开头或结尾,否则一律不大写。例如and、on、the这些词在标题中部保持小写,这就是 "Snow Whiteandthe Seven Dwarfs" 而不是 "Snow White And The Seven Dwarfs" 的原因。
转换效果速览
README 给出了一组直观的输入/输出对照,这些样例同时被固化在 src/index.test.ts 中作为 vitest 断言用例,保证行为可回归验证:
| Input | Output |
|---|---|
snowWhiteAndTheSevenDwarfs | Snow White and the Seven Dwarfs |
NewcastleUponTyne | Newcastle Upon Tyne |
brighton_on_sea | Brighton on Sea |
apple_releases_new_ipad | Apple Releases New iPad |
7-food-trends | 7 Food Trends |
从这张表能读出三条关键行为:
- 数字开头会被完整保留:
7-food-trends中的7不会被吞掉,输出首词 "7 Food Trends"; - 专有大小写词不随规则被"纠正":
ipad最终输出为 "iPad",而不是被粗暴首字母大写成 "Ipad"; - 介词/连词按位置决定大小写:
brighton_on_sea→ "BrightononSea",中部的on保持小写。
安装与基础用法
作为一个可通过 npm 单独安装的独立子包,@directus/format-title 的安装命令如下:
npm install @directus/format-title从 packages/format-title/package.json 可以看到,该包是 ESM("type": "module"),入口为./dist/index.js,构建工具使用 tsdown,因此引入方式为 ES import:
formatTitle(string, [separator]); formatTitle('snowWhiteAndTheSevenDwarfs'); // => Snow White and the Seven DwarfsformatTitle同时支持具名导出与默认导出两种方式,见 src/index.ts 末尾,方便不同引入风格:
import formatTitle, { formatTitle as namedFormatTitle } from '@directus/format-title'; // 两者指向同一个实现separator 参数:自定义切分字符
第二个可选参数separator是一个正则表达式,用来控制"在哪些字符处把字符串拆成单词"。按 README 说明,其默认值为/\s|-|_/g,也就是同时支持按空白、连字符、下划线三种字符切分:
formatTitle('hello_world'); formatTitle('hello-world'); formatTitle('hello world'); // 三者均输出 => Hello World默认值对应的实现可以在 src/index.ts 的函数签名 中看到:
export function formatTitle(title: string, separator: RegExp = new RegExp('\\s|-|_', 'g')): string { return decamelize(title).split(separator).map(capitalize).map(handleSpecialWords).reduce(combine); }传入自定义正则即可扩展切分字符集,例如希望额外按/或.切分:
formatTitle('admin/users/manage', /[/\s\-_.]/g); // 需要说明:切分后每个片段都会经过独立的大小写判定值得强调的是,camelCase/PascalCase 并不是靠 separator 拆开的,而是由管线最前端的decamelize统一先转换为下划线形式(详见下文),随后才交给separator正则切分。这意味着即使你传入了自定义 separator,camelCase 的拆分能力依然有效。
工作原理:五步处理管线的源码级拆解
formatTitle的实现只有一行,却是一条非常清晰的数据处理管线(src/index.ts#L6-L8):
decamelize → split(separator) → map(capitalize) → map(handleSpecialWords) → reduce(combine)第一步:decamelize —— 拆开驼峰并把所有字符转小写
utils/decamelize.ts 用两条正则完成驼峰拆分,随后统一.toLowerCase():
export function decamelize(string: string): string { return string .replace(/([a-z\d])([A-Z])/g, '$1_$2') // 小写/数字 + 大写之间补下划线 .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1_$2') // 连续大写后接"大写+小写"处补下划线 .toLowerCase(); }- 第一条正则处理
snowWhite→snow_White这类"小写/数字后紧跟大写"的边界; - 第二条正则处理连续缩写场景,例如
iPhoneXSupport中的XSupport边界,可正确拆出IPHONE_X_SUPPORT(随后统一转小写)。
因为拆完后已经全部转为小写,后续的切分与大小写判定就可以基于统一的小写词根进行。
第二步:split(separator) —— 按默认正则切词
拆完驼峰并转小写后的字符串,通过String.prototype.split以默认正则/\s|-|_/g切成单词数组,供后续逐词处理。
第三步:capitalize —— 每个词首字母大写
utils/capitalize.ts 只做一件事:把每个词的首字母转大写、其余保持不变:
export function capitalize(word: string): string { return word.charAt(0).toUpperCase() + word.substring(1); }此时snow_white_and_the_seven_dwarfs已被拆成词并逐词大写为Snow、White、And、The……但这还是"全首字母大写"版本,需要下一步按标题规范修正小词。
第四步:handleSpecialWords —— APA Title Case 的核心判定
这是整个包的灵魂所在。utils/handle-special-words.ts 对每个词按以下优先级依次判定:
- 专有大小写词(special-case)命中直接返回原样:遍历
specialCase列表,只要不区分大小写地命中(如输入ipad命中原词iPad),就返回词表中书写形式,即输出iPad而非Ipad; - 首字母缩写(acronym)命中则整体大写:若
str.toUpperCase()存在于acronyms列表中(如api、sql、pdf),返回全大写形式; - 位于标题首位/末位的词:即使它是介词或连词,也保持当前已大写形式返回(对应 README 中"除非它们位于标题开头或结尾"的规则);
- 长度 ≥ 4 的词:保持大写(因此
Seven、Dwarfs这类 4 个字母以上的主要词汇不会被误伤); - 介词表命中:转为小写返回;
- 连词表命中:转为小写返回;
- 冠词表命中:转为小写返回;
- 兜底:其余情况保持首字母大写后的形态。
第五步:combine —— 用空格拼接回完整标题
utils/combine.ts 是最朴素的一步,把处理完的单词用单个空格连接:
export function combine(acc: string, str: string): string { return `${acc} ${str}`; }支撑判定的五大词表常量
上述判定逻辑不写死在代码里,而是高度数据化地维护在 src/constants 目录下,便于持续扩充:
| 词表文件 | 内容说明 | 覆盖量(以仓库实际内容为准) |
|---|---|---|
| articles.ts | 英语冠词 | 3 个:a、an、the |
| conjunctions.ts | 连词 | and、but、or、that、when等 20+ 个 |
| prepositions.ts | 介词(含复合介词) | of、on、in front of、with respect to等 60+ 个 |
| acronyms.ts | 输出时应保持全大写的缩写 | API、SQL、HTML、PDF、URL、2FA等 80+ 个 |
| special-case.ts | 有独特大小写拼写方式的专有名词 | McDonalds、iPhone、YouTube、PostgreSQL、macOS等 40+ 个 |
README 特别举出的例子正是这些表存在的意义:"这个包里包含一份使用某种特殊大小写的词汇清单,例如 McDonalds、iPhone 和 YouTube。" 例如apple_releases_new_ipad之所以输出Apple Releases New iPad,正是因为处理ipad时命中了 special-case.ts 中的iPad,从而保留厂商官方的拼写方式。
同时注意判定顺序上 special-case 优先于 acronym:例如sql若同时出现在两表,special-case 先命中则不再进入 acronym 分支(当前词表中二者并不冲突,但顺序设计保证了扩展安全性)。从源码结构看,这几个词表均为默认导出数组常量,若要为本仓库之外的项目扩展词表,可直接 fork 后追加条目,无需改动判定逻辑本身。
测试与工程化保障
这个包的测试由 vitest 驱动,主测试文件 src/index.test.ts 以"输入→期望输出"的二维数组形式组织用例,并逐条生成测试名:
const tests: [string, string][] = [ ['snowWhiteAndTheSevenDwarfs', 'Snow White and the Seven Dwarfs'], ['NewcastleUponTyne', 'Newcastle Upon Tyne'], ['brighton_on_sea', 'Brighton on Sea'], ['apple_releases_new_ipad', 'Apple Releases New iPad'], ['7-food-trends', '7 Food Trends'], ];此外还有针对各内部工具与常量的单测:constants.test.ts校验词表数据,capitalize.test.ts、combine.test.ts、decamelize.test.ts、handle-special-words.test.ts则分别锁定四个工具函数的边界行为。在 package.json 的 scripts 中可用pnpm test(vitest run)执行全部测试,用pnpm build完成tsdown src/index.ts --dts的打包(同时生成类型声明)。
版本与许可
该包当前版本为 13.0.0(以 package.json 的 version 字段 为准),作者为 Directus 核心开发者,采用 MIT License,详见仓库内的 license 文件。作为 Directus 开源 monorepo 的一个独立发布包,你既可以随 Directus 生态一并使用,也可以作为通用字符串工具单独安装到任意 JS/TS 项目中。
适合的应用场景小结
综合 README 与源码,@directus/format-title 最适合用在以下位置:
- 字段名/集合名的人性化展示:把数据库里的
snowWhiteAndTheSevenDwarfs变为界面标题 "Snow White and the Seven Dwarfs"; - URL slug 与文件名美化:如 README 示例中的
7-food-trends→ "7 Food Trends",以及连字符/下划线/空格混排文本的归一化; - 配置键名、枚举值等机器标识符的阅读化输出:借助内置的 acronym 与 special-case 词表,
apple_releases_new_ipad这类包含品牌词的输入也能得到品牌官方拼写; - 任何需要"主要词大写、冠词介词连词小写"的标题排版需求:判定规则完整对齐 APA Title Case,可直接复用。
使用时只需记住一个 API 签名formatTitle(string, separator?)与一个默认行为:camelCase 由内置decamelize先行拆解,空白/连字符/下划线由默认正则/\s|-|_/g切分,最终输出遵循"首尾词大写、3 字以内虚词小写、4 字以上实词大写、缩写与专有名词特殊处理"的规范结果。
【免费下载链接】directusThe flexible backend for all your projects 🐰 Turn your DB into a headless CMS, admin panels, or apps with a custom UI, instant APIs, auth & more.项目地址: https://gitcode.com/GitHub_Trending/di/directus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考