Opened 7 years ago

Closed 4 years ago

#17196 closed enhancement (patchwelcome)

i18n: Explain and/or add ability to switch languages dynamically without reloading a page

Reported by: Paul Christopher Owned by:
Priority: undecided Milestone: 1.13
Component: Internationalization Version: 1.9.0
Keywords: Cc:
Blocked By: Blocking:

Description

I have already tried to kick off a discussion on the mailing list, alas with no success, see http://dojo-toolkit.33424.n3.nabble.com/i18n-How-to-switch-languages-without-reloading-a-page-a-dojox-app-application-td3996722.html . So I hope it is okay to place it here on the bug tracker...

Requirement

In a single page application (dojox/app application) I have the requirement to allow users to switch languages without reloading the whole application/ page (since otherwise all data and view states would be lost and the app "reset" to an initial state).

I have checked the documentation for dojo/i18n but could not find an explanation 1) how to load new nls files with a different language or 2) how to reload all nls files for a newly set language.

Simply changing dojo.locale and kicking off a new require call for a specific nls resources inside a widget seems not to work (since the files are cached by dojo/i18n? require.undef is not called? The i18n system is not prepared for that?).

Request

Thus the request is:

  • Could you please explain in the docs how to load a resource bundle that is different from the currently set language?
  • Even better: How to switch the language globally and relaod all nls files?

For a start, it would be enough for me to know how to simply get a resource bundle for the newly set language. I know there is much more work to do, like sending a global event "languageChange" with dojo/topic, listening to it in the various widget instances and then manually updating the DOM with the new resource strings etc. But my problem at the moment is more fundamental: I do not know how to load the new nls files at all since dojo/i18n seems to be not ready for this?

Attachments (1)

test_languageSwitch.html (2.0 KB) - added by Paul Christopher 7 years ago.

Download all attachments as: .zip

Change History (13)

comment:1 Changed 7 years ago by bill

I don't know if dynamically switching languages is something we want to complicate our code with.

But to answer the question about loading a resources bundle in a specific language, I quote from an email Ken sent to dojo-contributors:

I haven't tested it personally, but judging by the code, it seems like dojo/i18n! makes an attempt to detect whether you are requesting a specific locale (e.g. foo/nls/ja-jp/bar) or not (e.g. foo/nls/bar). In the latter case it tries to figure out the applicable bundles to look for, but in the former case it will just request the specified bundle.

You can also load "dojo/i18n", and use it as a function, rather than as a plugin, but I haven't tried that either.

Note though that there are higher level issues, like widgets (for example InlineEditBox, which has OK and cancel buttons) that have already loaded the messages for the page's initial language.

comment:2 Changed 7 years ago by Paul Christopher

Bill, thanks for the quick reply and the good hints how to solve the issue.

Indeed, dojo/i18n has a function called "getLocalization" which can be used to load a bundle separately. So I can use this to manually update existing widgets when the language changes. And I can use require.undef to reload widgets and recreate them with the new locale, see attached test case.

This works nicely in debug mode, but does not work in a built application: getLocalization does not return a value immediately, since - I guess - the new language layer file is not preloaded. require.undef seems not to work either.

All in all, I am wondering, if I have to use require.undef, whether this is a feasible approach at all: I guess it will load all widgets in an uncompressed format and not in a compressed one. And that's not really desired in a built application.

Changed 7 years ago by Paul Christopher

Attachment: test_languageSwitch.html added

comment:3 Changed 7 years ago by bill

About the asynchronous thing, it sounds like you just need to define extraLocales to list all the locales you will ever use in your app. About require.undef(), you shouldn't be using that. Just destroy and recreate your widgets with the specified language (ie: set the lang property). That lang property won't be there in 2.0 but it is there in 1.0.

comment:4 Changed 7 years ago by ben hockey

also, if you want to avoid setting extraLocales then don't use getLocalization. instead, specify the locale when calling dojo/i18n! and load it asynchronously then continue as bill said.

require(['dojo/i18n!dojo/nls/' + value + '/colors'], function (nls) {
    button.set('label', nls.blue);

    // ...
});

comment:5 Changed 7 years ago by Paul Christopher

Thank you very much both for your input! It was very helpful!

  • Using the widget's lang attribute is a good thing and works nicely. Sad to hear, that it is going to be desupported in 2.x. How could the issue of dynamic language switches be solved then in 2.x taking into account the growing importance of single page applications?
  • Specifying "extraLocals" in data-dojo-config indeed solves the issue for built applications. However this is not a feasible approach for me: My application supports 5 or more languages, and I cannot preload all those language layers. It's a matter of startup performance and memory consumption.
  • Using dojo/i18n with a require call (instead of using i18n.getLocalization as suggested by neonstalwart) works nicely, too. It even falls back to the default language if you specify an unsupported locale. But in a built application, I cannot use it - as far as I can see in my small dojox/app test application. I need to set an extraLocale, too, to make it work. Maybe because it is using the same functions as i18n.getLocalization and it runs into the same difficulties that there is no flattend nls bundle preloaded since no extraLocale has been specified?

All in all, my feeling is, that considering the drawbacks of removing a widget's lang attribute is worth a thought again? Shouldn't instead the extraLocale property be desupported and the i18n plugin changed in a way, that - if a certain bundle is requested in a built application - the flattened nls bundle is preloaded, cached and only the requested resource returned? I.e. the extraLocale list is not defined in data-dojo-config anymore but generated dynamically? I.e. if the user requests a certain bundle in a different language, dojo/i18n does not load the resource directly but the whole flattend nls bundle (which is in fact a collection of all the used nls files in an application) and than returns the requested resource only? And only if there is no flattend nls bundle, the single requested nls resource should be returned (but cached!)by dojo/i18n so if other parts of the application request the same resource, it is not loaded again and again?

comment:6 in reply to:  5 Changed 7 years ago by bill

Replying to Paul Christopher:

Thank you very much both for your input! It was very helpful!

  • Using the widget's lang attribute is a good thing and works nicely. Sad to hear, that it is going to be desupported in 2.x. How could the issue of dynamic language switches be solved then in 2.x taking into account the growing importance of single page applications?

Well, the thing I want to desupport is having a page in multiple languages at once. The portal guys love this idea, to support having some portlets that are translated but other that are not, but in general having a single language for the boilerplate whole page is sufficient. Of course, a page can still have data in multiple languages, like how my Facebook feed has some Japanese and some English, but the boilerplate is all English.

But as to dynamic language switching, that doesn't work in 1.x or 2.0, although I understand that you are trying to hack it to get things to work. I think we just haven't had enough interest to implement support for it.

  • Specifying "extraLocals" in data-dojo-config indeed solves the issue for built applications. However this is not a feasible approach for me: My application supports 5 or more languages, and I cannot preload all those language layers. It's a matter of startup performance and memory consumption.

Yes, understood. FYI though, this is complicated because of asynchronousness (if that is a word). For example, new InlineEditBox({lang: "ja-jp"}) is supposed to create the widget synchronously, but how can it do that if the language files are not already loaded?

comment:7 Changed 7 years ago by Paul Christopher

I don't think that much needs to be changed. The only thing I am struggling with at the moment is to get it work in a built application. If I were able to simply preload the new flattened layer file and get the desired resources from it, it would be fine. However this does not work:

// Load the flattened fr bundle retroactively since it is not set in extraLocale
require(['dojo/i18n!*preload*custom/nls/layerMultiSceneApp*["fr"]'], function(){
  // Loaded? Okay, get now the desired ressources from it
  var nls = i18n.getLocalization("custom/widgets", "ConfirmDialog", 'fr');
  self.button.set('label', nls.buttonCancel);
  ...
});

Note: dojo/i18n already supports the above syntax, but something still seems to be wrong.

My current workaround is to get the nls files directly by a simple require call like so - without using dojo/i18n:

require(['custom/widgets/nls/fr/ConfirmDialog'], function(nls){
  self.button.set('label', nls.buttonCancel);
  ...
});

Drawbacks: No fallback, no compression, many request, the need to distinguish between root and other nls files. To put it short: Not really usable, very fragile, but it works somehow.

Maybe Adam Peller, who is the creator of dojo/i18n, knows more?

As far as I can see, the only issue is how to retroactively load a flattened nls bundle and wait until it is loaded so that you can get ressources from it (see first code sample).

comment:8 in reply to:  7 Changed 7 years ago by ben hockey

Replying to Paul Christopher:

...However this does not work:

// Load the flattened fr bundle retroactively since it is not set in extraLocale
require(['dojo/i18n!*preload*custom/nls/layerMultiSceneApp*["fr"]'], function(){
  // Loaded? Okay, get now the desired ressources from it
  var nls = i18n.getLocalization("custom/widgets", "ConfirmDialog", 'fr');
  self.button.set('label', nls.buttonCancel);
  ...
});

this is not the right syntax. use what i already suggested above and let me know if/how it fails. ie

 // Load the flattened fr bundle retroactively since it is not set in extraLocale
 require(['dojo/i18n!./custom/nls/fr/layerMultiSceneApp'], function(nls){

   // DON'T USE getLocalization - IGNORE THAT IT EVEN EXISTS
   // use the return value from the dojo/i18n! call directly
   // var nls = i18n.getLocalization("custom/widgets", "ConfirmDialog", 'fr');

   self.button.set('label', nls.buttonCancel);
   ...
 });

i put together an example that uses dojo from a CDN (so the code is built) and you can see how locales can be switched - http://jsbin.com/aquviq/1/edit

comment:9 Changed 7 years ago by Adam Peller

Paul, as you've surmised, the initial design (perhaps 6 or 7 years ago) sought to "keep it simple" by not dealing with dynamic locale changes at runtime, period. (This should be documented, but may not be anymore as we've been through several documentation systems since then). The old thinking was that changing a user's locale was a relatively rare event, probably requiring regeneration of the UI anyway, and that restarting the application would not be all that intrusive. Supporting multiple languages via extraLocale was more for the textbook cases of providing examples on a page in language different than the user's native language, implemented more for completeness, though it's not clear how often such a feature is actually required.

Loader technology has since advanced and there are some ways to load the individual bundles, as you say, but not in a way that has been integrated with toolkit API. Using extraLocale to "preload" all possible locales and switch between them using Dijit's lang attribute or your own invention via getLocalization is the only supported mechanism, and I understand that's inadequate for your needs.

We should be revisiting all this for Dojo 2.0, especially where Dojo could be used on the server to handle different user requests. A recent thread on dojo-contrib touched on these topics: http://thread.gmane.org/gmane.comp.web.dojo.devel/18963

comment:10 Changed 7 years ago by ben hockey

here's a link to the docs that peller was possibly looking for - http://dojotoolkit.org/reference-guide/1.9/quickstart/internationalization/specifying-locale.html

Once Dojo is loaded, it is not possible to change the locale for the page.

In the unusual case where multiple locales are used on a single page, the dojoConfig.extraLocale property must be set, prior to bootstrap, listing the additional locales as elements in an array, otherwise they will not work at runtime.

comment:11 Changed 7 years ago by Paul Christopher

Thanks Adam for the insights into Dojo history! Thanks Ben for the test case and the link to the docs! I know that I am trying to do something that is not yet supported or undocumented. That's why I have marked this ticket as "enhancement" - either for the code or for the docs.

Ben's jsbin sample works nicely. I got it working, too. But I am pretty sure, when you do the same in a built application, in which only a compressed dojo.js layer, a custom app layer and one flattened nls file are loaded, it will fail. At least it does in my dojox/app application. The loader seems to be in some kind of sync mode and does not return the bundle immediately. All in all, all methods of dojo/i18n do not return a promise, so you cannot wait until the bundles are loaded.

And even if loading of nls files would work with require("dojo/i18n!...") in a built application, it would not be ideal, since the resources are not taken from the flattend nls file.

dojo/i18n has already a method called _preloadLocalizations. I need to check whether it could be used after Dojo has been boostraped at all. But if it could be used, one other problem remains: The method does not return a promise. So it's hard to know, when preloading has finished.

comment:12 Changed 4 years ago by dylan

Milestone: tbd1.12
Resolution: patchwelcome
Status: newclosed

Given that no one has shown interest in creating a patch in the past 2+ years, I'm closing this as patchwelcome.

Note: See TracTickets for help on using tickets.