I am facing an issue with i18n labels.
My application reads few i18n labels at js frontend using Granite.I18n.get('') function. The entire dictionary gets downloaded as ‘/libs/cq/i18n/dict.{+locale}.json’ as dictated in ‘/etc/clientlibs/foundation/shared/source/init.js’.
Now the en dictionary returns only the custom labels and is small size.
But other languages like fr,the dictionary file is aggregation of all /libs dictionary and is very huge. I noticed this in few other sites also.
tennantco.com
en dictionary - 118 KB
fr dictionary - 1.4 MB
Timewarnercable.com
en dictionary - 1.1 KB
fr dictionary - 1.2 MB
Thermofisher
en dictionary - 3 KB
fr dictionary - 695 KB
Our pain point with this is, cost of caching this heavy file at CDN increases and trying to find a way to reduce CDN cost.
I understand en labels are the key itself. But the ExportServlet is able to filter out the render custom dictionary only for en. Our dictionaries are similar to otb dictionaries under /libs. Then how come ExportServlet treat otb labels under en export?
Is this bug common in all CMS products or specific to Adobe? Also need a solution or workaround to get custom dictionary only for other languages.
The English dictionary is small because English entries are the keys and not the translation. French (and other languages) are big because they contain the key in English and further translation. Also, a lot of keys are only available in translated languages only because the key is used as default translation.
So for French, if you use Granite.I18n.get('Hello world!'), it will return the French translation if it finds it otherwise it will simply return 'Hello World', which doesn't require a translation if the language context is English.
Due to the nature of JS being evaluated on client side, the product is designed to download the full dictionary including the OOTB translation of the product itself as the i18n implementation is not context aware and cannot filter out unwanted translation.
While convenient, this is a limitation and side effect of using Granite.I18n.get('') unfortunately.
Possible Workarounds
Granite.I18n.* can be avoided by using server side i18n libs and rendering only the required translations on server and serving as partial HTML. This may not work for SPA.
If you are using SPA frameworks like Angular(x) then they support i18n factory initialisation which can be hooked into custom servlet response that downloads a filtered i18n. This can potentially be a lot of work and the size can still be a problem if too many terms are translated and dictionary grows large.
Compress, minimise and cache the dictionaries. You can do it with Apache modules or output filters. This will reduce the size and load on traffic but again it's not guaranteed that size will be small for the whole dictionary as the translations grow.
In general, the pages must only render what is required. Using JS to do late translation will force a dictionary download and Granite.i18n does not cater for optimised downloading experience.
I ended up writing a custom implementation as I didnt get much help from Adobe tickets as well on this issue.
OTB dictionary json is rendered by ResourceBundleExportServlet
I created a custom sling servlet that ll prepare and return json similar to ResourceBundleExportServlet
Modified /etc/clientlibs/granite/utils/source/I18n.js to call the custom servlet rather than otb servlet.
Custom servlet is coded to return only specific data dictionary and not all dictionaries.
This solved my problem. Though am not convinced as proper solution. There needs to an otb way of rendering this clean.
We faced the similar requirement to fetch I18n values at client side using Granite.i18n Library
This is what I did.
Created a custom servlet which returns JSON response similar to
ResourceBundleExportServlet.
Loaded the bundle using basename and locale parameter - ResourceBundle resourceBundle = req.getResourceBundle(basename,
pageLocale);
Added sling:basename="basename_constant"' in language specific i18n xml file which resides in /apps/project-name/i18n folder. In my
case, I am setting the value of locale itself ex: "zh_cn".
4.In clientlibs javascript file setting Granite.I18n.setUrlPrefix("/bin/custom/i18n/dict."); to fetch from
custom servlet URL. This doesn't requires modification of OOTB I18n.js
here is an answer that could help you when you are trying to get a specific dictionary: https://stackoverflow.com/a/55656153/14465431
look at the part where is mention the sling:basename
this also works for one site
example:
[sling:Language] > mix:language
mixin
- sling:basename (string)
- sling:basename (string) multiple
http://localhost:4502/libs/cq/i18n/dict.es.[sling:basename-value].json
OTB solution :)
Related
We have the requirement, that we shall host an SAPUI5 application inside a java-application's host, which our vendor offered to us by implementing and exposing jxbrowser.
This vendor's java application offers an api, which can be accessed from within our SAPUI5 application.
This java api offers also an environment (or config-settings), where, amongst a rich set of settings, also a user language can be obtained.
And this language is neither a sap-logon language param nor is it guaranteed to be always the language which is set up in the "jx-browser" ( if possible at all, like in all real browsers ), and it uses the standard i18n _xy_AB acronym's naming style.
And I want to load the proper i18n_xy.properties at onInit of my first view and set them in the code.
We do have currently 4 of them ( _de,_it,_fr ), and the fallback is also present.
I am a little idiot, because I found some quick n dirty code, but this was about one month ago and I simply forgot the link and all of that. So now I need to ask( and maybe even for a best practice solution to this...)
So, additionally, another (my personal) requirement is, that, once I retrieve the right i18n file, I want to set it, in a way, that, whenever later on, I would use
info = this.getView().getModel("i18n").getResourceBundle().getText("obfuscated");
I always obtain the right text in the right language.
What I think of, is: Load the proper file ( according to the environment settings from the api ) in onInit, set this i18n as the proper one for the rest of the application's runtime and use a easy name for it, which will then be referred to as, maybe this:
info = this.getView().getModel("i18n_loaded").getResourceBundle().getText("obfuscated");
Is this possible, is this the right way, and , if not, which one is, according to some kind of guidelines, the best practice for this scenario ?
You can pass the parameter in the url:
sap-ui-language=en
Or set the default language in your JavaScript code:
sap.ui.getCore().getConfiguration().setLanguage("en-US");
I was not completely aware, that the determination of the used locale also works the other way around.
Meaning, if the ressource bundle contains e.g. 4 i18's,called i18_en, i18_it, i18_de, i18_fr, and the either the app is set up by
sap.ui.getCore().getConfiguration().setLanguage("en-US");
or the url-param, this not only means, that:
All ui-elements will be translated propery, once the locale's acronym is properly spotted
ALSO this is automatically replaced by the proper spotted ressource file and the translated text is retrieved properly....
info = this.getView().getModel("i18n").getResourceBundle().getText("obfuscated");
I was aware of how this fallback determination of a locale works, but I was not aware that it works also the other way around.
I close my question and i do not care about any rewards.
I've been refactoring an existing Umbraco project to use more performant querying when getting back document data as everything was previously being returned using LINQ. I have been using a combination of Umbraco's querying via XPaths and Examine.
I am currently stumped on trying to get child documents using the Umbraco.ContentAtXPath() method. What I would like to do is get child document based on a path I parse to the method. This is what I have currently:
IEnumerable<IPublishedContent> umbracoPages = Umbraco.ContentAtXPath("//* [#isDoc]/descendant::/About [#isDoc]");
Running this returns a "Object reference not set to an instance of an object." error and unable to see exactly where I'm going wrong (new to this form of querying in Umbraco).
Ideally, I'd like to enhance the querying to also carry out sorting using the non-LINQ approach, as demonstrated here.
Up until Umbraco 8, content was cached in an XML file, which made XPath perfect for querying content efficiently. In v8, however, the so called "NuCache" is not file based nor XML based, so the XPath query support is only there for ... well... Old times sake, I guess? Either way it's probably not going to be super efficient and (I'd advise) not something to "aim for". That said I of course don't know what you are changing from (Linq can be a lot of things) :-/
It certainly depends on how big your dataset is.
As Umbraco has moved away from the XML backed cache, you should look into Linq queries against your content models. Make sure you use ModelsBuilder to generate the models.
On a small dataset Linq will be much quicker than examine. On a large dataset Examine/Lucene will be much more steady on performance.
Querying NuCache is pretty fast in Umbraco 8, only beaten by an Examine search.
Assuming you're using Models Builder, and your About page is a child of Home page, you could use:
var homePage = (HomePage) Model.Root();
var aboutPage = homePage?.Children<AboutPage>().FirstOrDefault();
var umbracoPages = aboutPage.Children();
Where HomePage is your home page Document Type Alias and AboutPage is your About page Document Type alias.
I have two websites and code base is same.
SiteA
SiteB
apps/company/components
How to pick different resource bundle translations for SiteA, SiteB in i18n ?
Thanks,
Sri
Can you please be specific, is this for server side translations or client side translations? For client-side translations, I had an issue with OTB ResourceBundleExportServlet detailed here.
sling:basename way:
Add a property sling:basename to mix:language node. Say sling:basename="siteA"
Pass the basename during bundle lookup. request.getResourceBundle("siteA", locale);
This will return keys from the specific basename only.
Client-side custom bundle exporter:
Keep separate dictionaries for SiteA and SiteB. For example: /apps/company/sitea/i18n, /apps/company/siteb/i18n.
If splitting dictionary is not possible, keep a nomenclature in your labels to identify site. For example all labels should be prefixed with siteA/siteA. Like siteA.clickhere, siteB.clickhere
Create custom servlet similar to ResourceBundleExportServlet. Keep the path as /libs/company/i18n/dict.
The custom servlet will check siteA or siteB from slingrequest and return respective labels only. Filtering the labels based of dictionary path(step 1) or prefix(step 2)
Create overlay to /libs/clientlibs/granite/utils/source/I18n.js. Change the urlPrefix to
var urlPrefix = "/libs/company/i18n/dict.";
Now the client side i18n lookup will pull entries from the custom exporter rather than OTB exporter
Server-side Resolver:
To differentiate sitea or siteb labels we need step 1 or 2 from above.
Once we know to identify site specific labels, we need only a helper util that checks site from request and resolves from specific dictionary or prefix
Hope this helps.
I would like to achieve the following thing-
Build a pagetype which has 3 different ContentArea's and that the user can put only a specific block type in each of these areas.
For example - ContentArea1 can only accept block type of "BlockType1", ContentArea2 can only accept "BlockType2" and so on. (It doesn't need to be generic, I can specify hard coded which type should fit in each Content Area.
Is it possible to achieve?
Maybe there is another way?
(I know you can create a property with the block type, but I want to use the same block in different places)
ps: using EPI-SERVER 8
From version 8.0 of EPiServer there is better support for AllowedTypes.
The feature was also available before version 8, but was more limited.
In short, you decorate your ContentArea property with the AllowedTypes attribute and EPiServer takes care of the rest.
Read more about it here:
http://world.episerver.com/blogs/Ben-McKernan/Dates/2015/2/the-new-and-improved-allowed-types/
What is the difference between translate.csv translations and the database method via the table core_translate?
Here is part of init() method from app/code/core/Mage/Core/Model/Translate.php
//Loading data from module translation files
foreach ($this->getModulesConfig() as $moduleName=>$info) {
$info = $info->asArray();
$this->_loadModuleTranslation($moduleName, $info['files'], $forceReload);
}
$this->_loadThemeTranslation($forceReload);
$this->_loadDbTranslation($forceReload);
From it you can see that Magento load translation in the following order, i.e. there are three options in Magento to add a custom translation to a text string: module translation, theme translation and inline translation.
Module translation
Module translations are stored in app/locale/languagecode_COUNTRYCODE/ folder in form of csv files, named as Namespace_Modulename.csv All string in extensions that are inside __() method can be translated this way
Theme translation
Strings can be translated inside your theme, for that you just need to set locale via Magento admin area, then create translate.csv in app/design/frontend/<package>/<theme>/locale/languagecode_COUNTRYCODE and put your translated strings inside this CSV
“My Cart”,”My Basket”
“My Account”,”Account”
Inline translation
To enable inline translation you need to log into Admin panel and go to System -> Configuration -> Developer and then find Translate inlined and set Enabled for frontend Yes
All translation made by this method will be stored in core_translate table inside your database. In order to understand better how this method works, check this video out.
The text above is a part of my article on our blog
core_translate table is for phrases that depends on StoreView
/app/design/frontend/YOUR PACKAGE/YOUR THEME/locale/YOUR LOCALE/translate.csv for phrases in YOUR LOCALE language for YOUR THEME. If you change theme this phrases will not be used (translate.csv from new theme will be used).
If phrase is available in database and in csv, then DB phrase will be used.
As I see it, core_translate is useful when you are running magento in a distributed method on multiple servers, and reading from the filesystem just isn't ideal.
I use core_translate with inline translations to handle translated content in CMS blocks. (a mod)
The reason for this is that it is faster to read from the db than to parse a .csv. (I do not know if this is true with caching turned on, but it seemed like the safest route to go)
I dug up this old forum that suggested a few things. Possibly Magento is trying use the inline core_translate approach and push out the translate.csv. However given the forum thread is from 2008 that doesn't seem to be the case. The other suggestion is that some languages use core_translate on the database while some keep the records in a .csv. Possibly the .csv is for local maintainers and the core_translate is for admins. Here's the thread http://www.magentocommerce.com/boards/viewthread/40510/