Svelte/SvelteKit 多语言配置指南
方案对比
| 方案 | 适用场景 | 复杂度 | 依赖大小 |
|---|---|---|---|
| 自定义 Store | SvelteKit 全栈 | 低 | 0 |
| svelte-i18n | 纯 Svelte 应用 | 低 | ~3KB |
| typesafe-i18n | 类型安全优先 | 中 | ~5KB |
| paraglide-js | 编译时优化 | 中 | ~2KB |
方案一:自定义 Store(推荐 SvelteKit)
最轻量的方案,无需额外依赖,代码完全可控些。
1. 目录结构
bash
src/lib/i18n/
├── translations.ts # 翻译数据聚合
├── index.ts # 导出接口
└── locales/
├── zh.ts # 中文
└── en.ts # 英文
2. 翻译文件
typescript
// src/lib/i18n/locales/zh.ts
export const zh = {
nav: {
home: '首页',
about: '关于'
},
welcome: '欢迎'
};
// src/lib/i18n/locales/en.ts
export const en = {
nav: {
home: 'Home',
about: 'About'
},
welcome: 'Welcome'
};
3. 核心实现
typescript
// src/lib/i18n/translations.ts
import { zh } from './locales/zh';
import { en } from './locales/en';
export const translations = { zh, en };
export type Language = keyof typeof translations;
export type TranslationType = typeof zh;
// 检测浏览器语言
export function detectLang(): Language {
if (typeof navigator === 'undefined') return 'zh';
const lang = navigator.language.toLowerCase();
return lang.startsWith('zh') ? 'zh' : 'en';
}
// 从 localStorage 读取
export function getStoredLang(): Language | null {
if (typeof localStorage === 'undefined') return null;
const stored = localStorage.getItem('lang');
return stored === 'zh' || stored === 'en' ? stored : null;
}
typescript
// src/lib/i18n/index.ts
import { writable, derived, get } from 'svelte/store';
import { translations, detectLang, getStoredLang, type Language } from './translations';
// 优先从 localStorage 读取,否则检测浏览器语言
const initialLang = getStoredLang() || detectLang();
// 当前语言 Store
export const currentLang = writable<Language>(initialLang);
// 翻译函数 Store
export const t = derived(currentLang, ($lang) => {
return (key: string): string => {
const keys = key.split('.');
let value: any = translations[$lang];
for (const k of keys) {
value = value?.[k];
}
// 回退到 key 本身
return typeof value === 'string' ? value : key;
};
});
// 切换语言
export function setLang(lang: Language) {
currentLang.set(lang);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('lang', lang);
}
}
// 获取当前语言(非响应式,用于脚本)
export function getLang(): Language {
return get(currentLang);
}
4. 组件中使用
svelte
<!-- +layout.svelte -->
<script lang="ts">
import { currentLang, t, setLang } from '$lib/i18n';
</script>
<nav>
<a href="/">{$t('nav.home')}</a>
<a href="/about">{$t('nav.about')}</a>
<button on:click={() => setLang($currentLang === 'zh' ? 'en' : 'zh')}>
{$currentLang === 'zh' ? 'EN' : '中文'}
</button>
</nav>
5. SSR 注意事项
svelte
<!-- 安全访问 localStorage -->
<script lang="ts">
import { onMount } from 'svelte';
import { setLang } from '$lib/i18n';
onMount(() => {
// 客户端才执行
const saved = localStorage.getItem('lang');
if (saved) setLang(saved as 'zh' | 'en');
});
</script>
方案二:svelte-i18n
社区最流行的方案,API 设计简洁。
- GitHub : github.com/kaisermann/...
- NPM : www.npmjs.com/package/sve...
bash
npm install svelte-i18n
初始化文件
typescript
// src/lib/i18n.ts
import { register, init, getLocaleFromNavigator, locale } from 'svelte-i18n';
// 注册语言文件(懒加载)
register('zh', () => import('./locales/zh.json'));
register('en', () => import('./locales/en.json'));
// 初始化配置
init({
fallbackLocale: 'zh',
initialLocale: getLocaleFromNavigator()
});
// 导出切换函数
export { locale };
export const setLocale = (lang: string) => locale.set(lang);
应用入口引入
typescript
// src/main.ts (纯 Svelte)
import './lib/i18n'; // ← 必须先导入初始化
import App from './App.svelte';
const app = new App({ target: document.body });
export default app;
typescript
// src/routes/+layout.ts (SvelteKit)
import { browser } from '$app/environment';
import { locale, waitLocale } from 'svelte-i18n';
import '$lib/i18n'; // 导入执行初始化
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async () => {
if (browser) {
const saved = localStorage.getItem('lang');
if (saved) locale.set(saved);
}
await waitLocale(); // 等待翻译加载完成
return {};
};
组件使用
svelte
<script>
import { _, locale } from 'svelte-i18n';
import { setLocale } from '$lib/i18n';
</script>
<h1>{$_('welcome')}</h1>
<p>{$_('footer.copyright')}</p>
<button on:click={() => setLocale($locale === 'zh' ? 'en' : 'zh')}>
Switch
</button>
带参数的翻译
json
{
"hello": "Hello {name}!",
"items": "You have {count} item | You have {count} items"
}
svelte
<p>{$_('hello', { values: { name: 'World' } })}</p>
<p>{$_('items', { values: { count: 5 } })}</p>
方案三:typesafe-i18n
类型安全的国际化方案,IDE 自动补全翻译 key。
- GitHub : github.com/ivanhofer/t...
- 文档 : typesafe-i18n.dev/
bash
npm install typesafe-i18n
npx typesafe-i18n --setup # 生成配置文件
自动生成类型
typescript
// src/i18n/i18n-types.ts(自动生成)
export type Translation = {
nav: {
home: string;
about: string;
};
welcome: string;
};
使用
svelte
<script lang="ts">
import { LL } from '$lib/i18n/i18n-svelte';
import { setLocale } from '$lib/i18n/i18n-util';
</script>
<h1>{$LL.welcome()}</h1>
<a href="/about">{$LL.nav.about()}</a>
方案四:paraglide-js
编译时优化的国际化方案,零运行时开销。
- GitHub : github.com/opral/inlan...
- 文档 : inlang.com/m/gerre34r/...
bash
npm install @inlang/paraglide-js
特点
- 编译时将翻译内联到代码中
- 只打包用到的翻译
- 支持 Tree Shaking
typescript
// 编译后直接使用
import * as m from '$lib/paraglide/messages.js';
console.log(m.hello_world()); // "Hello World!"
方案五:URL 路由级多语言(SvelteKit)
SEO 友好的方案,语言体现在 URL 中。
bash
/zh/about → 中文关于页
/en/about → 英文关于页
/about → 默认语言
路由配置
typescript
// src/params/lang.ts
import type { ParamMatcher } from '@sveltejs/kit';
export const match: ParamMatcher = (param) => {
return ['zh', 'en'].includes(param);
};
lua
src/routes/
├── [[lang=lang]]/ # 可选语言前缀
│ ├── +page.svelte
│ └── about/
│ └── +page.svelte
└── +layout.ts
加载翻译
typescript
// src/routes/[[lang=lang]]/+layout.ts
import type { LayoutLoad } from './$types';
import { translations } from '$lib/i18n/translations';
export const load: LayoutLoad = ({ params }) => {
const lang = (params.lang as 'zh' | 'en') || 'zh';
return {
lang,
t: translations[lang]
};
};
关键决策点
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 快速上线 | svelte-i18n | 生态成熟,文档丰富 |
| 类型安全 | typesafe-i18n | 编译时检查,IDE 提示 |
| SEO 优先 | URL 路由级 | 语言在 URL,搜索引擎友好 |
| 极简依赖 | 自定义 Store | 零依赖,完全可控 |
| 大型应用 | paraglide-js | 编译优化,性能最好 |
| 全栈 SSR | 自定义 Store | 服务端渲染无问题 |
最佳实践
1. 延迟加载翻译
typescript
// 不要:import zh from './locales/zh'; // 打包进主 bundle
// 要:
register('zh', () => import('./locales/zh.json')); // 按需加载
2. SSR 安全访问浏览器 API
svelte
<script>
import { browser } from '$app/environment';
import { onMount } from 'svelte';
// 方式一:onMount
onMount(() => {
localStorage.getItem('lang'); // 安全
});
// 方式二:browser 判断
if (browser) {
localStorage.getItem('lang'); // 安全
}
</script>
3. 回退机制
typescript
// 找不到翻译时回退到 key
export function t(key: string): string {
const value = getNestedValue(translations[lang], key);
return value || key; // 回退到 key
}
4. 类型安全(自定义 Store 版)
typescript
// 生成翻译 key 的类型
type DotPrefix<T extends string> = T extends '' ? '' : `.${T}`;
type DotPath<T> = (
T extends object ?
{ [K in keyof T]:
`${Exclude<K, symbol>}${DotPrefix<DotPath<T[K]>>}`
}[keyof T] :
''
) extends infer D ? Extract<D, string> : never;
export type TranslationKey = DotPath<typeof zh>;
// 使用
export const t = (key: TranslationKey) => ...
// IDE 提示: 'nav.home' | 'nav.about' | 'welcome' ...
5. 语言切换动画
svelte
{#key $currentLang}
<div in:fade={{ duration: 150 }}>
<h1>{$t('welcome')}</h1>
</div>
{/key}
参考资源
- Svelte 官方 : svelte.dev/
- SvelteKit 官方 : kit.svelte.dev/
- svelte-i18n : github.com/kaisermann/...
- typesafe-i18n : typesafe-i18n.dev/
- paraglide-js : inlang.com/m/gerre34r/...