news 2026/9/10 13:52:43

深入解析 @directus/format-title:把 camelCase、下划线与普通句子统一规范化为 Title Case 的字符串格式化工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入解析 @directus/format-title:把 camelCase、下划线与普通句子统一规范化为 Title Case 的字符串格式化工具

深入解析 @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 文案场景中直接用上这套能力。

包定位:解决"机器命名 → 人类可读标题"的最后一公里

在真实业务中,数据库字段往往叫snowWhiteAndTheSevenDwarfsNewcastleUponTyneapple_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 规范 明确援引):主要词汇使用大写,而定冠词、连词与介词除非出现在标题开头或结尾,否则一律不大写。例如andonthe这些词在标题中部保持小写,这就是 "Snow Whiteandthe Seven Dwarfs" 而不是 "Snow White And The Seven Dwarfs" 的原因。

转换效果速览

README 给出了一组直观的输入/输出对照,这些样例同时被固化在 src/index.test.ts 中作为 vitest 断言用例,保证行为可回归验证:

InputOutput
snowWhiteAndTheSevenDwarfsSnow White and the Seven Dwarfs
NewcastleUponTyneNewcastle Upon Tyne
brighton_on_seaBrighton on Sea
apple_releases_new_ipadApple Releases New iPad
7-food-trends7 Food Trends

从这张表能读出三条关键行为:

  1. 数字开头会被完整保留7-food-trends中的7不会被吞掉,输出首词 "7 Food Trends";
  2. 专有大小写词不随规则被"纠正"ipad最终输出为 "iPad",而不是被粗暴首字母大写成 "Ipad";
  3. 介词/连词按位置决定大小写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 Dwarfs

formatTitle同时支持具名导出与默认导出两种方式,见 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(); }
  • 第一条正则处理snowWhitesnow_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已被拆成词并逐词大写为SnowWhiteAndThe……但这还是"全首字母大写"版本,需要下一步按标题规范修正小词。

第四步:handleSpecialWords —— APA Title Case 的核心判定

这是整个包的灵魂所在。utils/handle-special-words.ts 对每个词按以下优先级依次判定:

  1. 专有大小写词(special-case)命中直接返回原样:遍历specialCase列表,只要不区分大小写地命中(如输入ipad命中原词iPad),就返回词表中书写形式,即输出iPad而非Ipad
  2. 首字母缩写(acronym)命中则整体大写:若str.toUpperCase()存在于acronyms列表中(如apisqlpdf),返回全大写形式;
  3. 位于标题首位/末位的词:即使它是介词或连词,也保持当前已大写形式返回(对应 README 中"除非它们位于标题开头或结尾"的规则);
  4. 长度 ≥ 4 的词:保持大写(因此SevenDwarfs这类 4 个字母以上的主要词汇不会被误伤);
  5. 介词表命中:转为小写返回;
  6. 连词表命中:转为小写返回;
  7. 冠词表命中:转为小写返回;
  8. 兜底:其余情况保持首字母大写后的形态。

第五步:combine —— 用空格拼接回完整标题

utils/combine.ts 是最朴素的一步,把处理完的单词用单个空格连接:

export function combine(acc: string, str: string): string { return `${acc} ${str}`; }

支撑判定的五大词表常量

上述判定逻辑不写死在代码里,而是高度数据化地维护在 src/constants 目录下,便于持续扩充:

词表文件内容说明覆盖量(以仓库实际内容为准)
articles.ts英语冠词3 个:aanthe
conjunctions.ts连词andbutorthatwhen等 20+ 个
prepositions.ts介词(含复合介词)ofonin front ofwith respect to等 60+ 个
acronyms.ts输出时应保持全大写的缩写APISQLHTMLPDFURL2FA等 80+ 个
special-case.ts有独特大小写拼写方式的专有名词McDonaldsiPhoneYouTubePostgreSQLmacOS等 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.tscombine.test.tsdecamelize.test.tshandle-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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!