l18n框架与编译时检查

时间:2012-04-20 15:09:24

标签: java internationalization translation

我目前正在开发更大的Java桌面应用程序,现在正处于我想要添加翻译的程度。让我对l18n系统感到困扰的是,它没有提供任何类型的编译时检查。

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

Locale currentLocale;
ResourceBundle messages;

currentLocale = new Locale(language, country);

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

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

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

为防止这种情况发生,使用提供编译时检查所用“密钥”的系统将是更好的主意。例如,这在Android中使用,您可以在XML文件中指定Resources,然后将其编入索引并映射到类(包括要使用的“Keys”)。这样,您可以得到类似的内容(来自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 个答案:

答案 0 :(得分:4)

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

messages.getString("greetings");

messages.getString(I18.GREETINGS_CODE);

并拥有相应的类;

public class I18 {

  public static final String GREETINGS_CODE = "greetings";

}

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

答案 1 :(得分:3)

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

成功因素很少:

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

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

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

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

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

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

    例如:

    • validation.invalid.username.error
    • validation.enter.pass.label
    • validation.dialog.title
    • menu.main.file.open.item
    • login.dialog.ok.button

    翻译人员的上下文(标签,对话框,标题,项目,错误,消息,模式等) 非常重要 。翻译通常根据具体情况而有所不同。这也是您不应该重复使用翻译的原因(除了常见的项目,如常用按钮名称“确定”,“取消”,“中止”,“帮助”或常用菜单名称和项目)。

答案 2 :(得分:2)

我从不关心可以为您的资源建立索引的工具。 Imho,相当不寻常的要求。其中一种方法 - 不是在代码中使用字符串文字,而是将它们全部移动到单个类中,使用静态最终修饰符声明它们。将此常量用作资源映射的键。编写测试非常容易,使用反射将检查,单个类中声明的所有资源都存在于资源的文件中。它更简单,然后编写代码分析器

答案 3 :(得分: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 一起编译,你就可以确定你会看到正确的翻译。

请参阅link above more details