mirror of
https://github.com/gotson/komga.git
synced 2026-05-07 20:15:47 +02:00
parent
75019c9a1e
commit
efe6476a90
11 changed files with 372 additions and 20689 deletions
2
komga-webui/.env
Normal file
2
komga-webui/.env
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
VUE_APP_I18N_LOCALE=en
|
||||||
|
VUE_APP_I18N_FALLBACK_LOCALE=en
|
||||||
20936
komga-webui/package-lock.json
generated
20936
komga-webui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,8 @@
|
||||||
"serve": "vue-cli-service serve --port 8081",
|
"serve": "vue-cli-service serve --port 8081",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"test:unit": "vue-cli-service test:unit",
|
"test:unit": "vue-cli-service test:unit",
|
||||||
"lint": "vue-cli-service lint --mode production"
|
"lint": "vue-cli-service lint --mode production",
|
||||||
|
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
|
|
@ -18,6 +19,7 @@
|
||||||
"qs": "^6.9.4",
|
"qs": "^6.9.4",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-cookies": "^1.7.3",
|
"vue-cookies": "^1.7.3",
|
||||||
|
"vue-i18n": "^8.22.4",
|
||||||
"vue-line-clamp": "^1.3.2",
|
"vue-line-clamp": "^1.3.2",
|
||||||
"vue-moment": "^4.1.0",
|
"vue-moment": "^4.1.0",
|
||||||
"vue-read-more-smooth": "^0.1.8",
|
"vue-read-more-smooth": "^0.1.8",
|
||||||
|
|
@ -36,6 +38,7 @@
|
||||||
"@types/lodash": "^4.14.158",
|
"@types/lodash": "^4.14.158",
|
||||||
"@types/vuedraggable": "^2.23.1",
|
"@types/vuedraggable": "^2.23.1",
|
||||||
"@types/vuelidate": "^0.7.13",
|
"@types/vuelidate": "^0.7.13",
|
||||||
|
"@types/webpack": "^4.4.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^3.7.1",
|
"@typescript-eslint/eslint-plugin": "^3.7.1",
|
||||||
"@typescript-eslint/parser": "^3.7.1",
|
"@typescript-eslint/parser": "^3.7.1",
|
||||||
"@vue/cli-plugin-babel": "^4.4.6",
|
"@vue/cli-plugin-babel": "^4.4.6",
|
||||||
|
|
@ -61,7 +64,9 @@
|
||||||
"ts-jest": "^26.1.4",
|
"ts-jest": "^26.1.4",
|
||||||
"typeface-roboto": "0.0.75",
|
"typeface-roboto": "0.0.75",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.7",
|
||||||
|
"vue-cli-plugin-i18n": "~1.0.1",
|
||||||
"vue-cli-plugin-vuetify": "^2.0.7",
|
"vue-cli-plugin-vuetify": "^2.0.7",
|
||||||
|
"vue-i18n-extract": "^1.1.11",
|
||||||
"vue-template-compiler": "^2.6.11",
|
"vue-template-compiler": "^2.6.11",
|
||||||
"vuetify-loader": "^1.6.0"
|
"vuetify-loader": "^1.6.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
komga-webui/src/i18n.ts
Normal file
23
komga-webui/src/i18n.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import VueI18n, {LocaleMessages} from 'vue-i18n'
|
||||||
|
|
||||||
|
Vue.use(VueI18n)
|
||||||
|
|
||||||
|
function loadLocaleMessages (): LocaleMessages {
|
||||||
|
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
|
||||||
|
const messages: LocaleMessages = {}
|
||||||
|
locales.keys().forEach(key => {
|
||||||
|
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
|
||||||
|
if (matched && matched.length > 1) {
|
||||||
|
const locale = matched[1]
|
||||||
|
messages[locale] = locales(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new VueI18n({
|
||||||
|
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
|
||||||
|
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
|
||||||
|
messages: loadLocaleMessages(),
|
||||||
|
})
|
||||||
12
komga-webui/src/locales/en.json
Normal file
12
komga-webui/src/locales/en.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"dashboard": {
|
||||||
|
"recently_added_series": "Recently Added Series",
|
||||||
|
"on_deck": "On Deck",
|
||||||
|
"keep_reading": "Keep Reading",
|
||||||
|
"recently_updated_series": "Recently Updated Series",
|
||||||
|
"recently_added_books": "Recently Added Books"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"nothing_to_show": "Nothing to show"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
komga-webui/src/locales/fr.json
Normal file
5
komga-webui/src/locales/fr.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"dashboard": {
|
||||||
|
"recently_added_series": "Séries ajoutées récemment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import _, { LoDashStatic } from 'lodash'
|
import _, {LoDashStatic} from 'lodash'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import VueCookies from 'vue-cookies'
|
import VueCookies from 'vue-cookies'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as lineClamp from 'vue-line-clamp'
|
import * as lineClamp from 'vue-line-clamp'
|
||||||
import Vuelidate from 'vuelidate'
|
import Vuelidate from 'vuelidate'
|
||||||
import { sync } from 'vuex-router-sync'
|
import {sync} from 'vuex-router-sync'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import actuator from './plugins/actuator.plugin'
|
import actuator from './plugins/actuator.plugin'
|
||||||
import httpPlugin from './plugins/http.plugin'
|
import httpPlugin from './plugins/http.plugin'
|
||||||
|
|
@ -21,6 +21,7 @@ import vuetify from './plugins/vuetify'
|
||||||
import './public-path'
|
import './public-path'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
|
import i18n from './i18n'
|
||||||
|
|
||||||
Vue.use(Vuelidate)
|
Vue.use(Vuelidate)
|
||||||
Vue.use(lineClamp)
|
Vue.use(lineClamp)
|
||||||
|
|
@ -50,6 +51,7 @@ new Vue({
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
vuetify,
|
vuetify,
|
||||||
|
i18n,
|
||||||
render: h => h(App),
|
render: h => h(App),
|
||||||
}).$mount('#app')
|
}).$mount('#app')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
<empty-state v-if="allEmpty"
|
<empty-state v-if="allEmpty"
|
||||||
title="Nothing to show"
|
:title="$t('common.nothing_to_show')"
|
||||||
icon="mdi-help-circle"
|
icon="mdi-help-circle"
|
||||||
icon-color="secondary"
|
icon-color="secondary"
|
||||||
>
|
>
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
<horizontal-scroller v-if="inProgressBooks.length !== 0" class="mb-4">
|
<horizontal-scroller v-if="inProgressBooks.length !== 0" class="mb-4">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">Keep Reading</div>
|
<div class="title">{{ $t('dashboard.keep_reading') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="inProgressBooks"
|
<item-browser :items="inProgressBooks"
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
|
|
||||||
<horizontal-scroller v-if="onDeckBooks.length !== 0" class="mb-4">
|
<horizontal-scroller v-if="onDeckBooks.length !== 0" class="mb-4">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">On Deck</div>
|
<div class="title">{{ $t('dashboard.on_deck') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="onDeckBooks"
|
<item-browser :items="onDeckBooks"
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
<horizontal-scroller v-if="newSeries.length !== 0" class="mb-4">
|
<horizontal-scroller v-if="newSeries.length !== 0" class="mb-4">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">Recently Added Series</div>
|
<div class="title">{{ $t('dashboard.recently_added_series') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="newSeries"
|
<item-browser :items="newSeries"
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
|
|
||||||
<horizontal-scroller v-if="updatedSeries.length !== 0" class="mb-4">
|
<horizontal-scroller v-if="updatedSeries.length !== 0" class="mb-4">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">Recently Updated Series</div>
|
<div class="title">{{ $t('dashboard.recently_updated_series') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="updatedSeries"
|
<item-browser :items="updatedSeries"
|
||||||
|
|
@ -88,7 +88,7 @@
|
||||||
|
|
||||||
<horizontal-scroller v-if="latestBooks.length !== 0" class="mb-4">
|
<horizontal-scroller v-if="latestBooks.length !== 0" class="mb-4">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">Recently Added Books</div>
|
<div class="title">{{ $t('dashboard.recently_added_books') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="latestBooks"
|
<item-browser :items="latestBooks"
|
||||||
|
|
@ -110,9 +110,9 @@ import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue'
|
||||||
import EmptyState from '@/components/EmptyState.vue'
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||||
import { ReadStatus } from '@/types/enum-books'
|
import {ReadStatus} from '@/types/enum-books'
|
||||||
import { BookDto } from '@/types/komga-books'
|
import {BookDto} from '@/types/komga-books'
|
||||||
import { BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
import {BOOK_CHANGED, LIBRARY_DELETED, SERIES_CHANGED} from '@/types/events'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import {SeriesDto} from "@/types/komga-series";
|
import {SeriesDto} from "@/types/komga-series";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,18 @@
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
|
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-icon>
|
||||||
|
<v-icon>mdi-translate</v-icon>
|
||||||
|
</v-list-item-icon>
|
||||||
|
<v-select v-model="locale"
|
||||||
|
:items="$i18n.availableLocales"
|
||||||
|
>
|
||||||
|
</v-select>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
|
||||||
<v-spacer/>
|
<v-spacer/>
|
||||||
|
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
|
|
@ -136,10 +148,11 @@
|
||||||
import Dialogs from '@/components/Dialogs.vue'
|
import Dialogs from '@/components/Dialogs.vue'
|
||||||
import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
|
import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
|
||||||
import SearchBox from '@/components/SearchBox.vue'
|
import SearchBox from '@/components/SearchBox.vue'
|
||||||
import { Theme } from '@/types/themes'
|
import {Theme} from '@/types/themes'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
|
|
||||||
const cookieTheme = 'theme'
|
const cookieTheme = 'theme'
|
||||||
|
const cookieLocale = 'locale'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'home',
|
name: 'home',
|
||||||
|
|
@ -171,12 +184,22 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.$cookies.isKey(cookieLocale)) {
|
||||||
|
const locale = this.$cookies.get(cookieLocale)
|
||||||
|
if (this.$i18n.availableLocales.includes(locale)) {
|
||||||
|
this.$i18n.locale = locale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', this.systemThemeChange)
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', this.systemThemeChange)
|
||||||
},
|
},
|
||||||
async beforeDestroy () {
|
async beforeDestroy () {
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.systemThemeChange)
|
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.systemThemeChange)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
locales (): string[] {
|
||||||
|
return this.$i18n.availableLocales
|
||||||
|
},
|
||||||
libraries (): LibraryDto[] {
|
libraries (): LibraryDto[] {
|
||||||
return this.$store.state.komgaLibraries.libraries
|
return this.$store.state.komgaLibraries.libraries
|
||||||
},
|
},
|
||||||
|
|
@ -196,6 +219,17 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
locale: {
|
||||||
|
get: function (): string {
|
||||||
|
return this.$i18n.locale
|
||||||
|
},
|
||||||
|
set: function (locale: string): void {
|
||||||
|
if (this.$i18n.availableLocales.includes(locale)) {
|
||||||
|
this.$i18n.locale = locale
|
||||||
|
this.$cookies.set(cookieLocale, locale, Infinity)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleDrawer () {
|
toggleDrawer () {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@
|
||||||
"types": [
|
"types": [
|
||||||
"webpack-env",
|
"webpack-env",
|
||||||
"jest",
|
"jest",
|
||||||
"vuetify"
|
"vuetify",
|
||||||
|
"webpack",
|
||||||
|
"webpack-env"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
|
|
@ -37,4 +39,4 @@
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ const _ = require('lodash')
|
||||||
// vue.config.js
|
// vue.config.js
|
||||||
module.exports = {
|
module.exports = {
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
|
|
||||||
chainWebpack: (config) => {
|
chainWebpack: (config) => {
|
||||||
config.plugins.delete('prefetch') // conflicts with htmlInject
|
config.plugins.delete('prefetch') // conflicts with htmlInject
|
||||||
config.plugins.delete('preload') // conflicts with htmlInject
|
config.plugins.delete('preload') // conflicts with htmlInject
|
||||||
|
|
@ -34,4 +35,13 @@ module.exports = {
|
||||||
config.plugin('momentLocalesPlugin')
|
config.plugin('momentLocalesPlugin')
|
||||||
.use(momentLocalesPlugin)
|
.use(momentLocalesPlugin)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pluginOptions: {
|
||||||
|
i18n: {
|
||||||
|
locale: 'en',
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
localeDir: 'locales',
|
||||||
|
enableInSFC: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue