加载本地化资源

4
在我正在工作的一个项目中,我希望根据当前语言环境加载不同的资源。对于字符串,这很容易实现:只需通过添加一些文件创建资源包,例如 Localization.propertiesLocalization_de.properties,然后通过 ResourceBundle.getBundle("domain.project.Localization") 加载它即可。
但是,如何加载非简单字符串的本地化资源呢?
具体而言,我想根据语言环境加载图像和 HTML 文件。获取到该资源的正确 URL 就足够了。例如,我想要有一个 Welcome.htmlWelcome_de.html 文件,在当前语言环境为德语时加载后者。
简单地使用 getClass().getResource("Welcome.html") 并不能解决问题,它总是返回那个文件,并不会处理语言环境。我还没有找到方便的方法来解决这个问题,除了根据当前语言环境手动更改文件名。
我想到的一个解决方案是,在我的 Localization.properties 文件中添加一个键,其中包含正确本地化 HTML 文件的名称。但这感觉有点糊弄人,不太对。
请注意,这不是一个 Android 应用程序,只是一个标准的 Java 8 桌面应用程序。

为什么不把文件和本地化映射放在不同的文件夹中呢?比如“/LANGUAGE/img/image1.jpg”,其中语言可以是“DE”或“EN”或其他任何语言。这样,您只需要在某个地方存储语言即可[可能需要处理缺少的文件(回退到肯定完整的资源上)]。 - Cyphrags
@Cyphrags:那其实是个好主意,我会考虑的。 - DarkDust
5个回答

5
实际上,获取与ResourceBundle.getBundle相同的行为非常容易。我已经查看了代码,并发现处理区域设置和文件名最重要的部分是ResourceBundle.Control
因此,这里有一个简单的方法,返回本地化资源的URL(遵循与资源包相同的文件名方案),不进行缓存,并支持当前语言环境:
/** Load localized resource for current locale.
 * 
 * @param baseName Basename of the resource. May include a path.
 * @param suffix File extension of the resource.
 * @return URL for the localized resource or null if none was found.
 */
public static URL getLocalizedResource(String baseName, String suffix) {
    Locale locale = Locale.getDefault();
    ResourceBundle.Control control = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT);
    List<Locale> candidateLocales = control.getCandidateLocales(baseName, locale);

    for (Locale specificLocale : candidateLocales) {
        String bundleName = control.toBundleName(baseName, specificLocale);
        String resourceName = control.toResourceName(bundleName, suffix);

        // Replace "Utils" with the name of your class!
        URL url = Utils.class.getResource(resourceName);
        if (url != null) {
            return url;
        }
    }

    return null;
}

如果有人想要将其扩展以支持泛型语言环境作为参数:在ResourceBundle.getBundleImpl实现中的重要部分是:
for (Locale targetLocale = locale;
     targetLocale != null;
     targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
         List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
         ...
}
< p > getCandidateLocales 的最后一个条目是空语言环境 (Locale.ROOT)。在查找默认资源的第一个 for 迭代之前,应忽略它,因为这时候默认资源已经被找到了,而无需测试后备语言环境。


1

(这是对我的评论的补充,以便更详细地解释)

您可以根据语言将资源放在不同的文件夹中。

文件树可能如下所示:

  • pathToRessources

    • DE

      • Images
        • Image1.png
        • Image2.png
      • Localization
        • MainPage // 更适合网站....
        • Impressum
    • EN

      • ...

然后使用类似于“file-localization-path-builder-class”(声称是最好的名称)的东西。

public static Locale currentLocale = Locale.GERMAN;

public static String baseRessourcePath = "Path to your basic folder for ressources";

public static String getLocalizedPathToFile(String ressourcePath)
{
    return Paths.get(currentLocale.getLanguage(), ressourcePath).toString();
}

无论何时需要加载资源,只需调用您的加载器:

getLocalizedPath(PathToFile)

其中PathToFile类似于/Images/Image1.png或其他内容。

这对客户端应用程序也非常容易使用。

希望这可以帮到你 :)


谢谢,我喜欢你的想法(已点赞),但与此同时,我找到了我真正需要的解决方案 - DarkDust

0

关于 ListResourceBundle 怎么样?

定义你的资源包:

public class MyBundle_fr_FR extends ListResourceBundle
{
    public Object[][] getContents()
    {
        return new Object[][] 
        {
            { "welcome_file", getClass().getResource("Welcome_fr.html") },
            { "another_file", getClass().getResource("foo_fr.html") }
        };
    }
}

public class MyBundle_de_DE extends ListResourceBundle
{
    public Object[][] getContents()
    {
        return new Object[][] 
        {
            { "welcome_file", getClass().getResource("Welcome_de.html") },
            { "another_file", getClass().getResource("foo_de.html") }
        };
    }
}

然后像这样使用:

ResourceBundle bundle = ResourceBundle.getBundle("MyBundle", Locale.GERMANY);
URL url = (URL) bundle.getObject("welcome_file");

这只是文件名-属性文件方案的一个变体。也就是说,对于每个资源,我都需要在ListResourceBundle中添加新条目。我本来会将文件名放在我的“Localization.properties”文件中;不过你的建议具有返回URL而不仅仅是文件名的优势。 - DarkDust

0
你现在的做法对我来说有点奇怪。我开发了一些国际应用程序,从未使用过这种方法。你的做法维护成本非常高。想象一下,如果你想要更改welcome.html文件的某个部分,你需要更改所有其他欢迎文件,这是一个巨大的工作量。
在Java中解决这个问题的常见方法是使用JSTL fmt taglib,并借助一些包含键值对的属性文件来获取帮助,其中键是固定的,而值根据所在的语言环境而变化。例如,在你的Localization.properties文件中,你可以有像键值对这样的内容:
page.welcome.welcome=welcome

你还有另一个文件名为Localization_de.properties,它具有相同的键但不同的值

page.welcome.welcome=willkommen

现在,在您的welcome.jsp中,您可以使用类似于以下代码的内容:<fmt:message key="page.welcome.welcome"/>以便在页面中呈现正确的值。对于图像,您可以采用相同的方法。只需将图像名称导入到每个属性文件中作为键,并适当命名其值,然后在jsp中使用该键即可。

对于i18n,请使用Eclipse插件(例如Resource Bundel Editor)使您的生活更加轻松。

在我使用的其他语言框架(如django)中,我看到了与上述方法相同的方法。


你真的会为了长长的帮助文件这样做吗?如果只是本地化一个只有几个字符串的文件,我想那可能有些烦人,但你还是可以做到的。但如果是一个有30个段落、加上表格和列表的HTML页面呢?那似乎非常麻烦,而且我不会在其他平台上比如Mac、iOS或Android上这么做,因为你可以通过将它们放入正确的目录中来本地化完整任意文件。特别是当你要本地化30个帮助文件时:你最终会得到一个庞大无比的资源属性文件,其中还很容易出现死条目。对我来说,这样做就感觉非常不对。 - DarkDust
我所从事的大多数应用程序都是以数据为中心的,通常没有太多的静态文件。我们通常使用数据库来获取本地化数据。对于在视图中向用户显示的数据,我们通常将它们放在属性文件中。针对您的具体问题,我认为Apache有一些静态文件国际化的工具,但我没有使用过。 - Reza

0

拥有保存本地化文件名称的资源确实是一个有效的解决方案,但正如您所提到的那样,它很繁琐。如果您需要在应用程序中显示HTML页面,为什么不嵌入小型Web服务器,并让它提供JSP页面呢?这样,整个HTML将保持不变,页面本身将被本地化。

另一种选择是使用本地模板引擎(例如FreeMarker)在提供服务之前动态生成HTML。


有趣的想法,但并不适合我的需求。我喜欢用长格式文本展示帮助页面。本地化的JSP页面似乎需要更多的工作量。而且,这感觉像是用大锤砸小螺母。最后,它也不能解决加载本地化图像的问题。 - DarkDust
这正是Eclipse正在做的事情-对于帮助,他们打开一个本地的Tomcat实例。此外,对于本地化的图像,您需要将图像名称保留为本地化资源。我们要讨论多少个页面? - David Rabinowitz
现在只有一个。我预计可能需要十几个或二十个。这方面有太多让我感到紧张的事情。似乎编辑这样的页面更加麻烦。Tomcat比我的应用程序更大,我想它的内存占用也更高。为了在WebView中显示HTML文件,设置和维护起来很费力。我真的很感激这个想法,但是对于我来说,这方面的问题太多了。 - DarkDust
Tomcat是Eclipse的方式,通常较小的应用程序使用Jetty。另外,你能否直接打开一个Web视图到你自己的Web服务器?如果可以,你可以将这些文件托管到服务器上,并将区域设置作为参数传递。 - David Rabinowitz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接