【问题标题】:l18n framework with compiletime checking带有编译时检查的 l18n 框架
【发布时间】:2012-04-20 15:09:24
【问题描述】:

我目前正在开发一个更大的 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 来告诉你它,但即使这样你也可能没有到达这一点,因为在对话框中使用了错误的“键”,可能在某些情况下不显示(比如说这是一个错误对话框)。

为了防止这种情况发生,使用一个提供编译时检查所用“键”的系统会是更好的主意。这例如在 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 中的普通桌面应用程序?还是我说的话大错特错?

【问题讨论】:

  • 使用 C10n github.com/rodionmoiseev/c10n 而不是 messages.getString("greetings"),您可以这样做 messages.greetings() 并使用注释定义每种语言。

标签: java internationalization translation


【解决方案1】:

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

messages.getString("greetings");

messages.getString(I18.GREETINGS_CODE);

并有相应的类;

public class I18 {

  public static final String GREETINGS_CODE = "greetings";

}

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

【讨论】:

    【解决方案2】:

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

    成功的因素很少:

    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. 为了避免混乱,团队必须决定单一资源键命名策略。我对键名的建议(每个模块一个文件)是:

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

    例如:

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

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

    【讨论】:

    • 感谢所有输入和命名模式的想法。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      我认为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

      【讨论】:

        猜你喜欢
        • 2011-02-24
        • 2013-03-16
        • 2012-09-09
        • 2011-11-12
        • 1970-01-01
        • 2016-12-20
        • 2011-03-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多