Browse Source

Add support to internationalization (#764)

* client-app: Improve development documentation

* client-app: add basic support to translation

* client-app: fix i18n and create lang state

* client-app: add language selector

* client-app: add translation EN and PT-BR
pull/766/head
Rafael Vieira 4 years ago committed by GitHub
parent
commit
2e1073eb53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ErsatzTV/client-app/.env
  2. 2
      ErsatzTV/client-app/README.md
  3. 678
      ErsatzTV/client-app/package-lock.json
  4. 6
      ErsatzTV/client-app/package.json
  5. 44
      ErsatzTV/client-app/src/components/Navigation/SideBarLanguageSelect.vue
  6. 2
      ErsatzTV/client-app/src/components/Navigation/SideBarMenuItem.vue
  7. 4
      ErsatzTV/client-app/src/components/Navigation/SideBarMenuItemExpandable.vue
  8. 2
      ErsatzTV/client-app/src/components/Navigation/SideBarVersion.vue
  9. 6
      ErsatzTV/client-app/src/components/Navigation/ToolBar.vue
  10. 19
      ErsatzTV/client-app/src/components/Navigation/ToolBarLinks.vue
  11. 2
      ErsatzTV/client-app/src/components/Navigation/ToolBarSearch.vue
  12. 168
      ErsatzTV/client-app/src/locales/en.json
  13. 168
      ErsatzTV/client-app/src/locales/pt-br.json
  14. 4
      ErsatzTV/client-app/src/main.ts
  15. 27
      ErsatzTV/client-app/src/plugins/i18n.ts
  16. 4
      ErsatzTV/client-app/src/plugins/vuetify.ts
  17. 50
      ErsatzTV/client-app/src/router/index.js
  18. 28
      ErsatzTV/client-app/src/stores/languageState.js
  19. 15
      ErsatzTV/client-app/src/views/ChannelsPage.vue
  20. 8
      ErsatzTV/client-app/src/views/HomePage.vue
  21. 10
      ErsatzTV/client-app/vue.config.js

2
ErsatzTV/client-app/.env

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
VUE_APP_I18N_LOCALE=en
VUE_APP_I18N_FALLBACK_LOCALE=en

2
ErsatzTV/client-app/README.md

@ -10,6 +10,8 @@ npm install @@ -10,6 +10,8 @@ npm install
npm run serve
```
To set a custom ErsatzTV api URL, use the following environment variable `VUE_APP_ETV_BASE_URL`.
### Compiles and minifies for production
```
npm run build

678
ErsatzTV/client-app/package-lock.json generated

@ -12,9 +12,11 @@ @@ -12,9 +12,11 @@
"axios": "^0.26.1",
"core-js": "^3.8.3",
"pinia": "^2.0.11",
"pinia-plugin-persistedstate": "^1.5.1",
"roboto-fontface": "*",
"vue": "^2.6.14",
"vue-class-component": "^7.2.6",
"vue-i18n": "^8.26.3",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.2.0",
"vuetify": "^2.6.0"
@ -37,6 +39,7 @@ @@ -37,6 +39,7 @@
"sass": "~1.32.0",
"sass-loader": "^10.0.0",
"typescript": "~4.5.5",
"vue-cli-plugin-i18n": "~2.3.1",
"vue-cli-plugin-vuetify": "~2.4.7",
"vue-template-compiler": "^2.6.14",
"vuetify-loader": "^1.7.0"
@ -4160,6 +4163,65 @@ @@ -4160,6 +4163,65 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-table3": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz",
"integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==",
"dev": true,
"dependencies": {
"object-assign": "^4.1.0",
"string-width": "^2.1.1"
},
"engines": {
"node": ">=6"
},
"optionalDependencies": {
"colors": "^1.1.2"
}
},
"node_modules/cli-table3/node_modules/ansi-regex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
"integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/cli-table3/node_modules/is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/cli-table3/node_modules/string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"dependencies": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/cli-table3/node_modules/strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"dependencies": {
"ansi-regex": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/clipboardy": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz",
@ -4235,6 +4297,16 @@ @@ -4235,6 +4297,16 @@
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
"dev": true
},
"node_modules/colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
"dev": true,
"optional": true,
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
@ -4836,6 +4908,15 @@ @@ -4836,6 +4908,15 @@
"callsite": "^1.0.0"
}
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
@ -5135,6 +5216,25 @@ @@ -5135,6 +5216,25 @@
"tslib": "^2.0.3"
}
},
"node_modules/dot-object": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/dot-object/-/dot-object-1.9.0.tgz",
"integrity": "sha512-7MPN6y7XhAO4vM4eguj5+5HNKLjJYfkVG1ZR1Aput4Q4TR6SYeSjhpVQ77IzJHoSHffKbDxBC+48aCiiRurDPw==",
"dev": true,
"dependencies": {
"commander": "^2.20.0",
"glob": "^7.1.4"
},
"bin": {
"dot-object": "bin/dot-object"
}
},
"node_modules/dot-object/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
@ -5666,6 +5766,15 @@ @@ -5666,6 +5766,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/espree": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz",
@ -6190,6 +6299,15 @@ @@ -6190,6 +6299,15 @@
"node": ">=8"
}
},
"node_modules/flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
"dev": true,
"bin": {
"flat": "cli.js"
}
},
"node_modules/flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@ -7253,6 +7371,15 @@ @@ -7253,6 +7371,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-valid-glob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz",
"integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@ -8860,6 +8987,14 @@ @@ -8860,6 +8987,14 @@
}
}
},
"node_modules/pinia-plugin-persistedstate": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-1.5.1.tgz",
"integrity": "sha512-X0jKWvA3kbpYe8RuIyLaZDEAFvsv3+QmBkMzInBHl0O57+eVJjswXHnIWeFAeFjktrE0cJbGHw2sBMgkcleySQ==",
"peerDependencies": {
"pinia": "^2.0.0"
}
},
"node_modules/pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@ -9852,6 +9987,12 @@ @@ -9852,6 +9987,12 @@
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@ -10236,6 +10377,12 @@ @@ -10236,6 +10377,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@ -11289,6 +11436,43 @@ @@ -11289,6 +11436,43 @@
"vue": "^2.0.0"
}
},
"node_modules/vue-cli-plugin-i18n": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/vue-cli-plugin-i18n/-/vue-cli-plugin-i18n-2.3.1.tgz",
"integrity": "sha512-1bNVZtLIAL9Pge8hiw986vixofyqF/tlgsqe4fF5JWn9c8xhsqVugEBuUeaYxevrE9efhhFk9mRmEDwBwQnbNg==",
"dev": true,
"dependencies": {
"debug": "^4.3.0",
"deepmerge": "^4.2.0",
"dotenv": "^8.2.0",
"flat": "^5.0.0",
"rimraf": "^3.0.0",
"vue": "^2.6.11",
"vue-i18n": "^8.17.0",
"vue-i18n-extract": "1.0.2"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/vue-cli-plugin-i18n/node_modules/deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vue-cli-plugin-i18n/node_modules/dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/vue-cli-plugin-vuetify": {
"version": "2.4.7",
"resolved": "https://registry.npmmirror.com/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.4.7.tgz",
@ -11438,6 +11622,192 @@ @@ -11438,6 +11622,192 @@
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==",
"dev": true
},
"node_modules/vue-i18n": {
"version": "8.27.1",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.27.1.tgz",
"integrity": "sha512-lWrGm4F25qReJ7XxSnFVb2h3PfW54ldnM4C+YLBGGJ75+Myt/kj4hHSTKqsyDLamvNYpvINMicSOdW+7yuqgIQ=="
},
"node_modules/vue-i18n-extract": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/vue-i18n-extract/-/vue-i18n-extract-1.0.2.tgz",
"integrity": "sha512-+zwDKvle4KcfloXZnj5hF01ViKDiFr5RMx5507D7oyDXpSleRpekF5YHgZa/+Ra6Go68//z0Nya58J9tKFsCjw==",
"dev": true,
"dependencies": {
"cli-table3": "^0.5.1",
"dot-object": "^1.7.1",
"esm": "^3.2.13",
"glob": "^7.1.3",
"is-valid-glob": "^1.0.0",
"yargs": "^13.2.2"
},
"bin": {
"vue-i18n-extract": "dist-node/index.bin.js"
}
},
"node_modules/vue-i18n-extract/node_modules/ansi-regex": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/vue-i18n-extract/node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/vue-i18n-extract/node_modules/cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"dependencies": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
"wrap-ansi": "^5.1.0"
}
},
"node_modules/vue-i18n-extract/node_modules/emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"node_modules/vue-i18n-extract/node_modules/find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"dependencies": {
"locate-path": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/vue-i18n-extract/node_modules/is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/vue-i18n-extract/node_modules/locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"dependencies": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/vue-i18n-extract/node_modules/p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"dependencies": {
"p-limit": "^2.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/vue-i18n-extract/node_modules/path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/vue-i18n-extract/node_modules/string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"dependencies": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/vue-i18n-extract/node_modules/strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"dependencies": {
"ansi-regex": "^4.1.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/vue-i18n-extract/node_modules/wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/vue-i18n-extract/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true
},
"node_modules/vue-i18n-extract/node_modules/yargs": {
"version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
"dev": true,
"dependencies": {
"cliui": "^5.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.1.2"
}
},
"node_modules/vue-i18n-extract/node_modules/yargs-parser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"dev": true,
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
},
"node_modules/vue-loader": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz",
@ -12148,6 +12518,12 @@ @@ -12148,6 +12518,12 @@
"node": ">= 8"
}
},
"node_modules/which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"node_modules/wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
@ -15436,6 +15812,50 @@ @@ -15436,6 +15812,50 @@
"integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==",
"dev": true
},
"cli-table3": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz",
"integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==",
"dev": true,
"requires": {
"colors": "^1.1.2",
"object-assign": "^4.1.0",
"string-width": "^2.1.1"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
"integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
}
}
},
"clipboardy": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz",
@ -15502,6 +15922,13 @@ @@ -15502,6 +15922,13 @@
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
"dev": true
},
"colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
"dev": true,
"optional": true
},
"commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
@ -15928,6 +16355,12 @@ @@ -15928,6 +16355,12 @@
"callsite": "^1.0.0"
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
@ -16160,6 +16593,24 @@ @@ -16160,6 +16593,24 @@
"tslib": "^2.0.3"
}
},
"dot-object": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/dot-object/-/dot-object-1.9.0.tgz",
"integrity": "sha512-7MPN6y7XhAO4vM4eguj5+5HNKLjJYfkVG1ZR1Aput4Q4TR6SYeSjhpVQ77IzJHoSHffKbDxBC+48aCiiRurDPw==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"glob": "^7.1.4"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
}
}
},
"dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
@ -16544,6 +16995,12 @@ @@ -16544,6 +16995,12 @@
}
}
},
"esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
"dev": true
},
"espree": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz",
@ -16958,6 +17415,12 @@ @@ -16958,6 +17415,12 @@
"path-exists": "^4.0.0"
}
},
"flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
"dev": true
},
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@ -17699,6 +18162,12 @@ @@ -17699,6 +18162,12 @@
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true
},
"is-valid-glob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz",
"integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=",
"dev": true
},
"is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@ -18924,6 +19393,12 @@ @@ -18924,6 +19393,12 @@
"vue-demi": "*"
}
},
"pinia-plugin-persistedstate": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-1.5.1.tgz",
"integrity": "sha512-X0jKWvA3kbpYe8RuIyLaZDEAFvsv3+QmBkMzInBHl0O57+eVJjswXHnIWeFAeFjktrE0cJbGHw2sBMgkcleySQ==",
"requires": {}
},
"pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@ -19605,6 +20080,12 @@ @@ -19605,6 +20080,12 @@
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@ -19895,6 +20376,12 @@ @@ -19895,6 +20376,12 @@
"send": "0.17.2"
}
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@ -20685,6 +21172,36 @@ @@ -20685,6 +21172,36 @@
"integrity": "sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==",
"requires": {}
},
"vue-cli-plugin-i18n": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/vue-cli-plugin-i18n/-/vue-cli-plugin-i18n-2.3.1.tgz",
"integrity": "sha512-1bNVZtLIAL9Pge8hiw986vixofyqF/tlgsqe4fF5JWn9c8xhsqVugEBuUeaYxevrE9efhhFk9mRmEDwBwQnbNg==",
"dev": true,
"requires": {
"debug": "^4.3.0",
"deepmerge": "^4.2.0",
"dotenv": "^8.2.0",
"flat": "^5.0.0",
"rimraf": "^3.0.0",
"vue": "^2.6.11",
"vue-i18n": "^8.17.0",
"vue-i18n-extract": "1.0.2"
},
"dependencies": {
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
},
"dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true
}
}
},
"vue-cli-plugin-vuetify": {
"version": "2.4.7",
"resolved": "https://registry.npmmirror.com/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.4.7.tgz",
@ -20778,6 +21295,161 @@ @@ -20778,6 +21295,161 @@
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==",
"dev": true
},
"vue-i18n": {
"version": "8.27.1",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.27.1.tgz",
"integrity": "sha512-lWrGm4F25qReJ7XxSnFVb2h3PfW54ldnM4C+YLBGGJ75+Myt/kj4hHSTKqsyDLamvNYpvINMicSOdW+7yuqgIQ=="
},
"vue-i18n-extract": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/vue-i18n-extract/-/vue-i18n-extract-1.0.2.tgz",
"integrity": "sha512-+zwDKvle4KcfloXZnj5hF01ViKDiFr5RMx5507D7oyDXpSleRpekF5YHgZa/+Ra6Go68//z0Nya58J9tKFsCjw==",
"dev": true,
"requires": {
"cli-table3": "^0.5.1",
"dot-object": "^1.7.1",
"esm": "^3.2.13",
"glob": "^7.1.3",
"is-valid-glob": "^1.0.0",
"yargs": "^13.2.2"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
"wrap-ansi": "^5.1.0"
}
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
}
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true
},
"yargs": {
"version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
"dev": true,
"requires": {
"cliui": "^5.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.1.2"
}
},
"yargs-parser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"vue-loader": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz",
@ -21308,6 +21980,12 @@ @@ -21308,6 +21980,12 @@
"isexe": "^2.0.0"
}
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",

6
ErsatzTV/client-app/package.json

@ -5,16 +5,19 @@ @@ -5,16 +5,19 @@
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"lint": "vue-cli-service lint",
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\""
},
"dependencies": {
"@mdi/font": "5.9.55",
"axios": "^0.26.1",
"core-js": "^3.8.3",
"pinia": "^2.0.11",
"pinia-plugin-persistedstate": "^1.5.1",
"roboto-fontface": "*",
"vue": "^2.6.14",
"vue-class-component": "^7.2.6",
"vue-i18n": "^8.26.3",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.2.0",
"vuetify": "^2.6.0"
@ -37,6 +40,7 @@ @@ -37,6 +40,7 @@
"sass": "~1.32.0",
"sass-loader": "^10.0.0",
"typescript": "~4.5.5",
"vue-cli-plugin-i18n": "~2.3.1",
"vue-cli-plugin-vuetify": "~2.4.7",
"vue-template-compiler": "^2.6.14",
"vuetify-loader": "^1.7.0"

44
ErsatzTV/client-app/src/components/Navigation/SideBarLanguageSelect.vue

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
<template>
<v-select
v-on:change="setUserLanguage($event)"
:value="language"
dense
outlined
:items="langs"
:label="$t('sidebar.languages')"
class="ma-2"
></v-select>
</template>
<script>
import { mapState } from 'pinia';
import { languageState } from '@/stores/languageState';
export default {
name: 'language-changer',
data() {
return {
langs: [
{ text: this.$t('languages-code.en'), value: 'en' },
{ text: this.$t('languages-code.pt-br'), value: 'pt-br' }
]
};
},
computed: {
...mapState(languageState, ['language', 'setLanguage']),
languageCode: {
get() {
return languageState.language;
},
set(value) {
this.setLanguage(value);
}
}
},
methods: {
setUserLanguage: ($event) => {
languageState().setLanguage($event);
}
}
};
</script>

2
ErsatzTV/client-app/src/components/Navigation/SideBarMenuItem.vue

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="name" />
<v-list-item-title v-text="$t(name)" />
</v-list-item-content>
</v-list-item>
</v-list-item-group>

4
ErsatzTV/client-app/src/components/Navigation/SideBarMenuItemExpandable.vue

@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title v-text="name"></v-list-item-title>
<v-list-item-title v-text="$t(name)"></v-list-item-title>
</v-list-item-content>
</template>
@ -25,7 +25,7 @@ @@ -25,7 +25,7 @@
/>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="child.name"></v-list-item-title>
<v-list-item-title v-text="$t(child.name)"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>

2
ErsatzTV/client-app/src/components/Navigation/SideBarVersion.vue

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
<template>
<span v-if="!isNavigationMini" class="text-center">
<v-divider inset></v-divider>
<h4 class="pt-2">ErsatzTV Version</h4>
<h4 class="pt-2">ErsatzTV {{ $t('sidebar.version') }}</h4>
<p>{{ currentServerVersion }}</p>
</span>

6
ErsatzTV/client-app/src/components/Navigation/ToolBar.vue

@ -14,7 +14,9 @@ @@ -14,7 +14,9 @@
</template>
<SideBarMenu />
<template v-slot:append>
<SideBarLanguageSelect />
<SideBarVersion />
</template>
</v-navigation-drawer>
@ -29,6 +31,7 @@ import ToolBarLinks from './ToolBarLinks'; @@ -29,6 +31,7 @@ import ToolBarLinks from './ToolBarLinks';
import ToolBarSearch from './ToolBarSearch';
import { mapState } from 'pinia';
import { applicationState } from '@/stores/applicationState';
import SideBarLanguageSelect from './SideBarLanguageSelect.vue';
export default {
name: 'NavToolbar',
@ -37,7 +40,8 @@ export default { @@ -37,7 +40,8 @@ export default {
SideBarLogo,
SideBarVersion,
ToolBarLinks,
ToolBarSearch
ToolBarSearch,
SideBarLanguageSelect
},
computed: {
...mapState(applicationState, [

19
ErsatzTV/client-app/src/components/Navigation/ToolBarLinks.vue

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
<v-icon>mdi-playlist-play</v-icon> M3U
</v-btn>
</template>
<span>{{ clickToCopyText }}</span>
<span>{{ $t('tool-bar.click-to-copy') }}</span>
</v-tooltip>
<v-tooltip bottom>
@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
<v-icon>mdi-xml</v-icon> XML
</v-btn>
</template>
<span>{{ clickToCopyText }}</span>
<span>{{ $t('tool-bar.click-to-copy') }}</span>
</v-tooltip>
<v-btn
@ -71,10 +71,7 @@ import { snackbarState } from '@/stores/snackbarState'; @@ -71,10 +71,7 @@ import { snackbarState } from '@/stores/snackbarState';
export default {
name: 'ToolBarLinks.vue',
data: () => ({
toast: false,
clickToCopyText: 'Click to copy to clipboard!',
successCopyText: 'Successfully copied text to clipboard!',
failedCopyText: 'Failed to copy text to clipboard! Error: '
toast: false
}),
computed: {
...mapState(applicationState, ['navBarURLs']),
@ -84,10 +81,12 @@ export default { @@ -84,10 +81,12 @@ export default {
copyTextToClipboard(text) {
try {
navigator.clipboard.writeText(text);
this.showSnackbar(this.successCopyText);
} catch (e) {
console.error(e);
this.showSnackbar(`${this.failedCopyText}${e}`);
this.showSnackbar(this.$t('tool-bar.copy-success'));
} catch (error) {
console.error(error);
this.showSnackbar(
this.$t('tool-bar.copy-failure', { message: error })
);
}
}
}

2
ErsatzTV/client-app/src/components/Navigation/ToolBarSearch.vue

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
<v-text-field
outlined
class="ml-5"
label="Search..."
:label="$t('tool-bar.search')"
prepend-inner-icon="mdi-magnify"
hide-details
dense

168
ErsatzTV/client-app/src/locales/en.json

@ -0,0 +1,168 @@ @@ -0,0 +1,168 @@
{
"$vuetify": {
"badge": "Badge",
"close": "Close",
"dataIterator": {
"noResultsText": "No matching records found",
"loadingText": "Loading items..."
},
"dataTable": {
"itemsPerPageText": "Rows per page:",
"ariaLabel": {
"sortDescending": "Sorted descending.",
"sortAscending": "Sorted ascending.",
"sortNone": "Not sorted.",
"activateNone": "Activate to remove sorting.",
"activateDescending": "Activate to sort descending.",
"activateAscending": "Activate to sort ascending."
},
"sortBy": "Sort by"
},
"dataFooter": {
"itemsPerPageText": "Items per page:",
"itemsPerPageAll": "All",
"nextPage": "Next page",
"prevPage": "Previous page",
"firstPage": "First page",
"lastPage": "Last page",
"pageText": "{0}-{1} of {2}"
},
"datePicker": {
"itemsSelected": "{0} selected",
"nextMonthAriaLabel": "Next month",
"nextYearAriaLabel": "Next year",
"prevMonthAriaLabel": "Previous month",
"prevYearAriaLabel": "Previous year"
},
"noDataText": "No data available",
"carousel": {
"prev": "Previous visual",
"next": "Next visual",
"ariaLabel": {
"delimiter": "Carousel slide {0} of {1}"
}
},
"calendar": {
"moreEvents": "{0} more"
},
"fileInput": {
"counter": "{0} files",
"counterSize": "{0} files ({1} in total)"
},
"timePicker": {
"am": "AM",
"pm": "PM"
},
"pagination": {
"ariaLabel": {
"wrapper": "Pagination Navigation",
"next": "Next page",
"previous": "Previous page",
"page": "Goto Page {0}",
"currentPage": "Current Page, Page {0}"
}
},
"rating": {
"ariaLabel": {
"icon": "Rating {0} of {1}"
}
}
},
"languages-code": {
"en": "English",
"pt-br": "Portuguese (Brazil)"
},
"tool-bar": {
"search": "Search...",
"click-to-copy": "Click to copy to clipboard!",
"copy-success": "Successfully copied text to clipboard!",
"copy-failure": "Failed to copy text to clipboard! Error: {message}"
},
"sidebar": {
"languages": "Languages",
"version": "Version"
},
"home": {
"title": "Home"
},
"ffmpeg-profiles": {
"title": "FFmpeg Profiles"
},
"watermarks": {
"title": "Watermarks"
},
"media-sources": {
"title": "Media Sources",
"local": {
"title": "Local"
},
"emby": {
"title": "Emby"
},
"jellyfin": {
"title": "Jellyfin"
},
"plex": {
"title": "Plex"
}
},
"lists": {
"title": "Lists",
"collections": {
"title": "Collections"
},
"trakt-lists": {
"title": "Trakt Lists"
},
"filler-presets": {
"title": "Filler Presets"
}
},
"schedules": {
"title": "Schedules"
},
"playouts": {
"title": "Playouts"
},
"settings": {
"title": "Settings"
},
"logs": {
"title": "Logs"
},
"media": {
"title": "Media",
"libraries": {
"title": "Libraries"
},
"trash": {
"title": "Trash"
},
"tv-shows": {
"title": "TV Shows"
},
"movies": {
"title": "Movies"
},
"music-videos": {
"title": "Music Videos"
},
"other-videos": {
"title": "Other Videos"
},
"songs": {
"title": "Songs"
}
},
"channels": {
"title": "Channels",
"table": {
"number": "Number",
"logo": "Logo",
"name": "Name",
"language": "Language",
"mode": "Mode",
"ffmpeg-profile": "FFmpeg Profile"
}
}
}

168
ErsatzTV/client-app/src/locales/pt-br.json

@ -0,0 +1,168 @@ @@ -0,0 +1,168 @@
{
"$vuetify": {
"badge": "Distintivo",
"close": "Fechar",
"dataIterator": {
"noResultsText": "Nenhum dado encontrado",
"loadingText": "Carregando itens..."
},
"dataTable": {
"itemsPerPageText": "Linhas por página:",
"ariaLabel": {
"sortDescending": "Ordenado decrescente.",
"sortAscending": "Ordenado crescente.",
"sortNone": "Não ordenado.",
"activateNone": "Ative para remover a ordenação.",
"activateDescending": "Ative para ordenar decrescente.",
"activateAscending": "Ative para ordenar crescente."
},
"sortBy": "Ordenar por"
},
"dataFooter": {
"itemsPerPageText": "Itens por página:",
"itemsPerPageAll": "Todos",
"nextPage": "Próxima página",
"prevPage": "Página anterior",
"firstPage": "Primeira página",
"lastPage": "Última página",
"pageText": "{0}-{1} de {2}"
},
"datePicker": {
"itemsSelected": "{0} selecionado(s)",
"nextMonthAriaLabel": "Próximo mês",
"nextYearAriaLabel": "Próximo ano",
"prevMonthAriaLabel": "Mês anterior",
"prevYearAriaLabel": "Ano anterior"
},
"noDataText": "Não há dados disponíveis",
"carousel": {
"prev": "Visão anterior",
"next": "Próxima visão",
"ariaLabel": {
"delimiter": "Slide {0} de {1} do carrossel"
}
},
"calendar": {
"moreEvents": "Mais {0}"
},
"fileInput": {
"counter": "{0} arquivo(s)",
"counterSize": "{0} arquivo(s) ({1} no total)"
},
"timePicker": {
"am": "AM",
"pm": "PM"
},
"pagination": {
"ariaLabel": {
"wrapper": "Navegação de paginação",
"next": "Próxima página",
"previous": "Página anterior",
"page": "Ir à página {0}",
"currentPage": "Página atual, página {0}"
}
},
"rating": {
"ariaLabel": {
"icon": "Rating {0} de {1}"
}
}
},
"languages-code": {
"en": "Inglês",
"pt-br": "Português (Brasil)"
},
"tool-bar": {
"search": "Busca...",
"click-to-copy": "Clique para copiar para a área de transferência!",
"copy-success": "Texto copiado com sucesso para a área de transferência!",
"copy-failure": "Falha ao copiar o texto para a área de transferência! Erro: {message}"
},
"sidebar": {
"languages": "Línguas",
"version": "Versão"
},
"home": {
"title": "Início"
},
"ffmpeg-profiles": {
"title": "Perfis FFmpeg"
},
"watermarks": {
"title": "Marcas d'água"
},
"media-sources": {
"title": "Fontes de mídia",
"local": {
"title": "Local"
},
"emby": {
"title": "Emby"
},
"jellyfin": {
"title": "Jellyfin"
},
"plex": {
"title": "Plex"
}
},
"lists": {
"title": "Listas",
"collections": {
"title": "Coleções"
},
"trakt-lists": {
"title": "Listas Trakt"
},
"filler-presets": {
"title": "Predefinições de preenchimento"
}
},
"schedules": {
"title": "Horários"
},
"playouts": {
"title": "Playouts"
},
"settings": {
"title": "Definições"
},
"logs": {
"title": "Logs"
},
"media": {
"title": "Mídias",
"libraries": {
"title": "Bibliotecas"
},
"trash": {
"title": "Lixo"
},
"tv-shows": {
"title": "Séries"
},
"movies": {
"title": "Filmes"
},
"music-videos": {
"title": "Vídeos de música"
},
"other-videos": {
"title": "Other Videos"
},
"songs": {
"title": "Songs"
}
},
"channels": {
"title": "Canais",
"table": {
"number": "Numero",
"logo": "Logo",
"name": "Nome",
"language": "Idioma",
"mode": "Modo",
"ffmpeg-profile": "Perfil FFmpeg"
}
}
}

4
ErsatzTV/client-app/src/main.ts

@ -3,14 +3,17 @@ import App from './App.vue'; @@ -3,14 +3,17 @@ import App from './App.vue';
import vuetify from './plugins/vuetify';
import router from './router';
import { createPinia, PiniaVuePlugin } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import autoPageTitleMixin from './mixins/autoPageTitle';
import 'roboto-fontface/css/roboto/roboto-fontface.css';
import '@mdi/font/css/materialdesignicons.css';
import i18n from './plugins/i18n';
Vue.config.productionTip = false;
Vue.use(PiniaVuePlugin);
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
// Mixin to automate the page title when navigating... Will default to "ErsatzTV if no title value exported from page.
Vue.mixin(autoPageTitleMixin);
@ -19,5 +22,6 @@ new Vue({ @@ -19,5 +22,6 @@ new Vue({
vuetify,
router,
pinia,
i18n,
render: (h) => h(App)
}).$mount('#app');

27
ErsatzTV/client-app/src/plugins/i18n.ts

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
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()
});

4
ErsatzTV/client-app/src/plugins/vuetify.ts

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import Vue from 'vue';
import Vuetify from 'vuetify';
import colors from 'vuetify/lib/util/colors';
import VueI18n from './i18n';
Vue.use(Vuetify);
@ -25,5 +26,8 @@ export default new Vuetify({ @@ -25,5 +26,8 @@ export default new Vuetify({
customProperties: true
},
dark: true
},
lang: {
t: (key, ...params) => VueI18n.t(key, params).toString()
}
});

50
ErsatzTV/client-app/src/router/index.js

@ -8,7 +8,7 @@ Vue.use(VueRouter); @@ -8,7 +8,7 @@ Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'Home',
name: 'home.title',
component: HomePage,
meta: {
icon: 'mdi-home',
@ -17,7 +17,7 @@ const routes = [ @@ -17,7 +17,7 @@ const routes = [
},
{
path: '/channels',
name: 'Channels',
name: 'channels.title',
component: ChannelsPage,
meta: {
icon: 'mdi-broadcast',
@ -26,7 +26,7 @@ const routes = [ @@ -26,7 +26,7 @@ const routes = [
},
{
path: '/ffmpeg-profiles',
name: 'FFmpeg Profiles',
name: 'ffmpeg-profiles.title',
meta: {
icon: 'mdi-video-input-component',
disabled: true
@ -34,7 +34,7 @@ const routes = [ @@ -34,7 +34,7 @@ const routes = [
},
{
path: '/watermarks',
name: 'Watermarks',
name: 'watermarks.title',
meta: {
icon: 'mdi-watermark',
disabled: true
@ -42,7 +42,7 @@ const routes = [ @@ -42,7 +42,7 @@ const routes = [
},
{
path: '/sources',
name: 'Media Sources',
name: 'media-sources.title',
meta: {
icon: 'mdi-server-network',
disabled: false
@ -50,7 +50,7 @@ const routes = [ @@ -50,7 +50,7 @@ const routes = [
children: [
{
path: '/sources/local',
name: 'Local',
name: 'media-sources.local.title',
meta: {
icon: 'mdi-folder',
disabled: true
@ -58,7 +58,7 @@ const routes = [ @@ -58,7 +58,7 @@ const routes = [
},
{
path: '/sources/emby',
name: 'Emby',
name: 'media-sources.emby.title',
meta: {
icon: 'mdi-emby',
disabled: true
@ -66,7 +66,7 @@ const routes = [ @@ -66,7 +66,7 @@ const routes = [
},
{
path: '/sources/jellyfin',
name: 'Jellyfin',
name: 'media-sources.jellyfin.title',
meta: {
icon: 'mdi-jellyfish',
disabled: true
@ -74,7 +74,7 @@ const routes = [ @@ -74,7 +74,7 @@ const routes = [
},
{
path: '/sources/plex',
name: 'Plex',
name: 'media-sources.plex.title',
meta: {
icon: 'mdi-plex',
disabled: true
@ -84,7 +84,7 @@ const routes = [ @@ -84,7 +84,7 @@ const routes = [
},
{
path: '/media',
name: 'Media',
name: 'media.title',
meta: {
icon: 'mdi-cog',
disabled: false
@ -92,7 +92,7 @@ const routes = [ @@ -92,7 +92,7 @@ const routes = [
children: [
{
path: '/media/libraries',
name: 'Libraries',
name: 'media.libraries.title',
meta: {
icon: 'mdi-library',
disabled: true
@ -100,7 +100,7 @@ const routes = [ @@ -100,7 +100,7 @@ const routes = [
},
{
path: '/media/trash',
name: 'Trash',
name: 'media.trash.title',
meta: {
icon: 'mdi-trash-can',
disabled: true
@ -108,7 +108,7 @@ const routes = [ @@ -108,7 +108,7 @@ const routes = [
},
{
path: '/media/tv-shows',
name: 'TV Shows',
name: 'media.tv-shows.title',
meta: {
icon: 'mdi-television-classic',
disabled: true
@ -116,7 +116,7 @@ const routes = [ @@ -116,7 +116,7 @@ const routes = [
},
{
path: '/media/movies',
name: 'Movies',
name: 'media.movies.title',
meta: {
icon: 'mdi-movie',
disabled: true
@ -124,7 +124,7 @@ const routes = [ @@ -124,7 +124,7 @@ const routes = [
},
{
path: '/media/music-videos',
name: 'Music Videos',
name: 'media.music-videos.title',
meta: {
icon: 'mdi-music-circle',
disabled: true
@ -132,7 +132,7 @@ const routes = [ @@ -132,7 +132,7 @@ const routes = [
},
{
path: '/media/other-videos',
name: 'Other Videos',
name: 'media.other-videos.title',
meta: {
icon: 'mdi-video',
disabled: true
@ -140,7 +140,7 @@ const routes = [ @@ -140,7 +140,7 @@ const routes = [
},
{
path: '/media/songs',
name: 'Songs',
name: 'media.songs.title',
meta: {
icon: 'mdi-album',
disabled: true
@ -150,7 +150,7 @@ const routes = [ @@ -150,7 +150,7 @@ const routes = [
},
{
path: '/lists',
name: 'Lists',
name: 'lists.title',
meta: {
icon: 'mdi-format-list-bulleted',
disabled: false
@ -158,7 +158,7 @@ const routes = [ @@ -158,7 +158,7 @@ const routes = [
children: [
{
path: '/lists/collections',
name: 'Collections',
name: 'lists.collections.title',
meta: {
icon: 'mdi-collage',
disabled: true
@ -166,7 +166,7 @@ const routes = [ @@ -166,7 +166,7 @@ const routes = [
},
{
path: '/lists/trakt-lists',
name: 'Trakt Lists',
name: 'lists.trakt-lists.title',
meta: {
icon: 'mdi-hammer',
disabled: true
@ -174,7 +174,7 @@ const routes = [ @@ -174,7 +174,7 @@ const routes = [
},
{
path: '/lists/filler-presets',
name: 'Filler Presets',
name: 'lists.filler-presets.title',
meta: {
icon: 'mdi-tune-vertical',
disabled: true
@ -184,7 +184,7 @@ const routes = [ @@ -184,7 +184,7 @@ const routes = [
},
{
path: '/schedules',
name: 'Schedules',
name: 'schedules.title',
meta: {
icon: 'mdi-calendar',
disabled: true
@ -192,7 +192,7 @@ const routes = [ @@ -192,7 +192,7 @@ const routes = [
},
{
path: '/playouts',
name: 'Playouts',
name: 'playouts.title',
meta: {
icon: 'mdi-clipboard-play-multiple',
disabled: true
@ -200,7 +200,7 @@ const routes = [ @@ -200,7 +200,7 @@ const routes = [
},
{
path: '/settings',
name: 'Settings',
name: 'settings.title',
meta: {
icon: 'mdi-cog',
disabled: true
@ -208,7 +208,7 @@ const routes = [ @@ -208,7 +208,7 @@ const routes = [
},
{
path: '/Logs',
name: 'Logs',
name: 'logs.title',
meta: {
icon: 'mdi-card-text',
disabled: true

28
ErsatzTV/client-app/src/stores/languageState.js

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
import { defineStore } from 'pinia';
import VueI18n from '../plugins/i18n';
export const languageState = defineStore('languageState', {
state: () => {
return {
language: 'en'
};
},
getters: {
currentLanguageCode: (state) => {
return state.language;
}
},
actions: {
setLanguage(languageCode) {
this.language = languageCode;
VueI18n.locale = languageCode;
}
},
persist: {
afterRestore: (context) => {
if (context.store.language) {
VueI18n.locale = context.store.language;
}
}
}
});

15
ErsatzTV/client-app/src/views/ChannelsPage.vue

@ -19,12 +19,15 @@ export default class Channels extends Vue { @@ -19,12 +19,15 @@ export default class Channels extends Vue {
private channels: Channel[] = [];
private headers = [
{ text: 'Number', value: 'number' },
{ text: 'Logo', value: 'logo' },
{ text: 'Name', value: 'name' },
{ text: 'Language', value: 'language' },
{ text: 'Mode', value: 'streamingMode' },
{ text: 'FFmpeg Profile', value: 'ffmpegProfile' }
{ text: this.$t('channels.table.number'), value: 'number' },
{ text: this.$t('channels.table.logo'), value: 'logo' },
{ text: this.$t('channels.table.name'), value: 'name' },
{ text: this.$t('channels.table.language'), value: 'language' },
{ text: this.$t('channels.table.mode'), value: 'streamingMode' },
{
text: this.$t('channels.table.ffmpeg-profile'),
value: 'ffmpegProfile'
}
];
title: string = 'Channels';

8
ErsatzTV/client-app/src/views/HomePage.vue

@ -1,13 +1,9 @@ @@ -1,13 +1,9 @@
<template>
<div>
<h1>{{ this.$route.name }}</h1>
<h1>{{ $t(this.$route.name) }}</h1>
</div>
</template>
<script>
export default {
title() {
return `Home`;
}
};
export default {};
</script>

10
ErsatzTV/client-app/vue.config.js

@ -9,5 +9,13 @@ module.exports = defineConfig({ @@ -9,5 +9,13 @@ module.exports = defineConfig({
},
publicPath: '/v2/',
outputDir: '../wwwroot/v2',
filenameHashing: false
filenameHashing: false,
pluginOptions: {
i18n: {
locale: 'en',
fallbackLocale: 'en',
localeDir: 'locales',
enableInSFC: true
}
}
});

Loading…
Cancel
Save