在 Capacitor SDK 中使用本地化和区域代码

为什么这很重要

当 Adapty 为流程选择本地化版本,以及当你为自定义付费墙读取远程配置时,语言区域代码就会发挥作用。

语言区域代码较为复杂,不同平台之间可能存在差异,因此 Adapty 在其支持的所有平台上统一采用一套内部标准。了解这套标准,有助于你预测用户最终会获取哪个本地化版本。

Adapty 的语言代码标准

Adapty 的语言代码基于略作修改的 BCP 47 标准:每个代码由小写子标签组成,以连字符分隔。示例:en(英语)、pt-br(葡萄牙语(巴西))、zh(简体中文)、zh-hant(繁体中文)。

语言区域代码匹配

当 Adapty 查找与用户语言区域匹配的本地化内容时,会按以下步骤进行:

  1. 将语言区域字符串转换为小写,并将所有下划线(_)替换为连字符(-
  2. Adapty 查找与完整语言区域代码完全匹配的本地化内容
  3. 如果未找到匹配项,Adapty 取第一个连字符前的子字符串(例如 pt-br 对应 pt),并查找匹配的本地化内容
  4. 如果仍未找到匹配项,Adapty 返回默认的 en 本地化内容

通过这种方式,'pt_BR'pt-BRpt-br 都会解析到同一个本地化内容。

实现本地化

在 SDK v4 中,获取 flow 时无需传入语言区域代码。

  • 流程编辑工具和付费墙编辑工具付费墙:Adapty 会根据设备设置及你在编辑工具中配置的本地化内容自动解析语言区域。使用 createFlowView 渲染 flow,无需传入语言区域代码。
  • 自定义(远程配置)付费墙getFlow 会在 flow.remoteConfigs 中返回所有已配置的本地化内容。每个条目包含一个 lang 代码和一个 data 对象。请自行选择与用户匹配的条目,并实现相应的降级逻辑:

const flow = await adapty.getFlow({ placementId: 'placement_id' });
const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0];
// read your values from config?.data

上述语言代码匹配规则描述了 Adapty 如何对每个远程配置中存储的 lang 代码进行规范化处理。

为什么这很重要

在某些场景下,区域设置代码会发挥关键作用——例如,当你需要根据应用的当前语言获取正确的付费墙时。

由于区域设置代码较为复杂,且在不同平台之间可能存在差异,我们为所有支持的平台制定了统一的内部标准。正因为这些代码较为复杂,了解你究竟向服务器发送了什么内容以获取正确的本地化版本,以及后续会发生什么,就显得尤为重要——这样你才能始终收到预期的结果。

Adapty 的语言代码标准

在语言代码方面,Adapty 采用了略有改动的 BCP 47 标准:每个代码由小写子标签组成,以连字符分隔。示例:en(英语)、pt-br(葡萄牙语(巴西))、zh(简体中文)、zh-hant(繁体中文)。

语言区域代码匹配

当 Adapty 收到客户端 SDK 发送的语言区域代码请求,并开始查找对应的付费墙本地化版本时,会按以下步骤处理:

  1. 传入的语言区域字符串转换为小写,并将所有下划线(_)替换为连字符(-
  2. 然后查找与完整语言区域代码完全匹配的本地化版本
  3. 如果未找到匹配项,则截取第一个连字符之前的子字符串(例如 pt-brpt),并查找对应的本地化版本
  4. 如果仍未找到匹配项,则返回默认的 en 本地化版本 这样,发送了 'pt_BR' 的 iOS 设备、发送了 pt-BR 的 Android 设备,以及发送了 pt-br 的其他设备,都会得到相同的结果。

如果你正在考虑本地化问题,很可能你的项目中已经有了本地化字符串文件。在这种情况下,我们建议在每个本地化文件中,以目标 Adapty 语言代码为值,添加一个键值对。然后在调用 SDK 时提取该键对应的值,示例如下:

// 1. Modify your localization files (e.g., using react-i18next)

/*
en.json
*/
{
  "adapty_paywalls_locale": "en"
}

/*
es.json
*/
{
  "adapty_paywalls_locale": "es"
}

/*
pt-BR.json
*/
{
  "adapty_paywalls_locale": "pt-br"
}

// 2. Extract and use the locale code

const MyComponent = () => {
  const { t } = useTranslation();
  
  const fetchPaywall = async () => {
    const locale = t('adapty_paywalls_locale');
    // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method
    const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale);
  };
};

这样,您就能完全掌控应用中每位用户所获取的本地化内容。

另一种实现本地化的方式

你也可以不为每个本地化单独指定语言区域代码,同样能达到类似(但不完全相同)的效果。这种方式是从平台提供的其他对象中提取语言区域代码,如下所示:


const getLocaleCode = () => {
  if (Capacitor.getPlatform() === 'ios') {
    return navigator.language || 'en';
  } else {
    return navigator.language || 'en';
  }
};

const fetchPaywall = async () => {
  const locale = getLocaleCode();
  // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method
  const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale);
};

请注意,我们不建议使用此方法,原因如下:

  1. 在 iOS 上,首选语言与当前语言区域设置并不相同。如果希望正确匹配本地化内容,你要么依赖 Apple 的内置逻辑(如果使用推荐的本地化字符串文件方式,该逻辑开箱即用),要么自行实现相同的逻辑。
  2. 很难预测 Adapty 服务器实际收到的内容。例如,在 iOS 上,设备可能会生成类似 ar_OM@numbers='latn' 这样的语言区域标识,并将其发送到我们的服务器。而对于这个请求,你得到的不会是你期望的 ar-om 本地化内容,而是 ar,这很可能出乎意料。 Should you decide to use this approach anyway — make sure you’ve covered all the relevant use cases.