Categories
Vue

vue-i18n — Formatting Text and Numbers

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to create multilingual Vue apps with vue-i18n.

Localizing Numbers

We can display numbers in the locale we want with vue-i18n.

To do that, we add the format that we want:

import Vue from "vue";
import App from "./App.vue";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);
Vue.config.productionTip = false;

const numberFormats = {
  "en-ca": {
    currency: {
      style: "currency",
      currency: "CAD"
    }
  },
  fr: {
    currency: {
      style: "currency",
      currency: "EUR",
      currencyDisplay: "symbol"
    }
  }
};

const i18n = new VueI18n({
  numberFormats
});

new Vue({
  i18n,
  render: h => h(App)
}).$mount("#app");

We add the number style with the style property.

currency tells us what currency it is, and currencyDisplay tells how to display tjhe currency value.

Then we can use the $n function in our component:

<template>
  <div id="app">
    <p>{{ $n(100, 'currency', 'fr') }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

to format the number.

Now we get:

100,00 €

on the screen.

Custom Formatting

We can format currencies our way.

The i18n-n component lets us format currencies.

For instance, we can write:

<template>
  <div id="app">
    <i18n-n :value="100" format="currency" locale="fr"></i18n-n>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

value has the currency value. format tells us how to format the number.

locale is the locale.

The component also accepts content in its slots for more customization.

We can add content to the currency slot to format it as a currency:

<template>
  <div id="app">
    <i18n-n :value="100" format="currency" locale="fr">
      <template v-slot:currency="{ currency }">
        <span style="color: red">{{ currency }}</span>
      </template>
    </i18n-n>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

We populate the currency slot with our own content.

We add a span into the slot and styled it red.

Now the currency symbol is red.

There’s also an integer slot for the integer part of the number.

group slot has the thousands group.

fraction slot has the fractional part.

Locale Messages Syntax

We can add locale messages in various ways.

For instance, we can write:

import Vue from "vue";
import App from "./App.vue";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);
Vue.config.productionTip = false;

const messages = {
  en: {
    foo: "foo",
    nested: {
      message: "bar"
    },
    messages: [
      "baz",
      {
        internal: "internal message"
      },
      ["qux"]
    ]
  },
  fr: {
    //...
  }
};

const i18n = new VueI18n({
  messages,
  locale: "en"
});

new Vue({
  i18n,
  render: h => h(App)
}).$mount("#app");

then we can write:

<template>
  <div id="app">
    <p>{{ $t('foo') }}</p>
    <p>{{ $t('nested.message') }}</p>
    <p>{{ $t('messages[0]') }}</p>
    <p>{{ $t('messages[1].internal') }}</p>
    <p>{{ $t('messages[2][0]') }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

in our component.

We can have messages in objects, arrays, and we can get them by their usual path with $t .

So we have:

foo

bar

baz

internal message

qux

displayed on the screen.

Linked Locale Messages

Locale messages can also reference other locale messages.

We can do that with @: .

For instance, we can write:

main.js

import Vue from "vue";
import App from "./App.vue";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);
Vue.config.productionTip = false;

const messages = {
  en: {
    foo: "foo",
    foobar: "@:foo bar"
  },
  fr: {}
};

const i18n = new VueI18n({
  messages,
  locale: "en"
});

new Vue({
  i18n,
  render: h => h(App)
}).$mount("#app");

App.vue

<template>
  <div id="app">
    <p>{{ $t('foo') }}</p>
    <p>{{ $t('foobar') }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

The @: symbol indicates that we match the message with the key that comes after it.

We can also format the linked messages.

The modifiers can be upper , lower , and capitalize .

For instance, we can write:

import Vue from "vue";
import App from "./App.vue";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);
Vue.config.productionTip = false;

const messages = {
  en: {
    foo: "foo",
    foobar: "@.upper:foo bar"
  },
  fr: {}
};

const i18n = new VueI18n({
  messages,
  locale: "en"
});

new Vue({
  i18n,
  render: h => h(App)
}).$mount("#app");

to make 'foo' upper case.

Now we get:

foo

FOO bar

displayed on the screen.

Conclusion

There are many ways to format numbers and locale strings with vue-i18n.

Messages can reference other messages. Numbers can be formatted as currencies.

Categories
Vue

Creating Multilingual Apps with vue-i18n

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to create multilingual Vue apps with vue-i18n.

Getting Started

To get started, we install the package by writing:

npm i vue-i18n

Then we can write:

main.js

import Vue from "vue";
import App from "./App.vue";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);

const messages = {
  en: {
    message: {
      hello: "hello"
    }
  },
  fr: {
    message: {
      hello: "bonjour"
    }
  }
};

const i18n = new VueI18n({
  locale: "fr",
  messages
});

Vue.config.productionTip = false;
new Vue({
  i18n,
  render: h => h(App)
}).$mount("#app");

And we can display the translation by writing:

<template>
  <div id="app">
    <p>{{ $t("message.hello") }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

Formatting

We can interpolate expression in our messages by writing:

import Vue from "vue";
import App from "./App.vue";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);

const messages = {
  en: {
    message: {
      hello: "{msg} world"
    }
  },
  fr: {
    message: {
      hello: "bonjour"
    }
  }
};

const i18n = new VueI18n({
  locale: "en",
  messages
});

Vue.config.productionTip = false;
new Vue({
  i18n,
  render: h => h(App)
}).$mount("#app");

And in our component, we can write:

<template>
  <div id="app">
    <p>{{ $t('message.hello', { msg: 'hello' })  }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

Then we get hello world displayed on screen.

List Formatting

We can format lists with vue-i18n.

For example, we can write:

const messages = {
  en: {
    message: {
      hello: "{0} world"
    }
  },
  fr: {
    message: {
      hello: "bonjour"
    }
  }
};

And we can write:

<template>
  <div id="app">
    <p>{{ $t('message.hello', ['hi']) }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

to display hello world .

We can also use an array-like object instead of an array:

<template>
  <div id="app">
    <p>{{ $t('message.hello', {'0': 'hi'}) }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

And we get the same thing.

Ruby on Rails Message Format

The placeholder can also be written in Ruby on Rails format.

For instance, we can write:

const messages = {
  en: {
    message: {
      hello: "%{msg} world"
    }
  },
  fr: {
    message: {
      hello: "bonjour"
    }
  }
};

And we write:

<template>
  <div id="app">
    <p>{{ $t('message.hello', {msg: 'hi'}) }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

Custom Formatter

We can add a custom formatter class:

class CustomFormatter {
  constructor (options) {
    // ...
  }

 interpolate (message, values) {
    return ['resolved message string']
  }
}

Then we can set it in the message that we pass into the VueI18n constructor:

const i18n = new VueI18n({
  locale: 'en',
  formatter: new CustomFormatter(),
  messages: {
    en: {
      // ...
    },
    // ...
  }
})

We just set the formatter property of the object we pass into the constructor.

Pluralization

vue-18n supports pluralization.

To add single and plural words to messages , we add the words to a string separated by the | character.

For instance, we can write:

import Vue from "vue";
import App from "./App.vue";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);

const messages = {
  en: {
    apple: "apple | apples",
    orange: "no oranges | one orange | {count} oranges"
  }
};

const i18n = new VueI18n({
  locale: "en",
  messages
});

Vue.config.productionTip = false;
new Vue({
  i18n,
  render: h => h(App)
}).$mount("#app");

Then we write the following in our component:

<template>
  <div id="app">
    <p>{{ $tc('apple', 1) }}</p>
    <p>{{ $tc('apple', 2) }}</p>

    <p>{{ $tc('orange', 0) }}</p>
    <p>{{ $tc('orange', 1) }}</p>
    <p>{{ $tc('orange', 5, { count: 5 }) }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

And we get:

apple

apples

no oranges

one orange

5 oranges

on the screen.

We used the $tc function instead of the $t function for regular translations.

The placeholder is in the braces.

DateTime Localization

Date and time can also be localized, we just have to pass in an object with the date formats we want to show.

To do that, we write:

import Vue from "vue";
import App from "./App.vue";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);

const dateTimeFormats = {
  en: {
    short: {
      year: "numeric",
      month: "short",
      day: "numeric"
    },
    long: {
      year: "numeric",
      month: "short",
      day: "numeric",
      weekday: "short",
      hour: "numeric",
      minute: "numeric"
    }
  }
};

const i18n = new VueI18n({
  locale: "en",
  dateTimeFormats
});

Vue.config.productionTip = false;
new Vue({
  i18n,
  render: h => h(App)
}).$mount("#app");

Then we can use the $d function as follows:

<template>
  <div id="app">
    <p>{{ $d(new Date(), 'short') }}</p>
    <p>{{ $d(new Date(), 'long', 'en') }}</p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

We pass in the 'long' and 'short' format strings and optionally the language.

Conclusion

We can use vue-i18n to format localize our apps.

Text, single and plural words, and dates can be added for different locales.

Categories
Vue

Create a Todo List with the Oruga Library for Vue

The Oruga library is made by the same developer as the popular Buefy library.

It works with Vue 2.x.

In this article, we’ll look at how to create a todo list app with the Oruga library.

Get Started

We can create a project with Vue CLI by running:

npx vue create todo-list

To get started, we run:

npm install @oruga-ui/oruga --save

or:

yarn add @oruga-ui/oruga

to install the library.

It’s also available from a CDN that we can add to our HTML with a link and script tag:

<link rel="stylesheet" href="//unpkg.com/oruga/dist/oruga.css" />
<script src="//unpkg.com/oruga/dist/oruga.js"></script>

Once we installed the library, we can use it by writing:

import Vue from "vue";
import App from "./App.vue";
import Oruga from "@oruga-ui/oruga";
import "@oruga-ui/oruga/dist/oruga.css";

Vue.use(Oruga);
Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

in main.js .

We add the CSS and the Oruga plugin.

The components can be added individually with Vue.use also.

Building the App

To build the app, we create the form with:

<form @submit.prevent="addTodo">
  <o-field>
    <o-input placeholder="todo" type="text" v-model="todo"></o-input>
    <o-button type="submit">add</o-button>
  </o-field>
</form>

in App.vue .

We use the o-field compoennt to create the field and the o-input component to add the input.

v-model binds the input value to the todo state.

We add a type button with the o-button component.

Then we add our todo list with:

<div v-for="(t, i) of todos" :key="t.id" class="row">
  <div>
    <o-checkbox v-model="t.done"></o-checkbox>
  </div>
  <div :style="{'text-decoration' : t.done ? 'line-through' : ''}">{{ t.todo }}</div>
  <div>
    <o-button @click="deleteTodo(i)">delete</o-button>
  </div>
</div>

We render a list by using the v-for directive to loop through the todos array.

We have the o-checkbox to create the checkbox.

v-model binds to the done property to let us check and uncheck the box.

t.done is used to show the strikethrough effect if the todo is checked off.

The strikethrough effect is added with the :style binding.

And we have an o-button compoennt to let us delete the todo item when it’s clicked.

In our script tag, we have:

<script>
import { v4 as uuidv4 } from "uuid";

export default {
  name: "App",
  data() {
    return {
      todo: "",
      todos: [],
      columns: [
        {
          field: "todo",
          label: "todo"
        }
      ]
    };
  },
  methods: {
    addTodo() {
      this.todos.push({
        id: uuidv4(),
        todo: this.todo
      });
    },
    deleteTodo(index) {
      this.todos.splice(index, 1);
    }
  }
};
</script>

We have the states used in the templaye.

And we have some methods.

We have the addTodo to add a todo item with a unique ID.

And we have the deleteTodo method to remove the item by its index.

We layout the list rows by writing:

<style>
.row {
  display: flex;
  justify-content: space-between;
}
</style>

This lets us space out the items in the row.

Now we have:

todo list

on the screen.

We can add, check off, and delete an item.

Conclusion

Oruga is an easy to use the component library from the developer that developed Buefy.

We can use it to build interactive Vue apps.

Categories
BootstrapVue

BootstrapVue — Tooltips

In this article, we look at how to use the v-b-tooltip directive to add tooltips.

Tooltips

We can add tooltips with the v-b-tooltip directive.

For example, we can write:

<template>
  <div id="app">
    <b-button v-b-tooltip.hover title="Tooltip">Hover Me</b-button>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

We have the v-b-tooltip directive on our button.

title has the content of the tooltip.

The hover modifier makes the tooltip open on hover.

A tooltip on a disabled element must be triggered by its wrapper element.

Positioning

We can change the position that the tooltip is displayed with modifiers.

For example, we can write:

<template>
  <div id="app">
    <b-button v-b-tooltip.hover.bottomright title="Tooltip">Hover Me</b-button>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

We have the bottomright modifier to display the tooltip on the bottom right.

Other possible choices include top, topleft, topright, right, righttop, rightbottom, bottom, bottomleft, bottomright, left, lefttop, and leftbottom .

Triggers

We can change how the tooltip is triggered by some modifiers.

We can have no modifier, which means it’s opened when an element is hovered or focused.

hover means the tooltip opens on hover.

click means the tooltip opens on click.

focus means the tooltip opens on focus.

We can write:

<template>
  <div id="app">
    <b-button v-b-tooltip.click title="Tooltip">Hover Me</b-button>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

Now the tooltip opens on click because of the click modifier.

Disabled Elements

We have to trigger the tooltip on a wrapper if we want to open a tooltip on a disabled element.

For example, we can write:

<template>
  <div id="app">
    <span v-b-tooltip title="Disabled tooltip">
      <b-button variant="primary" disabled>Disabled</b-button>
    </span>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

We put the v-b-tooltip on the span instead of the disabled button.

The result is still having the tooltip disabled on hover or focus.

Focus and Button

Since b-button is rendered as a tag if we add the focus trigger.

We need to add the tabindex='0' attribute.

For instance, we can write:

<template>
  <div id="app">
    <b-button href="#" tabindex="0" v-b-tooltip.focus title="Tooltip">Link</b-button>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

Dismiss on Next Click

We need to add the click and blur modifiers to add a tooltip that only opens when the element is clicked and closed when anything in the document is clicked or receives focus.

For example, we write:

<template>
  <div id="app">
    <b-button v-b-tooltip.click.blur title="Tooltip">Link</b-button>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

Title

We can set the value of the v-b-tooltip directive to an object to set the options for the tooltip.

The object can have the title property to set the title.

For example, we can write:

<template>
  <div id="app">
    <b-button v-b-tooltip="options">Link</b-button>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      options: {
        title: "hello <strong>world</strong>",
        html: true
      }
    };
  }
};
</script>

We have the title property with some HTML inside.

html is set to true so that the HTML is rendered.

Variants

To change the style, we can add the variant property to the object.

For example, we can write:

<template>
  <div id="app">
    <b-button v-b-tooltip="options">Link</b-button>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      options: {
        title: "hello <strong>world</strong>",
        html: true,
        variant: "info"
      }
    };
  }
};
</script>

Then variant is 'info' , so the background will be green.

Other possible values include, 'primary' , 'secondary' , 'warning' , 'success' , etc.

Make the Tooltip Non-Interactive

We can make a tooltip non-interactive with the noninteractive prop.

Hiding and Showing Tooltips via $root Events

To show a tooltip, we can write:

this.$root.$emit('bv::show::tooltip', 'trigger-button-id')

to show a tooltip that’s added to the element with ID trigger-button-id .

To hide all tooltips, we can write:

this.$root.$emit('bv::hide::tooltip')

this is the component.

Disabling and Enabling Tooltips via $root Events

We can disable all tooltips by writing:

this.$root.$emit('bv::disable::tooltip')

to disable a tooltip by element ID, we can write:

this.$root.$emit('bv::disable::tooltip', 'trigger-button-id')

and to enable a tooltip by ID, we can write:

this.$root.$emit('bv::enable::tooltip', 'trigger-button-id')

Conclusion

We can add a tooltip to an element with the v-b-tooltip directive.

The content can be plain text or HTML.

Categories
BootstrapVue

BootstrapVue — Reacting on Visibility

To make good looking Vue apps, we need to style our components.

To make our lives easier, we can use components with styles built-in.

In this article, we look at how to use the v-b-visible directive to run something when something becomes visible or not.

Directive Syntax

We can use the v-b-visible directive, by writing:

<div v-b-visible.[mod1].[mod2]="callback">content</div>

mod1 and mod2 are modifiers.

callback is a function that runs when the visibility status changes.

It has a parameter to indicate visibility.

The parameter can be true or false .

The callback will be run each time the element’s visibility changes except when the once modifier is used.

All modifiers are optional.

Example

We can use it by writing:

<template>
  <div v-b-visible="visibleHandler">...</div>
</template>

<script>
export default {
  methods: {
    visibleHandler(isVisible) {
      if (isVisible) {
        // Do something
      } else {
        // Do something else
      }
    }
  }
};
</script>

We have the visibleHandler function.

isVisible indicates whether the div is visible or not.

Then we can do something according to the visibility status.

Use v-b-visible with the once Modifier

We can use the once modifier with v-b-visible .

For example, we can write:

<template>
  <div v-b-visible.once="visibleHandler"> ... </div>
</template>

<script>
export default {
  methods: {
    visibleHandler(isVisible) {
      if (isVisible) {
        // ...
      } else {
        // ...
      }
    }
  }
}
</script>

Then visibleHandler is only called once.

So isVisible can be true once when the element becomes visible for the first time.

isVisible can be false for zero or more times.

It’ll never be false after the element has become visible.

Use with once and Offset Modifiers

We can also add an offset modifier.

For instance, we can write:

<template>
  <div v-b-visible.once.350="visibleHandler">...</div>
</template>
<script>
export default {
  methods: {
    visibleHandler(isVisible) {
      if (isVisible) {
        //...
      } else {
        //...
      }
    }
  }
};
</script>

The offset is the number of pixels away from the edge of the viewport to determine when the element is considered to be in the viewport.

Therefore, it’ll be considered to be in the viewport if it’s within 350px of the viewport.

For example, we write:

<template>
  <div
    v-b-visible.once.350="visibleHandler"
    :class="[isVisible ? 'bg-info' : 'bg-light', 'text-center']"
  >
    <p v-for="n in 100" :key="n">{{n}}</p>
    <b-badge v-b-visible="visibleHandler">Element with v-b-visible directive</b-badge>
    <p v-for="n in 100" :key="n">{{n}}</p>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isVisible: false
    };
  },
  methods: {
    visibleHandler(isVisible) {
      this.isVisible = isVisible;
    }
  }
};
</script>

We’ll see the background in green if we see the badge.

Otherwise, we’ll see a light-colored background.

This is because we added the v-b-visible directive to the b-badge .

The visibilityHandler is run when the badge becomes visible or not as we scroll.

The class prop on the div will change the class according to the isVisible state, which we update in visibleHandler .

CSS Display Visibility Detection

We can detect visibility with CSS.

For instance, we can write:

<template>
  <div>
    <b-button @click="show = !show">Toggle display</b-button>
    <p>Visible: {{ isVisible }}</p>
    <div class="border p-3">
      <div v-show="show" class="bg-info p-3">
        <b-badge v-b-visible="handleVisibility">Element with v-b-visible directive</b-badge>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      show: false,
      isVisible: false
    };
  },
  methods: {
    handleVisibility(isVisible) {
      this.isVisible = isVisible;
    }
  }
};
</script>

v-show shows and hides elements and components with CSS.

When it’s shown or hidden, handleVisibility will be triggered.

Therefore, the isVisible will be triggered at the same time as show .

Conclusion

We can use the v-b-visible directive to detect visibility changes of elements or components.

Then we can run callbacks to do something when the visibility of something changes.