3

Is there a way to achieve what d3 can do with precisionPrefix in the vue-i18n ecosystem?

There is an open issue here.

And I found an old issue for Intl object here.

Thanks for your help.

Existe Deja
  • 1,227
  • 3
  • 14
  • 36

2 Answers2

2

One hacky way will be replace VueI18n.n with your own handler.

  1. add custom property=abbreviate to VueI18n.numberFormats in order to determinate if apply special style.

  2. save origial VueI18n.n to VueI18n.n1 (we will still use VueI18n.n1 for other styles)

  3. uses your own handler to replace VueI18n.n, then inside the handler, check if numberFormatter.abbreviate is true, if yes, apply your special style (At below demo, simply uses regex expression to implement it).

Like below simple demo:

PS: below I uses one simple regex expression to apply the styles, for some special number format, it may not work, you need to improve it by yourself.

Vue.use(VueI18n)

const numberFormats = {
   'en-US': {
     currency: {
       style: 'currency', currency: 'USD', currencyDisplay: 'symbol', useGrouping: false
     }
   },
   'ja-JP': {
     currency: {
       style: 'currency', currency: 'JPY', currencyDisplay: 'name', 
abbreviate: true, // custom property to determinate whether apply special styles
       maximumSignificantDigits: 4, useGrouping: true
     }
   }
 }

const i18n = new VueI18n({
  numberFormats
})

i18n.n1 = i18n.n // save default i18n.n to i18n.n1

i18n.n= function (nValue, nStyle) {
  let numberFormatter = this.getNumberFormat(this.locale) // get NumberFormat Based on locale
  if (numberFormatter[nStyle].abbreviate) { // if custom property is true
    let newValue = Math.round(nValue/1000000) // divide 10^6 for millions, divide 10^3 for K .etc
    return this.n1(newValue, nStyle).replace(/(\d[\d|,]*)(\.\d+)?/, '$1 $2M')
  }
  return this.n1(nValue, nStyle) // for others , use default i18n.n to process.
}

Vue.config.productionTip = false

app = new Vue({
  el: "#app",
  i18n,
  data: {
    test: 1234567890,
    language: 'en-US'
  },
  computed: {
    computedTest: function () {
      return this.$n(this.test, 'currency')
    }
  },
  methods: {
    toggleLanguage: function () {
      this.language = this.language === 'en-US' ? 'ja-JP' : 'en-US'
      this.$i18n.locale = this.language
    }
  }
})
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-i18n/7.8.0/vue-i18n.js"></script>
<div id="app">
  <button @click="toggleLanguage()">Language </button>
  <p>Language: [{{language}}]</p>
  <p>Org: {{test}}</p>
  <p>localed: {{computedTest}}</p>
</div>

Edit: another approach: uses D3-format

Vue.use(VueI18n)

const numberFormats = {
   'en-US': {
     currency: {
       style: 'currency', currency: 'USD', currencyDisplay: 'symbol', useGrouping: false
     }
   },
   'ja-JP': {
     currency: {
       style: 'currency', currency: 'JPY', currencyDisplay: 'name', 
abbreviate: true, // custom property to determinate whether apply special styles
       maximumSignificantDigits: 4, useGrouping: true
     }
   }
 }

const i18n = new VueI18n({
  numberFormats
})

let d3Locales = {}

axios.get('https://unpkg.com/d3-format@1/locale/ja-JP.json')
.then(function (response) {
  d3Locales['ja-JP'] = response.data
})
.catch(function (error) {
  console.log(error);
});

axios.get('https://unpkg.com/d3-format@1/locale/en-US.json')
.then(function (response) {
  d3Locales['en-US'] = response.data
})
.catch(function (error) {
  console.log(error);
});

i18n.n1 = i18n.n // save default i18n.n to i18n.n1

i18n.n= function (nValue, nStyle) {
  let numberFormatter = this.getNumberFormat(this.locale) // get NumberFormat Based on locale
  if (numberFormatter[nStyle].abbreviate) { // if custom property is true
    d3.formatDefaultLocale(d3Locales[this.locale])
    let p = d3.precisionPrefix(1e4, 1e6),
        f = d3.formatPrefix("$." + p, 1e6)
    return f(nValue);
  }
  return this.n1(nValue, nStyle) // for others , use default i18n.n to process.
}

Vue.config.productionTip = false

app = new Vue({
  el: "#app",
  i18n,
  data: {
    test: 1234567890,
    language: 'en-US'
  },
  computed: {
    computedTest: function () {
      return this.$n(this.test, 'currency')
    }
  },
  methods: {
    toggleLanguage: function () {
      this.language = this.language === 'en-US' ? 'ja-JP' : 'en-US'
      this.$i18n.locale = this.language
    }
  }
})
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-i18n/7.8.0/vue-i18n.js"></script>
<script src="https://d3js.org/d3-format.v1.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
  <button @click="toggleLanguage()">Language </button>
  <p>Language: [{{language}}]</p>
  <p>Org: {{test}}</p>
  <p>localed: {{computedTest}}</p>
</div>
Sphinx
  • 10,519
  • 2
  • 27
  • 45
  • Thank you for your proposal. What you silently say is there is no classic way to do it... I will couple your surcharge approach with d3-format. I have to translate my numbers into 7 languages, with lot of different numbers, I can't do it from scratch. – Existe Deja Jun 18 '18 at 10:40
  • @GeorgeAbitbol because i18n uses [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat), but this API can't implement your requirements. Instead of Regex expression, another approach will be uses one thrid party lib to reach the goal, like [Numeral.js](http://numeraljs.com/) – Sphinx Jun 18 '18 at 15:40
  • @GeorgeAbitbol, I didn't use D3 before, but after google, it seems this library may fit your demands better, [D3-format](https://github.com/d3/d3-format) – Sphinx Jun 18 '18 at 15:52
  • The answer from the author of i18n > https://github.com/kazupon/vue-i18n/issues/369#issuecomment-398097036 – Existe Deja Jun 19 '18 at 08:27
0

After digging around for a while, I decided to write my own helper methods as the price displaying on my site is quite big so abbreviation is a must. @Sphinx solution seems to be too much effort just for making the number shorter.

I created a utils file and import i18n into it. The idea is from how to convert numbers to million in javascript

//Utils.js
import { i18n } from 'boot/i18n.ts'

export const CURRENCY_CALCULATOR = (price) => {
  let abbreviatedPrice = price
  if (i18n.locale === 'en-us') {
    abbreviatedPrice = Math.abs(Number(price)) >= 1.0e+6
      ? Math.abs(Number(price)) / 1.0e+6 + "M"
      : Math.abs(Number(price)) >= 1.0e+3
      ? Math.abs(Number(price)) / 1.0e+3 + "K"
      : Math.abs(Number(price));
  } else {
    // Other languages uses different units
    abbreviatedPrice = Math.abs(Number(price)) >= 1.0e+4
      ? Math.abs(Number(price)) / 1.0e+4 + i18n.t('sorting.tenThousands')
      : Math.abs(Number(price));
  }
  // setup currency symbol by yourself, because I use the same symbol for both
  return `$ ${abbreviatedPrice}` 
}

In your component

<template>
  <div class="col-auto">
    {{ calculateCurrency(item.price) }}
  </div>
</template>

<script>
import { CURRENCY_CALCULATOR } from '../constants/Utils'

export default {
  name: '<<component name here>>',
  props: ['<<your props here>>']
  methods: {
    calculateCurrency (price) {
      return CURRENCY_CALCULATOR(price)
    }
  }
}
</script>

Alan Ho
  • 646
  • 9
  • 5