← 中文资源

简体中文版本

支持 Unicode 的 URL Slug 生成

通过 NFKD 分解与去除变音符号生成简洁 URL slug,并支持长度上限与兜底值。

阅读英文原文

功能说明

该示例把标题转换成适合放进 URL 的 slug。它先做 NFKD 分解,把带音标的拉丁字符拆成基础字母与组合符号,再删除组合符号,最后只保留小写字母和数字并用分隔符连接。生成结果可直接用于文章路径、标签页或静态站点文件名。

关键代码

normalize 使用 NFKD,随后用正则匹配 Unicode 组合附加符号区间并删除。文本转小写后按非字母数字字符切分,过滤空片段再 join,避免出现连续分隔符。超过 maxLength 时先截断,再回退到最后一个分隔符,保证不会把单词切成半截。结果为空时返回 fallback,默认是 untitled,防止生成空路径。

使用注意

该实现只做拉丁字母的变音符号折叠,不做中文、日文或西里尔字母的音译,这些字符会被整体丢弃并落到 fallback。如果需要多语言 slug,应接入音译表或转写库。分隔符不能为空字符串,maxLength 必须为正整数。截断可能丢掉尾部词,重要信息应放在标题前部。生成后仍应检查是否与已有 slug 冲突。

验证方式

断言 Héllo, Wörld! 2026 生成 hello-world-2026,Café au Lait 生成 cafe-au-lait。中文输入应返回 untitled。用 maxLength 为 18 的标题验证截断发生在分隔符处而不是单词中间。再测试自定义分隔符、空分隔符抛 TypeError、非字符串输入抛 TypeError。

代码示例

const DIACRITICS = /[\u0300-\u036f]/g;

export function slugify(input, options = {}) {
  if (typeof input !== "string") throw new TypeError("input must be a string");
  const { maxLength = 80, separator = "-", fallback = "untitled" } = options;
  if (typeof separator !== "string" || separator.length === 0) {
    throw new TypeError("separator must be a non-empty string");
  }
  if (!Number.isInteger(maxLength) || maxLength < 1) {
    throw new RangeError("maxLength must be a positive integer");
  }
  const normalized = input.normalize("NFKD").replace(DIACRITICS, "").toLowerCase();
  const parts = normalized.split(/[^a-z0-9]+/).filter(Boolean);
  let slug = parts.join(separator);
  if (slug.length > maxLength) {
    slug = slug.slice(0, maxLength);
    const cut = slug.lastIndexOf(separator);
    if (cut > 0) slug = slug.slice(0, cut);
  }
  return slug || fallback;
}

console.log(slugify("Héllo, Wörld! 2026"));
console.log(slugify("  Café  au   Lait  "));
console.log(slugify("你好 世界"));
console.log(slugify("Express + TypeScript Guide", { maxLength: 18 }));