Skip to content

Better localisation support #686

@costas80

Description

@costas80

The approach currently used by this library to localise error messages is by setting Locale.setDefault(aLocale). For example, as documented here, one is expected to do as follows:

Locale.setDefault(Locale.GERMAN);
JsonSchemaFactory jsonFactoryInstance = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
JsonSchemaFactory schemaFactory = JsonSchemaFactory.builder(jsonFactoryInstance).build();

The problem with this approach is that this sets the default Locale for the entire JVM, which in any multi-threaded scenario like a web application is not what you would want. Moreover, checking the code of I18nSupport I see that the resource bundle is loaded in a static initialiser and stored in a static final variable:

public class I18nSupport {

    private static final ResourceBundle bundle;

    static {
        ResourceBundle tmpBundle = null;
        try {
            tmpBundle = ResourceBundle.getBundle(BASE_NAME);
        } catch (MissingResourceException mre) {
           ...
        }
        bundle = tmpBundle;
    }

}

In following this approach, there can be no locale switch after the class is initially loaded.

A better approach would be to introduce a "context" of sorts for a given validation run that allows you to set it with the desired Locale. Using this you could then do a Locale-aware lookup of the resource bundle to use and return the localised message.

Note that this is not a blocking issue for use in a multilingual context given that you can do something like this:

Locale aLocale = ... // Determine the locale to use.
// Load the jsv-messages resource bundle (or another one with the same keys).
ResourceBundle translationBundle = ResourceBundle.getBundle("jsv-messages", aLocale);
JsonSchema schema = ... // Load schema somehow.
JsonNode content = ... // Load content to validate somehow.

List<String> localisedMessages = schema.validate(content).stream().map((message) -> {
   String text = message.getMessage();
   if (this.translationBundle.containsKey(message.getType())) {
      var localisedTemplate = this.translationBundle.getString(message.getType());
      var arguments = message.getArguments();
      var params = new String[(arguments == null ? 0 : arguments.length) + 1];
      params[0] = message.getPath();
      if (arguments != null) {
         System.arraycopy(arguments, 0, params, 1, params.length - 1);
      }
      text = MessageFormat.format(localisedTemplate, (Object[]) params);
   }
   return text;
}

Although this workaround exists it would be a better design to foresee this kind of multilingual support in the library itself. Note also that if this workaround is used any custom messages would be forcibly ignored (although anyway custom messages don't support localisation as they are currently defined).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions