Saturday, February 10, 2018

[Vue] Internationalization with vue-i18n

  Vue.js    vue-form     validator


▌Introduction


vue-i18n is a internationalization plugin for Vue.js
I will show three ways for setting localization dictionary in this sample:

1.  Constant variable
2.  JSON file
3.  ASP.NET Resource file (.resx)

▌Environment


▋vue.js 2.5.13
▋vue-i18n 7.4.2



▌Implement


▋From constant variable

▋JS for creating vue-i18n instance

Vue.use(VueI18n);

const messages = {
   enUS: {
    "column": {
      "key": "Book title",
      "description": "Price",
      "createBy": "Create by",
      "createOn": "Create On",
      "updateBy": "Update by",
      "updateOn": "Update On"
    },
    "text": {
      "search": "Search"
    }
  },
   zhTW: {
    "column": {
      "key": "書名",
      "description": "價格",
      "createBy": "建立者",
      "createOn": "建立日期",
      "updateBy": "更新者",
      "updateOn": "更新日期"
    },
    "text": {
      "search": "搜尋"
    }
  }
}

// Create VueI18n instance with options
const i18n = new VueI18n({
    locale: '', // set locale
    fallbackLocale: 'enUS',
    messages, // set locale dictionary
});

Notice that you have to name the constant variable as “messages” or the data won’t be loaded to vue-i18n.



▋JS (Inject the vue-i18n instance)

var app = new Vue({
    el: '#app',
    i18n,  //In IE, you have to write like this... i18n: i18n
    data: {
        locale: '',
    },
created: function () {
        var vm = this;
        //vm.$i18n.setLocaleMessage('zh', messages.ja); //You can also set messages here
        vm.$i18n.locale = 'zhTW';
    }
}


▋HTML

<div id="app" class="container-fluid">
    <div class="container">
        <div class="row">
            <div class="col-2">
                <input type="text" v-model="keyword" class="form-control" />
            </div>
            <div class="col-2">
                <input type="button" class="btn btn-warning" :value="$t('text.search')" />
            </div>
        </div>
    </div>

    <div style="width:100%">
        <div class="col-centered">   </div>
        <table class="table">
            <thead class="thead-dark">
                <tr>
                    <th>#</th>
                    <th>{{ $t("column.key") }}</th>
                    <th class="col-xs-2">{{ $t("column.description") }}</th>
                    <th>{{ $t("column.createBy") }}</th>
                    <th>{{ $t("column.createOn") }}</th>
                    <th>{{ $t("column.updateBy") }}</th>
                    <th>{{ $t("column.updateOn") }}</th>
                </tr>
            </thead>
        </table>
</div>






PS. I skip the code of footer component which can changes the vm.$i18n.locale‘s value dynamically.


▋From json files

If we would like to put the localization dictionary to a JSON file but not in javascript, we can ajax the dictionary and lazy load it. (See more on vue-i18n: Lazy loading)

▋en-us.json

{
  "column": {
    "key": "Book title",
    "description": "Price",
    "createBy": "Create by",
    "createOn": "Create On",
    "updateBy": "Update by",
    "updateOn": "Update On"
  },
  "text": {
    "search": "Search"
  }
}




▋JS for creating vue-i18n instance

Vue.use(VueI18n);

// Create VueI18n instance with options
const i18n = new VueI18n({
    locale: '', // set locale
    fallbackLocale: 'enUS',
});

function i18nGetEnUS() {
    // return axios.get('/wwwroot/js/assets/en-us.json');
    return axios.get('/api/Resource/get/en-US');
}

function i18nGetZhTW() {
    // return axios.get('/wwwroot/js/assets/zh-tw.json')
    return axios.get('/api/Resource/get/zh-TW');
}

var i18nPromise = axios.all([i18nGetEnUS(), i18nGetZhTW()])
    .then(axios.spread(function (response1, response2) {
        i18n.setLocaleMessage('enUS', response1.data);
        i18n.setLocaleMessage('zhTW', response2.data);
        console.log(Vue.prototype.$locale);
        i18n.locale = Vue.prototype.$locale;
    }));




▋From ASP.NET resource file (.resx)

If you already manage the localization dictionary in ASP.NET resource file, here is a way you can restore them to vue-i18n instance by web api.



▋Resource factory

However, the most difficult part is dump the resource file as JSON string.
So first we create ResourceFactory to store the data in resource as Dictionary<TKey, TValue>.


public enum ResourceFileNameEnum
    {
        Column = 1,
        Text
    }


public static class ResourceFactory
{
        public static Dictionary<string, string> GetResource(ResourceFileNameEnum rsFileName, CultureInfo targetCulture)
        {
            Dictionary<string, string> resources = new Dictionary<string, string>();
            //FieldInfo[] fields = null;
            ResourceSet resourceSet = null;
            switch (rsFileName)
            {
                case ResourceFileNameEnum.Column:
                    resourceSet = Resx.Resources.Column.ResourceManager.GetResourceSet(targetCulture, true, true);
                    break;
                case ResourceFileNameEnum.Text:
                    resourceSet = Resx.Resources.Text.ResourceManager.GetResourceSet(targetCulture, true, true);
                    break;
            }
            IDictionaryEnumerator enumerator = resourceSet.GetEnumerator();
            while (enumerator.MoveNext())
            {
                resources.Add(enumerator.Key.ToString(), enumerator.Value.ToString());
            }

            return resources;
        }
}


2019-06-09 Updated: Get the resource-file’s content by using System.Resources.ResourceManager as following.

public static class ResourceFactory
 {
        public static string GetResource(CultureInfo targetCulture, string rsFileName)
        {
            string rsBaseName = $"MyProject.{rsFileName}";
            Dictionary<string, string> resources = new Dictionary<string, string>();
            ResourceSet resourceSet = null;
            var resourceManager = new ResourceManager(rsBaseName, Assembly.GetExecutingAssembly());
            resourceSet = resourceManager.GetResourceSet(targetCulture, true, true);
            IDictionaryEnumerator enumerator = resourceSet.GetEnumerator();
            while (enumerator.MoveNext())
            {
                resources.Add(enumerator.Key.ToString(), enumerator.Value.ToString());
            }

            var camelCaseFormatter = new JsonSerializerSettings();
            camelCaseFormatter.ContractResolver = new CamelCasePropertyNamesContractResolver();
            string json = JsonConvert.SerializeObject(resources, camelCaseFormatter);

            return json;
        }
 }







▋Web API

public class ResourceController : ApiController
{
        internal class LocaleResource
        {
            public Dictionary<string, string> Column { get; set; }
            public Dictionary<string, string> Text { get; set; }
        }

        [HttpGet]
        public async Task<HttpResponseMessage> Get(string id)
        {
            CultureInfo ci = new CultureInfo(id);

            var columnResources = ResourceFactory.GetResource(ResourceFileNameEnum.Column, ci);
            var textResources = ResourceFactory.GetResource(ResourceFileNameEnum.Text, ci);

            var resources = new LocaleResource() {
                Column= columnResources,
                Text = textResources
            };

        var camelCaseFormatter = new JsonSerializerSettings();
        camelCaseFormatter.ContractResolver = new CamelCasePropertyNamesContractResolver();
        string json = JsonConvert.SerializeObject(resources, camelCaseFormatter);
        var response = this.Request.CreateResponse(HttpStatusCode.OK);
        response.Content = new StringContent(json, Encoding.UTF8, "application/json");
            return response;
        }
}




Remember to convert the JSON string as lower case camel!
Now you can update the url when doing lazy loading of vue-i18n and have fun.


function i18nGetEnUS() {
ss
    return axios.get('/api/Resource/get/en-US');
}





▌Reference





No comments:

Post a Comment