2018年2月10日 星期六

[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





沒有留言:

張貼留言