mirror of
https://github.com/mokuappio/serverless-invoices.git
synced 2025-10-27 16:01:07 -04:00
Merge remote-tracking branch 'origin/main' into main
# Conflicts: # src/main.js
This commit is contained in:
54
package-lock.json
generated
54
package-lock.json
generated
@ -795,7 +795,6 @@
|
|||||||
"version": "7.11.2",
|
"version": "7.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
|
||||||
"integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
|
"integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
@ -942,6 +941,21 @@
|
|||||||
"node-fetch": "^2.6.0"
|
"node-fetch": "^2.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@panter/vue-i18next": {
|
||||||
|
"version": "0.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@panter/vue-i18next/-/vue-i18next-0.15.2.tgz",
|
||||||
|
"integrity": "sha512-7VX9GyxHJNEJKa2CRzC294Oz5EEbzVDZ1o3O/P8gL/PWBmcFOFsuivRbP/1a9ga2ihv/NBzoCWMCNIEEeCcONQ==",
|
||||||
|
"requires": {
|
||||||
|
"deepmerge": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"deepmerge": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@soda/friendly-errors-webpack-plugin": {
|
"@soda/friendly-errors-webpack-plugin": {
|
||||||
"version": "1.7.1",
|
"version": "1.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.1.tgz",
|
||||||
@ -3246,7 +3260,8 @@
|
|||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
|
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"coa": {
|
"coa": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
@ -6554,6 +6569,32 @@
|
|||||||
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
|
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"i18next": {
|
||||||
|
"version": "20.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-20.2.1.tgz",
|
||||||
|
"integrity": "sha512-JLruWDEQ3T6tKT6P7u+DsNtToMHUwUcQIYOMRcnNBdUhSfKkoIDUKdVDKgGtmqr//LrirxjADUdr3d5Gwbow6g==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.13.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
|
||||||
|
"integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"i18next-browser-languagedetector": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-NXbr/qPqkg6VyUwPrzmVOAafqIk1zdjzhYVxZWoSi338XEGmuOeroEglLdR8nJUJcf5BfOSHva80tqCPwXFTFQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.5.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
@ -8982,7 +9023,8 @@
|
|||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||||
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
@ -10131,8 +10173,7 @@
|
|||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.13.7",
|
"version": "0.13.7",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
|
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"regenerator-transform": {
|
"regenerator-transform": {
|
||||||
"version": "0.14.5",
|
"version": "0.14.5",
|
||||||
@ -10518,7 +10559,8 @@
|
|||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
|
||||||
"integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
|
"integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"rx-lite-aggregates": {
|
"rx-lite-aggregates": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@panter/vue-i18next": "^0.15.2",
|
||||||
"@vuex-orm/core": "^0.36.3",
|
"@vuex-orm/core": "^0.36.3",
|
||||||
"@vuex-orm/plugin-change-flags": "https://github.com/mareksmakosz/plugin-change-flags.git",
|
"@vuex-orm/plugin-change-flags": "https://github.com/mareksmakosz/plugin-change-flags.git",
|
||||||
"bootstrap": "^4.5.2",
|
"bootstrap": "^4.5.2",
|
||||||
@ -15,6 +16,8 @@
|
|||||||
"core-js": "^2.6.5",
|
"core-js": "^2.6.5",
|
||||||
"dayjs": "^1.10.3",
|
"dayjs": "^1.10.3",
|
||||||
"es6-promise": "^4.2.6",
|
"es6-promise": "^4.2.6",
|
||||||
|
"i18next": "^20.2.1",
|
||||||
|
"i18next-browser-languagedetector": "^6.1.0",
|
||||||
"localforage": "^1.9.0",
|
"localforage": "^1.9.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"register-service-worker": "^1.7.1",
|
"register-service-worker": "^1.7.1",
|
||||||
|
|||||||
6
public/locales/en.json
Normal file
6
public/locales/en.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"invoices": {
|
||||||
|
"title": "Invoices",
|
||||||
|
"new_invoice": "New invoice"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
public/locales/est.json
Normal file
6
public/locales/est.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"invoices": {
|
||||||
|
"title": "Arved",
|
||||||
|
"new_invoice": "Uus arve"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/components/LanguageSwitcher.vue
Normal file
35
src/components/LanguageSwitcher.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<b-dropdown v-if="selectedLang" variant="link" size="sm" no-caret right>
|
||||||
|
<template slot="button-content">
|
||||||
|
{{ selectedLang.code }}
|
||||||
|
<i class="material-icons">keyboard_arrow_down</i>
|
||||||
|
</template>
|
||||||
|
<template v-for="lang in languages" >
|
||||||
|
<b-dropdown-item-button @click="langChanged(lang)" :key="lang.code">{{ lang.name }}</b-dropdown-item-button>
|
||||||
|
</template>
|
||||||
|
</b-dropdown>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
import { BDropdown, BDropdownItemButton } from 'bootstrap-vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'language-switcher',
|
||||||
|
i18nOptions: { namespaces: 'language-switcher' },
|
||||||
|
components: {
|
||||||
|
BDropdown,
|
||||||
|
BDropdownItemButton,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
selectedLang: state => state.language.lang,
|
||||||
|
languages: state => state.language.all,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
langChanged(lang) {
|
||||||
|
this.$store.dispatch('language/changeLanguage', lang);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -6,6 +6,7 @@
|
|||||||
{{ theme === 'dark' ? 'wb_sunny' : 'brightness_2' }}
|
{{ theme === 'dark' ? 'wb_sunny' : 'brightness_2' }}
|
||||||
</i>
|
</i>
|
||||||
</button>
|
</button>
|
||||||
|
<LanguageSwitcher/>
|
||||||
<div>
|
<div>
|
||||||
<small v-b-tooltip.hover
|
<small v-b-tooltip.hover
|
||||||
title="All your data is saved in your browser and not on any server.
|
title="All your data is saved in your browser and not on any server.
|
||||||
@ -39,8 +40,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex';
|
||||||
import { VBTooltip } from 'bootstrap-vue';
|
import { VBTooltip } from 'bootstrap-vue';
|
||||||
|
import LanguageSwitcher from './LanguageSwitcher';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { LanguageSwitcher },
|
||||||
directives: {
|
directives: {
|
||||||
'b-tooltip': VBTooltip,
|
'b-tooltip': VBTooltip,
|
||||||
},
|
},
|
||||||
|
|||||||
35
src/config/i18n.config.js
Normal file
35
src/config/i18n.config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import i18next from 'i18next';
|
||||||
|
import VueI18Next from '@panter/vue-i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import app from '@/main';
|
||||||
|
import enJson from '../../public/locales/en';
|
||||||
|
import estJson from '../../public/locales/est';
|
||||||
|
|
||||||
|
Vue.use(VueI18Next);
|
||||||
|
|
||||||
|
i18next.use(LanguageDetector);
|
||||||
|
|
||||||
|
const initialized = i18next.init({
|
||||||
|
fallbackLng: 'en',
|
||||||
|
whitelist: ['en', 'est'],
|
||||||
|
resources: {
|
||||||
|
en: enJson,
|
||||||
|
est: estJson,
|
||||||
|
},
|
||||||
|
detection: {
|
||||||
|
order: ['querystring', 'path', 'localStorage', 'navigator'],
|
||||||
|
lookupQuerystring: 'lang',
|
||||||
|
caches: ['localStorage'],
|
||||||
|
checkWhitelist: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
initialized.then(() => app.$store.dispatch('language/initLanguage', i18next.language));
|
||||||
|
|
||||||
|
const i18n = new VueI18Next(i18next, {
|
||||||
|
loadComponentNamespace: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
i18n.initialized = initialized;
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
@ -7,6 +7,7 @@ import router from '@/router';
|
|||||||
import store from '@/store/store';
|
import store from '@/store/store';
|
||||||
import VueNotifications from 'vue-notification';
|
import VueNotifications from 'vue-notification';
|
||||||
import './registerServiceWorker';
|
import './registerServiceWorker';
|
||||||
|
import i18n from './config/i18n.config';
|
||||||
|
|
||||||
Vue.use(BVModalPlugin);
|
Vue.use(BVModalPlugin);
|
||||||
Vue.use(VueNotifications);
|
Vue.use(VueNotifications);
|
||||||
@ -16,6 +17,7 @@ Vue.config.productionTip = false;
|
|||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
|
i18n,
|
||||||
render: h => h(App),
|
render: h => h(App),
|
||||||
}).$mount('#app');
|
}).$mount('#app');
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Router from 'vue-router';
|
import Router from 'vue-router';
|
||||||
import store from '@/store/store';
|
import store from '@/store/store';
|
||||||
|
import i18n from '@/config/i18n.config';
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
@ -40,4 +41,15 @@ const router = new Router({
|
|||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (!to.query.hasOwnProperty('lang')) {
|
||||||
|
i18n.initialized.then(() => {
|
||||||
|
to.query.lang = i18n.i18next.language;
|
||||||
|
next(to);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
27
src/store/language.js
Normal file
27
src/store/language.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import app from '../main';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
lang: null,
|
||||||
|
all: [
|
||||||
|
{ name: 'English', code: 'en' },
|
||||||
|
{ name: 'Estonian', code: 'est' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
lang(state, lang) {
|
||||||
|
state.lang = lang;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
changeLanguage({ commit }, lang) {
|
||||||
|
app.$i18n.i18next.changeLanguage(lang.code);
|
||||||
|
app.$router.push({ query: { ...app.$route.query, lang: lang.code } });
|
||||||
|
commit('lang', lang);
|
||||||
|
},
|
||||||
|
initLanguage({ commit, state }, code) {
|
||||||
|
commit('lang', state.all.find(lang => lang.code === code));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -19,6 +19,7 @@ import teamFields from '@/store/team-fields';
|
|||||||
import themes from '@/store/themes';
|
import themes from '@/store/themes';
|
||||||
import taxes from '@/store/taxes';
|
import taxes from '@/store/taxes';
|
||||||
import data from '@/store/data';
|
import data from '@/store/data';
|
||||||
|
import language from '@/store/language';
|
||||||
import ClientField from '@/store/models/client-field';
|
import ClientField from '@/store/models/client-field';
|
||||||
import TeamField from '@/store/models/team-field';
|
import TeamField from '@/store/models/team-field';
|
||||||
import InvoiceClientField from '@/store/models/invoice-client-field';
|
import InvoiceClientField from '@/store/models/invoice-client-field';
|
||||||
@ -58,6 +59,7 @@ export default new Vuex.Store({
|
|||||||
themes,
|
themes,
|
||||||
taxes,
|
taxes,
|
||||||
data,
|
data,
|
||||||
|
language,
|
||||||
},
|
},
|
||||||
state: {},
|
state: {},
|
||||||
mutations: {},
|
mutations: {},
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 mb-4 pr-0 d-flex justify-content-between">
|
<div class="col-12 mb-4 pr-0 d-flex justify-content-between">
|
||||||
<h4 class="mb-0">Invoices</h4>
|
<h4 class="mb-0">{{ $t('title') }}</h4>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-sm btn-outline-dark" @click="createNewInvoice">New invoice</button>
|
<button class="btn btn-sm btn-outline-dark" @click="createNewInvoice">{{ $t('new_invoice') }}</button>
|
||||||
<b-dropdown variant="link" size="sm" no-caret right>
|
<b-dropdown variant="link" size="sm" no-caret right>
|
||||||
<template slot="button-content">
|
<template slot="button-content">
|
||||||
<i class="material-icons">more_vert</i>
|
<i class="material-icons">more_vert</i>
|
||||||
@ -30,6 +30,7 @@ import InvoicesList from '@/components/invoices/InvoicesList';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'invoices',
|
name: 'invoices',
|
||||||
|
i18nOptions: { namespaces: 'invoices' },
|
||||||
components: {
|
components: {
|
||||||
InvoicesList,
|
InvoicesList,
|
||||||
BDropdown,
|
BDropdown,
|
||||||
|
|||||||
Reference in New Issue
Block a user