在上一篇文章中,我们探讨了通过颗粒化方案实现平台的国际化改造。虽然该方案在减少语言包体积方面表现出色,但对开发者的友好度欠佳。
本文将介绍一个另一种方案,着重提升开发体验,同时保持语言包体积的优化。
该方案在第一版的基础上进行了优化,主要改进如下:
以下是 I18nService 的部分实现:
type NestedTranslation<T extends string> = {
[K in T]: string | NestedTranslation<T>
}
export type InheritTranslation<T extends NestedTranslation<string>> = {
[K in keyof T]: T[K] extends object ? InheritTranslation<T[K]> : string
}
type Language = 'zh' | 'en'
export class I18nService<T extends Record<string, NestedTranslation<string>>> {
public language: Language
private namespaces: Set<string>
private resources: Map<string, Record<Language, any>>
private defaultLanguage: Language = 'zh'
constructor({
defaultNS,
defaultLanguage,
}: {
defaultNS?: string
defaultLanguage?: Language
} = {}) {
if (defaultLanguage) this.defaultLanguage = defaultLanguage
if (defaultNS) this.currentNS = defaultNS
this.language = this.detectLanguage()
this.namespaces = new Set([])
this.resources = new Map()
}
private detectLanguage(): Language {
const storedLang = localStorage.getItem('language')
if (storedLang && ['zh', 'en'].includes(storedLang)) {
return storedLang as Language
}
// 2. 查看用户的浏览器语言
const userLang = navigator.language || navigator.userLanguage
if (userLang.startsWith('zh')) return 'zh'
if (userLang.startsWith('en')) return 'en'
// 3. 默认语言
return this.defaultLanguage
}
// ...
}
看一下 I18nService 内部的实现,最基础的内容就是语言的检测,如果需要通过其他参数可以自行处理。
通过 loadNS 和 unloadNS 方法,可以动态加载不同路由下的语言包内容。
同时也可以设置如 common 等通用语言包来进行全局使用,此功能的想法来此上一篇文章提及的 I18nProvider。
/**
* 加载资源
* @param ns 命名空间
* @param resource 资源
*/
public async loadNS(ns: string, resource: Partial<Record<Language, any>>) {
if (this.namespaces.has(ns)) return
// 只加载当前语言资源
this.resources.set(ns, {
...(this.resources.get(ns) || {}),
[this.language]: resource[this.language],
})
this.namespaces.add(ns)
}
public async unloadNS(ns: string) {
this.resources.delete(ns)
this.namespaces.delete(ns)
}
public setNS<K extends keyof T & string>(ns: K) {
this.currentNS = ns
}
同时也只会加载当前检测到的语言对应的语言包,能够进一步节省其语言包使用空间。