diff --git a/conf/configuration.go b/conf/configuration.go index d6e49262e..cbbc3fab0 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -48,6 +48,7 @@ type configOptions struct { EnableStarRating bool EnableUserEditing bool DefaultTheme string + DefaultLanguage string EnableCoverAnimation bool GATrackingID string EnableLogRedacting bool @@ -231,6 +232,7 @@ func init() { viper.SetDefault("enablestarrating", true) viper.SetDefault("enableuserediting", true) viper.SetDefault("defaulttheme", "Dark") + viper.SetDefault("defaultlanguage", "") viper.SetDefault("enablecoveranimation", true) viper.SetDefault("gatrackingid", "") viper.SetDefault("enablelogredacting", true) diff --git a/server/nativeapi/native_api.go b/server/nativeapi/native_api.go index 81873d5bc..083bccf50 100644 --- a/server/nativeapi/native_api.go +++ b/server/nativeapi/native_api.go @@ -31,30 +31,35 @@ func New(ds model.DataStore, broker events.Broker, share core.Share) *Router { func (n *Router) routes() http.Handler { r := chi.NewRouter() - r.Use(server.Authenticator(n.ds)) - r.Use(server.JWTRefresher) - n.R(r, "/user", model.User{}, true) - n.R(r, "/song", model.MediaFile{}, false) - n.R(r, "/album", model.Album{}, false) - n.R(r, "/artist", model.Artist{}, false) - n.R(r, "/genre", model.Genre{}, false) - n.R(r, "/player", model.Player{}, true) - n.R(r, "/playlist", model.Playlist{}, true) - n.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig) - n.RX(r, "/share", n.share.NewRepository, true) + // Public n.RX(r, "/translation", newTranslationRepository, false) - n.addPlaylistTrackRoute(r) + // Protected + r.Group(func(r chi.Router) { + r.Use(server.Authenticator(n.ds)) + r.Use(server.JWTRefresher) + n.R(r, "/user", model.User{}, true) + n.R(r, "/song", model.MediaFile{}, false) + n.R(r, "/album", model.Album{}, false) + n.R(r, "/artist", model.Artist{}, false) + n.R(r, "/genre", model.Genre{}, false) + n.R(r, "/player", model.Player{}, true) + n.R(r, "/playlist", model.Playlist{}, true) + n.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig) + n.RX(r, "/share", n.share.NewRepository, true) - // Keepalive endpoint to be used to keep the session valid (ex: while playing songs) - r.Get("/keepalive/*", func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte(`{"response":"ok", "id":"keepalive"}`)) + n.addPlaylistTrackRoute(r) + + // Keepalive endpoint to be used to keep the session valid (ex: while playing songs) + r.Get("/keepalive/*", func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"response":"ok", "id":"keepalive"}`)) + }) + + if conf.Server.DevActivityPanel { + r.Handle("/events", n.broker) + } }) - if conf.Server.DevActivityPanel { - r.Handle("/events", n.broker) - } - return r } diff --git a/server/serve_index.go b/server/serve_index.go index 430755a24..6ad363a36 100644 --- a/server/serve_index.go +++ b/server/serve_index.go @@ -38,6 +38,7 @@ func serveIndex(ds model.DataStore, fs fs.FS) http.HandlerFunc { "enableFavourites": conf.Server.EnableFavourites, "enableStarRating": conf.Server.EnableStarRating, "defaultTheme": conf.Server.DefaultTheme, + "defaultLanguage": conf.Server.DefaultLanguage, "enableCoverAnimation": conf.Server.EnableCoverAnimation, "gaTrackingId": conf.Server.GATrackingID, "losslessFormats": strings.ToUpper(strings.Join(consts.LosslessFormats, ",")), diff --git a/server/serve_index_test.go b/server/serve_index_test.go index bb9174612..223309de4 100644 --- a/server/serve_index_test.go +++ b/server/serve_index_test.go @@ -159,6 +159,17 @@ var _ = Describe("serveIndex", func() { Expect(config).To(HaveKeyWithValue("defaultTheme", "Light")) }) + It("sets the defaultLanguage", func() { + conf.Server.DefaultLanguage = "pt" + r := httptest.NewRequest("GET", "/index.html", nil) + w := httptest.NewRecorder() + + serveIndex(ds, fs)(w, r) + + config := extractAppConfig(w.Body.String()) + Expect(config).To(HaveKeyWithValue("defaultLanguage", "pt")) + }) + It("sets the enableCoverAnimation", func() { conf.Server.EnableCoverAnimation = true r := httptest.NewRequest("GET", "/index.html", nil) diff --git a/ui/src/config.js b/ui/src/config.js index 18ad34997..97b0d9655 100644 --- a/ui/src/config.js +++ b/ui/src/config.js @@ -18,6 +18,7 @@ const defaultConfig = { devFastAccessCoverArt: false, enableStarRating: true, defaultTheme: 'Dark', + defaultLanguage: '', enableUserEditing: true, devEnableShare: true, devSidebarPlaylists: true, diff --git a/ui/src/i18n/index.js b/ui/src/i18n/index.js index 100aae8b8..984757c80 100644 --- a/ui/src/i18n/index.js +++ b/ui/src/i18n/index.js @@ -1,4 +1,5 @@ import i18nProvider from './provider' +import { retrieveTranslation } from './provider' import useGetLanguageChoices from './useGetLanguageChoices' -export { i18nProvider, useGetLanguageChoices } +export { i18nProvider, retrieveTranslation, useGetLanguageChoices } diff --git a/ui/src/i18n/provider.js b/ui/src/i18n/provider.js index 2d3e1d355..b1f302f8c 100644 --- a/ui/src/i18n/provider.js +++ b/ui/src/i18n/provider.js @@ -18,7 +18,7 @@ const defaultLocale = function () { return 'en' } -function retrieveTranslation(locale) { +export function retrieveTranslation(locale) { return dataProvider.getOne('translation', { id: locale }).then((res) => { localStorage.setItem('translation', JSON.stringify(res.data)) return prepareLanguage(JSON.parse(res.data.data)) diff --git a/ui/src/layout/Login.js b/ui/src/layout/Login.js index 30c322917..2803a23a8 100644 --- a/ui/src/layout/Login.js +++ b/ui/src/layout/Login.js @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react' +import React, { useState, useCallback, useEffect } from 'react' import PropTypes from 'prop-types' import { Field, Form } from 'react-final-form' import { useDispatch } from 'react-redux' @@ -8,13 +8,22 @@ import CardActions from '@material-ui/core/CardActions' import CircularProgress from '@material-ui/core/CircularProgress' import TextField from '@material-ui/core/TextField' import { ThemeProvider, makeStyles } from '@material-ui/core/styles' -import { createMuiTheme, useLogin, useNotify, useTranslate } from 'react-admin' +import { + createMuiTheme, + useLogin, + useNotify, + useRefresh, + useSetLocale, + useTranslate, + useVersion, +} from 'react-admin' import Logo from '../icons/android-icon-192x192.png' import Notification from './Notification' import useCurrentTheme from '../themes/useCurrentTheme' import config from '../config' import { clearQueue } from '../actions' +import { retrieveTranslation } from '../i18n' const useStyles = makeStyles( (theme) => ({ @@ -322,9 +331,30 @@ Login.propTypes = { // the right theme const LoginWithTheme = (props) => { const theme = useCurrentTheme() + const setLocale = useSetLocale() + const refresh = useRefresh() + const version = useVersion() + + useEffect(() => { + if (config.defaultLanguage !== '') { + retrieveTranslation(config.defaultLanguage) + .then(() => { + setLocale(config.defaultLanguage).then(() => { + localStorage.setItem('locale', config.defaultLanguage) + }) + refresh(true) + }) + .catch((e) => { + throw new Error( + 'Cannot load language "' + config.defaultLanguage + '": ' + e + ) + }) + } + }, [refresh, setLocale]) + return ( - + ) }