作者| 赫曼斯·穆拉里
译者| 张伟斌
规划|蒂娜
速读
国际化(i18n)和本地化是Web开发中的重要过程,以确保软件适用于不同的语言和地区,并且软件实际上适应了这些特定的需求。以JavaScript 为中心的i18n 库(i18next、react-intl、react-i18next 等)是该领域的主流工具,可帮助开发人员高效处理翻译和本地化相关的配置,但这些只能与基于Web 的应用程序一起使用。我们需要一个独立于语言的国际化框架。 JSON 是一种广泛接受的格式,用于存储翻译和本地化相关的配置,可以轻松地跨不同应用程序集成和动态替换内容,无论语言或框架如何。内容交付网络(CDN) 的策略性使用可以有效地提供与本地化相关的配置文件,并减轻加载大型配置文件的潜在缺点。构建自定义国际化框架并将其与数据库或数据存储解决方案集成,可实现动态、上下文感知的翻译,从而改善不同地区和语言的用户体验。您是否已经涉足网络开发的广阔海洋?如果答案是肯定的,您很快就会意识到网络不仅仅是为英语使用者服务的,它是为全世界服务的。在我们受到类似抱怨的轰炸之前,让我们先解释一下什么是国际化(通常缩写为i18n)和本地化。
流行语“i18n”是什么意思?想象一个软件可以与任何人流畅交流的世界,无论他们的母语是什么。这就是国际化和本地化想要实现的目标。乍一看可能没什么特别的,但请记住,本地化应用程序不仅仅是翻译文本。它是根据用户的文化、区域和语言偏好提供定制体验。
但障碍就在这里。如果您深入研究i18n 库工具箱,您将看到以JavaScript 为中心的解决方案占主导地位,尤其是围绕React 的解决方案(i18next、react-intl、react-i18next 等)。
当您超出JavaScript 的范围时,您的选择就会变得越来越有限。更糟糕的是,这些现成的工具通常是“一刀切”并且缺乏适应特定用例的能力。
但不用担心。如果鞋子不合脚,为什么不自己做呢?了解如何从头开始构建国际化框架- 为您的应用程序量身定制的跨语言和跨框架解决方案。
您准备好为您的应用程序发布全球通行证了吗?
从基本角度理解国际化本质的一个简单方法是使用基于用户位置检索信息的函数。下面是一个用Java 编写的示例,它提供了一种基本但有效的方法。
public class InternationalizationExample { public static void main(String[] args) { System.out.println(getWelcomeMessage(getUserLocale())); } public static String getWelcomeMessage(String locale) { switch (locale) { case \’en_US\’: return \’Hello, World!\’; case \’fr_FR\’: return \’Bonjour le Monde!\’; case \’es_ES\’: return \’Hola Mundo!\’; } } public static String getUserLocale() //这是一个占位符方法。 //从用户设置或系统配置中获取。 //这只是一个例子。
在上面的示例中,getWelcomeMessage 以区域设置指定的语言返回欢迎消息。语言由getUserLocale 方法确定。尽管这种方法非常基础,但它说明了以用户特定的本地语言提供内容的原则。然而,随着我们的进步,我们将深入研究更先进的技术,并理解为什么这种基本方法对于大型应用程序来说不可扩展和高效。
优势
覆盖面广:所有翻译都嵌入在代码中,因此您可以使用多种语言工作,而不必担心外部依赖或丢失翻译。无网络调用:直接从代码中检索翻译,不会产生与从外部源检索翻译相关的网络开销或延迟。方便的代码搜索:所有翻译都是源代码的一部分,因此可以轻松搜索特定翻译并排查相关问题。可读性:开发人员可以快速理解选择特定翻译背后的流程和逻辑,简化调试和维护。减少外部依赖性:无需依赖外部翻译服务或数据库,从而减少应用程序的单点故障。
坏处:
更新操作需要发布新版本。对于移动或独立应用程序,用户必须下载并更新最新版本的应用程序才能添加新语言或调整现有翻译。冗余代码:随着支持的语言数量的增加,switch和条件语句的数量也随之增加,导致代码重复和臃肿。合并冲突:多个开发人员可能会对不同的语言进行添加或更改,从而增加版本控制系统中合并冲突的风险。代码维护挑战:随着时间的推移,随着您的应用程序扩展并支持更多本地语言,直接在代码中管理和更新翻译变得乏味且容易出错。灵活性有限:这种静态方法使得添加复数和特定于上下文的翻译等功能或动态获取翻译变得困难。性能开销:对于大型应用程序,加载大量翻译数据并仅使用其中的一部分可能会导致资源受限且效率低下。
基于配置的国际化建立在以前的方法的基础上,并努力保留其优点,同时解决其缺点。为了实现这一目标,我们从代码库中的硬编码字符串值转向基于配置的设置。为每种本地语言使用单独的配置文件,以JSON 格式编码。这种模块化方法简化了添加和更改翻译的过程,而无需更改代码。
英语和西班牙语的本地语言配置文件是:
文件名:en.json
{\’welcome_message\’: \’你好,世界\’}复制代码
文件名:es.json
{\’welcome_message\’: \’嗨,Mundo\’}复制代码
Java 实现:首先,我们需要一种读取JSON 文件的方法。 Jackson 或GSON 等库通常用于此目的。这个例子使用杰克逊。
导入com.fasterxml.jackson.databind.ObjectMapper;导入java.io.File;导入java.io.IOException;导入java.util.Map;公共类国际化{ 私有静态最终字符串CONFIG_PATH=\’/path_to_configs/\’;翻译; public Internationalization(String locale) throws IOException { ObjectMapper mapper=new ObjectMapper() Translations=mapper.readValue(new File(CONFIG_PATH + locale + \’.json\’), Map.class); } return Translations.getOrDefault(key, \’找不到密钥!\’); }} public static class Program { public static void main(String[] args) throws IOException { Internationalization i18n=new Internationalization(getUserLocale()); i18n.getTranslation(\’welcome_message\’)); } private static String getUserLocale() { //必须实现此方法才能获取用户的区域设置。 //为了简单起见,我们返回\’en\’。 \’en\’; }}复制代码
当实例化Internationalization 类时,会根据提供的本地语言读取上面代码中关联的JSON 配置。 getTranslation 方法使用标识符来检索所需的翻译字符串。
优势:
保留了上述方法的所有优点,例如更大的覆盖范围、加载后无需使用互联网即可翻译的能力以及代码可搜索和可读。动态加载:可以根据用户本地语言动态加载翻译。由于仅加载必要的翻译,因此可以提高性能。可扩展性:添加新语言变得容易。只需为该语言添加一个新的配置文件,您的应用程序就可以处理它,而无需更改任何代码。更简洁的代码:逻辑和转换是分离的,使您的代码更简单且更易于维护。集中管理:所有翻译都在一个文件中,便于管理、审阅和更新。这种方法提供了一种更具可扩展性和简洁的方式来处理国际化,特别是对于大型应用程序。坏处:
配置文件可能变得太大:随着应用程序的增长并支持多种语言,这些配置文件可能会变得非常大。这可能会导致应用程序初始加载延迟,特别是在需要配置文件的前端加载时。
从CDN 检索配置减少配置文件增长可能性的一种方法是将它们托管在内容交付网络(CDN) 上。这样,您的应用程序就可以根据用户的本地语言仅加载所需的配置文件。这不仅可以确保您的应用程序快速运行,还可以减少用户需要下载不必要的数据量。如果用户切换本地语言或检测到另一种本地语言,则可以根据需要从CDN 检索配置。这为大规模应用提供了速度和灵活性的最佳平衡。为简单起见,我们考虑使用基本的HTTP 库来检索配置文件。此Java 示例使用虚构的HttpUtil 库。
导入java.util.Map;导入org.json.JSONObject;public class InternationalizationService { private static Final String CDN_BASE_URL=\’https://cdn.example.com/locales/\’; public String getTranslatedString(String key) { String locale=getUserLocale(); } String configContent=fetchConfigFromCDN(locale); return configJson.optString(key, \’未找到翻译\’); } private String fetchConfigFromCDN(String locale) { String url=CDN_BASE_URL + locale + \’ .json \’; return HttpUtil.get(url); //假设此方法从指定的URL 检索内容} private String getUserLocale() { //实现一个方法来获取用户的区域设置//这是用户设置、系统设置等return \’en\’ //本例中默认为英语}} 复制代码
注意:上面的代码只是一个简化的例子。现实场景可能需要错误处理、缓存机制和其他优化。
这里的想法是根据用户的本地语言直接从CDN 检索所需的配置文件。用户的本地语言决定了配置文件URL,一旦检索到配置文件,就会对其进行解析以检索必要的翻译。如果没有找到对应的key,则返回默认信息。这种方法的优点是应用程序仅加载必要的翻译,确保最佳性能。
优势
继承了之前方法的所有优点。轻松组织和添加新的本地语言翻译。加载非常高效,因为您只获得所需的翻译。
坏处:
配置文件很大,会减慢应用程序初始化速度。字符串必须是静态的。不直接支持动态字符串或需要运行时计算的字符串。如果您需要将动态数据插入翻译中,这可能是一个限制。取决于外部服务(CDN)。如果CDN 出现故障或问题,您的应用程序将无法检索翻译的内容。但是,为了解决这些缺点,您可以执行以下操作: 第一个缺点可以通过将配置文件存储在CDN 上并根据需要加载来缓解。第二个缺点可以通过在静态字符串中使用占位符并在运行时根据上下文替换它们来克服。第三个缺点需要强大的错误处理机制和一些潜在的后备策略。
处理动态字符串如果某些正在翻译的字符串是动态的,则您需要更灵活的解决方案。以Facebook 为例,您可以看到News Feed 使用自定义字符串来表示每篇文章的“喜欢”信息。例如,如果帖子仅包含“喜欢”消息,则可能会说“约翰喜欢您的帖子”。如果您有两条“喜欢”的消息,您可能会看到“约翰和大卫喜欢您的帖子”。如果您有3 个或更多点赞,您可能会看到“John、David 和其他100 人点赞了您的帖子”。在这种情况下,需要进行一些定制。动词“喜欢”和“喜欢”是基于“喜欢”某篇文章的人数。这个怎么做?
考虑以下示例:“John、David 和其他100 个人最近回复了您的帖子。”其中“David”、“John”、“100”、“people”,所有“反应”都是动态元素。
我们来分析一下:
“David”和“John”是通过用户相关方法或数据库获取的用户名。 “100”可以是从与文章相关联的方法或数据库获得的对文章做出响应的总人数,不包括David和John。当提到一个群体时,“people”可以是名词“people”的复数形式。 “反应”允许用户用心、关注和愤怒图标而不是“喜欢”图标对文章做出反应。实现此类动态内容的一种方法是在配置文件中使用占位符,并在运行时根据上下文替换它们。
这是一个Java 示例:
配置文件(英文)
{ oneUserAction: {0} {1} 您的帖子,twoUserAction: {0} 和{1} {2} 您的帖子,multiUserAction: {0}、{1} 和{2} 其他{3}最近{4} 您的帖子,people: 人, likeSingular: like, likePlural: like,}复制代码
配置文件(法语):
{ oneUserAction: {0} {1} 投票发布,twoUserAction: {0} et {1} {2} 投票发布,multiUserAction: {0},{1} et {2} 作者批准{3} {4} votre Publications , people: 人, likeSingular: 目的, likePlural: 目的,}复制代码
Java实现:
import java.util.Locale;import java.util.ResourceBundle;public class InternationalizationExample { public static void main(String[] args) { //示例System.out.println(createMessage(\’David\’, null, 1, new Locale) ) (\’en\’, \’US\’))); //1 个用户System.out.println(createMessage(\’David\’, \’John\’, 2, new Locale(\’en\’, \’US\’))) ; users System.out.println(createMessage(\’David\’, \’John\’, 100, new Locale(\’en\’, \’US\’))); //多个用户//法语示例System.out .println(createMessage( \’ David\’, null, 1, new Locale(\’fr\’, \’FR\’))); //1 个用户System.out.println(createMessage(\’David\’, \’John\’, 2 , new Locale(\’fr\’) , \’FR\’))); //2 个用户System.out.println(createMessage(\’David\’, \’John\’, 100, new Locale(\’fr\’, \’FR\’ ))); //多个用户} private static String createMessage(String user1, String user2, int count, Locale locale) { //加载适当的资源包ResourceBundlemessages=ResourceBundle.getBundle(\’MessagesBundle\’, locale) ; if (count==0) { return \’\’//不会收到点赞} else if (count==1) { return String.format(messages.getString(\’oneUserAction\’), user1,messages.getString (\’likeSingular\’) ) //如果有则返回\’David\’ 1 喜欢。喜欢你的帖子! } else if (count==2) { return String.format(messages.getString(\’twoUserAction\’), user1, user2,messages.getString(\’likePlural\’) ); //两个赞如果是,则返回\’。大卫和约翰喜欢你的帖子! } else { return String.format(messages.getString(\’multiUserAction\’), user1, user2, count,messages.getString(\’people\’),messages.getString(\’likePlural\’ )/);/如果有2 个或更多点赞,则返回“David、John 和其他100 人点赞了您的帖子” } }}复制代码
结论开发有效的国际化(i18n) 和本地化(l10n) 框架对于任何规模的软件应用程序都很重要。这种方法可确保您的应用与用户的母语和文化背景产生共鸣。尽管字符串转换是国际化和本地化的关键组成部分,但这只是软件全球化这一更广泛挑战的一个方面。
有效的本地化不仅仅是翻译,还包括阿拉伯语等语言的字符方向(从右到左)和文本长度或大小的变化,泰米尔语等语言的文本(可能比英语长)等。其他重要问题也需要解决。通过根据您的特定本地化需求仔细调整这些策略,您可以为您的软件提供真正全球化且适合文化的用户体验。
原文链接:构建国际化框架,Web开发将解锁语言_Framework_InfoQ精选文章
本文和图片来自网络,不代表火豚游戏立场,如若侵权请联系我们删除:https://www.huotun.com/game/591127.html