与编译时检查l18n框架(l18n framework with compiletime check

2019-09-16 16:03发布

我目前工作的一个更大的Java桌面应用程序,现在都在这里我想补充的翻译点。 让我困扰的有关l18n-系统,它不提供任何形式的编译时检查。

在Java的系统,你必须像一个HashMap ,其中每一个本地化的字符串有一个“重点”和翻译字符串则是“价值”。 这看起来是这样的(取自教程例子 ):

Locale currentLocale;
ResourceBundle messages;

currentLocale = new Locale(language, country);

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

如果你有一个简单/小应用这工作不错。 但是,在数千翻译字符串的一个大的应用程序,它可能会因此发生,你有一个错字的“钥匙”,因此得到一个空的或错误的字符串。

随着一点点运气,应用程序将引发RuntimeException给大家介绍一下吧,但即使如此,很可能你甚至不来这一点,因为错误的“钥匙”,在对话框中使用,可能无法在某些露面的情况下(说这是一个错误对话框)。

为了防止这种情况发生,使用系统提供了使用“钥匙”的编译时检查将是更好的主意。 例如,这是在Android上,在那里你在一个XML文件,然后将其编入索引并映射到一个类(包括“键”使用)指定的资源使用。 这样,你得到这样的事情(从Android的文件 ):

// 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正常的桌面应用程序的系统? 还是我可怕的错误我在说什么?

Answer 1:

有一个很简单的解决这个问题。 首先,不要使用魔法字符串作为代码,定义常量和引用它们。 因此,在更改..

messages.getString("greetings");

messages.getString(I18.GREETINGS_CODE);

并具有相应的类;

public class I18 {

  public static final String GREETINGS_CODE = "greetings";

}

接下来写凡在你的每个代码一个测试用例I18类每种语言资源文件中查找。 如果任何资源文件丢失的代码,然后测试失败。 它不是编译时间,但如果你有任何问题,你的项目会失败的考验。



Answer 2:

I have seen few fairly large enterprise applications that use standard Resource Bundles and trust me, this is not a problem.

There are few success factors:

  1. Resource Organization
  2. Resource Helpers
  3. Resource Factories
  4. Key naming strategy

Ad 1. Your issues will be especially visible if you decide on using single Resource Bundle. Do not go this way. There is no way to maintain large, single file. You will face problems with key duplication, key naming and so. Besides, with large applications it is common to have few people working on the same set of files at the same time. In case of Resource Bundles it means a lot of merging and getting each other in the way. This is the problem in the regular programming team, but trust me with Localizers it is even worse.
What you need is multiple, small resource files. Depending on the application scale, it might be one file per module, or even one file per dialog. How to split the files depends on the actual application, but what I recommend is not having resource file longer than two screens of text.
With large number of files, the problem is how to organize them - put to valid directories. What I'd suggest is to separate resource files from the source code (place them in the separate directory called - for example - L10n). Under the resource files root it is good to have language folders (so that is easy to separate the translations for each target language), although it would require messing with ResourceBundle.Control class. Then under the language root you would create subdirectories for each module. Under the module root you would have either separate subdirectories (with very large application), or you will place your Resource Bundles directly.

Ad 2. There is no way to use ResourceBundle class directly. One of the problems is MissingResourceException thrown if there is no such key in the file(s), or there is no file you are looking for. Instead, you would probably like to see (on the User Interface) that something went wrong, for example the name of the key between exclamation marks (!the.key.does.not.exist!) - this is what Eclipse's "Externalize Strings" does by default.
You might decide on creating Resource Helper which might be either wrapper on ResourceBundle or may derive from it. The choice is yours (although with serious Resource Organization like above the reasonable choice is to derive, as you need to create your own ResourceBundle.Control class). Beside simple translation handling you can add additional functionality to Resource Helper, like handling Message Formatting, Plural Forms, etc.
For now it won't handle your problem of wrong resource key names. You would need actual UI testing - the typos would be visible. In case of using wrong keys (especially for someone who use Copy-Paste Design Pattern ;)) I don't think that there is any technology that would ever resolve the issue. Machines doesn't think. Anyway, you can handle the problems with keys by placing (for example) nested classes with key constants or nested enums. The problem with such solution is, you would need separate Resource Helper for each module, which is not necessary the good idea (especially in large application). It is feasible, but cumbersome. And of course IDE's built-in mechanism for externalizing strings won't work with this solution.

Ad 3. In order to work with large number of resource files, you need to build static Resource Factory (or Factories) that will create Resource Helpers for you. My strategy is to nest a publicly visible enum inside the Factory and have resource files as its constants. Then, I request valid bundle, for example:

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

Ad 4. In order to avoid mess, the team must decide on single resource key naming strategy. What I'd suggest (having one file per module) for key name is to have:

  • dialog or unit
  • descriptive name
  • the context

For example:

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

The context (label, dialog.title, item, error, message, pattern and so on) is very important for the translators. The translations usually vary depending on the context. This is also the reason why you should not re-use the translations (apart from very common items like common button names "OK", "Cancel", "Abort", "Help" or common menu names and items).



Answer 3:

我从来心脏这样一个工具,即可以索引你的资源。 恕我直言,相当不寻常的要求。 其中一个办法 - 而不是用在你的代码字符串字面量,它们都移动到一个类,静态final修饰符声明它们。 使用此常量作为你的资源图的关键。 而且它很容易写测试,使用反射将检查,在你出现在资源的文件单个类中声明的所有资源。 它更容易,然后写代码分析器



Answer 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编译你可以肯定你会看到正确的翻译。

见上面的详细信息的链接 。



文章来源: l18n framework with compiletime checking