默认数据也要做国际化:稳定 ID、legacyNames 与旧版本兼容

一个容易被漏掉的多语言场景

假设应用首次启动时自动创建:

早睡、喝水、运动、阅读、冥想、学习

如果直接把这些中文保存到 Habit.name,用户切换到英语后,界面其他内容变成英文,默认习惯却仍是中文。

如果为了切换语言而批量改名,又会遇到:

  • 用户可能已经手动把“早睡”改成其他名字;
  • 中英文名称可能与用户自定义名称冲突;
  • 旧版本保存过英文或日文;
  • 快速添加需要判断某个默认习惯是否已经存在。

这说明“默认数据”不是普通翻译文案,而是带身份的持久化实体。

一、默认习惯定义需要四类信息

项目定义:

export interface StarterHabit {
  resourceName: string;
  storedName: string;
  legacyNames: string[];
  icon: string;
  color: string;
}

示例:

{
  resourceName: 'starter_sleep',
  storedName: 'sleep-early',
  legacyNames: [
    '早睡',
    'Sleep early',
    '早寝'
  ],
  icon: 'moon_z',
  color: '#5E7CE2'
}

各字段职责:

字段作用
resourceName从当前语言资源获取显示名称
storedName新版本写入磁盘的稳定身份
legacyNames识别旧版本曾保存的翻译文本
icon/color默认视觉配置

二、创建时只保存稳定 storedName

export function makeStarterHabit(
  definition: StarterHabit
): Habit {
  const now: number = Date.now();

  return {
    id: newId(),
    name: definition.storedName,
    icon: definition.icon,
    color: definition.color,
    isActive: true,
    createdAtMillis: now,
    updatedAtMillis: now,
    archivedAtMillis: 0
  };
}

无论首次启动时界面是中文、英文还是日文,磁盘中都保存:

sleep-early

语言变化不会修改业务身份。

三、显示时再根据资源本地化

先判断某个 Habit.name 是否对应默认习惯:

private starterDefinition(
  name: string
): StarterHabit | undefined {
  return STARTER_HABITS.find(
    (starter: StarterHabit) =>
      starter.storedName === name ||
      starter.legacyNames.includes(name)
  );
}

然后显示:

private localizedHabitName(habit: Habit): string {
  const starter = this.starterDefinition(habit.name);

  return starter
    ? this.named(starter.resourceName)
    : habit.name;
}

因此:

磁盘:sleep-early
中文显示:早睡
英文显示:Sleep early
日文显示:早寝

用户自定义习惯找不到 StarterDefinition,就原样显示。

四、legacyNames 解决什么问题

假设早期测试版本已经把“早睡”直接写进磁盘。

升级后如果只识别 sleep-early,这个旧习惯会被当作普通自定义习惯:

  • 切到英文仍显示中文;
  • 快速添加可能再创建一个 sleep-early;
  • 默认图标迁移逻辑无法识别它。

加入:

legacyNames: ['早睡', 'Sleep early', '早寝']

可以在不清空旧数据的情况下继续识别。

legacyNames 是兼容层,不应该成为新写入格式。

五、快速添加去重也必须使用同一身份规则

private hasStarterHabit(
  starter: StarterHabit
): boolean {
  return this.habits.some((habit: Habit) =>
    habit.name === starter.storedName ||
    starter.legacyNames.includes(habit.name)
  );
}

快速添加前调用:

const alreadyExists = this.hasStarterHabit(starter);
if (!alreadyExists) {
  this.habits = [
    ...this.habits,
    makeStarterHabit(starter)
  ];
}

显示和去重必须共享同一套身份判断,否则页面认为它是默认习惯,添加逻辑却认为它不存在。

六、用户真正改名后应该发生什么

如果用户把“早睡”改成“23:00 前睡觉”,新名称不在 legacyNames 中。

之后它应被视为用户自定义名称:

  • 切换语言不自动翻译;
  • 显示用户输入原文;
  • 快速添加是否允许再次添加默认“早睡”,属于产品决策。

当前轻量模型通过 name 是否匹配默认集合来推断身份,简单但存在边界:

  • 用户恰好自定义了一个名为“早睡”的习惯,会被识别为默认项;
  • 只修改图标但未改名时,保存的本地化名称可能进入兼容路径;
  • 默认项与用户项没有显式类型字段。

更成熟的模型可以增加:

interface Habit {
  id: string;
  customName: string;
  starterKey: string;
  // ...
}

规则变为:

starterKey 有值且 customName 为空
→ 使用资源翻译

customName 有值
→ 显示用户名称

这样身份不再依赖名称碰撞。

七、首次初始化为什么要有 seededStarterHabits

只判断 habits.length === 0 不够。

用户可能主动删除全部习惯,此时数组为空,但应用不应该在下次启动时又自动创建六个默认习惯。

项目同时检查:

if (
  this.habits.length > 0 ||
  this.settings.seededStarterHabits
) {
  return;
}

首次引导结束后设置:

next.seededStarterHabits = true;

删除全部数据时也保持这个标记为 true,确保“删除”结果不会被自动初始化抵消。

八、默认数据应该在隐私同意前创建吗

不应该。

即使默认习惯不是用户输入,创建它也意味着应用开始写入本地状态。

项目在用户同意隐私并完成引导后才执行:

private async finishOnboarding(): Promise<void> {
  this.seedStarterHabits();
  // 保存引导状态
}

这样首次隐私门禁之前不会偷偷改变用户数据空间。

九、新增默认习惯需要同步哪些地方

新增一个默认习惯时至少要完成:

  1. STARTER_HABITS 添加稳定 storedName
  2. 添加中英日 legacyNames
  3. 三份 string.json 增加相同 resourceName
  4. 选择可用系统图标 Key;
  5. 验证快速添加去重;
  6. 验证首次引导初始化;
  7. 验证语言切换;
  8. 验证用户改名后保持原文;
  9. 运行资源 Key 校验脚本。

项目通过 Node 脚本比较三种语言的 Key 集合,防止只补一种语言。

十、默认数据兼容测试矩阵

测试数据中文英文日文
sleep-early早睡Sleep early早寝
旧值 早睡早睡Sleep early早寝
旧值 早寝早睡Sleep early早寝
自定义 23:00 睡觉原文原文原文

另外测试:

  • 旧值存在时快速添加不重复;
  • 删除默认习惯后不会自动复活;
  • 删除全部数据后重启仍为空;
  • 新增资源 Key 三种语言一致;
  • 导出 JSON 保留稳定身份。

总结

默认数据国际化的核心原则是:

  1. 显示名称与稳定身份分离;
  2. 新数据只保存稳定 Key;
  3. 旧翻译文本通过兼容集合识别;
  4. 显示和去重共享身份规则;
  5. 用户自定义名称不自动翻译;
  6. 自动初始化要有“一次性完成”标记;
  7. 模型复杂后应增加显式 starterKey。

只要默认内容会写入磁盘,它就不再是单纯的界面文案,而是需要版本兼容的数据协议。

本文案例来自“心晴手记(MoodMemoir)”HarmonyOS 版的六个默认习惯与中英日切换。

参考资料


RavioliGameTools-v2.10.zip是一个wallpaper engine壁纸提取相关的工具软件压缩包。该软件的版本号为2.10,很可能是在此版本中新增了或优化了wallpaper engine壁纸提取功能相关的操作。wallpaper engine是知名的游戏及应用程序启动器Steam上的一个软件,它允许用户将动态壁纸设定为电脑桌面背景,提供了丰富的壁纸资源和创意性的视觉体验。从软件名称中的“Ravioli”和“GameTools”部分可以推测,这款工具可能原本是为游戏用户提供辅助功能而设计的。其中“Ravioli”可能是一个特定的项目或者开发者名称,而“GameTools”则明确指出该软件包内含的是游戏工具。根据“提取”这一标签,我们可以推断RavioliGameTools-v2.10.zip的功能之一是能够从wallpaper engine中提取用户所需的壁纸资源,可能是提取壁纸的高清图片、视频或配置文件等。由于文件名称列表中只给出了“RavioliGameTools_v2.10”,这意味着解压后的文件夹或安装程序可能就是这个名称。用户在使用此工具时可能需要遵循一定的安装流程和使用说明,以确保壁纸提取功能能够正确无误地工作。提取过程中可能涉及到文件的读取、解码,以及wallpaper engine的交互等技术操作。此外,由于wallpaper engine壁纸多为动态视频或图片,提取过程可能还包含了视频解码和音频处理的相关技术。因此,RavioliGameTools-v2.10.zip可能包含一些专门为此设计的解码器或者处理算法,以便用户能够顺利提取所需的壁纸资源。这些工具的复杂性可能意味着它具有较高的技术含量和较好的用户体验设计。由于wallpaper engine壁纸的版权通常受保护,使用提取工具时用户需要注意版权法律的规定,只提取和使用那些允许被个人使用的壁纸资源。同时,提取壁纸的过程也应确保不会侵犯其他用户的隐私权或引发其他潜在的法律问题。RavioliGameTools-v2.10.zip是一个专门针对wallpaper engine用户设计的壁纸提取工具,它提供了方便快捷的方式来获取动态壁纸资源,并可能包含了处理视频、图片及相关配置文件的技术。用户在使用此类工具时应保持对版权的尊重,同时遵守相关法律法规,以合法合规的方式享受个性化壁纸带来的乐趣。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值