rmariuzzo/Lang.js

Does it even work with JSON object messages?

Opened this issue ยท 5 comments

I tried:

{
    "en": {"Yes": "Yes", "No": "No"}},
    "de": {"Yes": "Ja", "No": "Nein"}}
}
window.Lang.setMessages({
    'en': en,
    'de': de,
});

It parses keys to be flattened like this: de.No and then, of course, it cannot find the key.

Then I flattened my messages object to be so:

{
    "en.Yes": "Yes", 
    "en.No": "No", 
    "de.Yes": "Ja",
    "de.No": "Nein",
}
window.Lang.setMessages({
    'en': en,
    'de': de,
});

and it worked.

What is the point of this approach? Why would I need to loop through all my language keys and flatten them?

Since Laravel provided us JSON translations, you should make a configuration option for that.

    Lang.prototype._getMessage = function(key, locale) {
        locale = locale || this.getLocale();

        if (this.messages[locale] === undefined) {
            locale = this.getFallback();
        }

        if (this.messages[locale][key] === undefined) {
            locale = this.getFallback();
        }

        if (this.messages[locale][key] === undefined) {
            return null;
        }

        return this.messages[locale][key];
    }

I believe the code above is pretty self-explanatory.

I'm not a Javascript developer, your package seems to be the only "working" one, so please keep it up to date.

This snippet is a life saver, finally got it working with es.json.

Let me share the Laravel-end for future-comers. P.S: I am not sure if this is the most efficient way but works so far :)

class ComposerServiceProvider extends ServiceProvider
{
   public function boot()
       {
           $languages = $this->getLanguages();

           View::composer('*', function($view) use ($languages) {
               $view->with('languages', $languages);
           });
       }
   }

   protected function getLanguages() {
        $es = json_decode(file_get_contents(resource_path('lang') . "/es.json"));
        $esArray = (array) $es;

        $englishKeys = array_keys($esArray);
        $englishArray = array_combine($englishKeys, $englishKeys);

        return [
            'en' => $englishArray,
            'es' => $esArray
        ];
    }
}
// master.blade.php

 <script>
        window.default_locale = "{{ config('app.locale') }}";
        window.fallback_locale = "{{ config('app.fallback_locale') }}";
        window.languages = @json($languages);
</script>
// app.js

import Lang from 'lang.js';

Vue.prototype.trans = new Lang( { messages: window.languages, locale: window.default_locale, fallback: window.fallback_locale } );

// And Above Chunk ๐Ÿ‘ 
Lang.prototype._getMessage = function(key, locale) {
    locale = locale || this.getLocale();

    if (this.messages[locale] === undefined) {
        locale = this.getFallback();
    }

    if (this.messages[locale][key] === undefined) {
        locale = this.getFallback();
    }

    if (this.messages[locale][key] === undefined) {
        return null;
    }

   // Added this one - if key value doesn't found, return to fallback
   // To handle this case: {"Hello: ""}
   if (!this.messages[locale][key]) {
        locale = this.getFallback();
    }

    return this.messages[locale][key];
}

new Vue({ })

Great job as well, thanks mate!

Hello @OzanKurt, it is possible that it has been difficult for you to navigate different levels of an object and that's why he did it type Chain "language.property" (it's just a speculation) but seeing this I think it would be good a PR in this part and Talk to see if it is feasible.

I was also looking for a way to use translation strings as key (https://laravel.com/docs/7.x/localization#using-translation-strings-as-keys). As the OP has explained, the _parseKey seems to not care about this scenario.

So if I have the following language key

es: {'Hello': 'Hola'}

The expected result when I run the following is
Lang.get('Hello') is 'Hola'

But the _parseKey method instead attempts to find the key 'es.Hello' which of course does not exist.

Since laravel's JSON translation files are only 1 level deep, my solution was to alter _getMessage() like the following

* Returns a translation message. Use `Lang.get()` method instead, this methods assumes the key exists.
     *
     * @param key {string} The key of the message.
     * @param locale {string} The locale of the message
     *
     * @return {string} The translation message for the given key.
     */
    Lang.prototype._getMessage = function(key, locale) {
        locale = locale || this.getLocale();
        let originalLocale = locale;

        // Handle the scenario where the tranlation string is used as the key.
        // (https://laravel.com/docs/6.x/localization#using-translation-strings-as-keys)
        // In this case the Key should be present at the root of the locale.
        if (typeof(this.messages[locale]) === 'undefined') {
              // The given locale does not have keys at the root, use the fallback instead.
              locale = this.getFallback();
        }

        
        // See if the key is defined.
        if (typeof(this.messages[locale]) !== 'undefined') {
            if (typeof(this.messages[locale][key]) !== 'undefined') {
                return this.messages[locale][key];
            }
        }

        //Try with the fallback as well. if we haven't looked there already.
        if (locale === originalLocale) {
            locale = this.getFallback();
            if (typeof(this.messages[locale]) !== 'undefined') {
                if (typeof(this.messages[locale][key]) !== 'undefined') {
                    return this.messages[locale][key];
                }
            }
        }

        // If we reach here, that means the traslation key did not exist in the provided locale
        // nor the fallback locale. At this point proceed as normal and expect the rest of the code
        // to find a valid translation or return the key itself as the translation
        // (which also takes care of 'Translation strings as key')

        // Reset to the original locale.
        locale = originalLocale;
        
        key = this._parseKey(key, locale);

        // Ensure message source exists.
        if (this.messages[key.source] === undefined && this.messages[key.sourceFallback] === undefined) {
            return null;
        }

        // Get message from default locale.
        var message = this.messages[key.source];
        var entries = key.entries.slice();
        var subKey = entries.join('.');
        message = message !== undefined ? this._getValueInKey(message, subKey) : undefined;


        // Get message from fallback locale.
        if (typeof message !== 'string' && this.messages[key.sourceFallback]) {
            message = this.messages[key.sourceFallback];
            entries = key.entries.slice();
            subKey = '';
            while (entries.length && message !== undefined) {
                var subKey = !subKey ? entries.shift() : subKey.concat('.', entries.shift());
                if (message[subKey]) {
                    message = message[subKey]
                    subKey = '';
                }
            }
        }

        if (typeof message !== 'string') {
            return null;
        }

        return message;
    };

This code has worked for me when I load the JSON translation to the root like so:

    import Lang from 'lang.js';

    'fr':  require('../../../lang/fr.json'),
    'fr.misc': require('../../../lang/fr/misc.php'),
    'fr.messages': require('../../../lang/fr/messages.php'),
    'fr.pagination': require('../../../lang/fr/pagination.php'),
    // Fallbacks
    'en.misc': require('../../../lang/en/misc.php'),
    'en.messages': require('../../../lang/en/messages.php'),
    'en.pagination': require('../../../lang/en/pagination.php'),

    window.lang = new Lang({
        locale: 'fr',
        messages: messages,
        fallback: 'en',
    });

I am providing an English version of the JSON file as the key itself is the valid English translation.

Added a PR here #83