27

I have some resource bundles packaged in my main jar

widget_en.properties
widget_de.properties

I retrieve a resource bundle based on my default locale as folows

ResourceBundle.getBundle("widget", Locale.getDefault());

But I want to present the user with a list of available languages supported so that can select a language that may be different to their computers default

But I can't find a method in ResourceBundle that would list available locales, I don't want to hardcode a list as I may forget to update it when another resource bundle is added.

EDIT

As I only resource bundles for different language (I dont have country refinements) so I have got generated a list by iterating through all the known language codes and check for each one as a resource.

String[]langs = Locale.getISOLanguages();
for(String lang:langs)
{
      URL rb = ClassLoader.getSystemResource("widget_"+lang+".properties");
      if(rb!=null)
      {
            System.out.println("Found:"+rb.toString());
      }
}
Paul Taylor
  • 13,411
  • 42
  • 184
  • 351
  • 3
    this is a great question and a great opportunity for Java to provide a utility method to return application specific available resources. My research has turned up no useful solution other than iterating or searching files which seems a waste of resources. – JesseBoyd Sep 23 '17 at 21:50

5 Answers5

6

If you really package the resource files inside your JAR, then I would do it like this:

public static void main(String[] args) {
  Set<ResourceBundle> resourceBundles = getResourceBundles(A.class.getName());
  if (resourceBundles.isEmpty())
    // ...
}

public static Set<ResourceBundle> getResourceBundles(String baseName) {
  Set<ResourceBundle> resourceBundles = new HashSet<>();

  for (Locale locale : Locale.getAvailableLocales()) {
    try {
      resourceBundles.add(ResourceBundle.getBundle(baseName, locale));
    } catch (MissingResourceException ex) {
      // ...
    }
  }

  return Collections.unmodifiableSet(resourceBundles);
}

If you care about your JARs then you would at least get a set containing the default resource for a given baseName.

If you have only resources with names like baseName_<country> this method works perfectly, because only those ResourceBundles will be added to the set which are present in your JAR. It'll work even if you decide you need separate baseName_en_US and baseName_en_UK resources, which is not unheard of.

Shameless self-plug: I wrote a ResourceBundle.Control which takes a Charset as its argument, you might be interested in it if you want to load UTF-8 encoded resources files. It's available at GitHub.

Kohányi Róbert
  • 9,791
  • 4
  • 52
  • 81
  • Yes I really do package them within my jar, in a previous project I did not, but not Im using maven to build it seemed to want to put it in the jar, is that wierd ? – Paul Taylor Aug 22 '12 at 13:10
  • @PaulTaylor I like it when static resources are truly static. I don't like adding checks to my code like `new File(file).exists()` even if I only want to read a damn property from that file, which is *there* 99.9% of the time. Anyways, I think you have to apply *some level of hack* to your code if you want to load properties from external files with the ResourceBundle class - although I'm not sure - so it doesn't really matter either way. Bottom line: I think you're doing it just fine. – Kohányi Róbert Aug 22 '12 at 13:22
  • Oh I see you mean storing them outside of java file rather than storing them in properties file within/outside of jar. Storing the resource bundles as properties files allows me to use tools to easily edit and translate the properties to other languages. – Paul Taylor Aug 22 '12 at 15:06
  • @PaulTaylor I meant that if you have a JAR `application.jar` the resource property file should be inside this JAR (for localizable content at the least). Sorry if I was confusing. – Kohányi Róbert Aug 22 '12 at 16:07
  • Still don't understand your point, yes they are in the main jar thats what I said,or is application.jar a particular naming convention in application servers, if so not relevant for this desktop application – Paul Taylor Aug 22 '12 at 22:05
  • @PaulTaylor I was referring to the fact that you could have store your localizable properties elsewhere, like *beside* your JAR file. In this case, as I know, you couldn't have used the `ResourceBundle` class in the first place. Sorry again for being confusing, I was confused myself, because when I wrote my post I thought for a minute that you can use `ResourceBundle` to read stuff from arbitrary property files (although one could write a custom control for doing so). – Kohányi Róbert Aug 23 '12 at 05:01
5

I don't think there is an API for this because new valid locale objects can be created on the fly:

Locale locale = new Locale("abcd");

without the need to register it somewhere. And then you can use a resource bundle widget_abcd.properties without restrictions:

ResourceBundle resource= ResourceBundle.getBundle("widget", new Locale("abcd"));

From the java.util.Locale API docs:

Because a Locale object is just an identifier for a region, no validity check is performed when you construct a Locale. If you want to see whether particular resources are available for the Locale you construct, you must query those resources.

To solve the problem you can still iterate over all files called "widget_" in the resource directory and discover the new added resource bundles.

Note that Locale.getAvailableLocales() is not 100% sure for the above reason: you might some day define a non standard locale. But if you'll add only a few standard locales you can use this static method to iterate over the system locales and get the corresponding bundles.

Uluk Biy
  • 48,655
  • 13
  • 146
  • 153
dcernahoschi
  • 14,968
  • 5
  • 37
  • 59
  • Okay I see wht you mean. I'm thinking about doing the iteration but the files are within a jar not directly within a os folder, so how do I do that ? – Paul Taylor Aug 22 '12 at 12:17
  • The best idea that comes into my mind is to use the java Zip API and to look inside the jar for files named `widget_*.properties` – dcernahoschi Aug 22 '12 at 12:29
  • Problem is where the jar file is is relative to your working directory ectera (i/e on oSX this can be trickly to establish), so I tried using classloader but it doesnt support any kind of wildcard seaerch i.e ClassLoader.getSystemResources("widget_en.properties"); finds one file but ClassLoader.getSystemResources("widget_*"); returns nothing – Paul Taylor Aug 22 '12 at 12:36
  • If you'll add only a few standard locales you can use Locale.getAvailableLocales() to iterate over the system locales and get the corresponding bundles. But is not 100% sure. – dcernahoschi Aug 22 '12 at 12:42
2

If you can make two basic assumptions:

1) You have a default resource bundle with no locale, or at least one locale that you know is there.

2) All your resources are in the same location (ie. the same path within a single jar file)

Then you can get the URL for a single resource:

URL url = SomeClass.class.getClassLoader().getResource("widget.properties");

once you have that, you should be able to parse the URL.

If you use commons-vfs, you should be able to convert the URL into a FileObject:

FileSystemManager manager = VFS.getManager();
FileObject resource = manager.resolveFile(url.toExternalForm());
FileObject parent = resource.getParent();
FileObject children[] = parent.getChildren();
// figure out which ones are bundles, and parse the names into a list of locales.

This will save you the trouble of dealing with the complexities of jar: style url's and such, since vfs will handle that for you.

Matt
  • 11,523
  • 2
  • 23
  • 33
2

A solve it by listing files.

public class JavaApplication1 {

    public static final ArrayList<String> LOCALES = new ArrayList<>();

    static {
        try {
            File f = new File(JavaApplication1.class.getResource("/l10n").toURI());
            final String bundle = "widget_";// Bundle name prefix.
            for (String s : f.list(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.startsWith(bundle);
                }
            })) {
                LOCALES.add(s.substring(bundle.length(), s.indexOf('.')));
            }
        } catch (URISyntaxException x) {
            throw new RuntimeException(x);
        }
        LOCALES.trimToSize();
    }

    ...

}
Daniel De León
  • 13,196
  • 5
  • 87
  • 72
0

This is my solution,hope can help people.

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.PrefixFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.lang3.StringUtils; 
private static Locale[] getAvailableLocales(String name) { 
    String path = thisClass.class.getClassLoader().getResource("").getPath();
    if (path.contains("%20")) {
        path = path.replaceFirst("%20", " ");
    }
    File file = new File(path);
    Locale[] array = FileUtils.listFiles(file, new AndFileFilter(new PrefixFileFilter(name), new SuffixFileFilter(".properties")),null)
            .stream()
            .map(File::getName)
            .map(v -> StringUtils.substringBetween(v, name + '_', ".properties"))
            .map(v->v.replaceFirst("_","-"))
            .map(Locale::forLanguageTag)
            .toArray(Locale[]::new);
    return array;
}