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


在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。


6970
2017-08-09 07:47


起源

如果您希望人们懒得回答您的问题,请不要低估答案。我打算修改我的答案,但我不认为这是值得的。 - Stephen C
@Stephen C:作为一个反驳论点:向下投票不好的答案。这就是投票系统的重点。在这里可以轻松区分那些对某些人来说足够清楚的人和那些没人理解的人。它也应该作为正确性度量标准(尽管通常不会)。 - Paweł Dyda


答案:


我总是使用这样的东西一个我的键列出的界面。 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);
}

1
2017-08-09 07:55



我对此的评论是与“界面描述类型”“最佳实践”(Checkstyle,Bloch)背道而驰。 - Alistair A. Israel
同意@AlistairIsrael。这是Bloch描述的“恒定界面”反模式的典型例子。请不要使用它。 - Mike
同意aisrael和Mike。如果你想要一个类似的方法,那么Enum最适合这个(正如Arne Burmeister所指出的那样),如果你不喜欢额外的key()调用,你可以简单地使用静态最终常量。 - JavierJ


基本上,似乎我们都同意需要某种常数。谈到常数,我更喜欢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

这是一个有着共轭概念的奇怪语言的不幸问题......


7
2017-08-09 17:23





我也认为第一个是最糟糕的选择。在大多数情况下(键仅由一个类使用)我更喜欢第二个使用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
2017-08-09 08:03



不知怎的,当我读到这个问题时,我能够省略你的答案......多么不幸。我非常同意枚举用法:) - Paweł Dyda


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

  • 如果你只使用一个类中的键,最好将它们放在那里。
  • 如果在代码中重复使用相同的键,最好将它们放在辅助类中(全局或每个包类,具体取决于密钥用法和密钥数)

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

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


1
2017-08-09 08:04





这里解释了这样做的好方法之一: 关于使用NLS的新方法的简短文章 与ResourceBundle方法相比具有一些优势。


1
2017-12-14 15:01





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

BaseName_langCode.properties

要么

BaseName_langCode_countryCode.properties

您可以使用属性文件。


0
2017-08-09 08:16



将密钥放在代码中的资源文件命名到底是什么? - Arne Burmeister


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


-1
2017-08-09 07:53