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

@gulibs/vgrove-i18n

v0.0.3

Published

A Vite plugin for automatic i18n locales generation and management

Downloads

6

Readme

@gulibs/vgrove-i18n

一个智能的 Vite 插件,用于自动生成国际化资源和类型安全的翻译键文件。

🎉 Version 0.0.1 - 全新发布!基于文件系统的智能国际化解决方案,提供完整的 TypeScript 支持和优雅的开发体验!

✨ 核心特性

  • 🔍 自动文件扫描 - 递归扫描翻译文件,支持嵌套目录结构
  • 📦 虚拟模块生成 - 零运行时开销,编译时生成优化的资源对象
  • 🎯 类型安全的 Keys - 自动生成翻译键文件,提供完整的 TypeScript 支持
  • 🔥 智能热更新 - 文件变化时自动重新生成,支持增量更新
  • 🎨 优雅的调试输出 - 带时间戳的彩色日志,便于开发调试
  • 🌍 灵活的目录结构 - 支持多种组织方式:目录分离、文件分离
  • TypeScript 支持 - 原生支持 TypeScript 翻译文件,自动编译和热更新

📦 安装

npm install @gulibs/vgrove-i18n --save-dev
# 或
pnpm add @gulibs/vgrove-i18n -D
# 或
yarn add @gulibs/vgrove-i18n -D

TypeScript 支持(可选)

如果您需要使用 TypeScript 翻译文件,请安装 TypeScript:

npm install typescript --save-dev
# 或
pnpm add typescript -D
# 或
yarn add typescript -D

🚀 快速开始

1. 配置插件

// vite.config.ts
import { defineConfig } from 'vite';
import i18nPlugin from '@gulibs/vgrove-i18n';

export default defineConfig({
  plugins: [
    i18nPlugin({
      basePath: 'src/locales',           // 翻译文件根目录
      keysOutput: 'src/i18n-keys.ts',   // 生成类型安全的键文件
      debug: true                        // 开启调试模式查看详细日志
    })
  ]
});

2. 创建翻译文件

支持 JSONJavaScriptTypeScript 格式:

src/locales/
├── zh/                     # 中文翻译
│   ├── common.json        # 静态翻译文案
│   ├── auth.ts           # TypeScript 动态翻译
│   ├── messages.js       # JavaScript 动态翻译
│   └── modules/          # 模块化组织
│       ├── user.json
│       └── product.ts
└── en/                    # 英文翻译
    ├── common.json
    ├── auth.ts
    └── modules/
        ├── user.json
        └── product.ts

示例翻译文件:

// src/locales/zh/common.json
{
  "welcome": "欢迎",
  "buttons": {
    "save": "保存",
    "cancel": "取消"
  }
}
// src/locales/zh/auth.ts
export default {
  login: {
    title: "用户登录",
    username: "用户名",
    password: "密码",
    // 支持动态计算
    currentYear: `${new Date().getFullYear()} 年`,
    // 支持条件判断
    mode: process.env.NODE_ENV === 'development' ? '开发模式' : '生产模式'
  },
  register: {
    title: "用户注册"
  }
} as const; // 使用 as const 获得更好的类型推断
// src/locales/zh/messages.js
const currentHour = new Date().getHours();

export default {
  greeting: currentHour < 12 ? '早上好' : currentHour < 18 ? '下午好' : '晚上好',
  notifications: {
    count: (num) => `您有 ${num} 条新消息`
  }
};

3. 使用虚拟模块

// 导入自动生成的资源
import { resources, supportedLocales } from '@gulibs/i18n-locales';

// 输出所有翻译资源的结构化数据
console.log('所有语言资源:', resources);
// {
//   zh: {
//     common: { welcome: "欢迎", buttons: { save: "保存", cancel: "取消" } },
//     auth: { login: { title: "用户登录", username: "用户名", password: "密码", currentYear: "2024 年", mode: "生产模式" }, register: { title: "用户注册" } },
//     messages: { greeting: "下午好", notifications: { count: [Function] } },
//     modules: { user: {...}, product: {...} }
//   },
//   en: { ... }
// }

console.log('支持的语言:', supportedLocales);
// ['zh', 'en']

4. 使用类型安全的翻译键

// 导入自动生成的翻译键
import { i18nKeys, type I18nKeys } from './src/i18n-keys';

// 类型安全的翻译函数
function t(key: I18nKeys, locale = 'zh'): string {
  const keys = key.split('.');
  let value = resources[locale];

  for (const k of keys) {
    value = value?.[k];
  }

  return value || key;
}

// ✅ 编译时类型检查通过
const welcomeText = t('common.welcome');
const saveButton = t('common.buttons.save');
const loginTitle = t('auth.login.title');
const greeting = t('messages.greeting');

// ❌ TypeScript 编译错误:不存在的键
// const invalid = t('nonexistent.key');

🛠️ TypeScript 翻译文件支持详解

编译机制

插件自动处理 TypeScript 翻译文件的编译过程:

  1. 自动检测:识别 .ts 扩展名的翻译文件
  2. 智能编译
    • 优先使用项目的 TypeScript 编译器
    • 如果 TypeScript 不可用,使用内置的简化编译器
  3. 临时编译:编译后的代码写入临时 .mjs 文件
  4. 动态导入:导入编译后的模块获取翻译内容
  5. 自动清理:导入完成后自动删除临时文件

支持的 TypeScript 功能

// src/locales/zh/advanced.ts

// ✅ 支持类型注解
interface User {
  name: string;
  age: number;
}

// ✅ 支持计算属性
const currentYear = new Date().getFullYear();

// ✅ 支持条件逻辑
const isDevelopment = process.env.NODE_ENV === 'development';

// ✅ 支持函数
function getTimeGreeting(): string {
  const hour = new Date().getHours();
  if (hour < 12) return '早上好';
  if (hour < 18) return '下午好';
  return '晚上好';
}

// ✅ 支持 as const 断言
export default {
  user: {
    welcome: (user: User) => `欢迎,${user.name}!`,
    profile: {
      age: `${currentYear} 年出生`,
      status: isDevelopment ? '开发用户' : '正式用户'
    }
  },
  greetings: {
    time: getTimeGreeting(),
    formal: '您好'
  }
} as const;

类型安全最佳实践

// src/locales/zh/typed-messages.ts

// 定义消息类型
export interface MessageTemplates {
  user: {
    welcome: (name: string) => string;
    count: (count: number) => string;
  };
  system: {
    loading: string;
    error: (code: number) => string;
  };
}

// 实现类型安全的消息
const messages: MessageTemplates = {
  user: {
    welcome: (name: string) => `欢迎回来,${name}!`,
    count: (count: number) => `共 ${count} 项`
  },
  system: {
    loading: '加载中...',
    error: (code: number) => `错误代码:${code}`
  }
};

export default messages;

热更新支持

TypeScript 翻译文件享受完整的热更新支持:

  • 文件修改:保存 .ts 文件后自动重新编译和加载
  • 类型检查:实时的 TypeScript 类型错误检测
  • 增量更新:只重新编译修改的文件
  • 缓存管理:智能的编译缓存,避免重复编译

性能优化

// vite.config.ts - 针对 TypeScript 翻译文件的性能优化

export default defineConfig({
  plugins: [
    i18nPlugin({
      basePath: 'src/locales',
      // 如果只使用 TypeScript 文件,可以限制扩展名
      extensions: ['.ts'],
      // 排除大型文件提升扫描速度
      exclude: ['**/node_modules/**', '**/dist/**', '**/*.d.ts'],
      debug: true
    })
  ]
});

🛠️ 实现原理详解

核心架构

插件的核心由三个主要组件构成:

1. LocaleParser (src/parser.ts)

作用:负责扫描和解析翻译文件 实现位置src/parser.ts:394-567

class LocaleParser {
  // 递归扫描翻译文件
  async scanLocaleFiles(localesPath: string): Promise<string[]>

  // 解析单个翻译文件(支持 JSON/TS/JS)
  async parseLocaleFile(filePath: string, basePath: string): Promise<any>

  // 提取语言代码和命名空间
  createFileInfo(filePath: string, basePath: string): FileInfo
}

核心功能

  • 使用 fast-glob 高效扫描文件
  • 支持动态导入 TypeScript/JavaScript 文件
  • 智能提取语言代码(目录名或文件名)
  • 自动生成命名空间(基于目录结构)

2. I18nContext (src/context.ts)

作用:管理插件状态、缓存和代码生成 实现位置src/context.ts:15-367

class I18nContext {
  // 生成虚拟模块代码
  async generateLocales(): Promise<string>

  // 生成独立的翻译键文件
  private async generateKeysFile(resources: LocaleResourceMap): Promise<void>

  // 智能缓存管理
  private invalidateCache(): void

  // 优雅的调试日志
  private logDebug(message: string): void
  private logSuccess(message: string): void
  private logError(message: string): void
}

核心功能

  • 智能缓存系统:避免重复解析,提升性能
  • 虚拟模块生成:将翻译资源编译为优化的 JavaScript 代码
  • 类型安全的 Keys 生成:自动分析所有翻译键,生成联合类型
  • 热更新支持:监听文件变化,增量更新

3. Plugin Factory (src/plugin.ts)

作用:Vite 插件接口实现 实现位置src/context.ts:875-904

function createI18nPlugin(options = {}) {
  const context = new I18nContext(options);

  return [{
    name: '@gulibs/vgrove-i18n',
    configureServer(server) {
      // 设置开发服务器和文件监听
      context.setupServer(server);
    },
    resolveId(id) {
      // 解析虚拟模块 ID
      if (MODULE_IDS.includes(id)) {
        return `${VIRTUAL_MODULE_ID}?id=${id}`;
      }
    },
    async load(id) {
      // 加载虚拟模块内容
      const { moduleId, pageId } = context.parseRequest(id);
      if (moduleId === VIRTUAL_MODULE_ID && pageId && MODULE_IDS.includes(pageId)) {
        return await context.generateLocales();
      }
    }
  }];
}

🎯 翻译键文件生成机制

智能键收集算法

实现位置src/context.ts:244-280

// 递归收集所有翻译键
private collectAllResourceKeys(resources: LocaleResourceMap): string[] {
  const allKeys = new Set<string>();

  Object.values(resources).forEach(localeResources => {
    Object.entries(localeResources).forEach(([namespace, content]) => {
      if (namespace === 'default') {
        // 默认命名空间:common.welcome
        this.collectKeysFromObject(content, '', allKeys);
      } else {
        // 命名空间前缀:auth.login.title
        this.collectKeysFromObject(content, namespace, allKeys);
      }
    });
  });

  return Array.from(allKeys).sort();
}

类型生成策略

实现位置src/context.ts:283-309

根据文件扩展名智能选择输出格式:

  • .ts 文件:生成 TypeScript 代码,包含 as const 断言和联合类型
  • .js 文件:生成纯 JavaScript 代码,无类型信息
  • undefined:在虚拟模块中包含键数组
// TypeScript 输出示例
export const i18nKeys = ["auth.login", "common.welcome"] as const;
export type I18nKeys = 'auth.login' | 'common.welcome';

// JavaScript 输出示例
export const i18nKeys = ["auth.login", "common.welcome"];

🔥 热更新机制

文件监听器

实现位置src/context.ts:75-95

private setupWatcher(watcher: any) {
  const localesPath = path.resolve(process.cwd(), this.options.basePath);

  // 监听文件添加
  watcher.on('add', (filePath: string) => {
    if (filePath.startsWith(localesPath)) {
      this.logDebug(`Added locale file: ${filePath}`);
      this.invalidateCache();
    }
  });

  // 监听文件修改
  watcher.on('change', (filePath: string) => {
    if (filePath.startsWith(localesPath)) {
      this.logDebug(`Changed locale file: ${filePath}`);
      this.invalidateCache();
    }
  });

  // 监听文件删除
  watcher.on('unlink', (filePath: string) => {
    if (filePath.startsWith(localesPath)) {
      this.logDebug(`Removed locale file: ${filePath}`);
      this.invalidateCache();
    }
  });
}

缓存失效策略

实现位置src/context.ts:97-113

private invalidateCache() {
  // 清除内存缓存
  this.cache.clear();

  if (this.server) {
    // 通知 Vite 重新加载虚拟模块
    const module = this.server.moduleGraph.getModuleById(VIRTUAL_MODULE_ID);
    if (module) {
      this.server.reloadModule(module);
      this.logDebug('Hot reloaded locale resources');
    }

    // 重新生成独立的 keys 文件
    if (this.options.keysOutput) {
      this.regenerateKeysFile();
    }
  }
}

🎨 调试日志系统

优雅的日志输出

实现位置src/context.ts:46-64

参考了 @gulibs/vgrove-autoroutes 的设计,提供统一的日志格式:

private logDebug(message: string) {
  if (this.options.debug) {
    console.log(
      chalk.gray(this.getTimestamp()),
      chalk.blueBright.bold(`▸ ${this.getPrefix()}`),
      chalk.gray(message)
    );
  }
}

private logSuccess(message: string) {
  if (this.options.debug) {
    console.log(
      chalk.gray(this.getTimestamp()),
      chalk.blueBright.bold(`▸ ${this.getPrefix()}`),
      chalk.green(`✓ ${message}`)
    );
  }
}

private logError(message: string) {
  console.error(
    chalk.gray(this.getTimestamp()),
    chalk.blueBright.bold(`▸ ${this.getPrefix()}`),
    chalk.red(`✗ ${message}`)
  );
}

输出示例

14:25:30 ▸ [vgrove-i18n] 🔍 Scanning locale files...
14:25:30 ▸ [vgrove-i18n] Added locale file: /path/to/zh/common.json
14:25:30 ▸ [vgrove-i18n] ✓ Generated 2 locales with 8 files
14:25:30 ▸ [vgrove-i18n] ✓ Generated keys file: src/i18n-keys.ts (25 keys)

⚙️ 配置选项

| 选项 | 类型 | 默认值 | 描述 | |------|------|--------|------| | basePath | string | 'src/locales' | 翻译文件根目录路径 | | extensions | string[] | ['.json', '.ts', '.js'] | 支持的文件扩展名 | | localePattern | 'directory' \| 'filename' | 'directory' | 语言代码提取模式 | | keysOutput | string | undefined | 翻译键文件输出路径(.ts/.js) | | hmr | boolean | true | 是否启用热模块替换 | | deep | boolean | true | 是否启用深度目录扫描 | | exclude | string[] | [] | 排除的文件 / 目录模式 | | include | string[] | [] | 包含的文件 / 目录模式 | | defaultLocale | string | 'en' | 默认语言代码 | | debug | boolean | false | 是否启用调试模式 |

🎯 高级用法

1. 命名空间组织

src/locales/
├── zh/
│   ├── default.json        # 默认命名空间(键名:welcome)
│   ├── auth.json          # auth 命名空间(键名:auth.login)
│   └── modules/
│       ├── user.json      # modules.user 命名空间
│       └── admin/
│           └── panel.json # modules.admin.panel 命名空间

2. 动态翻译文件

// src/locales/zh/dynamic.ts
const currentYear = new Date().getFullYear();

export default {
  copyright: `© ${currentYear} 公司版权所有`,
  dynamicMessage: process.env.NODE_ENV === 'development' ? '开发模式' : '生产模式'
};

3. 类型安全的翻译 Hook

import { type I18nKeys } from './src/i18n-keys';
import { resources } from '@gulibs/i18n-locales';

export function useTranslation(locale: string = 'zh') {
  const t = useCallback((key: I18nKeys, params?: Record<string, any>): string => {
    const keys = key.split('.');
    let value = resources[locale];

    for (const k of keys) {
      value = value?.[k];
    }

    if (typeof value !== 'string') {
      console.warn(`Translation key "${key}" not found or not a string`);
      return key;
    }

    // 简单的参数替换
    if (params) {
      return value.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
        return params[paramKey] || match;
      });
    }

    return value;
  }, [locale]);

  return { t };
}

// 使用示例
function MyComponent() {
  const { t } = useTranslation('zh');

  return (
    <div>
      <h1>{t('common.welcome')}</h1>
      <button>{t('common.buttons.save')}</button>
      <p>{t('auth.login.title')}</p>
    </div>
  );
}

4. 开发时的最佳实践

自动检查翻译完整性

// scripts/check-translations.ts
import { i18nKeys } from '../src/i18n-keys';
import { resources, supportedLocales } from '@gulibs/i18n-locales';

function checkTranslationCompleteness() {
  const results: Record<string, string[]> = {};

  supportedLocales.forEach(locale => {
    const missingKeys: string[] = [];

    i18nKeys.forEach(key => {
      const keys = key.split('.');
      let value = resources[locale];

      for (const k of keys) {
        value = value?.[k];
      }

      if (value === undefined) {
        missingKeys.push(key);
      }
    });

    if (missingKeys.length > 0) {
      results[locale] = missingKeys;
    }
  });

  if (Object.keys(results).length > 0) {
    console.error('❌ 发现缺失的翻译:', results);
    process.exit(1);
  } else {
    console.log('✅ 所有翻译完整');
  }
}

checkTranslationCompleteness();

ESLint 规则强制使用类型安全键

// .eslintrc.json
{
  "rules": {
    "no-restricted-syntax": [
      "error",
      {
        "selector": "CallExpression[callee.name='t'] Literal[raw=/^['\"][^'\"]*['\"]$/]",
        "message": "请使用 I18nKeys 类型的常量,而不是字符串字面量"
      }
    ]
  }
}

🔧 故障排除

常见问题及解决方案

1. 虚拟模块导入失败

错误信息

Failed to resolve import "@gulibs/i18n-locales"

解决方案

  • 检查 vite.config.ts 中插件是否正确配置
  • 确认虚拟模块 ID 正确:@gulibs/i18n-locales
  • 重启开发服务器

2. 翻译键文件未生成

检查步骤

# 1. 启用调试模式查看详细日志
# vite.config.ts: debug: true

# 2. 检查输出目录权限
ls -la src/

# 3. 手动触发构建
npm run build

常见原因

  • keysOutput 路径配置错误
  • 输出目录不存在或无写入权限
  • 翻译文件格式错误导致解析失败

3. TypeScript 类型错误

错误信息

Type '"invalid.key"' is not assignable to parameter of type 'TranslationKey'

解决方案

  • 检查翻译键是否存在于生成的文件中
  • 重新生成翻译键文件:删除后重启开发服务器
  • 确认文件编码为 UTF-8

4. 热更新不工作

检查步骤

  • 确认 hmr: true(默认开启)
  • 检查文件监听权限(macOS 可能需要授权)
  • 查看开发服务器控制台是否有错误信息

5. 性能问题

如果翻译文件过多导致构建缓慢:

// 优化配置
i18nPlugin({
  exclude: ['**/node_modules/**', '**/dist/**'],  // 排除无关目录
  extensions: ['.json'],                           // 仅扫描 JSON 文件
  deep: false                                     // 禁用深度扫描
})

📝 更新日志

v0.0.1 (2024-03-XX)

🚀 新特性

  • 🎉 首次发布:智能的基于文件系统的国际化解决方案
  • 🎉 新功能:支持 JSON、JavaScript 和 TypeScript 翻译文件
  • 🎉 新功能:自动生成类型安全的翻译键文件
  • 🎉 新功能:虚拟模块系统,零运行时开销
  • 🎉 新功能:智能热更新和增量编译
  • 🎉 新功能:优雅的调试日志系统
  • 🎉 新功能:灵活的命名空间组织
  • 🎉 新功能:完整的 TypeScript 支持和类型推断

✨ 核心功能

  • 智能文件扫描:使用 fast-glob 高效扫描翻译文件
  • 类型安全:自动生成 I18nKeys 联合类型,提供编译时检查
  • 热更新支持:文件变化时自动重新生成资源和类型
  • 性能优化:智能缓存机制,避免重复解析
  • 开发体验:带时间戳的彩色调试日志

📄 许可证

MIT License - 详见 LICENSE 文件

相关项目


💡 提示:如果遇到问题,请启用 debug: true 查看详细的执行日志,这有助于快速定位问题所在。