将upGrad.com国际化
几个月前,upGrad决定拓展国际市场,因此需要在各个面向客户的技术产品中支持这一扩张。第一个需要重大改造的是课程列表网站——upgrad.com。在这篇文章中,我将讨论我们如何规划和实施这一改造,以及在此过程中学到的经验。
国际化第一阶段的目标
对于国际化的第一阶段,平台需要满足以下期望:内部团队应该能够在不同国家独立营销upGrad的课程,即每个国家显示的内容可以不同,并且应该能够单独维护。此外,当访问者尝试打开upgrad.com网站上的任何页面时,应自动重定向到他们所在国家特定的URL。这是许多网站的通用行为,其中内容通过附加到基础URL的特定代码来区分。
一个可能不太明显的额外要求是在访问者会话期间保持这个附加代码。如果有人访问upgrad.com/us,那么所有后续的路由处理都应该考虑初始的位置代码。例如,如果查看者点击导致路由跳转到/data-science-pgd-iiitb的超链接或按钮,那么最终的URL应该是upgrad.com/us/data-science-pgd-iiitb。
总的来说,我们需要解决三个问题:
- 在数据库中启用保存特定位置页面并允许相应编辑
- 处理客户端路由
- 根据用户位置自动重定向到这些特定页面
更新CMS以处理国际化页面
我们有一个内部的内容管理系统(CMS),名为Apollo,用于管理upgrad.com上的内容。“页面”可以描述为数据库中映射到唯一slug的文档。以前,只能有直接映射到upgrad.com上URL的单级slug。例如,data-science-pgd-iiitb映射到upgrad.com/data-science-pgd-iiitb。为了允许页面拥有像upgrad.com/us/data-science-pgd-iiitb这样的两级URL,我们在页面文档中引入了一个名为i18n的新属性。这是一个对象,包含locale的值,locale是分配给每个国家的唯一两位代码。Locale是国家的良好文本标识符,可以直接映射到URL。
在创建页面时,编辑者可以指定他们希望创建哪个国家页面,创建后该值将附加到页面文档。我们将先前用于页面的slug标识符分为两部分,即name和i18n.locale。
|
|
name属性将包含页面所属程序的更逻辑值。这可以用于后续将同一名称下的所有页面分组。如上代码片段所述,slug属性现在包含文档所表示的完整URL,即,如果请求美国页面/data-science-pgd-iiitb,我可以预期从上述文档获取数据。然而,页面数据加载逻辑并不那么直接,我将在后面讨论。
你可能会在这里质疑为什么我们在name、slug和i18n.locale中维护重复信息。答案很简单——为了简化各种获取场景。在这个项目中,我们希望尽可能减少破坏性更改,并在可能的情况下提供回退机制。我们内部称为Apollo的CMS以前仅由Web客户端使用。但在引入upGrad的移动应用程序后,它扩展到了Android和iOS移动客户端。因此,保持CMS API端点和响应模式不变至关重要。
回退机制
为了能够覆盖尽可能多的位置,同时保持低数据占用,国际营销页面采用了按区域回退的机制。如果以直接的方式思考,为每个国家的每个程序创建一个页面将意味着我们的数据会呈m*n倍增长。这也意味着内容维护者必须更新多个页面才能对特定程序的所有国家页面进行更改。这不是一个理想的场景。
为了解决这个问题,我们的想法是维护最少数量的国际化页面,并将这些少量页面的相关内容提供给全球所有位置。这意味着我们只需要维护少量页面,并且可以动态更新特定位置应提供哪个页面。
概括来说,我们在API中引入了一个配置对象。这是一个JSON结构,包含按区域分组的位置。逻辑很简单,这棵树叶子节点的值表示可能有关联页面的各个国家/位置。如果没有,页面获取API将向上移动一级,并递归尝试获取与该locale关联的页面。
例如,如果客户端请求locale=ae的页面,我们将检查ae页面是否存在,如果不存在,则检查asia页面是否存在,如果仍然不存在,则返回所请求实体的全局页面。
|
|
引入这个配置使我们能够将国家关联到特定区域,以便我们可以在属于该区域的每个国家提供区域数据。为了避免使这个配置过于庞大,我们在自身层面确保每个页面都存在一个全局页面变体。这确保了如果请求一个在我们配置中不存在的国家的页面,它总是会回退到全局页面。
页面端点现在接受一个locale参数,客户端可以通过它指定需要哪个国家页面。这主要覆盖了大部分兼容性问题,特别是随着移动客户端的逐步更新,因为端点保持不变,即使没有发送locale查询参数,API也默认假定它在印度(in),这是国际化之前的情况。
处理客户端路由
我们使用Nuxt的通用模式来提供我们的营销页面。幸运的是,Nuxt(以及整个Vue社区)为常见用例提供了一套官方模块,包括国际化。多亏了nuxt/i18n模块,我们的很多工作得以简化。这提供了启动时静态路由的自动生成、客户端路由,并提供了多种配置方式。
在许多地方,我们正在将用户重定向到应用程序内部或外部的URL。使用i18n后,这必须通过一个转换器函数来推送,该函数将当前locale值附加到新URL上。nuxt/i18n为此暴露了一个名为localePath的函数。我们发现这是一个清理现有重定向逻辑并将其集中到单一位置的好机会,通过编写一个包装函数来重定向到URL,内部可选地使用localePath(如果URL是内部URL)。
当应用程序启动时,nuxt/i18n从URL中保存与当前locale相关的数据。例如,upgrad.com/us/data-science-pgd-iiitb将locale值us设置到store中,并附带其他相关信息。利用这一点,Nuxt应用程序触发对CMS API的调用,其中locale查询参数等于模块设置的值。此后,API负责返回精确匹配的数据或应用某种回退机制来检索最接近的匹配数据。一旦数据返回,Nuxt应用程序根据数据中指定的布局进行渲染。
自动重定向到国家特定URL
这是整个项目中最具美感的功能。无论后台进行了多少数据库和API更改,用户看到URL神奇地变为他们所在位置的代码时都会感到惊讶。但作为开发者,我们知道在确保平台其余部分像以前一样正常工作的同时,实现这一点付出了多少努力。
我们现有的营销网站通过CloudFront提供服务,通过启用边缘缓存来实现低延迟。我们决定利用这个设置来增加一个额外的路由步骤,指向位置URL。重申一下当前的问题:
当查看者从,比如说,美国打开upgrad.com/data-science-pgd-iiitb时,请求应重定向到upgrad.com/us/data-science-pgd-iiitb。
这是一个完美的按需执行函数用例——AWS Lambda。AWS Lambda有一个名为Lambda@Edge的CloudFront变体,它创建函数的副本并将它们部署在CloudFront分发网络中。然后可以配置它在某些触发器上执行。
如上图所述,我们将Lambda函数放置在origin request触发器上。当CloudFront从源服务器请求内容时,此事件触发——函数位于CloudFront和源服务器之间。Lambda的职责是创建重定向或将URL传递给源服务器。为了检测用户位置,CloudFront的国家头信息派上了用场。除此之外,我们还使用了由MaxMind的IP数据库支持的内部位置解析API。
要访问CloudFront的国家头信息,需要将它们添加到行为配置的白名单头信息中。你可以通过名称CloudFront-Viewer-Country找到它。
用Node.js实现的Lambda函数大致如下:
|
|
一旦这个Lambda发布到边缘位置,源请求开始流经lambda函数,从世界各地查看网站的用户看到了不同的内容。这是整个流程中的最后一步(在用户旅程中是第一步),如果我们一步步回溯,所有事情都各就各位。
当用户从美国打开upgrad.com网站时,lambda检测位置并分配locale值为us,并在到达源服务器(Nuxt应用程序)之前重定向到/us。nuxt/i18n模块接管并将Nuxt上下文中的locale值设置为us,这导致触发API调用并附加相应的查询参数。API服务器尝试在数据库中查找美国页面,并将最接近的匹配文档返回给客户端。收到这些数据后,客户端成功渲染页面并将其返回给CloudFront。然后CloudFront将结果返回给查看者,并针对原始请求URL以及重定向后的URL进行缓存。就这样,大功告成!
未来展望
这是营销网站国际化的第一阶段,现在已经能够在多个国家营销我们的课程,并且我们能够在各地独立驱动内容。下一步将包括添加本地语言支持和允许手动更改区域。除此之外,我们认为Lambda方法还有改进的空间。目前,跳过某些不符合此条件的URL的逻辑有些分散。我们正在考虑如何使其更加集中,以便可以通过单一事实来源进行配置。如果你在你的应用程序中实现了类似的功能,请在评论中告诉我们,很乐意听取不同的观点。
如果没有整个团队的成功努力,这个项目是不可能完成的。向Maitrey、Jitesh、Shahir、Abhishek、Harshita、Omkar、Akshay、Vishal、Yuvaraj及团队,以及所有其他贡献者致敬。请访问upGrad.com查看我们完全在线的项目!如果你希望与我们充满热情的团队一起工作,请查看我们的招聘页面。我们一直在寻找有抱负、有才华的人!