vuedev-com / vue-localize

Localization plugin for vue.js based applications
44 stars 3 forks source link

vue-localize

Localization plugin for implementation multilingual functionality in VueJS based applications with Vuex and VueRouter

Demo

Working example

Example sources

Important

You can NOT use this plugin without Vuex

You can NOT use this plugin without VueRouter, however it will be possible at an early date (and this line will be deleted immediately).

Links

Functionality and features

Installation

In your project folder (where is package.json)

$ npm install vue-localize --save

Integration

Full-featured example of integration in app with Vuex and VueRouter.

To use full set of VueLocalize features you need to do following simple steps:

Importing and registering VueLocalize plugin

In your entry point (usually it's index.js or main.js)

import Vue from 'vue'

import VueRouter from 'vue-router'
Vue.use(VueRouter)

var router = new VueRouter({
  // your set of options
})

// Import router config obejct
import routes from './config/routes'

// Import plugin config
import vlConfig from './config/vue-localize-conf'

// Import vuex store (required by vue-localize)
import store from './vuex/store'

// Import VueLocalize plugin
import VueLocalize from 'vue-localize'

Vue.use(VueLocalize, {// All options is required
  store,
  config: vlConfig,
  router: router,
  routes: routes
})

// Import App component - root Vue instance of application
import App from './App'

// Application start
router.start(App, '#app')

Pay attention (!) there is no line with router.map(routes) in the code above. When using automatic routes localization, VueLocalize will transform your initial router config and VueRouter will use it already transformed. So this line of code is built into the plugin. The more detailed explanation provided below.

Adding Vuex store module

Note that VueLocalize contains built-in Vuex store module, so if Vuex states and mutations in your application don't splitted in sub-modules, it's time to do so. Here you can find the information about how to split state and mutations in sub modules http://vuex.vuejs.org/en/structure.html

Also note that it is important to use the exact name of module vueLocalizeVuexStoreModule in your code.

Code for your store.js

import Vuex from 'vuex'
import Vue from 'vue'
import { vueLocalizeVuexStoreModule } from 'vue-localize'
// import other Vuex modules

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    vueLocalizeVuexStoreModule,
    // other Vuex modules
  }
})

Setting up an initial state

You can't know in advance, what exact route will initialize your application. It can be either route with leading language part or without. And VueLocalize needs to understand, what exact language it should set as the initial. It can be a language from the route, or saved in local storage if there is no language part in route (e.g. in the admin section), or the default language.

And there is the global method $vueLocalizeInit($route) for this purpose. It's just a function which gets a route object as the attribute.

We recommend place the call of this method in the ready hook of the root Vue instance, which was passed in router.start() In our example it's the App.vue component.

<script>
import store from '../vuex/store'
export default {
  // injecting Vuex store into the root Vue instance
  store,
  ready: function () {
    this.$vueLocalizeInit(this.$route)
  }
}
</script>

Configuration file

import translations from './vue-localize-translations'
export default {
  languages: {
    en: {
      key: 'eng',
      enabled: true
    },
    ru: {
      key: 'rus',
      enabled: true
    },
    de: {
      key: 'deu',
      enabled: false
    }
  },
  defaultLanguage: 'en',
  translations: translations,
  defaultLanguageRoute: true,
  resaveOnLocalizedRoutes: false,
  defaultContextName: 'global',
  fallbackOnNoTranslation: false,
  fallbackLanguage: 'en',
  supressWarnings: false
}

Options explanation

Translations file structure, contexts and global context

Translations structure is just a JSON object, so you can to structure translations as you want.

export default {
  // global context
  'global': {
    'project-name': {
      en: 'Name of the project in English',
      ru: 'Название проекта на Русском'
    },
    // translations for language selector items
    lang: {
      eng: {
        en: 'English',
        ru: 'Английский'
      },
      rus: {
        en: 'Russian',
        ru: 'Русский'
      }
    }
  },
  // context for translations of frontend phrases (public section of your site)
  'site': {
    // context for translations of header
    'header': {
      // context for translations for anchors in nav menu
      'nav': {
        // key of the anchor of the link to homepage
        'home': {
          // translation of the anchor of the link to homepage into the English
          en: 'Home',
          ru: 'Главная'
        },
        // key of the anchor of the link to about page
        'about': {
          en: 'About',
          ru: 'О проекте'
        },
        // key of the anchor of the link to contacts page
        'contacts': {
          en: 'Contacts',
          ru: 'Контакты'
        },
        'loginbox': {
          // ...
        }
      }
    },
    'footer': {
      // translations of footer phrases
    }
  },
  'admin': {
    // translations of administration panel phrases
  }
}

E.g. to get the translation of the anchor of the link to homepage into the current language, you should pass the path to the phrase key to the translation mechanism. In this case site.header.nav.home is the path, the part site.header.nav of this path is the "context" and home is the key of a phrase. So the path to any node which does not contain leafs is a context, each node which contains leafs is a key of a phrase and each leaf is the translation for the exact language.

Global context

The Global context is the root-level key, defined in the corresponding option of the VueLocalize configuration file. The feature of the global context is that you don't need include its name in the path which passing into translation method/filter/directive. E.g. to translate phrase with path global.project-name you can write just {{ 'project-name' | translate }} instead of full path global.project-name.

Router config for automatic routes localization

Example below assumes an application of a website, that consists of the public and administrative sections and assumes that the public section should working with localized routes paths and the administrative section shouldn't.

import SiteLayout from './components/SiteLayout'
import HomePage from './components/HomePage'
import SiteLayout from './components/AboutPage'
import SiteLayout from './components/ContactsPage'
import SiteLayout from './components/AdminLayout'
// ... importing other components

export default {
    // the parent route for public section of your application
    '/': {
      localized: true, // (!!!) the only thing you have to add for localize this route and all nested routes recursively
      component: SiteLayout,
      subRoutes: {
        '/': {
          name: 'home-page',
          component: HomePage
        },
        '/about': {
          name: 'about-page',
          component: AboutPage
        },
        '/contacts': {
          name: 'contacts-page',
          component: ContactsPage
        },
      }
    },
    '/admin': {
        component: AdminLayout
        subRoutes: {
          // administration area subroutes
        }
    }
})

Pay attention to the localized: true option of the parent route for public section of application. This is really the only thing you have to add to internationalize path of this and all nested routes recursively. And you have to add this option only in the parent (root-level) routes and not in any sub routes.

What will happen?

If use the above described router config as is, we'll have the following paths of public section:

yourdomain.com/
yourdomain.com/about
yourdomain.com/contacts

VueLocalize will transform initial config automatically and in the end we'll have the following set of paths:

yourdomain.com/en
yourdomain.com/en/about
yourdomain.com/en/contacts
yourdomain.com/ru
yourdomain.com/ru/about
yourdomain.com/ru/contacts

Transitions between routes e.g. yourdomain.com/en/about and yourdomain.com/ru/about (when switching languages via language selector) will reuse the same component. So if you have any data on the page (in the component bound to the current route), and the switching to another language, data will not be affected despite the fact that the route has been actually changed. VueLocalize simply performs reactive translation of all the phrases at the page.

Excluding leading language part from routes paths for default language

Note that it's easy to exclude leading language part from routes for default language if needed. E.g. if English is defined as default application language, the only thing we have to do for - set to false the defaultLanguageRoute option in the config. Then we'll have the following set of paths:

# for English
yourdomain.com/
yourdomain.com/about
yourdomain.com/contacts
# for Russian
yourdomain.com/ru
yourdomain.com/ru/about
yourdomain.com/ru/contacts

And the dump of the transformed router config below helps to understand better what will happen with initial router config and how exactly it will be transformed.

export default {
    '/en': {
      localized: true,
      lang: 'en',
      component: SiteLayout,
      subRoutes: {
        '/': {
          name: 'en_home-page',
          component: HomePage
        },
        '/about': {
          name: 'en_about-page',
          component: AboutPage
        },
        '/contacts': {
          name: 'en_contacts-page',
          component: ContactsPage
        },
      }
    },
    '/ru': {
      localized: true,
      lang: 'ru',
      component: SiteLayout,
      subRoutes: {
        '/': {
          name: 'ru_home-page',
          component: HomePage
        },
        '/about': {
          name: 'ru_about-page',
          component: AboutPage
        },
        '/contacts': {
          name: 'ru_contacts-page',
          component: ContactsPage
        },
      }
    },
    '/admin': {
        component: AdminLayout
        subRoutes: {
          // ...
        }
    }
})

As you can see

There is two important things you should consider when using this plugin:

Language selector example

Simple selector with bootstrap dropdown

<template>
  <li class="dropdown" :class="{'open': opened}">
    <a href="https://github.com/vuedev-com/vue-localize/blob/master/javascript:;" @click="toggle">{{ currentLanguage | uppercase }} <span class="caret"></span></a>
    <ul class="dropdown-menu">
      <li v-for="(code, language) in $localizeConf.languages" v-if="code !== currentLanguage && language.enabled !== false">
          <a href="https://github.com/vuedev-com/vue-localize/blob/master/{{ $localizeRoutePath($route, code) }}" @click.prevent="changeLanguage(code)">
            {{ code | uppercase }} | {{ 'global.lang.' + language.key | translate null code }}<br />
            <small class="text-muted">{{ 'global.lang.' + language.key | translate null currentLanguage }}</small>
          </a>
      </li>
    </ul>
  </li>
</template>
<script>
import { replace } from 'lodash'
export default {
  data () {
    return {
      opened: false
    }
  },
  methods: {
    toggle: function () {
      this.opened = !this.opened
    },
    changeLanguage: function (code) {
      this.toggle()
      if (!this.$route.localized) {
        this.$store.dispatch('SET_APP_LANGUAGE', code)
      } else {
        var oldRouteName = this.$route.name
        var routeName = replace(oldRouteName, /^[a-z]{2}/g, code)
        this.$router.go({name: routeName})
      }
    }
  }
}
</script>

The example above uses the following features:

Read more about these features in the "API" section below. Pay attention that in the example above we dispatch mutation only for non localized routes, but if route has flag localized: true we perform router.go() and in this case mutation will be dispatched automatically inside the VueLocalize plugin

Usage

Translating

VueLocalize provides three ways for translating phrases:

Ultimately in all these cases translation will be performed by the same internal mechanism of the plugin, which is just a function with following three arguments: (path, [vars], [lang])

Let's look at examples of usage listed above different translating methods

Translating via Vue filter

Just a translating into the current (selected) language

<span>{{ 'site.header.nav.home' | translate }}</span>

Translating into exact language, e.g. English

<span>{{ 'site.header.nav.home' | translate null 'en' }}</span>

Translating via direct method call

Translating into current language

<span>{{ $translate('site.header.nav.home') }}</span>
or
<span v-text="{{ $translate('site.header.nav.home') }}"></span>

Translating into exact language, e.g. English

<span>{{ $translate('site.header.nav.home', null, 'en') }}</span>

Translating via v-localize directive

Translating into current language

<span v-localize="{path: 'site.header.nav.home'}"></span>

Translating into exact language, e.g. English

<span v-localize="{path: 'site.header.nav.home', lang: 'en'}"></span>

Injection custom variables into complete translation

Lets define some variables just for example

export default {
  data () {
    return {
      vars: {
        '%foo%': 'Foo',
        '%bar%': 'Bar'
      }
    }
  }
}

and add the example phrase with translations into the global context:

export default {
  // global context
  'global': {
    'project-name': {
      en: 'Name of the project in English',
      ru: 'Название проекта на Русском'
    },
    'injection-test': { // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< our phrase for injection test
      en: 'Some string in English contains %foo% and only then %bar%',
      ru: 'Перевод фразы на русский, содержащий наоборот сначала %bar% и только потом %foo%'
    },
    // ...
  },
  // ....
}

Injecting with Vue filter

{{ 'injection-test' | translate vars }}

Injecting with direct call

{{ $translate('injection-test', vars) }}

or

<span v-text="$translate('injection-test', vars)"></span>

Injecting with directive

<span v-localize="{path: 'injection-test', vars: vars}"></span>

API

Global properties and methods

Filters

Directives

Mixins

Mutations