In NOVA LAB, every space station module relies on proven, tested systems — from navigation to communication. In the Vue ecosystem, there are popular plugins that solve common problems. Let's explore them and learn how to create advanced plugins.
vue-i18n is the official Vue plugin for multilingual support:1import { createI18n } from 'vue-i18n'
2
3const i18n = createI18n({
4 locale: 'pl',
5 fallbackLocale: 'en',
6 messages: {
7 pl: {
8 station: {
9 name: 'Stacja NOVA LAB',
10 status: 'Status: {status}',
11 crew: 'Załoga: {count} osób'
12 }
13 },
14 en: {
15 station: {
16 name: 'NOVA LAB Station',
17 status: 'Status: {status}',
18 crew: 'Crew: {count} members'
19 }
20 }
21 }
22})
23
24app.use(i18n)In a component:
1<script setup>
2import { useI18n } from 'vue-i18n'
3
4const { t, locale } = useI18n()
5</script>
6
7<template>
8 <h1>{{ t('station.name') }}</h1>
9 <p>{{ t('station.status', { status: 'Operational' }) }}</p>
10 <button @click="locale = 'en'">EN</button>
11 <button @click="locale = 'pl'">PL</button>
12</template>VeeValidate is a plugin for form validation:1<script setup>
2import { useForm, useField } from 'vee-validate'
3import * as yup from 'yup'
4
5const schema = yup.object({
6 missionName: yup.string().required('Mission name is required'),
7 crewCount: yup.number().min(1, 'Minimum 1 person').required()
8})
9
10const { handleSubmit } = useForm({ validationSchema: schema })
11
12const { value: missionName, errorMessage: nameError } = useField('missionName')
13const { value: crewCount, errorMessage: crewError } = useField('crewCount')
14
15const onSubmit = handleSubmit(values => {
16 console.log('Mission:', values)
17})
18</script>
19
20<template>
21 <form @submit="onSubmit">
22 <input v-model="missionName" placeholder="Mission name" />
23 <span v-if="nameError">{{ nameError }}</span>
24
25 <input v-model="crewCount" type="number" placeholder="Crew" />
26 <span v-if="crewError">{{ crewError }}</span>
27
28 <button type="submit">Start mission</button>
29 </form>
30</template>vue-toastification is a ready-made notification system:1import Toast from 'vue-toastification'
2import 'vue-toastification/dist/index.css'
3
4app.use(Toast, {
5 position: 'top-right',
6 timeout: 3000,
7 closeOnClick: true
8})The provide/inject pattern is the best way to share data from a plugin:
1import { ref, readonly } from 'vue'
2
3const NotificationPlugin = {
4 install(app, options = {}) {
5 const notifications = ref([])
6 let nextId = 0
7
8 function notify(message, type = 'info') {
9 const id = nextId++
10 notifications.value.push({ id, message, type })
11 setTimeout(() => {
12 notifications.value = notifications.value.filter(n => n.id !== id)
13 }, options.duration || 4000)
14 }
15
16 app.provide('notifications', {
17 list: readonly(notifications),
18 notify,
19 success: (msg) => notify(msg, 'success'),
20 error: (msg) => notify(msg, 'error'),
21 warning: (msg) => notify(msg, 'warning')
22 })
23 }
24}The key element —
readonly() prevents accidental modification of the notification list from outside the plugin.A good plugin accepts configuration with sensible default values:
1const ThemePlugin = {
2 install(app, options = {}) {
3 const defaults = {
4 primaryColor: '#00ff88',
5 darkMode: true,
6 fontFamily: 'monospace',
7 borderRadius: '8px'
8 }
9
10 const theme = ref({ ...defaults, ...options })
11
12 function setTheme(newOptions) {
13 theme.value = { ...theme.value, ...newOptions }
14 }
15
16 app.provide('theme', {
17 current: readonly(theme),
18 setTheme
19 })
20
21 // Global directive using the theme
22 app.directive('themed', (el, binding) => {
23 const t = theme.value
24 el.style.color = t.primaryColor
25 el.style.fontFamily = t.fontFamily
26 if (binding.modifiers.bordered) {
27 el.style.border = '1px solid ' + t.primaryColor
28 el.style.borderRadius = t.borderRadius
29 }
30 })
31 }
32}Installation:
1app.use(ThemePlugin, {
2 primaryColor: '#00b4d8',
3 darkMode: true
4})Plugins intended for distribution (e.g., as an npm package) should support auto-install:
1const MyPlugin = {
2 install(app, options) {
3 // plugin logic
4 }
5}
6
7// Auto-install when Vue is globally available (CDN)
8if (typeof window !== 'undefined' && window.Vue) {
9 window.Vue.use(MyPlugin)
10}
11
12export default MyPlugin| Feature | Composable | Plugin | |---------|-----------|--------| | Registration | import in component | app.use() in main.js | | Scope | Local (per component) | Global (entire application) | | Global components | No | Yes (app.component) | | Global directives | No | Yes (app.directive) | | Provide/Inject | Possible | Natural | | Dependency tree | Explicit imports | Implicit (inject) |
Rule of thumb: If the functionality is needed in many places and requires global configuration (e.g., theme, i18n, toast) — use a plugin. If it's logic specific to a few components — use a composable.