本文讨论的是字符串和标签的本地化和全球化最佳实践。

130
我是一个团队的成员,团队有20多个开发人员。每个开发人员都在独立的模块上工作(大约10个模块)。在每个模块中,我们可能至少有50个CRUD表单,这意味着我们目前有约500个“添加按钮”、“保存按钮”、“编辑按钮”等。
但是,因为我们想要全球化应用程序,所以我们需要能够翻译应用程序中的文本。例如,无论何处,单词“添加”对于法语用户都应变为“ajouter”。
到目前为止,我们所做的是,对于UI或Presentation Layer中的每个视图,我们都有一个键/值对翻译字典。然后,在渲染视图时,我们使用该字典翻译所需的文本和字符串。但是,采用这种方法,我们已经拥有了接近500个“添加”翻译字典。这意味着我们违反了DRY原则。
另一方面,如果我们将常见字符串集中在一起,比如将“添加”放在一个地方,并要求开发人员在任何地方都使用它,那么我们会遇到一个问题,即不确定一个字符串是否已经在集中式字典中定义。
另一种选项可能是没有翻译字典,而是使用在线翻译服务,如Google翻译、必应翻译等。
我们遇到的另一个问题是,一些在交付项目期限方面承受压力的开发人员可能会忘记翻译键。例如,对于添加按钮的文本,一个开发人员使用了“add”,而另一个开发人员使用了“new”等。
对于应用程序字符串资源的全球化和本地化,最佳实践或最常用的方法是什么?

3
JS EU大会上Alex Sexton关于"客户端国际化"的演讲是个不错的起点。 - Minko Gechev
3个回答

53
据我所知,JavaScript中有一个名为“localeplanet”的很好的库用于本地化和国际化。此外,我认为它是原生的,并且不依赖于其他库(例如jQuery)。
以下是该库的网站:http://www.localeplanet.com/ 同时,请查看Mozilla的这篇文章,您可以找到客户端翻译的非常好的方法和算法:http://blog.mozilla.org/webdev/2011/10/06/i18njs-internationalize-your-javascript-with-a-little-help-from-json-and-the-server/ 所有这些文章/库的共同点是它们使用了一个“i18n”类和一个“get”方法(有时还定义了一个较小的函数名,如“_”)来检索/转换“key”的“value”。在我的解释中,“key”表示要翻译的字符串,“value”表示已翻译的字符串。
然后,您只需要一个JSON文档来存储“key”和“value”。
例如:
var _ = document.webL10n.get;
alert(_('test'));

这里是JSON:

{ test: "blah blah" }

我相信使用目前流行的库解决方案是一个不错的方法。


2
没有冒犯的意思,但这不是阿夫申已经尝试过的吗?他的问题在于不同的开发人员难以记住要使用哪些键。我同意你所描述的方法是正确的。我不明白还有其他的方法。顺便感谢您提供的好链接。 - Spock

48
当你面临需要解决的问题时(实际上,现在谁不是呢?),我们计算机人通常采用的基本策略被称为“分而治之”。它的步骤如下:
1. 将特定问题概念化为一组较小的子问题。 2. 解决每个较小的问题。 3. 将结果组合成特定问题的解决方案。
但是,“分而治之”并不是唯一可能的策略。我们也可以采取更加普遍的方法:
1. 将特定问题概念化为更一般问题的特殊情况。 2. 解决更一般的问题。 3. 将更一般问题的解决方案适应到特定问题中。
- Eric Lippert
我相信像ASP.Net/C#这样的服务器端语言已经存在许多解决方案来解决这个问题。
我已经概述了问题的一些主要方面:
- 问题:我们需要仅为所需语言加载数据。 - 解决方案:为此,我们将数据保存到每种语言的单独文件中。

例:res.de.js、res.fr.js、res.en.js、res.js(默认语言)

  • 问题:每个页面的资源文件应该分开,这样我们只获取需要的数据

    解决方案:我们可以使用一些已经存在的工具,比如https://github.com/rgrove/lazyload

  • 问题:我们需要一个键值对结构来保存我们的数据

    解决方案:我建议使用JavaScript对象代替字符串/字符串air。 我们可以从IDE的智能感知中受益

  • 问题:通用成员应该存储在公共文件中,所有页面都应该访问它们

    解决方案:为此,我在Web应用程序的根目录下创建了一个名为Global_Resources的文件夹,以及一个用于存储每个子文件夹全局文件的文件夹,我们将其命名为“Local_Resources”

  • 问题:每个子系统/子文件夹/模块成员应该覆盖其范围内的Global_Resources成员

    解决方案:我考虑了每个文件

应用程序结构

root/
    Global_Resources/
        default.js
        default.fr.js
    UserManagementSystem/
        Local_Resources/
            default.js
            default.fr.js
            createUser.js
        Login.htm
        CreateUser.htm

文件的相应代码:

Global_Resources/default.js

var res = {
    Create : "Create",
    Update : "Save Changes",
    Delete : "Delete"
};

Global_Resources/default.fr.js

var res = {
    Create : "créer",
    Update : "Enregistrer les modifications",
    Delete : "effacer"
};

从Global_Resource选择的页面上应该加载所需语言的资源文件 - 这应该是所有页面上首先加载的文件。

UserManagementSystem/Local_Resources/default.js

res.Name = "Name";
res.UserName = "UserName";
res.Password = "Password";

UserManagementSystem/Local_Resources/default.fr.js

res.Name = "nom";
res.UserName = "Nom d'utilisateur";
res.Password = "Mot de passe";

UserManagementSystem/Local_Resources/createUser.js

// Override res.Create on Global_Resources/default.js
res.Create = "Create User"; 

UserManagementSystem/Local_Resources/createUser.fr.js

// Override Global_Resources/default.fr.js
res.Create = "Créer un utilisateur";

manager.js文件(此文件应在最后加载)

res.lang = "fr";

var globalResourcePath = "Global_Resources";
var resourceFiles = [];

var currentFile = globalResourcePath + "\\default" + res.lang + ".js" ;

if(!IsFileExist(currentFile))
    currentFile = globalResourcePath + "\\default.js" ;
if(!IsFileExist(currentFile)) throw new Exception("File Not Found");

resourceFiles.push(currentFile);

// Push parent folder on folder into folder
foreach(var folder in parent folder of current page)
{
    currentFile = folder + "\\Local_Resource\\default." + res.lang + ".js";

    if(!IsExist(currentFile))
        currentFile = folder + "\\Local_Resource\\default.js";
    if(!IsExist(currentFile)) throw new Exception("File Not Found");

    resourceFiles.push(currentFile);
}

for(int i = 0; i < resourceFiles.length; i++) { Load.js(resourceFiles[i]); }

// Get current page name
var pageNameWithoutExtension = "SomePage";

currentFile = currentPageFolderPath + pageNameWithoutExtension + res.lang + ".js" ;

if(!IsExist(currentFile))
    currentFile = currentPageFolderPath + pageNameWithoutExtension + ".js" ;
if(!IsExist(currentFile)) throw new Exception("File Not Found");

希望它能有所帮助 :)

7
我不喜欢这种方法的唯一之处在于本地化和开发紧密耦合...因此,当添加一个英语(默认)字符串时,其他语言必须通过代码进行更新。我更愿意使用工具从某种类型的翻译文件创建 JSON 文件。但仍是很好的表现! - Nate-Wilkins
你可以像本地化一样完成相同的操作,你可以在这个查询中看到:https://stackoverflow.com/q/53864279/4061006。唯一的问题是如何将 Global_Resources/default.js 翻译成 Global_Resources/default.fr.js?你使用哪个工具/套件将这些文件转换为所需的语言?因为我也需要这个。 - Jayavel
应该为每个键存储一个人类可读的注释,描述字符串的位置和含义,以便在添加新语言时向翻译者(或自己)提供更多上下文,并且忘记一些字符串的含义。可以像本地化Chrome扩展程序中所做的那样,像"Create": {"message": "Create", "description": "text on the button that opens the editor with a blank Foo"}这样处理。或者创建一个单独的文件来存储这些注释。 - user3064538

14

jQuery.i18n 是一个轻量级的 jQuery 插件,可用于在您的网页中启用国际化。它允许您将自定义资源字符串打包到“.properties”文件中,就像 Java 资源束一样。它基于提供的语言或浏览器报告的语言来加载和解析资源束(.properties)。

要了解更多信息,请查看使用 JQuery 国际化页面的方法?


链接已经消失。 - Alexander Farber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接