Managing localized messages via backoffice


Introduction

There is a well-known mechanism for internationalization (i18n) in Java known as resource bundles. They contain locale-specific resources, as usual, a file per the language version. Hybris uses the following naming convention for the files used as resource bundles: messagebundlesfiles.png There are different families of the bundles for different websites and add-ons.  Each resource bundle in the family contains the same items, but the items have been translated for the locale represented by that resource bundle. Those are key/value pairs. The keys uniquely identify a locale-specific object in the bundle. Hybris uses this concept for system messages, labels, button titles used in the templates to make them  dependent on some website state, such as the language version. Bundle properties are named after the contents they are supposed to describe. There is a convention to use dot-separated words. For example, 
 
2016-10-11_00h53_45
There are more than 2000 items used mainly in templates. Some of the items are used directly from the Java code. There are many of them that are not used at all. exc.png Using CMS you can change page components, their parameters, but these messages are not editable. If you need to change them, you need to ask the developers, because these files are part of the codebase. It can be said that these messages are hard-coded. It is good that all of them are in the same place and tagged, but the truth is that they are not editable by administrators. If you find a shameful typo when your website is up and running, it can take hours to fix that. From the technical perspective, there is no a big difference between fixing a minor bug in the code and a typo in the resource files. In both cases, you need to create a patch and apply it to the cluster. That is why some of my clients are asking to move the messages from the file system to the database to make them editable. In addition to that, managing the localized messages is very convenient for the translators. For example, some design elements may not be suitable for the translated text if it much longer or shorter than the original version. For example, there is a property named
checkout.multi.paymentMethod.addPaymentDetails.generalError
Let’s compare its  values for English and German: English version:An error occurred contacting the payment provider.  Wait a few minutes and try again.  If the problem persist, please contact our sales team.(139 chars) German version:Während der Kontaktaufnahme zum Zahlungsanbieter ist ein Problem aufgetreten. Warten Sie einige Minuten und versuchen Sie es erneut. Wenn das Problem weiterhin auftritt, wenden Sie sich an unser Vertriebsteam.” (209 chars) The difference is 70 chars that could be too much for the window or area designed for the message. It is clear that all these cases should be verified before the system is live, but multiply the number of language version with the number of messages in these files (~2000) and you will have a number of items in your checklist for the QA. However, it suffices to say that most of these messages are tightly connected to the visual design and functionality. For example, the property named
general.month.january
 contains “July” in the English package, and nobody wants to change it to any other value. Some messages have parameters, such as
popup.cart.showing
 with the value “Showing {0} of {1} Items“. Some messages have HTML tags. Some messages can be used in different contexts. In the article about the CMS template structure you will find the template/property mapping that is useful for analysis.

Solution

The extension I would like to introduce pulls the messages from the database instead of the filesystem. It also allows administrators to manage the localized properties using the backoffice interface. All changes are immediately applied to the storefront. The important feature of this solution that it is fully compatible with the existing templates. localization messages.png

Screenshots

2016-10-10_15h15_28.png

Technical details

  • Messages are in the database now. There is a key/value list in hybris, a new object called HybrisResourceItem, and a backoffice extension to work with the object data (key/value/storage).
  • There is a storage that plays the same role as a property file does for the file system. Storage is used to group the key/value items. The same key can be created in the different storages. Different language versions or different websites are different storages. There is an object named HybrisResource for the If your e-shop uses 10 different property files, you will have 10 records in the HybrisResource.
  • There is a memory cache for the messages to speed up the retrieval. The memory cache is implemented as a simple Map, key->storage content. This cache is invalidated each time you change the HybrisResourceItem.
  • There are two large components of the solution:
    • New resource management module:
      • Extends the spring resource management module (replaces built-in property management with the one that uses hybris services)
    • Synchronization:
      • Async event-based synchronization
        • HybrisResourceItem -> HybrisResource (async merging key-value items into a storage item to speed up a retrieval)
        • HybrisResource -> HybrisResourceItem (async splitting the storage item into key-value items)
      • Sync synchronization
        • HybrisResource -> Memory Cache (if not cached before or when the cache was invalidated)

HybrisResourceLoader

public class HybrisResourceLoader extends DefaultResourceLoader implements ResourceLoader {
    Map<String, Resource> resourceCache = new HashMap<>();
    @Override
    public Resource getResource(String s) {
       Resource resource = getResourceFromCache(s);
       if (resource == null) {
         Resource hybrisMessageResource = getResourceFromHybris(s);
         addResourceToCache(s, fileresource);
         ...
       }
       ...
     }
    }
    private addResourceToCache(String s, Resource resource ) { resources.add(s, resource) }
    private getResourceFromCache(String name) { return resources.get(s); }
    private getResourceFromHybris(String s) { ... }
    public clearCache (String name)
}

HybrisReloadableResourceBundleMessageSource

public class HybrisReloadableResourceBundleMessageSource extends ReloadableResourceBundleMessageSource {
    @Resource
    HybrisResourceLoader hybrisResourceLoader;
    HybrisReloadableResourceBundleMessageSource(){
        super.setResourceLoader(hybrisResourceLoader);
    }
    public void setResourceLoader(ResourceLoader resourceLoader) {
        super.setResourceLoader(hybrisResourceLoader);
    }
}

HybrisStorefrontResourceBundleSource

public class HybrisStorefrontResourceBundleSource extends StorefrontResourceBundleSource {
    @Resource
    HybrisResourceLoader hybrisResourceLoader;
    protected AbstractMessageSource createMessageSource(final String basename)
    {
        ReloadableResourceBundleMessageSource messageSource = (ReloadableResourceBundleMessageSource) super.createMessageSource(basename);
        messageSource.setResourceLoader(hybrisResourceLoader);
        return messageSource;
    }
}

HybrisResourceUpdatedEventListener

public class HybrisResourceUpdatedEventListener extends AbstractEventListener {
    static final private Logger LOG = Logger.getLogger(HybrisResourceUpdatedEventListener.class);
    @Autowired
    HybrisResourceLoader hybrisResourceLoader;
    @Override
    public void onEvent(final HybrisResourceUpdatedEvent event)
    {
        hybrisResourceLoader.clearCache(event.getName());
    }
}
© Rauf Aliev, October 2016
								
																
							

3 Responses

  1. Three things that every hybris project should consider | hybrismart | SAP hybris under the hood

    Three things that every hybris project should consider | hybrismart | SAP hybris under the hood

    Reply

    16 November 2016 at 21:55

    […] The solution of the most listed issues is similar to explained in one of my previous posts, “Managing localized messages in Backoffice” […]

  2. Julio Argüello

    Julio Argüello

    Reply

    24 November 2016 at 03:43

    Interesting topic, we have implemented a similar solution.

    One important thing to take into account is cache invalidation at cluster level. A ClusterAwareEvent whose listener just “refresh” the resource bundle does the trick.

    Another related topic is how does Hybris implements resource bundle hierarchy, in some cases it is too deep (mainly when addons are in place) and most of time the key is retrieved from the last bundle: this worst case is most of times the most frequent case… …so a lot of contention appear during stress tests…

Leave a Reply