alexanderwallin / node-gettext

A JavaScript implementation of gettext, a localization framework.
Other
188 stars 37 forks source link

Trying to make this dynamic on the client side, can I? #42

Closed sabsaxo closed 6 years ago

sabsaxo commented 6 years ago

The problem with all this localization is that ALL languages seem to be spit into the application like:

import en from 'en.json';
import da from 'da.json';
import es from 'es.json';

Problem is; that load ALL translation files into the application ... not good!

So I'm trying to do this in a more dynamic way (client side wise – to ONLY have the language needed loaded in the browser).

So, the user select a language from a drop down (forget about the default language for now), the server response with a JSON object that I attach to the gettext (async) by:

gt.addTranslations( <dynamic locale set here> , 'messages', data );

'gt' is my gettext instance 'data', is the returned JSON object

This SEEMS to work just fine. The problem I'm left with is how to make the actual translation happen. As far as I understand the gt.gettext('String here'), is ONLY used to extract strings to be translated. But how do I actually translate the strings on the client dynamically? That seems to baffle me a lot with gettext. I'm pretty surpriced that this seems to be so hard to find any info about ... or maybe it's just because I expect gettext to be more clever than it actually is ... seems weird that ALL translations have to be available at all times ... as if we were still coding applications to be run from an installtion on a hard drive.

ArmorDarks commented 6 years ago

What exactly you're trying to make?

The translation shouldn't be available all the time. You can dynamically load a chunk (a translation), for instance, with Webpack — it will move translation file into a separate bundle and load it only when you require it.

Then you just need to register loaded locale and set the language to registered locale:

This is not super accurate code, but you can get the idea:

const loadLocale = async (locale) => {
  // As far as I can remember, in latest Webpack `import` marks a standalone chunk
  const po = await import(`${localesPath}/${locale}.json`)

  gt.addTranslation(locale, 'messages', po)
  gt.setLocale(locale)
}

// ...
// somewhere in your component you're calling it when user changes locale

<button @click='loadLocale('de')'>DE</button> // will load only `de.json` bundle
<button @click='loadLocale('es')'>ES</button> // will load only `es.json` bundle

Now, you should take into consideration, that node-gettext isn't backward-reactive. When you will load specific locale and set it as currently active, you will need to re-render your components with current settings, to ensure that now node-gettext will execute gt.gettext() with a current active locale.

In other words, what you need is code splitting and dynamic imports.

sabsaxo commented 6 years ago

Well, that's pretty much what I do now. I use a precompiled handlebars template that receives a data object when rendered:

let data = {
        title : gt.gettext('The title'),
        label : gt.gettext('A random label')
};

But when I change the locale on gt and re-render the template, it still shows the original english strings ... so I'm quite confused about where to look for issues. I can see the the catalogs get set properly in gt, but the gt.gettext() does nothing ... ?

And by the way, you add po as the first parameter (which should be the locale as a string, and data is not set – I know this isn't 'super accurate code' but just to make sure I'm not making any mistakes. According to the docs, the first parameter is the locale as a string, the second parameter the domain and the third parameter the actual translations as a JSON object.

…
gt.addTranslations('sv-SE', 'messages', swedishTranslations)
gt.setLocale('sv-SE')
…
sabsaxo commented 6 years ago

Oh, and I see now that addTranslations actually ADDS translations ... but that again bloats the app – everytime the user changes language it gets added to the catalog (memory consumption). How can I have only ONE translation in the catalog at a time (apart from clearing the catalog myself)?

sabsaxo commented 6 years ago

OK, when setting debug to true I get:

No translation was found for msgid "Login" in msgctxt "" and domain "messages"

Now, that's confusing; why suddenly 'msgid' and 'msgctxt'? I have my catalog in JSON-format ... pure key / value pairs ... !?

I'm not using any .po or .pot files in the browser ... how can I make node-gettext use pure JSON catalog?

Yeah, made sure to use the correct gettext-parser format from here on ... : )

sabsaxo commented 6 years ago

Can you explain to me why 'No translation was found ...' by looking at the attached image? I simply cannot figure out why it won't work.

screen shot 2018-02-28 at 12 32 34

sabsaxo commented 6 years ago

And even though I use gettext-parser, I don't get the same file structure as in the gettext-parser README file example. I don't have a 'translations' entry in my JSON file ... so something seems to be not working correctly in the relationship between gettext-parser and node-gettext?

ArmorDarks commented 6 years ago

So, that issue seems to be related to #43? Is it still not working?

Oh, and I see now that addTranslations actually ADDS translations ... but that again bloats the app – everytime the user changes language it gets added to the catalog (memory consumption). How can I have only ONE translation in the catalog at a time (apart from clearing the catalog myself)?

I'm not sure that node-gettext has any built-in means to manage this, but I wouldn't delete already loaded translation (if they are loaded dynamically, on demand, and not at once), otherwise, you will force users to re-download locale if they will decide to switch locale back and forth.

But if you really wont to do this, it is trivial to manage manually, since all gettext instance is exposed to you, and you can simply use delete operator to remove object property with no longer needed translation.

And by the way, you add po as the first parameter (which should be the locale as a string, and data is not set – I know this isn't 'super accurate code' but just to make sure I'm not making any mistakes. According to the docs, the first parameter is the locale as a string, the second parameter the domain and the third parameter the actual translations as a JSON object.

Yeah, I've mistyped it. It had to be

-  gt.addTranslation(po, 'messages', data)
+  gt.addTranslation(locale, 'messages', po)
sabsaxo commented 6 years ago

Yes, managed to mix up the issues. All is good, and I can also clean the catalogs if I decide to. Thanks.