npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hd-translate/core

v0.1.5

Published

Framework-agnostic i18n core engine

Readme

@hd-translate/core

通用国际化核心引擎 — 框架无关、API 驱动、内置 axios fetcher 与三级缓存

@hd-translate/core 是 hd-translate SDK 的核心包,提供翻译获取、缓存、复数、插值等纯逻辑能力,不依赖任何前端框架。各框架适配层(Vue / React / Vanilla / AngularJS)均基于此构建。

特性

  • 框架无关 — 纯逻辑实现,可在浏览器、Node.js、SSR、小程序等任意 JS 环境运行
  • API 驱动 — 翻译从远程接口获取,内置 axios fetcher(超时 + 重试 + 请求去重),支持全量与增量更新
  • 三级缓存 — 内存 → localStorage → API,TTL + 版本号双重失效
  • 请求去重 — 同一 locale + 命名空间的并发请求自动合并为一次
  • 命名空间 — 按模块/路由按需加载翻译,避免首屏拉全量
  • 复数规则 — 内置中/英/俄语复数规则,可通过 pluralRules 扩展其他语言
  • 插值 — 支持 {{name}}{name} 两种风格
  • locale 持久化 — 默认把上次语言写入 localStorage(按 appId 隔离),刷新后自动恢复;Node/SSR 自动降级
  • TypeScript — 完整类型定义

安装

pnpm add @hd-translate/core
# 或 npm / yarn
npm install @hd-translate/core

快速开始

import { createI18n } from '@hd-translate/core'

const i18n = createI18n({
  appId: 'my-product',
  apiUrl: 'https://i18n.example.com/api/translations',
  locale: 'zh-CN',
  fallbackLocale: 'en',
  namespaces: ['common', 'menu'],
  // 内置 axios fetcher 配置(不传走默认:超时 5s、重试 1 次)
  fetcherOptions: {
    timeout: 10000,
    retries: 2,
    headers: { 'X-App-Version': '1.0.0' },
  },
  // 三级缓存:内存 + localStorage,按版本号失效
  cache: {
    ttl: 24 * 60 * 60 * 1000, // 24h
    persistent: true,          // 启用 localStorage 持久化
    version: '1.0',            // 发版改 version 即整体失效
  },
})

// 翻译加载是异步的,监听 loaded 后再取值
i18n.on('loaded', ({ locale, namespaces }) => {
  console.log(i18n.t('common:hello', { name: 'World' }))
})

API

createI18n(options): I18nInstance

创建 i18n 实例。会自动触发首次翻译加载。

I18nOptions

| 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | appId | string | 是 | - | 产品标识,API 按此隔离翻译与 locale 持久化 | | apiUrl | string | 是 | - | 翻译 API 地址(注入自定义 fetcher 时可空) | | locale | string | 是 | - | 当前语言,如 'zh-CN' | | fallbackLocale | string | 否 | 'en' | 回退语言,当前 locale 缺失 key 时使用 | | namespaces | string[] | 否 | ['common'] | 初始加载的命名空间 | | defaultNamespace | string | 否 | 'common' | key 不含 : 时使用的命名空间 | | interpolationStyle | 'double-brace' \| 'brace' | 否 | 'double-brace' | 插值语法,对应 {{name}} / {name} | | cache.ttl | number | 否 | 86400000 | 缓存过期时间(ms) | | cache.persistent | boolean | 否 | true | 是否持久化到 localStorage | | cache.memory | boolean | 否 | true | 是否启用持久化缓存内部的内存层(仅 persistent: true 时生效);设为 false 后读写直接走 storage,不再维护内存 Map,避免大翻译表占用双份内存 | | cache.version | string | 否 | '' | 缓存版本号,变更时整体失效 | | persistLocale | boolean | 否 | true | 是否持久化 locale 到 localStorage(按 appId 隔离),Node/SSR 自动降级 | | fetcher | Fetcher | 否 | 内置 axios fetcher | 自定义翻译获取器;注入后 fetcherOptions 被忽略 | | fetcherOptions.timeout | number | 否 | 5000 | 内置 axios fetcher 的请求超时(ms) | | fetcherOptions.retries | number | 否 | 1 | 内置 axios fetcher 的失败重试次数 | | fetcherOptions.headers | Record<string, string> | 否 | - | 内置 axios fetcher 的自定义请求头 | | onLocaleChange | (locale: string) => void | 否 | - | 语言切换回调 | | onFetched | (payload: FetchedPayload) => void | 否 | - | 接口请求成功回调,拿到 fetcher 返回的完整 payload(含 locale/version/timestamp/data + 本次请求的 namespaces);命中缓存时不触发 | | onFetchError | (error: Error) => void | 否 | - | 请求失败回调 | | onMissingKey | (key: string, locale: string) => string | 否 | - | 缺失 key 回调,返回值作为翻译结果(同步调用,非事件) |

I18nInstance

| 方法 | 说明 | |------|------| | t(key, params?) | 翻译,key 格式 namespace:pathpath | | t(key, locale, params?) | 指定语言翻译(不切换当前 locale) | | setLocale(locale) | 切换语言,自动加载所需命名空间并持久化 | | getLocale() | 获取当前语言 | | loadNamespace(ns) | 按需加载单个命名空间到当前 locale | | loadNamespaces(ns[]) | 一次加载多个命名空间到当前 locale(单次 fetcher 请求,比循环调 loadNamespace 更高效) | | reload() | 清空缓存并重新加载当前 locale 全部命名空间 | | on(event, handler) | 监听 localeChange / loaded / fetched / fetchError,返回取消函数 | | destroy() | 销毁实例,清空消息表、缓存、进行中请求与事件监听 |

用法

命名空间按需加载

初始只加载 common,进入子路由时再加载对应命名空间:

await i18n.loadNamespace('order')
console.log(i18n.t('order:list_title'))

一次加载多个命名空间(单次 fetcher 请求合并拉取,比循环调 loadNamespace 高效):

await i18n.loadNamespaces(['order', 'menu', 'profile'])
console.log(i18n.t('order:list_title'))
console.log(i18n.t('menu:dashboard'))
console.log(i18n.t('profile:name'))

复数与插值

翻译 JSON(按 _zero / _one / _other 后缀区分复数类别):

{
  "items_zero": "没有项目",
  "items_one": "{{count}} 个项目",
  "items_other": "{{count}} 个项目"
}

_count 触发复数规则(按 locale 自动选 zero/one/other),count 是普通插值参数:

i18n.t('common:items', { _count: 0, count: 0 })   // "没有项目"
i18n.t('common:items', { _count: 1, count: 1 })   // "1 个项目"
i18n.t('common:items', { _count: 5, count: 5 })   // "5 个项目"

// 指定语言翻译(不切换当前 locale)
i18n.t('common:items', 'en', { _count: 5, count: 5 }) // "5 items"

内置中/英/俄复数规则,可通过 pluralRules 扩展其他语言:

import { pluralRules } from '@hd-translate/core'

pluralRules['ja'] = (count) => (count === 0 ? 'zero' : 'other')

缓存与强制刷新

后端修改翻译后,前端调用 reload() 清空缓存重新加载;发版时改 cache.version 让旧版本缓存整体失效:

await i18n.reload()

关闭内存层只走 localStorage

默认 persistent: true 时缓存走「内存 + localStorage」双写:内存层提供热访问速度,storage 层跨刷新复用。翻译表较大、不想同时占用 JS 堆与 storage 时,可设 cache.memory: false,让 get/set/remove/clear 直接落 storage:

const i18n = createI18n({
  appId: 'my-product',
  apiUrl: '/api/translations',
  locale: 'zh-CN',
  cache: {
    persistent: true,   // 走 localStorage
    memory: false,      // 关闭内存层,只走 storage
    version: '1.0',
  },
})

语言切换

await i18n.setLocale('en')   // 触发 localeChange,自动加载 en 翻译并持久化
i18n.getLocale()             // 'en'

const off = i18n.on('localeChange', (locale) => {
  console.log('语言已切换:', locale)
})
off() // 取消监听

接口数据透传

业务侧需要在翻译加载完成后拿到接口返回的原始数据(如做埋点、自定义缓存、写日志),通过 onFetched 钩子或 i18n.on('fetched', ...) 订阅。仅 fetcher 真正请求成功时触发,命中缓存不会触发

// 方式一:option 形式
const i18n = createI18n({
  appId: 'my-product',
  apiUrl: '/api/translations',
  locale: 'zh-CN',
  onFetched: (payload) => {
    // payload = { locale, version, timestamp, data, namespaces }
    console.log('收到翻译数据:', payload.locale, payload.version, payload.namespaces)
    // 业务侧自定义处理:上报、二次缓存、热更新等
  },
})

// 方式二:事件订阅形式(适合运行时动态注册)
const off = i18n.on('fetched', (payload) => {
  console.log('data keys:', Object.keys(payload.data), 'ns:', payload.namespaces)
})
off() // 取消监听

Node.js / SSR

Node 环境无 localStorage,关闭持久化,用内存缓存 + 等待加载完成:

import { createI18n } from '@hd-translate/core'

const i18n = createI18n({
  appId: 'my-product',
  apiUrl: 'http://internal-i18n/api/translations',
  locale: 'zh-CN',
  fallbackLocale: 'en',
  namespaces: ['common', 'page-home'],
  cache: {
    ttl: 5 * 60 * 1000,    // 5 分钟内存缓存
    persistent: false,      // Node.js 无 localStorage
  },
})

await new Promise(resolve => i18n.on('loaded', resolve))
console.log(i18n.t('common:hello', { name: 'SSR' }))

缺失 key 处理

通过 onMissingKey 同步返回占位文案:

const i18n = createI18n({
  appId: 'my-product',
  apiUrl: '/api/translations',
  locale: 'zh-CN',
  onMissingKey: (key, locale) => {
    console.warn(`缺失翻译: ${key} (${locale})`)
    return `[${key}]`
  },
})

销毁实例

页面卸载或单测结束时清理,避免内存泄漏与事件累积:

i18n.destroy()

翻译 API 接口约定

SDK 内置 fetcher 请求格式(后端需实现此接口):

GET /api/translations?appId=demo&locale=zh-CN&ns=common,menu

Response:
{
  "locale": "zh-CN",
  "version": "1.0.5",
  "timestamp": 1718000000000,
  "data": {
    "common": {
      "hello": "你好,{{name}}",
      "confirm": "确认"
    },
    "menu": {
      "home": "首页"
    }
  }
}

增量更新通过 since 参数:

GET /api/translations?appId=demo&locale=zh-CN&since=1718000000000

自定义 Fetcher

后端接口格式不同时,注入自定义 fetcher

import { createI18n } from '@hd-translate/core'

const i18n = createI18n({
  appId: 'my-product',
  apiUrl: '',  // 使用自定义 fetcher 时不需要
  locale: 'zh-CN',
  fetcher: {
    async fetch({ appId, locale, namespaces }) {
      const res = await fetch(`/api/v2/i18n/${locale}?ns=${namespaces.join(',')}`)
      const data = await res.json()
      return {
        locale,
        version: data.version,
        timestamp: Date.now(),
        data: data.translations,
      }
    },
  },
})

工具函数与缓存

createI18n 外,core 还导出可独立使用的能力:

import {
  interpolate,                // 字符串插值
  getPluralCategory,          // 取复数类别
  resolvePluralKey,           // 生成复数候选 key 列表
  pluralRules,                // 复数规则表(可扩展)
  resolveKeyPath,             // 按 dotted path 取嵌套翻译
  createEventEmitter,         // 事件发射器
  createCache,                // 默认缓存(按 options 决定 memory/persistent)
  createMemoryCache,           // 内存缓存
  createPersistentCache,       // 持久化缓存(可注入自定义 storage)
} from '@hd-translate/core'

createPersistentCache 支持注入自定义 storage,适配小程序等非浏览器环境:

import { createPersistentCache } from '@hd-translate/core'

const cache = createPersistentCache({
  ttl: 86400000,
  version: '1.0',
  storageKey: 'my-app-i18n',
  storage: wx.storage, // 任何实现 getItem/setItem/removeItem/clear 的对象
})

License

MIT