Merge pull request #44 from jellyfin/feat/i18n

feat(i18n): add i18n support
This commit is contained in:
Cameron 2020-09-05 22:48:54 +01:00 committed by GitHub
commit 97eb470491
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 182 additions and 42 deletions

View File

@ -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"
}

View 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>

View File

@ -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%;
}

View File

@ -26,7 +26,7 @@ export default Vue.extend({
return {
menuItems: [
{
title: 'Logout',
title: this.$t('logout'),
action: () => {
this.$auth.logout();
this.$user.clear();

View File

@ -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
View 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
View 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"
}

View File

@ -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

View File

@ -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": {

View File

@ -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">

View File

@ -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">

View File

@ -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>

View File

@ -20,7 +20,8 @@
"@nuxt/types",
"@nuxtjs/axios",
"@nuxtjs/auth-next",
"@nuxtjs/vuetify"
"@nuxtjs/vuetify",
"nuxt-i18n"
]
},
"exclude": ["node_modules", ".nuxt", "dist"]

View File

@ -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"