在Java中放置i18n键字符串的位置

时间:2011-08-09 07:47:31

标签: java internationalization

在Java中进行国际化时,为每条消息分配一个字符串键。什么是最佳实践,在哪里放置这些字符串键。目标是允许轻松重构(例如,密钥名称更改),清晰可读的代码,关注点的分离,但即使从代码的不同部分调用,仍然没有重复的密钥/消息。

//bad way, strings directly in code
messages.getString("hello_key");

-

// better way, use String constants
public static final String HELLO_KEY = "hello_key";
...
messages.getString(HELLO_KEY);

-

// other (better?) way, put all keys in one huge central class
public class AllMessageKeys {
  public static final String HELLO_KEY = "hello_key";
  ...
}

public class Foo {
  ...
  messages.getString(AllMessageKeys.HELLO_KEY);
}

-

// other (better?) way, put all keys in neighbor class
public class FooMessageKeys {
  public static final String HELLO_KEY = "hello_key";
}

public class Foo {
  ...
  messages.getString(FooMessageKeys.HELLO_KEY);
}

还有其他提案吗?哪个最好?我在Eclipse IDE上,如果这使得重构部分更清晰。

澄清:在上面的示例中,“messages”的类型为ResourceBundle。

8 个答案:

答案 0 :(得分:7)

基本上,似乎我们都同意需要某种常数。说到常量,我更喜欢Enums。 Java Enum功能非常强大,并且未得到充分利用:

String title = Messages.getString(RunDialogMessages.TITLE);

好的,但我必须做些什么让它看起来像这样?简单的界面,枚举和对标准消息访问例程的轻微修改。让我们从界面开始:

public interface MessageKeyProvider {
    String getKey();
}

枚举:

public enum RunDialogMessages implements MessageKeyProvider {
    TITLE("RunDialog.Title"),
    PROMPT("RunDialog.Prompt.Label"),
    RUN("RunDialog.Run.Button"),
    CANCEL("RunDialog.Cancel.Button");


    private RunDialogMessages(String key) {
        this.key = key;
    }

    private String key;

    @Override
    public String getKey() {
        return key;
    }
}

修改了getString()方法:

public static String getString(MessageKeyProvider provider) {
    String key = provider.getKey();
    try {
        return RESOURCE_BUNDLE.getString(key);
    } catch (MissingResourceException e) {
        return '!' + key + '!';
    }
}

为了完成图片,让我们看一下RunDialog.properties(我会尽快提出一点意见):

RunDialog.Title=Run
RunDialog.Prompt.Label=Enter the name of the program to run:
RunDialog.Run.Button=Run
RunDialog.Cancel.Button=Cancel

显然,您可以使用Enum从属性文件中读取(通过嵌入ResourceBundle),但是它可能违反单一责任原则(以及不要重复自己,因为访问代码需要重复)。

回到属性文件,我有一种感觉(我可能在这里错了),你的目标之一是避免重复翻译。这就是为什么我在上面的例子中放两个运行。你看,这个词会根据上下文以不同的方式翻译(实际上很常见)。在这个例子中,如果我要将它翻译成波兰语,它将如下所示:

RunDialog.Title=Uruchamianie
RunDialog.Prompt.Label=Wpisz nazwę programu do uruchomienia:
RunDialog.Run.Button=Uruchom
RunDialog.Cancel.Button=Anuluj

这是一个令人遗憾的问题,一些奇怪的语言有一个共轭的概念......

答案 1 :(得分:2)

我也认为第一个是最糟糕的选择。在大多数情况下(键仅由一个类使用)我更喜欢第二个使用String常量的解决方案。

如果密钥是从多个类引用的,则邻居类是更好的方法(使用像@moohkooh所提到的接口)。

具有一个中心类的解决方案会创建一个依赖性磁体,这在我看来是一个糟糕的设计。每个包含常量的邻居接口会更好。

如果您不希望接口保存常量,可以使用丰富的枚举:

public enum DESCMessage {

  HELLO("hello_key"),
  OTHER("other_key");

  private final String key;

  private DESCMessage(String key) {
    this.key = key;
  }

  public String key() {
    return key;
  }
}

这可以用作:

messages.getString(DESCMessage.HELLO.key());

答案 2 :(得分:1)

我总是使用这样的东西来列出我的密钥。 Interace的名称主要是DESC = Issue / short descrition / topic和键值。 这样你可以制作一些漂亮的接口和一些通用的接口,例如对于OK或Abort键。

// other (better?) way, put all keys in neighbor class
public interface DESCMessage {
  public static final String HELLO_KEY = "hello_key";
}

public class Foo {
  ...
  messages.getString(DESCMessage.HELLO_KEY);
}

答案 3 :(得分:1)

字符串常量是要走的路。在您定义它们的地方,它实际上取决于您的代码结构和密钥的使用。例如:

  • 如果你只使用一个班级的钥匙,最好把它们放在那里。
  • 如果在代码中重复使用相同的密钥,最好将它们放在帮助程序类中(根据密钥用法和密钥数量的全局或每个包类)

从重构的角度来看,将常量从一个类移动到另一个类比重命名它们或更改它们的值要复杂得多(需要更多更改)。

更改其值时,您无法自动更改已定义的资源。

答案 4 :(得分:1)

这里解释了这样做的好方法之一:Short article on a new approach using NLS与ResourceBundle方法相比具有一些优势。

答案 5 :(得分:0)

IMHO ResourceBundle有助于使用特定于语言环境的属性文件。为了使用ResourceBundle,应根据以下约定命名属性文件: -

BaseName_langCode.properties

OR

BaseName_langCode_countryCode.properties

您可以使用属性文件。

答案 6 :(得分:0)

我很久以前就设计了这种方法,以便获得给定字符串的本地化版本以及轻松添加新条目所需的最短键入时间。我认为它对许多类型的Java项目也很有用。

  1. 枚举类ME(缩写为MessagesEnum)具有所有定义,并且其构造函数将LOREM_IPSUM转换为lorem.ipsum,因此您不必类型构造函数参数。

  2. 一个I18NManager类,负责从上下文中获取Locale并构造/缓存ResourceBundle,最好是Spring @Component

  3. 可选的ServiceRegistry类,用于从I18NManager方法静态访问ME.get()实例以完成循环。

枚举:

public enum ME {

    USERNAME,
    PASSWORD,
    LOGIN,
    LOGOUT,
    DASHBOARD,
    MENU,
    OK,
    CANCEL,
    YES,
    NO,
    CONFIRM,
    DELETE,
    CREATE,
    NEW,
    EDIT,
    ONLINE,
    OFFLINE,
    SALES_REPORTS,
    ...;

    private String key;

    ME(String key){
        this.key = key;
    }

    ME(){
        this.key = name().toLowerCase(Locale.ENGLISH).replace("_", ".");
    }

    public String get(){
        return ServiceRegistry.get().getI18nManager().getMessage(key);
    }

    public String get(Object ...args ){
        return ServiceRegistry.get().getI18nManager().getMessage(key, args);
    }

    public String key(){
        return key;
    }

}

I18NManager:

import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;

public interface I18nManager {

    Locale getCurrentUserLocale();

    ResourceBundle getResourceBundle(Locale locale);

    default String getMessage(String key) {
        return getResourceBundle(getCurrentUserLocale()).getString(key); //FIXME add error handling
    }

    default String getMessage(String key, Object... args) {
        return MessageFormat.format(getMessage(key), args);
    }
}

用法很简单:

ME.SALES_REPORTS.get()

答案 7 :(得分:-1)

恕我直言定义这些键及其相关字符串的最佳位置是NLS文件。您必须将它们保存在ResourceBundle文件中