Localization Helper in 3.3.0



  • Hi,

    Our template structure looks like this
    0_1642670143335_upload-62b01d74-67bd-49cb-80b6-3f3150d45b42

    Besides other functions we have a translate function in our global-helper.js since all our localization is located in a sub-folder translation, it''s quite handy in a report templates to write {{translate "text"}}.

    const jsreport = require('jsreport-proxy');
    
    function translate(key) {
        return jsreport.localization.localize(key, "translations");
    }
    

    Since the upgrade to jsreport 3.3.0 we face the following error message with our setup.

    Report "reportB" render failed.
    
    Error when evaluating engine handlebars for template /reportB/reportB
    localize helper couldn't find asset with data at translations/en.json
    Error: localize helper couldn't find asset with data at translations/en.json
        at Object.localize (/app/node_modules/@jsreport/jsreport-localization/lib/worker.js:44:17)
        at async /app/node_modules/@jsreport/jsreport-core/lib/worker/render/executeEngine.js:133:40
        at async Promise.all (index 0)
        at async executionFn (/app/node_modules/@jsreport/jsreport-core/lib/worker/render/executeEngine.js:132:9)
    

    Are we doing something wrong? Was this changed from 3.2.0 to 3.3.0 and if so, is there a possibility to fix this? Is there another way to solve the situation?

    The release notes of 3.3.0 state "fix not resolving relative path correctly when calling asset code from another asset". Is this maybe the reason?

    You can find a setup here

    Thanks in advance for any advice.



  • Yes, this was changed in 3.3.0 because we considered it as a bug. Now everything is resolved relative to the particular entity. We consider it as a better approach.

    The following approach could work for you

    const jsreport = require('jsreport-proxy');
    
    async function translate(key) {
        const template = jsreport.req.template
        const templatePath = await jsreport.folders.resolveEntityPath(template, 'templates')
        const folderPath = templatePath.substring(0, templatePath.lastIndexOf('/'))
        return jsreport.localization.localize(key, folderPath + '/translations');
    }
    

    https://playground.jsreport.net/w/anon/K78DqxIW

    We are considering some improvements for the future versions:

    • default to "translations" folder in localize function so the second argument won't be needed
    • a one-liner function to resolve the current template path or folder

    Thank you for the demo and the great description!



  • Thanks for the fast response, works well.

    Just out of curiosity, are you considering a fallback to a default localization if no language is found? Currently an exception is thrown and the whole report won't build.

    Edit: I added our fallback handling in the playground example from above.



  • The default language can be set on the template itself. See in the studio template properties -> localization -> Template language

    Another option is to use a custom script, which can be scoped globaly or also to a particular folder.
    https://jsreport.net/learn/scripts#script-scope

    async function beforeRender (req, res) {
        req.options.localization = req.options.localization  || { language: 'en' }
    }
    


  • Thanks for the clarification.

    What I meant was that we fallback for each translation individually. If not found in the given language we search in english and if we don't find it there we return the [key] instead of undefined. Outlined in reportB of the example.
    https://playground.jsreport.net/w/anon/K78DqxIW



  • I see, thank you for the suggestion. It seems to me that the requirements can differ and not yet sure which approach should be provided from us by design. I think we would start by documenting how you can adapt the localization function and see in time what is the most common usage.

    The current localization is missing explicit language specification. We will add it in the next release.
    Then a complex use case like yours can be implemented like this.

    const jsreport = require('jsreport-proxy');
    const defaultLanguage = 'cz'
    
    // add to helpers custom localization function
    async function translate(key, translationsPath) {   
        if (typeof translationsPath !== 'string') {
            // default to specific translations folder
            const template = jsreport.req.template
            const templatePath = await jsreport.folders.resolveEntityPath(template, 'templates')
            const folderPath = templatePath.substring(0, templatePath.lastIndexOf('/'))
            translationsPath = folderPath + '/translations'
        }
    
        let localizedVal
        try {
            localizedVal = await jsreport.localization.localize(key, translationsPath)
        } catch (e) {
             // asset with requested language not found, try default language
             try {
                localizedVal = await jsreport.localization.localize({
                    key, 
                    folder: translationsPath,
                    language: defaultLanguage
                })
             } catch (e) {
                 // asset for default language not found
                 return `[${key}]`
             }
        }
    
        if (localizedVal == null) {
            // key not found in the asset, try search for key in the asset for default language
            try {
                localizedVal = await jsreport.localization.localize({
                    key, 
                    folder: translationsPath,
                    language: defaultLanguage
                })
            } catch (e) {
                // asset for default language not found
                return `[${key}]`
            }
            if (localizedVal == null) {
                return `[${key}]`
            }
            return localizedVal
        }
    
        return localizedVal
    }
    

    Do you have some thoughts/comments on what we plan?



  • For me it would be fine to stay with your current implementation and document the use-case somewhere in a how-to.

    Currently our complex case is implemented like this.

    async function translateWithFallback(key) {
        const template = jsreport.req.template
        const templatePath = await jsreport.folders.resolveEntityPath(template, 'templates')
        const folderPath = templatePath.substring(0, templatePath.lastIndexOf('/'))
    
        return jsreport.localization.localize(key, folderPath + '/translations')
            .then(translation => {
                return !translation ? fallbackTranslation(key) : translation
            })
            .catch((e) => fallbackTranslation(key));
    }
    
    function fallbackTranslation(key) {
        const defaultDataPath = "translations/en.json";
        return jsreport.folders.resolveEntityFromPath(defaultDataPath, 'assets')
            .then(function(resolvedValue) {
                return JSON.parse(resolvedValue.entity.content.toString());
            })
            .then(function(data) {
                var v = data[key]
                return !v ?  "[" + key + "]" : "*" + v;
            })
    }
    

    Of course neither capable of special translation folders nor any other fallback language then English.


Log in to reply
 

Looks like your connection to jsreport forum was lost, please wait while we try to reconnect.