@shelchin/svelte-i18n
Version:
The last Svelte i18n library you'll ever need. Type-safe, AI-powered, zero-config.
852 lines (630 loc) • 19.7 kB
Markdown
# @shelchin/svelte-i18n
> 您需要的最后一个 Svelte 国际化库。类型安全、零配置、无缝 SSR/CSR 支持。
[](https://www.npmjs.com/package/@shelchin/svelte-i18n)
[](https://opensource.org/licenses/MIT)
[](https://svelte.dev)
[](https://www.typescriptlang.org/)
[English](./README.md) • [在线演示](https://atshelchin.github.io/svelte-i18n/) • [示例代码](./src/routes)
> ⚠️ **警告**: 该库目前处于积极开发阶段,还不建议用于生产环境。API 可能会在未来版本中更改。文档可能不完善或包含错误。
## ✨ 特性
### 🎯 核心特性
- **🔒 完全类型安全** - 自动生成所有翻译键的 TypeScript 类型
- **🚀 零配置** - 开箱即用,拥有合理的默认设置
- **📦 优化的包体积** - 压缩后约 35KB,支持 tree-shaking
- **🌐 SSR/CSR 支持** - 无缝支持服务端和客户端渲染
- **🔄 热模块替换** - 开发时即时更新翻译
- **🎨 丰富的格式化** - 通过原生 Intl API 内置数字、日期、货币和列表格式化
- **📱 智能语言检测** - 从 URL 路径、浏览器、Cookie 或 localStorage 检测
### 🛠️ 开发体验
- **🤖 强大的 CLI** - 提取键、验证翻译、生成类型
- **🔍 运行时验证** - 在开发时捕获翻译错误
- **📚 命名空间支持** - 为包和库隔离翻译
- **🎯 智能回退** - 使用回退语言优雅降级
- **💾 持久化** - 跨会话记住用户的语言偏好
- **🌍 150+ 语言** - 内置所有主要语言的元数据
### 🏗️ 架构
- **🧩 原生 Svelte 5** - 从零开始使用 runes 构建
- **🔌 统一 API** - 应用程序和 npm 包使用相同的 API
- **📊 懒加载** - 按需加载翻译以获得更好的性能
- **🎛️ 配置继承** - 库自动继承应用程序配置
## 📦 安装
```bash
# 安装依赖
pnpm add @shelchin/svelte-i18n
# 或
npm install @shelchin/svelte-i18n
# 或
yarn add @shelchin/svelte-i18n
```
## 🚀 快速开始
### 1. 运行初始化命令
运行初始化命令自动生成配置:
```bash
# 运行初始化命令(自动检测项目类型并生成配置)
pnpm exec svelte-i18n init
# 或
npx svelte-i18n init
```
这将:
- 创建 `src/translations/` 目录结构
- 生成示例翻译文件(`locales/en.json`、`locales/zh.json`)
- 创建带类型安全设置的 `i18n.ts` 配置文件
- 生成 TypeScript 类型定义
生成的 `i18n.ts` 如下:
```typescript
// src/translations/i18n.ts (自动生成)
import { createI18n } from '@shelchin/svelte-i18n';
import type { I18nPath } from './types/i18n-generated.js';
// 自动扫描并导入 locales 目录中的翻译
const translationModules = import.meta.glob('./locales/*.json', {
eager: true,
import: 'default'
});
const translations: Record<string, unknown> = {};
// 从文件路径提取语言代码并构建翻译对象
for (const [path, module] of Object.entries(translationModules)) {
const match = path.match(/\/([^/]+)\.json$/);
if (match && match[1]) {
const langCode = match[1];
translations[langCode] = module;
}
}
// 创建带类型安全的 i18n 实例
export const i18n = createI18n<I18nPath>({
namespace: 'app',
isMain: true,
translations,
defaultLocale: 'en',
fallbackLocale: 'en'
});
export default i18n;
```
### 2. 配置 SvelteKit
#### 配置 `+layout.server.ts` 用于 SSR:
```typescript
// src/routes/+layout.server.ts
import { loadI18nSSR } from '@shelchin/svelte-i18n';
import { i18n } from '$src/translations/i18n.js';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ request }) => {
const locale = await loadI18nSSR(i18n, request);
return {
locale
};
};
```
#### 配置 `+layout.ts` 用于通用加载:
```typescript
// src/routes/+layout.ts
import { loadI18nUniversal } from '@shelchin/svelte-i18n';
import { i18n } from '$src/translations/i18n.js';
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async ({ data }) => {
await loadI18nUniversal(i18n, data?.locale);
return {
locale: data?.locale
};
};
```
#### 配置 `+layout.svelte` 用于客户端:
```svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { setupI18nClient } from '@shelchin/svelte-i18n';
import { i18n } from '$src/translations/i18n.js';
onMount(async () => {
await setupI18nClient(i18n);
});
</script>
<slot />
```
### 3. 在组件中使用
```svelte
<script lang="ts">
import { i18n } from '$src/translations/i18n.js';
import { LanguageSwitcher } from '@shelchin/svelte-i18n';
let name = $state('世界');
// 带自动补全的类型安全翻译
const welcome = i18n.t('welcome');
const hello = i18n.t('hello', { name });
</script>
<h1>{welcome}</h1>
<p>{hello}</p>
<!-- 直接使用 -->
<nav>
<a href="/">{i18n.t('navigation.home')}</a>
<a href="/about">{i18n.t('navigation.about')}</a>
<a href="/contact">{i18n.t('navigation.contact')}</a>
</nav>
<!-- 语言切换器组件 -->
<LanguageSwitcher {i18n} />
```
### 4. 在库/包中使用
对于库包,使用命名空间以避免冲突:
```typescript
// 在库中:src/lib/translations/i18n.ts
import { createI18n } from '@shelchin/svelte-i18n';
import type { LibI18nPath } from './types/i18n-generated.js';
// 自动导入翻译
const translationModules = import.meta.glob('./locales/*.json', {
eager: true,
import: 'default'
});
const translations: Record<string, unknown> = {};
for (const [path, module] of Object.entries(translationModules)) {
const match = path.match(/\/([^/]+)\.json$/);
if (match && match[1]) {
translations[match[1]] = module;
}
}
export const libI18n = createI18n<LibI18nPath>({
namespace: 'my-ui-lib', // 使用您的包名
translations
});
// 在库组件中使用
libI18n.t('button.save');
```
## 🛠️ CLI 命令
### 生成 TypeScript 类型
```bash
# 从翻译文件生成类型
pnpm exec svelte-i18n generate-types
# 或使用自定义路径
pnpm exec svelte-i18n generate-types --dir ./src/translations/locales --out ./src/lib/types/i18n-generated.ts
```
### 验证翻译
```bash
# 检查缺失的翻译
pnpm exec svelte-i18n validate src/translations/locales
```
### 提取翻译键
```bash
# 从源代码提取键
pnpm exec svelte-i18n extract ./src ./template.json
```
## 🎯 类型安全
`init` 命令会自动生成 TypeScript 类型。要在更改后重新生成:
```bash
pnpm exec svelte-i18n generate-types
```
这会创建类型定义,为所有翻译键提供自动完成:
```typescript
// 在 src/translations/types/i18n-generated.d.ts 中自动生成的类型
export type I18nPath =
| 'welcome'
| 'hello'
| 'navigation.home'
| 'navigation.about'
| 'navigation.contact';
// 已在您的 i18n.ts 中配置了类型安全
import type { I18nPath } from './types/i18n-generated.js';
export const i18n = createI18n<I18nPath>({
// ... 配置
});
// 现在 TypeScript 确保只使用有效的键
i18n.t('welcome'); // ✅ 有效
i18n.t('hello', { name: 'John' }); // ✅ 带参数有效
i18n.t('invalid.key'); // ❌ TypeScript 错误
```
## 🌍 格式化
使用原生 Intl API 的内置格式化器(零依赖):
```typescript
const i18n = getI18n();
// 数字
i18n.formatNumber(1234567.89); // "1,234,567.89" (en) / "1.234.567,89" (de)
i18n.formatNumber(0.15, 'percent'); // "15%"
i18n.formatNumber(123456789, 'compact'); // "1.2亿" (zh) / "123M" (en)
// 货币(基于语言自动检测)
i18n.formatCurrency(99.99); // "$99.99" (en-US) / "¥99.99" (zh-CN)
i18n.formatCurrency(99.99, 'EUR'); // "€99.99"
// 日期
i18n.formatDate(new Date()); // "2024/1/15" (zh) / "1/15/2024" (en-US)
i18n.formatDate(new Date(), 'full'); // "2024年1月15日星期一"
// 时间
i18n.formatTime(new Date()); // "下午3:30" / "3:30 PM"
// 相对时间
i18n.formatRelativeTime(-2, 'day'); // "2天前" / "2 days ago"
i18n.formatRelativeTime(3, 'hour'); // "3小时后" / "in 3 hours"
// 列表
i18n.formatList(['苹果', '香蕉', '橙子']); // "苹果、香蕉和橙子"
```
## 🎨 组件
### 语言切换器
预构建的、可访问的语言切换器组件:
```svelte
<script>
import { LanguageSwitcher } from '@shelchin/svelte-i18n';
import { i18n } from '../app/i18n';
</script>
<!-- 默认切换器 -->
<LanguageSwitcher {i18n} />
<!-- 自定义样式和位置 -->
<LanguageSwitcher
{i18n}
class="my-custom-class"
position="top-left"
showFlags={true}
showLabels={true}
/>
```
### 验证弹窗(仅开发环境)
在开发时显示翻译错误:
```svelte
<script>
import { ValidationPopup } from '@shelchin/svelte-i18n';
import { i18n } from '../app/i18n';
</script>
{#if import.meta.env.DEV}
<ValidationPopup {i18n} />
{/if}
```
## 📚 高级功能
### 基于 URL 的语言检测
从 URL 路径自动检测语言:
```typescript
// 支持以下模式:
// /zh/about -> 中文
// /en-US/products -> 美式英语
// /de-DE/contact -> 德语
export const load: LayoutLoad = async ({ data, url }) => {
// url 参数启用路径语言检测
return await loadI18nUniversal(i18n, data, url);
};
```
### 动态翻译加载
动态加载翻译以进行代码分割:
```typescript
// 选项 1:动态导入
async function loadTranslations(locale: string) {
const translations = await import(`../translations/${locale}.json`);
await i18n.loadLanguage(locale, translations.default);
}
// 选项 2:从 API 获取
async function fetchTranslations(locale: string) {
const response = await fetch(`/api/translations/${locale}`);
const translations = await response.json();
await i18n.loadLanguage(locale, translations);
}
```
### 库的命名空间支持
库可以拥有不与应用冲突的隔离翻译:
```typescript
// 在您的库中 (my-ui-lib)
export const libI18n = createI18n({
namespace: 'my-ui-lib',
translations: {
en: { button: { save: 'Save', cancel: 'Cancel' } },
zh: { button: { save: '保存', cancel: '取消' } }
}
});
// 库翻译自动添加命名空间
libI18n.t('button.save'); // 内部使用 "my-ui-lib.button.save"
// 库自动继承应用的语言设置
// 当应用切换到 'zh' 时,库也切换到 'zh'
```
### 带 Cookie 持久化的 SSR
带语言持久化的服务端渲染:
```typescript
// +layout.server.ts
import type { LayoutServerLoad } from './$types';
import { loadI18nSSR } from '@shelchin/svelte-i18n';
export const load: LayoutServerLoad = async ({ cookies }) => {
const locale = cookies.get('i18n-locale') || 'en';
return loadI18nSSR(locale, ['en', 'zh', 'ja']);
};
```
### 复数形式
正确处理所有语言的复数形式:
```typescript
// 英语:0 = 复数,1 = 单数,2+ = 复数
"items.count": "No items | One item | {count} items"
// 中文:简单规则
"items.count": "没有项目 | 一个项目 | {count} 个项目"
// 使用
i18n.t('items.count', { count: 0 }); // "没有项目"
i18n.t('items.count', { count: 1 }); // "一个项目"
i18n.t('items.count', { count: 5 }); // "5 个项目"
```
### 插值
翻译中的动态值:
```typescript
// 基本插值
"welcome": "欢迎 {name}!"
i18n.t('welcome', { name: '张三' }); // "欢迎张三!"
// 嵌套值
"user.greeting": "你好 {user.firstName} {user.lastName}"
i18n.t('user.greeting', {
user: { firstName: '张', lastName: '三' }
}); // "你好张三"
// 自定义插值标记
const i18n = createI18n({
interpolation: {
prefix: '{{',
suffix: '}}'
}
});
// 现在使用:"welcome": "欢迎 {{name}}!"
```
### 运行时验证
在开发时捕获翻译问题:
```typescript
const i18n = createI18n({
translations,
validateInDev: true, // 启用验证
validateOptions: {
checkInterpolation: true, // 验证 {变量} 匹配
checkPluralization: true, // 验证复数形式
checkHTML: false, // 允许翻译中的 HTML
checkMissing: true, // 报告缺失的键
checkExtra: true // 报告多余的键
}
});
// 在开发时显示带错误的验证弹窗
```
## 🛠️ CLI 工具
### 初始化项目
交互式地在项目中设置 i18n:
```bash
npx svelte-i18n init
```
这将:
- 创建翻译目录
- 生成初始配置文件
- 设置类型定义
- 创建示例翻译
### 提取翻译键
扫描代码并提取所有翻译键:
```bash
# 从源代码提取
npx svelte-i18n extract ./src ./translations/template.json
# 指定文件扩展名
npx svelte-i18n extract ./src ./translations/template.json js ts svelte
```
### 验证翻译
检查所有语言中缺失或多余的键:
```bash
# 基本验证
npx svelte-i18n validate ./translations
# 严格验证(以错误代码退出)
npx svelte-i18n validate ./translations --strict
# 使用特定的基础语言
npx svelte-i18n validate ./translations --base zh
```
### 生成 TypeScript 类型
为翻译键生成类型定义:
```bash
# 为应用翻译生成(默认)
npx svelte-i18n generate-types
# 自定义路径
npx svelte-i18n generate-types \
--dir ./translations \
--out ./src/types/i18n.ts \
--locale en
# 跳过其他语言的验证
npx svelte-i18n generate-types --no-validate
```
## 📖 API 参考
### 核心函数
#### `createI18n<TPath>(config)`
创建类型化的 i18n 实例。
```typescript
const i18n = createI18n<TranslationPaths>({
translations, // 翻译数据
defaultLocale: 'en', // 默认语言
fallbackLocale: 'en', // 缺失翻译的回退语言
namespace: 'app', // 命名空间(用于库)
isMain: true, // 是否为主应用实例?
validateInDev: true, // 启用开发验证
interpolation: {
// 插值选项
prefix: '{',
suffix: '}'
}
});
```
#### `i18n.t(key, params?)`
获取带可选插值的翻译文本。
```typescript
i18n.t('welcome', { name: '张三' }); // "欢迎张三!"
i18n.t('items.count', { count: 5 }); // "5 个项目"
```
#### `i18n.setLocale(locale)`
更改当前语言(异步)。
```typescript
await i18n.setLocale('zh'); // 切换到中文
```
#### `i18n.setLocaleSync(locale)`
同步更改语言(用于 SSR)。
```typescript
i18n.setLocaleSync('zh'); // 立即切换
```
#### `i18n.loadLanguage(locale, translations)`
动态加载翻译。
```typescript
await i18n.loadLanguage('ja', japaneseTranslations);
```
### 属性
```typescript
i18n.locale; // 当前语言 ('zh')
i18n.locales; // 可用语言 (['en', 'zh', 'ja'])
i18n.isLoading; // 加载状态 (true/false)
i18n.errors; // 验证错误(仅开发环境)
i18n.meta; // 语言元数据(方向、原生名称等)
```
### SvelteKit 集成
#### `loadI18nUniversal(i18n, data, url?, options?)`
用于 +layout.ts 的通用加载函数。
```typescript
await loadI18nUniversal(i18n, data, url, {
storageKey: 'i18n-locale', // localStorage 键
cookieName: 'i18n-locale', // Cookie 名称
defaultLocale: 'zh', // 默认语言
detectFromPath: true // 从 URL 路径检测
});
```
#### `loadI18nSSR(locale, locales, options?)`
用于 +layout.server.ts 的服务端加载函数。
```typescript
loadI18nSSR('zh', ['en', 'zh'], {
cookieName: 'i18n-locale'
});
```
#### `setupI18nClient(i18n, data, options?)`
用于 +layout.svelte 的同步客户端设置。
```typescript
const result = setupI18nClient(i18n, data, {
defaultLocale: 'zh',
restoreFromStorage: true
});
```
#### `initI18nOnMount(i18n, data, options?)`
在 onMount 中的异步初始化。
```typescript
await initI18nOnMount(i18n, data, {
initFunction: async (i18n) => {
// 自定义初始化
}
});
```
### 格式化函数
所有格式化器都支持语言感知和响应式:
```typescript
formatNumber(value, style?, options?)
formatCurrency(value, currency?, options?)
formatDate(date, style?, options?)
formatTime(date, style?, options?)
formatRelativeTime(value, unit, options?)
formatList(items, style?, options?)
```
### 实用函数
```typescript
// 检测浏览器语言
detectBrowserLanguage(); // 'zh-CN'
// 验证翻译模式
validateSchema(translations, options);
// 合并翻译对象
mergeTranslations(target, source);
// 从注册表获取可用语言
getAvailableLocales(registry);
// 检查语言是否可用
isLocaleAvailable(registry, 'zh');
```
## 🔧 配置
### 完整配置选项
```typescript
interface I18nConfig {
// 基础
defaultLocale?: string; // 默认:'en'
fallbackLocale?: string; // 默认:与 defaultLocale 相同
supportedLocales?: string[]; // 如果未设置则自动检测
// 功能
validateInDev?: boolean; // 默认:true
loadingDelay?: number; // 默认:200ms
namespace?: string; // 默认:'app'
isMain?: boolean; // 对于 'app' 默认为 true
// 格式化
interpolation?: {
prefix?: string; // 默认:'{'
suffix?: string; // 默认:'}'
escapeValue?: boolean; // 默认:false
};
pluralization?: {
separator?: string; // 默认:'|'
};
// 验证
validateOptions?: {
checkInterpolation?: boolean;
checkPluralization?: boolean;
checkHTML?: boolean;
checkMissing?: boolean;
checkExtra?: boolean;
};
}
```
### 环境变量
```bash
# .env
VITE_I18N_DEFAULT_LOCALE=zh
VITE_I18N_FALLBACK_LOCALE=zh
VITE_I18N_SUPPORTED_LOCALES=en,zh,ja,de,fr
VITE_I18N_DEBUG=true
```
## 🎯 最佳实践
### 1. 组织您的翻译
```
src/
translations/
en.json # 英语(基础)
zh.json # 中文
ja.json # 日语
locales/ # 替代结构
zh/
common.json
errors.json
forms.json
```
### 2. 使用类型安全
始终生成并使用类型:
```typescript
// 翻译更改后生成类型
npm run i18n:types
// 导入并使用
import type { I18nPath } from '$lib/types/i18n-generated';
export const i18n = createI18n<I18nPath>({ ... });
```
### 3. 处理加载状态
```svelte
{#if i18n.isLoading}
<LoadingSpinner />
{:else}
<Content />
{/if}
```
### 4. 优化包大小
```typescript
// ❌ 不要静态导入所有翻译
import * as allTranslations from './translations';
// ✅ 只导入需要的或使用动态导入
import zh from './translations/zh.json';
const en = await import('./translations/en.json');
```
### 5. 测试您的翻译
```typescript
// 在 CI/CD 中运行验证
npm run i18n:validate
// 使用不同语言测试
npm run dev -- --locale=zh
```
## 🤝 贡献
我们欢迎贡献!请查看我们的[贡献指南](CONTRIBUTING.md)了解详情。
### 开发设置
```bash
# 克隆仓库
git clone https://github.com/atshelchin/svelte-i18n.git
# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
# 运行测试
pnpm test
# 构建库
pnpm build
```
## 📄 许可证
MIT © [Shelchin](https://github.com/atshelchin)
## 🙏 致谢
使用以下技术构建 ❤️:
- [Svelte 5](https://svelte.dev) - 神奇的消失框架
- [SvelteKit](https://kit.svelte.dev) - 构建 Svelte 应用的最快方式
- [TypeScript](https://www.typescriptlang.org) - 带类型语法的 JavaScript
- [Vite](https://vitejs.dev) - 下一代前端工具
特别感谢所有[贡献者](https://github.com/atshelchin/svelte-i18n/graphs/contributors)帮助改进这个项目!
---
<div align="center">
**[文档](https://github.com/atshelchin/svelte-i18n#readme)** •
**[在线演示](https://atshelchin.github.io/svelte-i18n/)** •
**[示例](https://github.com/atshelchin/svelte-i18n/tree/main/src/routes)** •
**[报告问题](https://github.com/atshelchin/svelte-i18n/issues)**
由 [Shelchin](https://github.com/atshelchin) 用 ❤️ 制作
</div>