5

我目前正在开发一个更大的 Java 桌面应用程序,现在我想添加翻译。l18n 系统让我困扰的是,它不提供任何类型的编译时检查。

在java的系统中,你有类似a的东西HashMap,其中每个本地化字符串都有一个“Key”,翻译后的字符串就是“Value”。这看起来像这样(取自教程示例):

Locale currentLocale;
ResourceBundle messages;

currentLocale = new Locale(language, country);

messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
System.out.println(messages.getString("greetings"));

如果您有一个简单/小型的应用程序,这很好用。但是在一个包含数千个翻译字符串的大型应用程序中,您可能会在“Key”中出现拼写错误,从而得到一个空字符串或错误字符串。

运气好的话,应用程序会抛出一个RuntimeException告诉你它的信息,但即便如此,你也可能没有达到这一点,因为在某些情况下可能不会显示的对话框中使用了错误的“键” (说这是一个错误对话框)。

为了防止这种情况发生,使用提供编译时检查使用的“键”的系统会是更好的主意。这例如在 Android 中使用,您在 XML 文件中指定资源,然后将其索引并映射到类(包括要使用的“键”)。这样,你会得到这样的东西(来自Android Docs):

// Set the text on a TextView object using a resource ID
TextView msgTextView = (TextView) findViewById(R.id.msg);
msgTextView.setText(R.string.hello_message);

如果你在那个“键”中有错字,你会在编译时得到一个错误(你的 IDE 也有“自动完成”)。

现在,要完成这样的工作,您需要一个小工具/脚本来执行索引部分并生成资源类 ( R.java)。在 Android 中,Eclipse 插件(或一般的 IDE)会为您执行此操作。

我现在的问题是:是否已经有一个系统可以用于 Java 中的普通桌面应用程序?还是我说的话大错特错?

4

4 回答 4

4

这个问题有一个相当简单的解决方案。首先,不要使用魔术字符串作为代码,定义常量并引用它们。所以你改变..

messages.getString("greetings");

messages.getString(I18.GREETINGS_CODE);

并有相应的班级;

public class I18 {

  public static final String GREETINGS_CODE = "greetings";

}

接下来编写一个测试用例,I18在每个语言资源文件中查找类中的每个代码。如果任何资源文件缺少代码,则测试失败。它不是编译时间,但如果您有任何问题,您的项目将无法通过测试。

于 2012-04-20T15:27:32.993 回答
3

我见过一些相当大的企业应用程序使用标准资源包并相信我,这不是问题。

成功的因素很少:

  1. 资源组织
  2. 资源助手
  3. 资源工厂
  4. 关键命名策略

广告 1. 如果您决定使用单个资源包,您的问题将特别明显。不要走这条路。没有办法维护大的单个文件。您将面临密钥重复、密钥命名等问题。此外,对于大型应用程序,很少有人同时处理同一组文件是很常见的。在资源包的情况下,这意味着大量的合并和相互阻碍。这是常规编程团队的问题,但相信我,使用 Localizers 会更糟
您需要的是多个小型资源文件。根据应用程序规模,它可能是每个模块一个文件,甚至每个对话框一个文件。如何拆分文件取决于实际应用程序,但我建议不要让资源文件长于两屏文本。
对于大量文件,问题在于如何组织它们 - 放入有效目录。我的建议是分开源代码中的资源文件(将它们放在名为 - 例如 - L10n 的单独目录中)。在资源文件根目录下,最好有语言文件夹(这样很容易将每种目标语言的翻译分开),尽管它需要弄乱 ResourceBundle.Control 类。然后在语言根目录下为每个模块创建子目录。在模块根目录下,您将拥有单独的子目录(具有非常大的应用程序),或者您将直接放置资源包。

广告 2.没有办法直接使用 ResourceBundle 类。如果文件中没有这样的键,或者没有您要查找的文件,则会引发MissingResourceException问题之一。相反,您可能希望(在用户界面上)看到出现问题,例如感叹号之间的键名 (!the.key.does.not.exist!) - 这就是 Eclipse 的“外部化字符串” " 默认情况下。
您可能决定创建 Resource Helper,它可能是 ResourceBundle 的包装器,也可能是从它派生的。选择是你的(尽管像上面这样严肃的资源组织,合理的选择是派生,因为你需要创建自己的 ResourceBundle.Control 类)。除了简单的翻译处理之外,您还可以向 Resource Helper 添加其他功能,例如处理消息格式、复数形式等
。目前它不会处理您错误资源键名称的问题。您将需要实际的 UI 测试 - 拼写错误是可见的。如果使用错误的键(特别是对于使用复制粘贴设计模式的人;))我认为没有任何可以解决问题的技术。机器不思考。无论如何,您可以通过放置(例如)具有键常量或嵌套枚举的嵌套类来处理键问题。这种解决方案的问题是,每个模块都需要单独的资源助手,这不是好主意(尤其是在大型应用程序中)。这是可行的,但很麻烦。当然,IDE 用于外部化字符串的内置机制不适用于此解决方案。

广告 3. 为了处理大量资源文件,您需要构建静态资源工厂(或工厂)来为您创建资源助手。我的策略是在工厂内嵌套一个公开可见的枚举,并将资源文件作为其常量。然后,我请求有效的捆绑包,例如:

ResourceHelper helper = ResourceFactory(
                          ResourceFactory.Bundles.ERRORS, Locale locale);

Ad 4. 为了避免混乱,团队必须决定单一资源键的命名策略。我对键名的建议(每个模块一个文件)是:

  • 对话或单元
  • 描述性名称
  • 上下文

例如:

  • 验证.invalid.username.error
  • 验证.enter.pass.label
  • 验证对话框标题
  • menu.main.file.open.item
  • login.dialog.ok.button

上下文(标签、dialog.title、item、error、message、pattern 等)对译者来说非常重要。翻译通常因上下文而异。这也是您不应该重复使用翻译的原因(除了非常常见的项目,如常见的按钮名称“OK”、“Cancel”、“Abort”、“Help”或常见的菜单名称和项目)。

于 2012-04-21T08:05:17.520 回答
2

我从不介意这样一个可以索引您的资源的工具。恕我直言,相当不寻常的要求。其中一种方法 - 不要在代码中使用字符串文字,而是将它们全部移动到一个类中,使用 static final 修饰符声明它们。将此常量用作资源映射的键。编写测试非常容易,使用反射将检查在单个类中声明的所有资源是否存在于资源文件中。这要容易得多,然后编写代码分析器

于 2012-04-20T15:22:12.157 回答
1

我认为c10n是您正在寻找的(正如@eee 所指出的那样,谢谢!)。

整个想法是让您免于编写容易出错的键,并用编译时检查的纯 Java 接口方法替换它们,并用翻译值进行注释。

例如:

public interface Bundle{
  @En("Hello, World!")
  @De("Hallo Welt!")
  String greeting();
}

// ... somewhere in your code
public class MyClass{
  private static final Bundle bundle = C10N.get(Bundle.class);

  public void myMethod(){
    System.out.println(bundle.greeting());
  }
}

您可以对Bundle接口进行任何重构,重命名和移动方法,添加更多接口,扩展它们,组合它们。只要它使用javac编译,您就可以确定您会看到正确的翻译。

更多详细信息请参见上面的链接

于 2012-04-21T09:21:39.427 回答