mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2024-10-07 03:23:37 +00:00
Merge pull request #44 from jellyfin/feat/i18n
feat(i18n): add i18n support
This commit is contained in:
commit
97eb470491
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -1,4 +1,7 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"vetur.format.defaultFormatter.html": "prettier"
|
||||
}
|
||||
"editor.formatOnSave": true,
|
||||
"i18n-ally.keepFulfilled": true,
|
||||
"i18n-ally.localesPaths": "locales",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"vetur.format.defaultFormatter.html": "prettier"
|
||||
}
|
||||
|
32
components/LocaleSwitcher.vue
Normal file
32
components/LocaleSwitcher.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn icon size="36" v-bind="attrs" v-on="on">
|
||||
<v-icon>mdi-web</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="(item, index) in menuItems"
|
||||
:key="index"
|
||||
@click="$i18n.setLocale(item.code)"
|
||||
>
|
||||
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
computed: {
|
||||
menuItems: {
|
||||
get() {
|
||||
return this.$i18n.locales;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -18,7 +18,7 @@
|
||||
v-model="login.username"
|
||||
outlined
|
||||
label="Username"
|
||||
:rules="[(v) => !!v || 'Username is required']"
|
||||
:rules="[(v) => !!v || $t('usernameRequired')]"
|
||||
required
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
@ -58,9 +58,9 @@ export default Vue.extend({
|
||||
loginIn: false,
|
||||
rules: {
|
||||
serverUrlTest: [
|
||||
(v: string) => !!v || 'Server address is required',
|
||||
(v: string) => !!v || this.$t('serverAddressRequired'),
|
||||
(v: string) =>
|
||||
/^https?:\/\/.+/.test(v) || 'Server address must be a valid URL'
|
||||
/^https?:\/\/.+/.test(v) || this.$t('serverAddressMustBeUrl')
|
||||
]
|
||||
}
|
||||
};
|
||||
@ -84,18 +84,18 @@ export default Vue.extend({
|
||||
this.$auth.setUser(response.data.User);
|
||||
this.$user.set(response.data.User.Id, this.serverUrl, accessToken);
|
||||
} catch (error) {
|
||||
let errorMessage = 'Unexpected Error';
|
||||
let errorMessage = this.$t('unexpectedError');
|
||||
|
||||
if (!error.response) {
|
||||
errorMessage = 'Server Not Found';
|
||||
errorMessage = this.$t('serverNotFound');
|
||||
} else if (error.response.status === 500) {
|
||||
errorMessage = 'Incorrect Password';
|
||||
errorMessage = this.$t('incorrectUsernameOrPassword');
|
||||
} else if (error.response.status === 400) {
|
||||
errorMessage = 'Bad Request. Try Again';
|
||||
errorMessage = this.$t('badRequest');
|
||||
}
|
||||
|
||||
this.loginIn = false;
|
||||
this.$snackbar(errorMessage, 'error');
|
||||
this.$snackbar(errorMessage as string, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,6 +103,7 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* HACK: Snackbar positioning -- See: https://github.com/vuetifyjs/vuetify/issues/11781#issuecomment-655689025 */
|
||||
div.v-snack:not(.v-snack--absolute) {
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export default Vue.extend({
|
||||
return {
|
||||
menuItems: [
|
||||
{
|
||||
title: 'Logout',
|
||||
title: this.$t('logout'),
|
||||
action: () => {
|
||||
this.$auth.logout();
|
||||
this.$user.clear();
|
||||
|
@ -65,6 +65,7 @@
|
||||
</v-btn>
|
||||
<v-toolbar-title v-text="title" />
|
||||
<v-spacer />
|
||||
<locale-switcher />
|
||||
<user-button v-if="$auth.loggedIn" />
|
||||
</v-app-bar>
|
||||
<v-main>
|
||||
@ -93,14 +94,14 @@ export default Vue.extend({
|
||||
items: [
|
||||
{
|
||||
icon: 'mdi-home',
|
||||
title: 'Home',
|
||||
title: this.$t('home'),
|
||||
to: '/'
|
||||
}
|
||||
],
|
||||
configItems: [
|
||||
{
|
||||
icon: 'mdi-cog',
|
||||
title: 'Settings',
|
||||
title: this.$t('settings'),
|
||||
to: '/settings'
|
||||
}
|
||||
],
|
||||
|
15
locales/en-US.json
Normal file
15
locales/en-US.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"badRequest": "Bad request. Try again",
|
||||
"home": "Home",
|
||||
"incorrectUsernameOrPassword": "Incorrect username or password",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"more": "More",
|
||||
"play": "Play",
|
||||
"serverAddressMustBeUrl": "Server address must be a valid URL",
|
||||
"serverAddressRequired": "Server address is required",
|
||||
"serverNotFound": "Server not found",
|
||||
"settings": "Settings",
|
||||
"unexpectedError": "Unexpected error",
|
||||
"usernameRequired": "Username is required"
|
||||
}
|
15
locales/fr-FR.json
Normal file
15
locales/fr-FR.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"badRequest": "Mauvaise requête. Réessayez",
|
||||
"home": "Accueil",
|
||||
"incorrectUsernameOrPassword": "Nom d'utilisateur ou mot de passe incorrect",
|
||||
"login": "Connexion",
|
||||
"logout": "Se déconnecter",
|
||||
"more": "Plus",
|
||||
"play": "Lire",
|
||||
"serverAddressMustBeUrl": "L'adresse du serveur doit être une URL valide",
|
||||
"serverAddressRequired": "L'adresse du serveur est requise",
|
||||
"serverNotFound": "Serveur introuvable",
|
||||
"settings": "Paramètres",
|
||||
"unexpectedError": "Erreur inattendue",
|
||||
"usernameRequired": "Nom d'utilisateur requis"
|
||||
}
|
@ -64,13 +64,14 @@ const config: NuxtConfig = {
|
||||
** Nuxt.js modules
|
||||
*/
|
||||
modules: [
|
||||
// Doc: https://axios.nuxtjs.org/usage
|
||||
'nuxt-i18n',
|
||||
[
|
||||
'nuxt-vuex-localstorage',
|
||||
{
|
||||
localStorage: ['user']
|
||||
}
|
||||
],
|
||||
// Doc: https://axios.nuxtjs.org/usage
|
||||
'@nuxtjs/axios',
|
||||
'@nuxtjs/auth-next',
|
||||
'@nuxtjs/pwa'
|
||||
@ -141,6 +142,15 @@ const config: NuxtConfig = {
|
||||
},
|
||||
plugins: ['plugins/userInit.ts']
|
||||
},
|
||||
i18n: {
|
||||
locales: [
|
||||
{ code: 'en', iso: 'en-US', name: 'English', file: 'en-US.json' },
|
||||
{ code: 'fr', iso: 'fr-FR', name: 'Français', file: 'fr-FR.json' }
|
||||
],
|
||||
lazy: true,
|
||||
langDir: 'locales/',
|
||||
strategy: 'no_prefix'
|
||||
},
|
||||
/*
|
||||
** vuetify module configuration
|
||||
** https://github.com/nuxt-community/vuetify-module
|
||||
|
@ -28,6 +28,7 @@
|
||||
"@nuxtjs/axios": "^5.12.0",
|
||||
"@nuxtjs/pwa": "^3.0.0-beta.20",
|
||||
"nuxt": "^2.14.0",
|
||||
"nuxt-i18n": "^6.13.12",
|
||||
"nuxt-vuex-localstorage": "^1.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,11 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-img :src="getImageLink(item.Id, 'backdrop')"></v-img>
|
||||
<h1>{{ item.Name }}</h1>
|
||||
<p>{{ item.Overview }}</p>
|
||||
<v-btn color="primary">Play {{ item.Type }}</v-btn>
|
||||
<v-btn>More</v-btn>
|
||||
</div>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col cols="4">
|
||||
<v-img :src="getImageLink(item.Id, 'primary')"></v-img>
|
||||
</v-col>
|
||||
<v-col cols="8">
|
||||
<h1>{{ item.Name }}</h1>
|
||||
<p>{{ item.Overview }}</p>
|
||||
<v-btn color="primary">{{ $t('play') }}</v-btn>
|
||||
<v-btn>{{ $t('more') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -1,17 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>
|
||||
<span>{{ name }}</span>
|
||||
</h1>
|
||||
<div class="d-flex flex-wrap justify-space-around">
|
||||
<card
|
||||
v-for="item in items"
|
||||
:key="item.Id"
|
||||
class="card mt-5"
|
||||
:item="item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<h1>
|
||||
<span>{{ name }}</span>
|
||||
</h1>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<div class="d-flex flex-wrap">
|
||||
<card
|
||||
v-for="item in items"
|
||||
:key="item.Id"
|
||||
class="card mt-5"
|
||||
:item="item"
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -2,7 +2,7 @@
|
||||
<v-container fill-height>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col md="4">
|
||||
<h1 class="text-h4 mb-6 text-center">Login</h1>
|
||||
<h1 class="text-h4 mb-6 text-center">{{ $t('login') }}</h1>
|
||||
<login-form />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
@ -20,7 +20,8 @@
|
||||
"@nuxt/types",
|
||||
"@nuxtjs/axios",
|
||||
"@nuxtjs/auth-next",
|
||||
"@nuxtjs/vuetify"
|
||||
"@nuxtjs/vuetify",
|
||||
"nuxt-i18n"
|
||||
]
|
||||
},
|
||||
"exclude": ["node_modules", ".nuxt", "dist"]
|
||||
|
57
yarn.lock
57
yarn.lock
@ -256,7 +256,7 @@
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.7.0":
|
||||
"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.5.5", "@babel/parser@^7.7.0", "@babel/parser@^7.9.6":
|
||||
version "7.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037"
|
||||
integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==
|
||||
@ -851,7 +851,7 @@
|
||||
"@babel/parser" "^7.10.4"
|
||||
"@babel/types" "^7.10.4"
|
||||
|
||||
"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5", "@babel/traverse@^7.7.0":
|
||||
"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5", "@babel/traverse@^7.5.5", "@babel/traverse@^7.7.0":
|
||||
version "7.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3"
|
||||
integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==
|
||||
@ -1045,6 +1045,21 @@
|
||||
minimatch "^3.0.4"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@intlify/vue-i18n-extensions@^1.0.1":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/vue-i18n-extensions/-/vue-i18n-extensions-1.0.2.tgz#ab7f8507f7d423c368e44fa21d6dece700261fca"
|
||||
integrity sha512-rnfA0ScyBXyp9xsSD4EAMGeOh1yv/AE7fhqdAdSOr5X8N39azz257umfRtzNT9sHXAKSSzpCVhIbMAkp5c/gjQ==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.9.6"
|
||||
|
||||
"@intlify/vue-i18n-loader@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/vue-i18n-loader/-/vue-i18n-loader-1.0.0.tgz#4350a9b03fd62e7d7f44c7496d5509bff3229c79"
|
||||
integrity sha512-y7LlpKEQ01u7Yq14l4VNlbFYEHMmSEH1QXXASOMWspj9ZcIdCebhhvHCHqk5Oy5Epw3PtoxyRJNpb6Wle5udgA==
|
||||
dependencies:
|
||||
js-yaml "^3.13.1"
|
||||
json5 "^2.1.1"
|
||||
|
||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
||||
@ -4043,7 +4058,7 @@ cookie@^0.3.1:
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||
|
||||
cookie@^0.4.1:
|
||||
cookie@^0.4.0, cookie@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
|
||||
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
|
||||
@ -4555,6 +4570,13 @@ deep-is@^0.1.3, deep-is@~0.1.3:
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
|
||||
|
||||
deepcopy@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/deepcopy/-/deepcopy-2.1.0.tgz#2deb0dd52d079c2ecb7924b640a7c3abd4db1d6d"
|
||||
integrity sha512-8cZeTb1ZKC3bdSCP6XOM1IsTczIO73fdqtwa2B0N15eAz7gmyhQo+mc5gnFuulsgN3vIQYmTgbmQVKalH1dKvQ==
|
||||
dependencies:
|
||||
type-detect "^4.0.8"
|
||||
|
||||
deepmerge@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||
@ -6908,6 +6930,11 @@ is-hexadecimal@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
|
||||
integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
|
||||
|
||||
is-https@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-https/-/is-https-2.0.2.tgz#7009d303c72580f15897d5c063d6b6bc1f838fef"
|
||||
integrity sha512-UfUCKVQH/6PQRCh5Qk9vNu4feLZiFmV/gr8DjbtJD0IrCRIDTA6E+d/AVFGPulI5tqK5W45fYbn1Nir1O99rFw==
|
||||
|
||||
is-nan@^1.2.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03"
|
||||
@ -7668,7 +7695,7 @@ json-stringify-safe@~5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||
|
||||
json5@2.x, json5@^2.1.2:
|
||||
json5@2.x, json5@^2.1.1, json5@^2.1.2:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
|
||||
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
|
||||
@ -8684,6 +8711,21 @@ num2fraction@^1.2.2:
|
||||
resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
|
||||
integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
|
||||
|
||||
nuxt-i18n@^6.13.12:
|
||||
version "6.13.12"
|
||||
resolved "https://registry.yarnpkg.com/nuxt-i18n/-/nuxt-i18n-6.13.12.tgz#590e1b69f5dbfd2b7d777a8cc0231f23e0f438f5"
|
||||
integrity sha512-lpk59Fs8+heoXSJ/7VAHmSLXvyegaPmlYe+wSeI7g4VHVHL7YHIHypUPu/pQfEe0h1f5GY0R/w1Y8AEEPaXiVw==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.5.5"
|
||||
"@babel/traverse" "^7.5.5"
|
||||
"@intlify/vue-i18n-extensions" "^1.0.1"
|
||||
"@intlify/vue-i18n-loader" "^1.0.0"
|
||||
cookie "^0.4.0"
|
||||
deepcopy "^2.1.0"
|
||||
is-https "^2.0.0"
|
||||
js-cookie "^2.2.1"
|
||||
vue-i18n "^8.18.1"
|
||||
|
||||
nuxt-vuex-localstorage@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/nuxt-vuex-localstorage/-/nuxt-vuex-localstorage-1.2.7.tgz#6d609407d9e7b449907e85aeecbbc249fda8c97e"
|
||||
@ -11974,7 +12016,7 @@ type-check@~0.3.2:
|
||||
dependencies:
|
||||
prelude-ls "~1.1.2"
|
||||
|
||||
type-detect@4.0.8:
|
||||
type-detect@4.0.8, type-detect@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
@ -12397,6 +12439,11 @@ vue-hot-reload-api@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
||||
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
|
||||
|
||||
vue-i18n@^8.18.1:
|
||||
version "8.21.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.21.0.tgz#526450525fdbb9c877685b5ba6cb9573b73d3940"
|
||||
integrity sha512-pKBq6Kg5hNacFHMFgPbpYsFlDIMRu4Ew/tpvTWns14CZoCxt7B3tmSNdrLruGMMivnJu1rhhRqsQqT6YwHkuQQ==
|
||||
|
||||
vue-jest@^3.0.4:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.6.tgz#27f79d75dcddbe6b3d8327ca1450a107b9cd6f38"
|
||||
|
Loading…
Reference in New Issue
Block a user