replexica 仓库解析:@lingo.dev/locales 的 locale 解析、校验与多语言名称解析实战指南
【免费下载链接】replexicaOpen-source localization engineering tools. Connects to Lingo.dev localization engineering platform for consistent, quality translations.项目地址: https://gitcode.com/GitHub_Trending/re/replexica
本文基于 replexica 仓库中的 packages/locales/README.md 及
packages/locales包源码,系统讲解@lingo.dev/locales的核心能力:把"en-US"、"zh-Hans-CN"这类 locale 字符串拆解为语言/文字/地区组件、按 ISO 标准校验其真实性、并获取 200+ 语言的国名/语言名/文字名。读完本文,你将能在任何 Node.js / TypeScript 国际化项目中直接落地这套解析、校验与名称解析方案,并理解其底层数据流与性能设计。
一、认识 @lingo.dev/locales:面向国际化工程的 locale 工具包
@lingo.dev/locales是 replexica 仓库中位于 packages/locales 的一个轻量 JavaScript/TypeScript 包(源码包名为@lingo.dev/_locales,详见 package.json)。在 replexica 这套开源本地化工程工具链中,它承担着"语言代码(locale code)处理"这一基础职责:无论是配置多语言项目、校验用户提交的翻译目标语言,还是为语言切换器生成可读的语言名称,都需要先把 locale 字符串变成结构化的、可验证的、可展示的数据。
它提供四类能力:
- Locale 解析(Locale Parsing):把
"en-US"、"zh-Hans-CN"拆解为 language(语言)、script(文字)、region(地区)三个组件; - 校验(Validation):判断 locale 字符串格式是否规范,且其中的语言、文字、地区代码是否对应真实的 ISO 标准代码;
- 名称解析(Name Resolution):异步获取国家、语言、文字在 200+ 种显示语言下的本地化名称;
- 工程友好:核心包约 12KB(ESM)/ 14KB(CJS),数据按需加载,完整 TypeScript 类型支持。
安装
npm install @lingo.dev/locales安装后即可在项目中直接导入(包以 ESM 为主,package.json中"type": "module"、"module": "build/index.mjs"、"main": "build/index.cjs",同时提供"types": "build/index.d.ts",Node 与打包器均可使用)。该包还声明了"sideEffects": false,配合 tsup 产出的 ESM/CJS 双格式,可以放心交给打包器做 tree-shaking。
二、Locale 解析:从字符串到结构化组件
解析 API 是整套工具的地基。包从 src/index.ts 统一导出parseLocale、getLanguageCode、getScriptCode、getRegionCode。
2.1 parseLocale:一次性拆解完整 locale
import { parseLocale, getLanguageCode, getScriptCode, getRegionCode, } from "@lingo.dev/locales"; // 解析完整 locale parseLocale("en-US"); // { language: "en", region: "US" } parseLocale("zh-Hans-CN"); // { language: "zh", script: "Hans", region: "CN" } parseLocale("sr-Cyrl-RS"); // { language: "sr", script: "Cyrl", region: "RS" } parseLocale("es"); // { language: "es" }返回值是LocaleComponents对象:language必填,script与region仅在存在时出现。
2.2 单独提取组件
getLanguageCode("en-US"); // "en" getScriptCode("zh-Hans-CN"); // "Hans" getScriptCode("en-US"); // null(无 script 时返回 null) getRegionCode("en-US"); // "US" getRegionCode("es"); // null(无 region 时返回 null)getScriptCode与getRegionCode在没有对应组件时返回null,而非空字符串,便于在类型层面区分"缺失"状态。
2.3 源码解析:大小写规范化 + 正则匹配
从源码看,parseLocale的实现位于 src/parser.ts,内部分两步走:
- 大小写规范化(normalizeLocaleCase):
"EN-US"→"en-US","en-us"→"en-US","zh-hans-cn"→"zh-hans-CN"。规则是:语言部分一律转小写,地区部分一律转大写,文字部分保留原样(脚本代码如Hans、hans均按原样保留后交给正则)。 - 正则匹配:使用 src/constants.ts 中导出的
LOCALE_REGEX:
export const LOCALE_REGEX = /^([a-z]{2,3})(?:-_)?(?:-_)?$/;该正则对每个组件的形态做了明确约束:
| 组件 | 匹配规则 | 说明 |
|---|---|---|
| language | [a-z]{2,3} | 2–3 位小写字母,覆盖 ISO 639-1 两位码与 ISO 639-2/3 三位码(如fil、bar) |
| script | [A-Za-z]{4} | 4 位字母,对应 ISO 15924 文字代码(如Hans、Latn) |
| region | [A-Z]{2}或[0-9]{3} | 2 位大写字母(ISO 3166-1 alpha-2)或 3 位数字(UN M.49,如419) |
这也解释了为什么parseLocale("es-419")能正确解析出{ language: "es", region: "419" }——地区部分支持数字形式的联合国 M.49 编码。
2.4 补充 API:parseLocaleWithDetails
除了 README 主打的四个函数,源码还导出了一个带诊断信息的解析函数 parseLocaleWithDetails。它不会抛异常,而是返回包含组件、分隔符、是否合法及错误信息的ParseResult,适合需要容错处理的场景:
parseLocaleWithDetails("en_US"); // { // components: { language: "en", region: "US" }, // delimiter: "_", // isValid: true, // } parseLocaleWithDetails("bad input"); // { // components: { language: "" }, // delimiter: null, // isValid: false, // error: "Invalid locale format: bad input", // }2.5 测试佐证
src/parser.spec.ts 用 Vitest 覆盖了各种形态:连字符/下划线、纯语言、语言-文字-地区、数字地区码、大小写规范化,以及空字符串、非字符串输入抛出对应错误的边界情况("Locale cannot be empty"、"Locale must be a string")。这些断言与实际源码行为完全一致,可作为理解解析规则的权威参考。
三、校验:确保 locale 代码真实可用
格式合法不等于代码真实。@lingo.dev/locales的校验层会在正则通过后,进一步对照真实 ISO 数据逐项核对。
import { isValidLocale, isValidLanguageCode, isValidScriptCode, isValidRegionCode, } from "@lingo.dev/locales"; // 校验完整 locale isValidLocale("en-US"); // true isValidLocale("en_US"); // true(下划线同样支持) isValidLocale("en-FAKE"); // false(地区代码不存在) isValidLocale("xyz-US"); // false(语言代码不存在) isValidLocale("invalid"); // false(格式不合法) // 校验单个组件 isValidLanguageCode("en"); // true isValidLanguageCode("xyz"); // false isValidScriptCode("Hans"); // true isValidScriptCode("Fake"); // false isValidRegionCode("US"); // true isValidRegionCode("ZZ"); // false3.1 校验背后的标准数据
实现集中在 src/validation.ts,核心是三个预构建的常量集合:
- 语言代码集合:通过依赖
iso-639-3(见 package.json 的 dependencies)把 ISO 639-1 两位码、ISO 639-2 书目/术语三位码、ISO 639-3 三位码全部展开为小写后放入Set。这意味着isValidLocale("fil")(菲律宾语)、isValidLocale("bar")(巴伐利亚语)、isValidLocale("nap")(那不勒斯语)这类没有两位码等价物的三位码语言同样被接受——这正是 validation.spec.ts 中专门验证的场景。 - 文字代码集合:内嵌 ISO 15924 常用代码表(
Hans、Hant、Latn、Cyrl、Arab等),并区分大小写匹配。 - 地区代码集合:ISO 3166-1 alpha-2 国家/地区码(如
US、CN、GB)与 UN M.49 数字地区码(如001世界、419拉丁美洲、142亚洲)两个集合取并集,因此isValidLocale("es-419")与isValidLocale("en-001")均返回true。
isValidLocale的执行顺序是:先做类型与空值检查 → 正则匹配 → 依次校验语言/文字/地区组件,任何一步失败都返回false而非抛异常,天然适合表单校验等"不要中断流程"的调用方。
3.2 测试佐证
src/validation.spec.ts 系统覆盖了合法/非法两面的用例:连字符与下划线、纯语言、三位码语言、三位码 + 文字 + 地区组合、数字地区码;同时拒绝"en-"、"-US"、"en-US-"等残缺格式,以及xyz-US、en-Fake-US、en-ZZ等"格式合法但代码不存在"的输入。
四、名称解析:异步获取多语言的国家/语言/文字名称
解析与校验解决"代码是否真实",名称解析解决"代码如何展示"。三个异步函数getCountryName、getLanguageName、getScriptName通过第二个可选参数displayLanguage(默认"en")指定展示语言:
import { getCountryName, getLanguageName, getScriptName, } from "@lingo.dev/locales"; // 国家名称(默认英语) await getCountryName("US"); // "United States" // 指定展示语言 await getCountryName("US", "es"); // "Estados Unidos" await getCountryName("CN", "fr"); // "Chine" // 语言名称 await getLanguageName("en"); // "English" await getLanguageName("en", "es"); // "inglés" await getLanguageName("zh", "fr"); // "chinois" // 文字名称 await getScriptName("Hans"); // "Simplified Han" await getScriptName("Hans", "es"); // "han simplificado" await getScriptName("Latn", "zh"); // "拉丁文"4.1 实现细节:代码规范化
从 src/names/index.ts 可以看到,三个函数在查询前都会先规范化入参:国家码统一toUpperCase(),语言码统一toLowerCase(),因此getCountryName("us")与getCountryName("US")结果一致(集成测试integration.spec.ts中对此有专门断言)。若查询不到对应名称,函数会抛出Country code "XX" not found之类的错误。
4.2 底层数据加载:CLDR + 内存缓存 + 英文回退
名称数据来自 Unicode CLDR(Common Locale Data Repository),加载逻辑在 src/names/loader.ts:
- 按需拉取:
loadTerritoryNames/loadLanguageNames/loadScriptNames分别以fetch请求unicode-org/cldr-json仓库中对应语言的territories.json/languages.json/scripts.json,并从data.main[displayLanguage].localeDisplayNames下提取名称映射表——因此包体积不会随语言数量膨胀; - 内存缓存:模块级
Map以territories-en、languages-fr这样的 key 缓存已加载数据,避免同一语言的重复网络请求; - 英文回退:若指定语言加载失败,先
console.warn提示,再递归回退到英语数据,保证"graceful degradation"; - 可配置数据源:读取
process.env.CLDR_BASE_URL环境变量可覆盖默认的 CDN 地址,便于在离线/内网环境自建镜像; - 文字名增强:加载脚本名称时会把
Hans-alt-stand-alone、Hant-alt-stand-alone这类独立形式的名称提升为Hans/Hant的主名称,使中文简繁体文字的展示名更完整。
由于名称解析涉及网络请求,务必使用await;集成测试 src/names/integration.spec.ts 通过 mock loader 验证了不同展示语言下的查询结果、国家码大写规范化以及加载失败时的错误透传。
五、支持的格式与分隔符
包同时支持连字符(-)与下划线(_)两种分隔符,且可以混合出现在同一 locale 的不同位置:
| 输入 | 解析结果 |
|---|---|
en-US或en_US | { language: "en", region: "US" } |
zh-Hans-CN或zh_Hans_CN | { language: "zh", script: "Hans", region: "CN" } |
这与LOCALE_REGEX中每段分隔符独立匹配[-_]的设计一致。parseLocaleWithDetails还会在返回结果中记录原始字符串实际使用的分隔符(delimiter: "-" | "_" | null),方便需要还原原始格式的场景。
六、数据来源与性能设计
6.1 三层数据来源
- Locale 解析:基于正则(
LOCALE_REGEX)+ ISO 标准校验,纯本地、零网络开销; - 名称解析:使用 Unicode CLDR 数据,覆盖 200+ 种显示语言的国家/语言/文字名称;
- 校验:以官方 ISO 639-1、ISO 639-2/3(经
iso-639-3包)、ISO 15924、ISO 3166-1、UN M.49 标准为依据。
6.2 性能特征
按 packages/locales/README.md 的说明与源码印证:
- 包体积:核心包约 12KB(ESM)/ 14KB(CJS),本地化名称数据不打包进核心产物,而是按需从远端加载;
- 运行时数据:按需、按语言、按类别(territories/languages/scripts)分别请求,用多少拉多少;
- 缓存:内存级
Map缓存已加载的数据映射表,重复查询零网络请求; - 回退:指定语言数据不可用时优雅回退到英语,保证接口始终可用。
这套设计的直接收益是:解析与校验是同步的、零依赖网络;名称解析是异步的、按需的,两者组合可以在国际化 UI 中做到"首屏只有核心包、展开语言选择器时才拉名称数据"。
七、错误处理:可预测的失败模式
所有函数都有明确的错误处理约定,官方 README 给出了两个典型示例:
try { parseLocale("invalid"); } catch (error) { console.log(error.message); // "Invalid locale format: invalid" } try { await getCountryName("XX"); } catch (error) { console.log(error.message); // "Country code "XX" not found" }结合源码可以总结出完整的失败模式表:
| 场景 | 行为 | 错误/返回值 |
|---|---|---|
parseLocale传入非字符串 | 抛错 | Locale must be a string |
parseLocale传入空字符串 | 抛错 | Locale cannot be empty |
parseLocale格式不合法 | 抛错 | Invalid locale format: <输入> |
parseLocaleWithDetails任意失败 | 返回结果而非抛错 | { isValid: false, error: "..." } |
isValid*系列 | 返回boolean,不抛错 | false |
getCountryName/getLanguageName/getScriptName查询不到 | 抛错 | Country code "XX" not found等 |
| 名称数据加载失败 | 非英语自动回退英语;英语失败则抛错 | 回退或抛错 |
因此推荐的用法是:需要硬校验的流程用isValidLocale(返回布尔值);需要解析强保证的用parseLocale(失败即抛错);需要容错解析的用parseLocaleWithDetails(返回结构化结果);需要展示名称的用三个异步名称函数(记得await并捕获错误)。
八、TypeScript 支持与公开类型
包内置完整类型定义(build/index.d.ts),公开的核心类型定义在 src/types.ts:
interface LocaleComponents { language: string; script?: string; region?: string; } type LocaleDelimiter = "-" | "_"; interface ParseResult { components: LocaleComponents; delimiter: LocaleDelimiter | null; isValid: boolean; error?: string; }同时 src/index.ts 作为统一出口,将所有类型、常量与函数按类别组织导出:类型(LocaleComponents等)、常量(LOCALE_REGEX)、解析函数、校验函数、异步名称函数,便于使用者按需导入并享受完整的编辑器类型提示。
九、综合实战:构建一个"解析 + 校验 + 名称"完整流程
把三组 API 串起来,可以轻松实现国际化项目中常见的"语言配置面板"逻辑:校验用户输入的 locale、拆解出结构化信息、并生成人类可读的展示名。
import { parseLocale, isValidLocale, getCountryName, getLanguageName, } from "@lingo.dev/locales"; async function describeLocale(locale: string, displayLanguage = "en") { if (!isValidLocale(locale)) { return { ok: false as const, reason: `invalid locale: ${locale}` }; } const { language, script, region } = parseLocale(locale); const languageName = await getLanguageName(language, displayLanguage); const countryName = region ? await getCountryName(region, displayLanguage) : null; return { ok: true as const, language, script: script ?? null, region: region ?? null, languageName, countryName, }; } await describeLocale("zh-Hans-CN", "zh"); // { // ok: true, // language: "zh", // script: "Hans", // region: "CN", // languageName: "中文", // countryName: "中国", // }校验失败时流程立即返回ok: false,不会触发任何网络请求;校验通过后才发起名称解析,与包"按需加载"的性能设计完全一致。
十、源码导航
想深入研读实现,可按以下路径在仓库内继续探索:
- 统一导出入口:packages/locales/src/index.ts
- 类型定义:packages/locales/src/types.ts
- 正则常量:packages/locales/src/constants.ts
- 解析实现与单测:packages/locales/src/parser.ts、packages/locales/src/parser.spec.ts
- 校验实现与单测:packages/locales/src/validation.ts、packages/locales/src/validation.spec.ts
- 名称解析与数据加载:packages/locales/src/names/index.ts、packages/locales/src/names/loader.ts、packages/locales/src/names/integration.spec.ts
- 构建与依赖配置:packages/locales/package.json
- 官方文档:packages/locales/README.md
结语
@lingo.dev/locales在 replexica 仓库中是一个小而精的"基础设施型"包:同步的解析与校验保证了正确性,异步按需的名称解析保证了轻量,sideEffects: false+ 完整类型定义保证了工程集成体验。无论你是要在 CLI 工具里校验--lang参数、在前端语言切换器里展示"Español / Français / 中文",还是为翻译流水线规范化 locale 输入,这套 API 都能直接复用——它既是 replexica 本地化工具链的基石,也可以独立成为你自己国际化项目的地基。
【免费下载链接】replexicaOpen-source localization engineering tools. Connects to Lingo.dev localization engineering platform for consistent, quality translations.项目地址: https://gitcode.com/GitHub_Trending/re/replexica
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考