Showing posts with label Internalization or i18N. Show all posts
Showing posts with label Internalization or i18N. Show all posts

December 28, 2020
Estimated Post Reading Time ~

How to Get AEM i18n Dictionary in JSON Format

AEM i18n dictionary out of the box tools does not offer an elegant way to get the JSON representation of targeted the i18n dictionary. Without out of the box AEM i18n dictionary JSON formatting tools, we first solutionize to what we know the most.

As of AEM developers, we all know that we can trigger the JSON default rendering by appending a .json extension to a request, which triggers the default Sling GET servlet returning application/json. And of course, we all know about using the infinity selector in combination with the .json extension, which recursively returns the entire JCR structure in JSON format. Our first attempt is likely to attempt to get the formatted JSON from .infinity.json.

Illustrated below, on the left-hand side, you can see a structured AEM i18n dictionary from the out of the box commerce components library (these nodes should exist in a clean installation of AEM 6.4+). On the right-hand side, you can see the attempt to get a JSON format of this structure, using the selector, “infinity”, and the extension, “json”, 

http://localhost:4502/libs/commerce/components/search/i18n.infinity.json.
        

As you can see, the default Sling GET servlet’s JSON response returns a lot of extra unwanted properties. These unwanted properties will increase the file size of the outputted JSON file, which will impact load time, which will ultimately impact the overall performance of your website. Also, all the unwanted JSON properties will add complexity to consumers, whether it’s a front-end or to a third-party.

In this article, I will share with you a way that I go forward with getting a JSON representation of an AEM i18n dictionary, without the unwanted properties. The solution will make the JSON response from the above AEM i18n dictionary structure, return JSON in this format:

{
    sort: "Sort by:",
    firstPage: "First",
    timelessStatisticsText: "Results {0} - {1} of {2} for <b>{3}</b>.",
    lastPage: "Last",
}


Custom servlet to format all AEM i18N structured dictionaries
That’s right. In most of my AEM project implementations, I would introduce a servlet that can be used for all AEM i18N structured dictionaries. It’s very simple.

Requesting on http://localhost:4502/libs/commerce/components/search/i18n.jsonform.en or http://localhost:4502/libs/commerce/components/search/i18n.jsonform.de will return me a JSON formatted AEM i18n dictionary by language, and the JSON response will only include the dictionary keys.

Servlet Request: http://localhost:4502/libs/commerce/components/search/i18n.jsonform.en
Selector: jsonform
Extension: en|fr|de|kr
The extension is the target language that you want to receive. For example, if the extension was set for “de”, then the JCR searched would be /libs/commerce/components/search/i18n/de.

Here is the implementation:
package com.sourcedcode.core.servlets;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.servlet.Servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

import static org.apache.jackrabbit.JcrConstants.NT_FOLDER;
import static org.apache.sling.api.servlets.HttpConstants.METHOD_GET;
import static org.apache.sling.jcr.resource.api.JcrResourceConstants.NT_SLING_FOLDER;

@Component(service = Servlet.class, configurationPolicy = ConfigurationPolicy.OPTIONAL)
@SlingServletResourceTypes(
        resourceTypes = { NT_FOLDER, NT_SLING_FOLDER},
        selectors = "jsonform",
        methods = METHOD_GET)
public class JsonDictionaryFormatServlet extends SlingSafeMethodsServlet {

    private static final Logger LOGGER = LoggerFactory.getLogger(JsonDictionaryFormatServlet.class);
   
    @Activate
    protected void activate(Map<String, Object> properties) {
        LOGGER.info("Servlet activated");
    }

    @Deactivate
    protected void deactivate() {
        LOGGER.info("Servlet deactivated");
    }

    @Override
    protected void doGet(SlingHttpServletRequest req, SlingHttpServletResponse res) throws IOException {
        try {
            jsonFormatDictionary(req, res);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    private void jsonFormatDictionary(SlingHttpServletRequest req, SlingHttpServletResponse res) throws RepositoryException, IOException, JSONException {  
        String language = req.getRequestPathInfo().getExtension();
        PrintWriter writer = res.getWriter();
        if (language != null) {
            Node rootLanguageNode = req.getResource().adaptTo(Node.class);
            if (rootLanguageNode.hasNode("i18n")) {
                rootLanguageNode = rootLanguageNode.getNode("i18n");
            }
            Node languageNode = rootLanguageNode.getNode(language);
            if (languageNode != null) {
                res.setContentType("application/json; charset=UTF-8");
                res.setCharacterEncoding("UTF-8");
                JSONObject rootObject = new JSONObject();
                NodeIterator iterator = languageNode.getNodes();
                while (iterator.hasNext()) {
                    Node languageItem = (Node) iterator.next();
                    String key = languageItem.getName();
                    String value = languageItem.getProperty("sling:message").getString();
                    rootObject.put(key, value);
                }
                writer.write(rootObject.toString());
            } else {
                LOGGER.error("{} language is not exist", language);
            }
        } else {
            LOGGER.error("no language have been found in the extension");
        }
    }
}

HTTP Get Request:
http://localhost:4502/libs/commerce/components/search/i18n.jsonform.en

JSON output:
{
    sort: "Sort by:",
    firstPage: "First",
    timelessStatisticsText: "Results {0} - {1} of {2} for <b>{3}</b>.",
    lastPage: "Last",
}

  • An example of an i18n served to production-live would be a path that looks like this, /etc/commerce/components/search/i18n.jsonform.en
  • Only because, /apps and /libs are reserved areas, which means for security purposes, no one should have access to these areas. Using the /etc path to access dictionaries are fine.


By aem4beginner

August 19, 2020
Estimated Post Reading Time ~

Use Case For i18n In AEM: Converting Components Language Based on User Preference

As an Author, at some point, you must have felt the agony of not having the language support other than English in the component’s fields. This becomes even more dire for the multinational organizations who have regional authors. Realizing this, AEM has come up with a unique feature ‘i18n’, enabling Internationalization of components and dialog’s so that the UI strings can be presented in different languages. Components that are designed for internationalization enable UI strings to be externalized, translated, then imported to the repository and on the basis of the user’s preference, the displayed language in the UI is determined. It’s worth mentioning that it has been named “i18n” because there are 18 characters between ‘I’ and ‘N’ in the word ‘INTERNATIONALIZATION’.

Developers used this ‘i18n’ feature to change the component’s fields and the same is being presented below in the form of use-case to understand the enablement of the ‘i18n’ in AEM more profoundly.

Here, we want to add multiple languages to title and description component.

Step 1: Navigate to the component path which you want to be multilingual.


Step 2: Create a folder named ‘i18n’ under the component (here, it is ‘titleanddescription‘).


Step 3: Create a folder with name as language code (ISO code). For ex: En for English, de for German, for for French etc.


Step 4: Click on the ‘Mixins’ from the tool bar.


Step 5: Click on ‘+’ button in Mixins window and add ‘mix:language’ property and after that Click ‘OK’.


Step 6: Add ‘jcr:language’ property to the same ‘en’ node. And it’s value is the language code (ex: en)


Step 7: Create two nodes under ‘en’ node of type ‘sling:messageEntry’. Names of the nodes can be anything (preferably keep the FieldLabel names of the dialog), and later on we will use this same Name for configuring the component’s dialog.


In this case, I have created ‘training.title’ and ‘training.description’ to give more sense to the names because my component has two fields ‘title’ and ‘description’.


Step 8: Add ‘sling:message’ property with value. To recall, the value of respective language depends on the language node that you are in.
Here, as we are in ‘en’ node, writing the value as ‘Description’ in English. See below images for more understanding.


Here, I am in German(de) node. So, the value has ‘Description’ as ‘Beschreibung.


Step 9: Similarly, create the same folder structure from Step 6 to Step 8 for all the languages that you want to have for the component.


Step 10: Navigate to the component’s dialog fields and add values to the ‘fieldLabel’ property. Values are the node names under language folder.


Step 11: Navigate to the User Admin page(‘http://localhost:4502/useradmin’).

Search for the username for which you are logged in ( by default it is ‘admin’). Click on the Preferences tab and select the language from the drop-down that you want the component to be displayed in.


Step 12: Navigate to the page where your component is authored and click on ‘edit’ and here you can see that the field names are displayed in the language you have selected in user preferences. Here, I have selected display language as ‘French’.


Above, we have just presented a simple use case to understand working process of i18n (Internationalization) for AEM. Apart from this the ‘i18n’ can also be used by multi-national multi-lingual websites. These websites often use translation vendor for a page translation and they pay the vendors on the basis of word-by-word. In multi-national multi-lingual websites, there are always some labels/text-strings which remain same and because of this, it doesn’t make sense to have them translated with a translation vendor every time they send a page for translation. To tackle this issue of those websites, the AEM provides a separate in-built ‘i18n’ translator tool, where all these labels/text-strings and their translated content is stored and can be used as many times as required (Of course, without any payment). 

In conclusion, we can say that i18n is a powerful, versatile tool that comes with AEM to Internationalize your components and dialogs to display UI strings in different languages. If you have any query then do let us know, we would be more than happy to help.



By aem4beginner

August 13, 2020
Estimated Post Reading Time ~

Steps to convert Excel Sheet to XLIFF file

As we usually get i18N translations from 3rd party companies (eg: SDL) in an excel sheet, but AEM accepts only .xliff. So, it is hard to add all the translations in the i18N dictionary manually as we have hundreds of keys.

So, in that case, we can use Microsoft Excel to convert this excel sheet into the xliff format file.

 

In AEM we can import or export one language at a time so whenever we get the excel sheet from SDL, we need to create a new Sheet in the excel with 3 columns 1- id, 2- i18N String 3-Translated language (vi_VI) as shown below.

 

 

Once you create a sheet with these 3 columns, there is an out of the box MS Excel functionality to convert this .xlsx to .xml format. That is called the Developer Tab. By default, if this tab is unavailable you can enable this by using steps mentioned in this article.

 

Once the Developer Tab is enabled follow the below steps to convert.

On the Developer tab, in the XML group, click Source to open the XML Source task pane.

   

On this screen click on the XML Maps option highlighted in the above screenshot.

 

Then here select the existing base.xml file. Which looks like the below screenshot.

base.xml

 

It basically has a header for the xliff file and some examples of how this xml body should be. So, that Excel can read this file and give you the option to replace the values by matching the columns.  

 

So, once you import the file (base.xml) on the right side you would see options (id, source/value, target/value). Now we need to match the rows on the right side with the columns in the excel sheet by dragging on to the excel sheet as shown below.

 

For Example: id to id, source/value to a string, and target/value to i18n translated language.

 

Once the mapping is done, again go to Developer Tab and then Export this document here you can add xliff extension.

 

Now we have successfully converted Excel file to xliff file as shown below.

Output.xml

 

With this output xml file, we still need to do some changes in order to work as an xliff file.

Below are the changes that we need to do on output.xml file

 

1.     Replace <source> with <source xml:lang="en"> ... here en is the i18n key

2.     Replace < target> with <target xml:lang="fi_fi"> ... here fi_fi is the destination translated language.

3.     Finally, we need to replace the <file>, <header>, <?xml>, <xliff> tags with as shown below in the output.xml file.

 

<?xml version="1.0" encoding="utf-8" ?>

<xliff version="1.1">

  <file

    original="/libs/cq/i18n/en_us"

    source-language="en"

    target-language="fi_fi"

    datatype="x-javaresourcebundle"

    tool-id="com.day.cq.cq-i18n"

    date="Fri Aug 07 16:26:27 MST 2020"

  >

    <header>

      <tool

        tool-id="com.day.cq.cq-i18n"

        tool-name="Adobe Granite I18N Module"

        tool-version="5.5.16"

        tool-company="Adobe Systems Incorporated"

      />

    </header>

 

After updating these changes, we will get the working xliff file.

 

So using the OOTB translator we can import this file.

 

AEM i18N Translator URL: Navigate to this URL http://localhost:4502/libs/cq/i18n/translator.html

 

And then you need to select our BW i18N dictionary in the Dictionaries dropdown. Then click on the Import option and click on XLIFF Translations so that you can import the xliff which we created.

 



By aem4beginner