diff --git a/ui/package-lock.json b/ui/package-lock.json
index 4ff349c94..058194c0d 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -13127,6 +13127,11 @@
"resolved": "https://registry.npmjs.org/ra-language-english/-/ra-language-english-3.2.0.tgz",
"integrity": "sha512-/XmwYWoQoB4MBkkzBCbg/ykCuRGjHQOHLk2ik6n1aM10AWHxiiJNyRw2aoLzH7Vc5rcp4BBJQCuhT+DgfYIJ2Q=="
},
+ "ra-language-portuguese": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/ra-language-portuguese/-/ra-language-portuguese-1.6.0.tgz",
+ "integrity": "sha512-9PAxgrisjmDOTRefjCe2y2ruYQw/iqXnXgUt09vOYUcjY4J0ctabJ4+joGI0jV/x9icF9c7Pui2USc5QDRTktQ=="
+ },
"ra-ui-materialui": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/ra-ui-materialui/-/ra-ui-materialui-3.3.3.tgz",
diff --git a/ui/package.json b/ui/package.json
index 4a68f7c14..44e28d62f 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -12,6 +12,7 @@
"md5-hex": "^3.0.1",
"prop-types": "^15.7.2",
"ra-data-json-server": "^3.3.3",
+ "ra-language-portuguese": "^1.6.0",
"react": "^16.13.1",
"react-admin": "^3.3.3",
"react-dom": "^16.13.1",
diff --git a/ui/src/album/AlbumList.js b/ui/src/album/AlbumList.js
index 6a6947a0d..6649252fc 100644
--- a/ui/src/album/AlbumList.js
+++ b/ui/src/album/AlbumList.js
@@ -8,7 +8,8 @@ import {
NumberInput,
ReferenceInput,
SearchInput,
- Pagination
+ Pagination,
+ useTranslate
} from 'react-admin'
import { Title } from '../common'
import { withWidth } from '@material-ui/core'
@@ -17,21 +18,25 @@ import AlbumListView from './AlbumListView'
import AlbumGridView from './AlbumGridView'
import { ALBUM_MODE_LIST } from './albumState'
-const AlbumFilter = (props) => (
-
-
- ({ name: [searchText] })}
- >
-
-
-
-
-
-)
+const AlbumFilter = (props) => {
+ const translate = useTranslate()
+ return (
+
+
+ ({ name: [searchText] })}
+ >
+
+
+
+
+
+ )
+}
const getPerPage = (width) => {
if (width === 'xs') return 12
diff --git a/ui/src/audioplayer/Player.js b/ui/src/audioplayer/Player.js
index 5751137c6..a41e939a3 100644
--- a/ui/src/audioplayer/Player.js
+++ b/ui/src/audioplayer/Player.js
@@ -27,12 +27,30 @@ const Player = () => {
glassBg: false,
showThemeSwitch: false,
showMediaSession: true,
- panelTitle: translate('player.panelTitle'),
defaultPosition: {
top: 300,
left: 120
},
locale: {
+ playListsText: translate('player.playListsText'),
+ openText: translate('player.openText'),
+ closeText: translate('player.closeText'),
+ notContentText: translate('player.notContentText'),
+ clickToPlayText: translate('player.clickToPlayText'),
+ clickToPauseText: translate('player.clickToPauseText'),
+ nextTrackText: translate('player.nextTrackText'),
+ previousTrackText: translate('player.previousTrackText'),
+ reloadText: translate('player.reloadText'),
+ volumeText: translate('player.volumeText'),
+ toggleLyricText: translate('player.toggleLyricText'),
+ toggleMiniModeText: translate('player.toggleMiniModeText'),
+ destroyText: translate('player.destroyText'),
+ downloadText: translate('player.downloadText'),
+ removeAudioListsText: translate('player.removeAudioListsText'),
+ controllerTitle: translate('player.controllerTitle'),
+ clickToDeleteText: (name) =>
+ translate('player.clickToDeleteText', { name }),
+ emptyLyricText: translate('player.emptyLyricText'),
playModeText: {
order: translate('player.playModeText.order'),
orderLoop: translate('player.playModeText.orderLoop'),
diff --git a/ui/src/i18n/en.js b/ui/src/i18n/en.js
index 69e8f346e..851c5fa5d 100644
--- a/ui/src/i18n/en.js
+++ b/ui/src/i18n/en.js
@@ -2,6 +2,7 @@ import deepmerge from 'deepmerge'
import englishMessages from 'ra-language-english'
export default deepmerge(englishMessages, {
+ languageName: 'English',
resources: {
song: {
name: 'Song |||| Songs',
@@ -45,12 +46,34 @@ export default deepmerge(englishMessages, {
menu: {
library: 'Library',
settings: 'Settings',
- personal: 'Personal',
version: 'Version %{version}',
- theme: 'Theme'
+ theme: 'Theme',
+ personal: {
+ name: 'Personal',
+ options: {
+ theme: 'Theme'
+ }
+ }
},
player: {
- panelTitle: 'Play Queue',
+ playListsText: 'Play Queue',
+ openText: 'Open',
+ closeText: 'Close',
+ notContentText: 'No music',
+ clickToPlayText: 'Click to play',
+ clickToPauseText: 'Click to pause',
+ nextTrackText: 'Next track',
+ previousTrackText: 'Previous track',
+ reloadText: 'Reload',
+ volumeText: 'Volume',
+ toggleLyricText: 'Toggle lyric',
+ toggleMiniModeText: 'Minimize',
+ destroyText: 'Destroy',
+ downloadText: 'Download',
+ removeAudioListsText: 'Delete audio lists',
+ controllerTitle: '',
+ clickToDeleteText: `Click to delete %{name}`,
+ emptyLyricText: 'No lyric',
playModeText: {
order: 'In order',
orderLoop: 'Repeat',
diff --git a/ui/src/i18n/index.js b/ui/src/i18n/index.js
index 032424835..a63e142da 100644
--- a/ui/src/i18n/index.js
+++ b/ui/src/i18n/index.js
@@ -1,3 +1,13 @@
import en from './en'
+import pt from './pt'
-export default { en }
+// When adding a new translation, import it above and add it to the list bellow
+
+const allLanguages = { en, pt }
+
+// "Hack" to make "albumSongs" resource use the same translations as "song"
+Object.keys(allLanguages).forEach(
+ (k) => (allLanguages[k].resources.albumSong = allLanguages[k].resources.song)
+)
+
+export default allLanguages
diff --git a/ui/src/i18n/pt.js b/ui/src/i18n/pt.js
new file mode 100644
index 000000000..2ea9c1b24
--- /dev/null
+++ b/ui/src/i18n/pt.js
@@ -0,0 +1,112 @@
+import deepmerge from 'deepmerge'
+import en from './en'
+import portugueseMessages from 'ra-language-portuguese'
+
+export default deepmerge.all([
+ en,
+ portugueseMessages,
+ {
+ languageName: 'Português',
+ resources: {
+ song: {
+ name: 'Música |||| Músicas',
+ fields: {
+ title: 'Título',
+ artist: 'Artista',
+ album: 'Álbum',
+ path: 'Caminho',
+ genre: 'Gênero',
+ compilation: 'Coletânea',
+ duration: 'Duração',
+ year: 'Ano',
+ trackNumber: '#'
+ },
+ bulk: {
+ addToQueue: 'Play Later'
+ }
+ },
+ album: {
+ name: 'Álbum |||| Álbuns',
+ fields: {
+ name: 'Nome',
+ artist: 'Artista',
+ songCount: 'Songs',
+ genre: 'Gênero',
+ playCount: 'Plays',
+ compilation: 'Coletânea',
+ duration: 'Duração',
+ year: 'Ano'
+ },
+ actions: {
+ playAll: 'Play',
+ playNext: 'Play Next',
+ addToQueue: 'Play Later',
+ shuffle: 'Shuffle'
+ }
+ },
+ artist: {
+ name: 'Artista |||| Artistas',
+ fields: {
+ name: 'Nome'
+ }
+ },
+ user: {
+ name: 'Usuário |||| Usuários',
+ fields: {
+ name: 'Nome'
+ }
+ },
+ transcoding: {
+ name: 'Conversão |||| Conversões',
+ fields: {
+ name: 'Nome'
+ }
+ },
+ player: {
+ name: 'Tocador |||| Tocadores',
+ fields: {
+ name: 'Nome'
+ }
+ }
+ },
+ ra: {
+ auth: {
+ welcome1: 'Thanks for installing Navidrome!',
+ welcome2: 'To start, create an admin user',
+ confirmPassword: 'Confirm Password',
+ buttonCreateAdmin: 'Create Admin'
+ },
+ validation: {
+ invalidChars: 'Please only use letter and numbers',
+ passwordDoesNotMatch: 'Password does not match'
+ }
+ },
+ menu: {
+ library: 'Biblioteca',
+ settings: 'Configurações',
+ version: 'Versão %{version}',
+ personal: {
+ name: 'Pessoal',
+ options: {
+ theme: 'Tema'
+ }
+ }
+ },
+ player: {
+ playListsText: 'Fila de Execução',
+ openText: 'Abrir',
+ closeText: 'Fechar',
+ clickToPlayText: 'Clique para tocar',
+ clickToPauseText: 'Clique para pausar',
+ nextTrackText: 'Próxima faixa',
+ previousTrackText: 'Faixa anterior',
+ clickToDeleteText: `Clique para remover %{name}`,
+ playModeText: {
+ order: 'Em ordem',
+ orderLoop: 'Repetir tudo',
+ singleLoop: 'Repetir',
+ shufflePlay: 'Aleatório'
+ }
+ }
+ }
+])
diff --git a/ui/src/layout/PersonalMenu.js b/ui/src/layout/PersonalMenu.js
index 9f8bfcf93..8f0e46d7d 100644
--- a/ui/src/layout/PersonalMenu.js
+++ b/ui/src/layout/PersonalMenu.js
@@ -16,7 +16,7 @@ const PersonalMenu = forwardRef(({ onClick, sidebarIsOpen, dense }, ref) => {
}
onClick={onClick}
className={classes.menuItem}
diff --git a/ui/src/personal/Personal.js b/ui/src/personal/Personal.js
index dc528fe0e..1b10d5ff9 100644
--- a/ui/src/personal/Personal.js
+++ b/ui/src/personal/Personal.js
@@ -21,10 +21,11 @@ const Personal = () => {
return (
-
+
{
diff --git a/ui/src/song/SongList.js b/ui/src/song/SongList.js
index 81908a14b..4e3ea35f7 100644
--- a/ui/src/song/SongList.js
+++ b/ui/src/song/SongList.js
@@ -6,7 +6,8 @@ import {
List,
NumberField,
SearchInput,
- TextField
+ TextField,
+ useTranslate
} from 'react-admin'
import { useMediaQuery } from '@material-ui/core'
import {
@@ -30,6 +31,7 @@ const SongFilter = (props) => (
)
const SongList = (props) => {
+ const translate = useTranslate()
const dispatch = useDispatch()
const isXsmall = useMediaQuery((theme) => theme.breakpoints.down('xs'))
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
@@ -63,7 +65,13 @@ const SongList = (props) => {
rowClick={(id, basePath, record) => dispatch(setTrack(record))}
>
- {isDesktop && }
+ {isDesktop && (
+
+ )}
{isDesktop && }
{isDesktop && }