A scalable Vue2 PWA boilerplate with Vuetify & Vue I18n

Hoàng Tuấn
4 min readMar 24, 2020

A few months ago, I’ve started a journey on building a side project. It’s a Vue2 PWA (progressive web app) which leverages Vuetify — a material design component framework — for developing web pages layout & UI elements with beauty & ease. At the time when I decided to add localization support to the web app, I couldn’t find out a suitable boilerplate for my need. That’s why I try to did it myself, with vue-i18n from Kazupon (now managed by intlify organization).


If you want to deep dive quickly, here’s the Github repo of the web app template. To see the code in action, please visit the demo URL.


First things first, let’s list some characteristics of the target template:

  • It should be scalable by allow placing locale translations both in global language files and in Vue SFC modules;
  • It should allow i18n translation usage outside Vue components (i.e. the place where Vue instance is NOT available);
  • The prefered locale should be persistent after page refresh;
  • Implementing i18n in new Vue SFC should be easy because developers would throw the boilerplate away if they have to add a bunch lines of code to enable i18n.

Quite enough? Let’s get started!


Here’s my development environments:

  • NodeJS version 12.3.1;
  • VueCLI version 3.9.3.

For the sake of brevity, I will omit some trivial tasks (such as how to create Vue PWA project, how to add a npm package, etc).

For Github page URL compliance, I have to set publicPath='/demo-webapp-vuetify-i18n/' in vue.config.js file to match Github repository’s name. In this case, to access the web app from localhost, you must use the URL http://localhost:8080/demo-webapp-vuetify-i18n. This is quite inconvenience if you don’t host the web app’s distribution at Github, so you could remove this configuration in order to access via usual URL http://localhost:8080.

Layout & Routing

The web app has a main layout HomeLayout.vue, which allows user to switch locale with an upper right text button. The bottom navigation will direct user to a desired child components(recent page or favorites page). The child page’s content will be loaded into the area between a top app bar and the bottom navigation menu.

Here’s what the routes path look like:

As you can see, the login page is a independent component — named LoginPage.vue — , i.e. its content does not get injected into home layout. User is unable to change locale here because the switch button will not be presented.

Vuex Store

Nothing much to consider here, Vuex store is the first name that comes to mind when we need to centrally store the web app’s current locale state.

Please note that Vuex state is not persistent across a browser refresh, so I decided to use VuexPersist to save/load that state to/from browser’s local storage.


This is the most interesting part that would make the boilerplate to be useful for developers… or totally not if we don’t craft it carefully ^^.

Firstly, the home layout shows a locale text button and allows user clicking to change the locale. When user click that button, the logical processing steps should be:

  1. The current locale will be changed to user’s chosen value and saved to Vuex;
  2. The i18n instance will need to be informed about new locale value and takes action according to;
  3. And last but not least, on Vue components’ created hook, they need to be able to get locale value of the previous session and start a new life cycle with that value.

Secondly, we could observe that step #1 is only applied to home layout where the locale button’s placed while the two later steps would happen to any Vue component in the web app. Therefore, we need a method to encapsulate that two logical steps.

Finally, Vue Mixins is the chosen method. In a nutshell, It’s a kind of inheritance in VueJS. You can read more here.

The mixins code would look like:

Then, the localeMixin usage in home layout:

And in login page:

If you check Favorite or Recent Vue component, you may see the missing of localeMixin. The reason is that these components are used as nested routes of the HomeLayout component (a kind of child-parent relationship).


Fortunately, VueI18n has already had support adding locale messages within Vue SFC — via <i18n> custom block. Hence, beside global files for common locale messages, we also have places for component-related locale messages.

This separation would help us to keep the global locale files as small as possible while more and more components are added to the project’s code base. That’s why we will get scalability naturally.

Final Thoughts

So far, you may get the main idea behind the scene (I hope so). All other details will be revealed when you discover the Github code base.

Thanks for reading! Feel free to follow me for further posts.

Happy Coding \(^_^)/