如何优雅且类型安全地实现多语言数据转换(TS+RN+MongoDB)
优雅实现类型安全的多语言对象转换
核心思路
你的方向是对的,问题出在缺少类型层面的递归映射——没有告诉TypeScript转换后的对象会把所有MultilingualString字段替换为string。通过定义一个泛型类型Localized<T>,可以递归地完成类型转换,同时配合转换函数确保运行时逻辑和类型一致。
步骤1:定义递归泛型类型Localized<T>
这个类型会遍历对象的所有字段,把MultilingualString类型替换为string,同时处理嵌套对象的情况:
type MultilingualString = { en: string; he: string; }; type Localized<T> = T extends MultilingualString ? string : T extends object ? { [K in keyof T]: Localized<T[K]> } : T;
步骤2:修改转换函数,返回Localized<T>类型
重构determineObjectLanguage函数,返回新对象(避免修改原对象带来的副作用),并指定返回类型为Localized<T>:
export const isMultilingualString = (value: unknown): value is MultilingualString => { return ( typeof value === 'object' && value !== null && 'en' in value && typeof (value as MultilingualString).en === 'string' && 'he' in value && typeof (value as MultilingualString).he === 'string' ); }; function determineObjectLanguage<T>(object: T): Localized<T> { // 实际从i18n-next获取当前语言,比如:i18n.language const currentLanguageCode: keyof MultilingualString = "en"; const result: Partial<Localized<T>> = {}; for (const key of Object.keys(object) as Array<keyof T>) { const value = object[key]; if (isMultilingualString(value)) { // 替换为当前语言的字符串 result[key] = value[currentLanguageCode] as Localized<T>[typeof key]; } else if (typeof value === 'object' && value !== null) { // 递归处理嵌套对象 result[key] = determineObjectLanguage(value) as Localized<T>[typeof key]; } else { // 非对象/多语言字段直接保留 result[key] = value as Localized<T>[typeof key]; } } return result as Localized<T>; }
步骤3:类型安全的使用示例
定义数据库层的类型,转换后自动得到本地化后的类型,无需联合类型:
// 数据库存储的对象类型 interface DBItem { name: MultilingualString; description: MultilingualString; price: number; category: { title: MultilingualString; id: string; }; } // 模拟从数据库获取的数据 const dbItem: DBItem = { name: { en: "Apple", he: "תפוח" }, description: { en: "Fresh organic apple", he: "תפוח אורגני טרי" }, price: 2.99, category: { title: { en: "Fruits", he: "פירות" }, id: "cat-fruit" } }; // 转换后得到Localized<DBItem>类型,所有多语言字段都是string const localizedItem = determineObjectLanguage(dbItem); // 类型安全:localizedItem.name 是 string,无需类型断言 console.log(localizedItem.name); // "Apple" console.log(localizedItem.category.title); // "Fruits"
额外优化建议
- 异步处理:如果当前语言需要异步获取(比如从存储读取),可以把函数改成
async,返回Promise<Localized<T>>,逻辑保持一致。 - 数组支持:如果需要处理数组,可以在函数中添加数组判断逻辑,遍历数组元素进行转换:
else if (Array.isArray(value)) { result[key] = value.map(item => determineObjectLanguage(item)) as Localized<T>[typeof key]; } - 不可变数据:返回新对象而非修改原对象,更符合React-Native的状态管理原则,避免意外的副作用。
内容的提问来源于stack exchange,提问作者Sagi8767
相关产品推荐
相关产品推荐

