在 Capacitor SDK 中使用本地化和区域代码
为什么这很重要
区域设置代码在以下情况下会发挥作用:Adapty 为流程或用户引导选择本地化内容,以及您为自定义付费墙读取远程配置时。
区域设置代码较为复杂,且在不同平台之间可能存在差异,因此 Adapty 在其支持的所有平台上采用统一的内部标准。了解该标准有助于您预测用户会收到哪种本地化内容。
Adapty 的语言代码标准
Adapty 的语言代码基于略作修改的 BCP 47 标准:每个代码由小写子标签组成,以连字符分隔。示例:en(英语)、pt-br(葡萄牙语(巴西))、zh(简体中文)、zh-hant(繁体中文)。
语言代码匹配
在 SDK v4 中,流程和用户引导的语言代码匹配方式有所不同:流程由 SDK 在设备端完成本地化,而用户引导则由 Adapty 服务器完成本地化。
流程与付费墙编辑工具付费墙
在 SDK v4 中,通过付费墙编辑工具构建的付费墙会以流程的形式交付,因此以下规则同时适用于两者。
匹配规则为精确匹配。SDK 会逐字符比较你传入的代码与流程本地化代码:不会转换大小写,不会将下划线(_)替换为连字符(-),也不会回退到语言子标签。对于带有 pt-br 本地化配置的流程,只有 pt-br 能够匹配:pt-BR、pt_BR 和 pt-PT 均无法匹配。
当代码未匹配任何本地化时,流程会静默地以其默认语言渲染——SDK 不会返回错误,也不会打印警告。
当代码匹配成功时,Adapty 会将该本地化与默认本地化合并:匹配的本地化中未定义的字符串和资源将从默认本地化中获取。
省略语言代码与请求流程的默认本地化并不相同:SDK 会固定替换为 en。如果某个流程的默认语言为 de,但它包含 en 本地化,则仍会以 en 渲染;只有在没有 en 本地化时,才会回退到 de。
请严格按照看板中配置的格式传递语言代码——小写子标签,以连字符分隔。不要直接传入系统语言标识符:navigator.language 返回的是 pt-BR,这会导致回退到默认本地化。请在应用中先对该值进行转换,再传递给 SDK。
用户引导
用户引导的本地化在服务端进行,服务端规则对其他格式有一定的容错性。当你向 getOnboarding 传入 locale 时:
- locale 字符串会被转换为小写,并将所有下划线(
_)替换为连字符(-) - Adapty 查找与完整 locale 代码完全匹配的本地化内容
- 如果未找到匹配项,Adapty 取第一个连字符之前的子字符串(例如
pt-br取pt),并查找匹配的本地化内容 - 如果仍未找到匹配项,Adapty 返回该用户引导默认语言的内容
这样,pt_BR、pt-BR 和 pt-br 都会解析为同一个用户引导本地化。
实现本地化
在 SDK v4 中,获取流程时无需传入语言代码——getFlow 会返回包含所有本地化内容的流程,Adapty 会在构建流程视图时应用相应的本地化。
-
在编辑器中构建的流程:SDK 不会读取设备语言,因此需要在应用中自行解析,并通过
createFlowView的locale选项传入。该参数为可选项——若省略,流程将以en渲染;若流程没有en本地化,则使用其默认语言渲染。import { createFlowView } from '@adapty/capacitor'; const view = await createFlowView(flow, { locale: 'es' });
view.locale 报告的是视图构建时所使用的本地化语言。locale 选项和 view.locale 均需要 Capacitor SDK 4.0.1-beta.1,在更早版本中 view.locale 为 undefined。onAppeared 处理程序 也会返回相同的值。
- 自定义(远程配置)付费墙:
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?.dataAdapty 将 lang 代码以Adapty 的语言代码标准中描述的格式存储。SDK 不会将远程配置与语言区域进行匹配,因此使用哪个条目由您的应用决定。
为什么这很重要
在某些场景下,区域设置代码会发挥关键作用——例如,当你需要根据应用的当前语言获取正确的付费墙时。
由于区域设置代码较为复杂,且在不同平台之间可能存在差异,我们为所有支持的平台制定了统一的内部标准。正因为这些代码较为复杂,了解你究竟向服务器发送了什么内容以获取正确的本地化版本,以及后续会发生什么,就显得尤为重要——这样你才能始终收到预期的结果。
Adapty 的语言代码标准
在语言代码方面,Adapty 采用了略有改动的 BCP 47 标准:每个代码由小写子标签组成,以连字符分隔。示例:en(英语)、pt-br(葡萄牙语(巴西))、zh(简体中文)、zh-hant(繁体中文)。
语言区域代码匹配
当 Adapty 收到来自客户端 SDK 的语言区域代码请求并开始查找付费墙对应的本地化版本时,会按以下步骤处理:
- 将传入的语言区域字符串转换为小写,并将所有下划线(
_)替换为连字符(-) - 查找与完整语言区域代码完全匹配的本地化版本
- 如果未找到匹配项,则截取第一个连字符之前的子字符串(例如
pt-br取pt),并继续查找匹配的本地化版本 - 如果仍未找到匹配项,则返回该付费墙默认语言区域的内容
这样,发送了 '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);
};请注意,我们不建议使用此方法,原因如下:
- 在 iOS 上,首选语言与当前语言区域设置并不相同。如果希望正确匹配本地化内容,你要么依赖 Apple 的内置逻辑(如果使用推荐的本地化字符串文件方式,该逻辑开箱即用),要么自行实现相同的逻辑。
- 很难预测 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.