Svelte/SvelteKit 多语言配置指南
方案对比方案适用场景复杂度依赖大小自定义 StoreSvelteKit 全栈低0svelte-i18n纯 Svelte 应用低~3KBtypesafe-i18n类型安全优先中~5KBparaglide-js编译时优化中~2KB方案一自定义 Store推荐 SvelteKit最轻量的方案无需额外依赖代码完全可控。GitHub: 无纯手写1. 目录结构src/lib/i18n/ ├── translations.ts # 翻译数据聚合 ├── index.ts # 导出接口 └── locales/ ├── zh.ts # 中文 └── en.ts # 英文2. 翻译文件// src/lib/i18n/locales/zh.tsexportconstzh{nav:{home:首页,about:关于},welcome:欢迎};// src/lib/i18n/locales/en.tsexportconsten{nav:{home:Home,about:About},welcome:Welcome};3. 核心实现// src/lib/i18n/translations.tsimport{zh}from./locales/zh;import{en}from./locales/en;exportconsttranslations{zh,en};exporttypeLanguagekeyoftypeoftranslations;exporttypeTranslationTypetypeofzh;// 检测浏览器语言exportfunctiondetectLang():Language{if(typeofnavigatorundefined)returnzh;constlangnavigator.language.toLowerCase();returnlang.startsWith(zh)?zh:en;}// 从 localStorage 读取exportfunctiongetStoredLang():Language|null{if(typeoflocalStorageundefined)returnnull;conststoredlocalStorage.getItem(lang);returnstoredzh||storeden?stored:null;}// src/lib/i18n/index.tsimport{writable,derived,get}fromsvelte/store;import{translations,detectLang,getStoredLang,typeLanguage}from./translations;// 优先从 localStorage 读取否则检测浏览器语言constinitialLanggetStoredLang()||detectLang();// 当前语言 StoreexportconstcurrentLangwritableLanguage(initialLang);// 翻译函数 Storeexportconsttderived(currentLang,($lang){return(key:string):string{constkeyskey.split(.);letvalue:anytranslations[$lang];for(constkofkeys){valuevalue?.[k];}// 回退到 key 本身returntypeofvaluestring?value:key;};});// 切换语言exportfunctionsetLang(lang:Language){currentLang.set(lang);if(typeoflocalStorage!undefined){localStorage.setItem(lang,lang);}}// 获取当前语言非响应式用于脚本exportfunctiongetLang():Language{returnget(currentLang);}4. 组件中使用$前缀的作用Svelte 中$storeName是storeName.subscribe()的语法糖表示自动订阅该 Store值变化时组件自动更新。!-- layout.svelte --scriptlangtsimport{currentLang,t,setLang}from$lib/i18n;// 不带 $获取 Store 对象本身console.log(currentLang);// Store 对象 { subscribe, set, update }// 带 $获取 Store 的当前值自动订阅console.log($currentLang);// zh 或 en/scriptnav!-- 使用 $t() 获取翻译$currentLang 获取当前语言 --ahref/{$t(nav.home)}/aahref/about{$t(nav.about)}/abutton on:click{() setLang($currentLang zh ? en : zh)} {$currentLang zh ? EN : 中文}/button/nav对比写法含义使用场景currentLangStore 对象传递给函数、调用方法$currentLangStore 的值模板中显示、读取当前值5. SSR 服务端渲染支持SvelteKit 原生支持 SSR语言从 URL/Cookie 检测服务端预加载翻译。服务端与客户端的差异环境可用不可用服务端URL、Cookie、HeaderlocalStorage、navigator客户端全部—Cookie 工具函数// src/lib/i18n/cookies.tsimporttype{Cookies}fromsveltejs/kit;exportfunctiongetLangFromCookies(cookies:Cookies):zh|en{conststoredcookies.get(lang);returnstoredzh||storeden?stored:zh;}exportfunctionsetLangCookie(cookies:Cookies,lang:zh|en){cookies.set(lang,lang,{path:/,maxAge:60*60*24*365// 1年});}Layout Load服务端预加载// src/routes/layout.tsimporttype{LayoutLoad}from./$types;import{translations}from$lib/i18n/translations;import{getLangFromCookies,setLangCookie}from$lib/i18n/cookies;exportconstload:LayoutLoad({cookies,url}){// 服务端从 Cookie 或 URL 参数获取语言constlangParamurl.searchParams.get(lang);constlang(langParamen?en:zh);// 同步 CookiesetLangCookie(cookies,lang);// 预加载翻译数据constttranslations[lang];return{lang,t};};Layout接管切换!-- src/routes/layout.svelte --scriptlangtsimport{onMount}fromsvelte;import{setLang}from$lib/i18n;let{data,children}$props();// 初始化语言setLang(data.lang);// 语言切换functionswitchLang(){constnewLang$currentLangzh?en:zh;setLang(newLang);// 跳转刷新window.location.href/?lang${newLang};}/scriptnavahref/?langzh中文/aahref/?langenEN/abuttonon:click{switchLang}当前: {$currentLang}/button/nav{render children()}工作原理用户访问/?langen服务端从 URL 读取参数同步到 Cookie返回预加载了英文的 HTML客户端setLang(data.lang)同步 Store页面已有翻译用户切换语言 → 跳转/?langzh→ 服务端返回中文 HTMLSEO 友好搜索引擎爬虫访问/?langzh抓中文内容访问/?langen抓英文内容每个语言都有独立 URL5. SSR 注意事项!-- 安全访问 localStorage --scriptlangtsimport{onMount}fromsvelte;import{setLang}from$lib/i18n;onMount((){// 客户端才执行constsavedlocalStorage.getItem(lang);if(saved)setLang(savedaszh|en);});/script方案二svelte-i18n 纯svelte推荐社区最流行的方案API 设计简洁。GitHub: https://github.com/kaisermann/svelte-i18nNPM: https://www.npmjs.com/package/svelte-i18nnpminstallsvelte-i18n初始化文件// src/lib/i18n.tsimport{register,init,getLocaleFromNavigator,locale}fromsvelte-i18n;// 注册语言文件懒加载register(zh,()import(./locales/zh.json));register(en,()import(./locales/en.json));// 初始化配置init({fallbackLocale:zh,initialLocale:getLocaleFromNavigator()});// 导出切换函数export{locale};exportconstsetLocale(lang:string)locale.set(lang);应用入口引入// src/main.ts (纯 Svelte)import./lib/i18n;// ← 必须先导入初始化importAppfrom./App.svelte;constappnewApp({target:document.body});exportdefaultapp;// src/routes/layout.ts (SvelteKit)import{browser}from$app/environment;import{locale,waitLocale}fromsvelte-i18n;import$lib/i18n;// 导入执行初始化importtype{LayoutLoad}from./$types;exportconstload:LayoutLoadasync(){if(browser){constsavedlocalStorage.getItem(lang);if(saved)locale.set(saved);}awaitwaitLocale();// 等待翻译加载完成return{};};组件使用scriptimport{_,locale}fromsvelte-i18n;import{setLocale}from$lib/i18n;/scripth1{$_(welcome)}/h1p{$_(footer.copyright)}/pbutton on:click{() setLocale($locale zh ? en : zh)} 切换/button带参数的翻译{hello:Hello {name}!,items:You have {count} item | You have {count} items}p{$_(hello, { values: { name: World } })}/p p{$_(items, { values: { count: 5 } })}/p方案三typesafe-i18n类型安全的国际化方案IDE 自动补全翻译 key。GitHub: https://github.com/ivanhofer/typesafe-i18n文档: https://typesafe-i18n.dev/npminstalltypesafe-i18n npx typesafe-i18n--setup# 生成配置文件自动生成类型// src/i18n/i18n-types.ts自动生成exporttypeTranslation{nav:{home:string;about:string;};welcome:string;};使用scriptlangtsimport{LL}from$lib/i18n/i18n-svelte;import{setLocale}from$lib/i18n/i18n-util;/scripth1{$LL.welcome()}/h1ahref/about{$LL.nav.about()}/a方案四paraglide-js编译时优化的国际化方案零运行时开销。GitHub: https://github.com/opral/inlang-sdk文档: https://inlang.com/m/gerre34r/library-inlang-paraglideJsnpminstallinlang/paraglide-js特点编译时将翻译内联到代码中只打包用到的翻译支持 Tree Shaking// 编译后直接使用import*asmfrom$lib/paraglide/messages.js;console.log(m.hello_world());// Hello World!方案五URL 路由级多语言SvelteKitSEO 友好的方案语言体现在 URL 中。/zh/about → 中文关于页 /en/about → 英文关于页 /about → 默认语言路由配置// src/params/lang.tsimporttype{ParamMatcher}fromsveltejs/kit;exportconstmatch:ParamMatcher(param){return[zh,en].includes(param);};src/routes/ ├── [[langlang]]/ # 可选语言前缀 │ ├── page.svelte │ └── about/ │ └── page.svelte └── layout.ts加载翻译// src/routes/[[langlang]]/layout.tsimporttype{LayoutLoad}from./$types;import{translations}from$lib/i18n/translations;exportconstload:LayoutLoad({params}){constlang(params.langaszh|en)||zh;return{lang,t:translations[lang]};};关键决策点场景推荐方案理由快速上线svelte-i18n生态成熟文档丰富类型安全typesafe-i18n编译时检查IDE 提示SEO 优先URL 路由级语言在 URL搜索引擎友好极简依赖自定义 Store零依赖完全可控大型应用paraglide-js编译优化性能最好SSR SEO自定义 Store Cookie服务端预加载客户端接管切换最佳实践1. 延迟加载翻译// 不要import zh from ./locales/zh; // 打包进主 bundle// 要register(zh,()import(./locales/zh.json));// 按需加载2. SSR 安全访问浏览器 APIscriptimport{browser}from$app/environment;import{onMount}fromsvelte;// 方式一onMountonMount((){localStorage.getItem(lang);// 安全});// 方式二browser 判断if(browser){localStorage.getItem(lang);// 安全}/script3. 回退机制// 找不到翻译时回退到 keyexportfunctiont(key:string):string{constvaluegetNestedValue(translations[lang],key);returnvalue||key;// 回退到 key}4. 类型安全自定义 Store 版// 生成翻译 key 的类型typeDotPrefixTextendsstringTextends?:.${T};typeDotPathT(Textendsobject?{[KinkeyofT]:${ExcludeK,symbol}${DotPrefixDotPathT[K]}}[keyofT]:)extendsinferD?ExtractD,string:never;exporttypeTranslationKeyDotPathtypeofzh;// 使用exportconstt(key:TranslationKey)...// IDE 提示: nav.home | nav.about | welcome ...5. 语言切换动画{#key $currentLang}divin:fade{{duration:150}}h1{$t(welcome)}/h1/div{/key}参考资源Svelte 官方: https://svelte.dev/SvelteKit 官方: https://kit.svelte.dev/svelte-i18n: https://github.com/kaisermann/svelte-i18ntypesafe-i18n: https://typesafe-i18n.dev/paraglide-js: https://inlang.com/m/gerre34r/library-inlang-paraglideJs