Svelte/SvelteKit 多语言配置指南

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 设计简洁。

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。

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

编译时优化的国际化方案,零运行时开销。

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}

参考资源

相关推荐
咸鱼翻身了么2 小时前
微服务-乾坤
前端
Wect2 小时前
深度解析浏览器本地存储:原理、方案与实战指南
前端·面试·浏览器
前端那点事2 小时前
Vue自定义指令全解析(Vue2+Vue3适配)| 底层DOM操作必备
前端
|晴 天|2 小时前
实现草稿自动保存功能:5秒无操作自动保存
前端·vue.js·typescript
Cisyam^2 小时前
Bright Data Web Scraping 指南:用 MCP + Dify 自动采集 TikTok 与 LinkedIn数据
大数据·前端·人工智能
XGeFei3 小时前
【表单处理】——如何防止CSRF(跨站请求伪造)攻击的?
前端·网络·csrf
还不秃顶的计科生3 小时前
多模态模型下载
java·linux·前端
GISer_Jing3 小时前
笑不活了!蒸馏Skill竟能复刻前任、挽留同事?三大热门项目+完整地址汇总
前端·人工智能
Bigger4 小时前
🚀 mini-cc:打造你的专属轻量级 AI 编程智能体
前端·node.js·claude