Implement locales in codebase
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
"clear-queue": "Clear Queue",
|
"clear-queue": "Clear Queue",
|
||||||
"no-results": "No results...",
|
"no-results": "No results...",
|
||||||
"album": "Album",
|
"album": "Album",
|
||||||
|
"albums": "Albums",
|
||||||
"all-albums": "All Albums",
|
"all-albums": "All Albums",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"music": "Music",
|
"music": "Music",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type LocaleKeys = 'play-next'
|
|||||||
| 'clear-queue'
|
| 'clear-queue'
|
||||||
| 'no-results'
|
| 'no-results'
|
||||||
| 'album'
|
| 'album'
|
||||||
|
| 'albums'
|
||||||
| 'all-albums'
|
| 'all-albums'
|
||||||
| 'search'
|
| 'search'
|
||||||
| 'music'
|
| 'music'
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Album from './stacks/Album';
|
|||||||
import RecentAlbums from './stacks/RecentAlbums';
|
import RecentAlbums from './stacks/RecentAlbums';
|
||||||
import Search from './stacks/Search';
|
import Search from './stacks/Search';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
const Stack = createStackNavigator<StackParams>();
|
const Stack = createStackNavigator<StackParams>();
|
||||||
|
|
||||||
@@ -17,10 +18,10 @@ const navigationOptions = {
|
|||||||
function MusicStack() {
|
function MusicStack() {
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator initialRouteName="RecentAlbums" screenOptions={navigationOptions}>
|
<Stack.Navigator initialRouteName="RecentAlbums" screenOptions={navigationOptions}>
|
||||||
<Stack.Screen name="RecentAlbums" component={RecentAlbums} options={{ headerTitle: 'Recent Albums' }} />
|
<Stack.Screen name="RecentAlbums" component={RecentAlbums} options={{ headerTitle: t('recent-albums') }} />
|
||||||
<Stack.Screen name="Albums" component={Albums} />
|
<Stack.Screen name="Albums" component={Albums} options={{ headerTitle: t('albums') }} />
|
||||||
<Stack.Screen name="Album" component={Album} />
|
<Stack.Screen name="Album" component={Album} options={{ headerTitle: t('album') }} />
|
||||||
<Stack.Screen name="Search" component={Search} />
|
<Stack.Screen name="Search" component={Search} options={{ headerTitle: t('search') }} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import TouchableHandler from 'components/TouchableHandler';
|
|||||||
import useCurrentTrack from 'utility/useCurrentTrack';
|
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||||
import { colors } from 'components/Colors';
|
import { colors } from 'components/Colors';
|
||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
type Route = RouteProp<StackParams, 'Album'>;
|
type Route = RouteProp<StackParams, 'Album'>;
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ const Album: React.FC = () => {
|
|||||||
<AlbumImage source={{ uri: getImage(album?.Id) }} style={colors.imageBackground} />
|
<AlbumImage source={{ uri: getImage(album?.Id) }} style={colors.imageBackground} />
|
||||||
<Text style={styles.name} >{album?.Name}</Text>
|
<Text style={styles.name} >{album?.Name}</Text>
|
||||||
<Text style={styles.artist}>{album?.AlbumArtist}</Text>
|
<Text style={styles.artist}>{album?.AlbumArtist}</Text>
|
||||||
<Button title="Play Album" onPress={selectAlbum} color={THEME_COLOR} />
|
<Button title={t('play-album')} onPress={selectAlbum} color={THEME_COLOR} />
|
||||||
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
|
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
|
||||||
<TouchableHandler
|
<TouchableHandler
|
||||||
key={trackId}
|
key={trackId}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useRecentAlbums } from 'store/music/selectors';
|
|||||||
import { Header } from 'components/Typography';
|
import { Header } from 'components/Typography';
|
||||||
import ListButton from 'components/ListButton';
|
import ListButton from 'components/ListButton';
|
||||||
import { colors } from 'components/Colors';
|
import { colors } from 'components/Colors';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
artist: {
|
artist: {
|
||||||
@@ -28,9 +29,9 @@ const NavigationHeader: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ListContainer>
|
<ListContainer>
|
||||||
<ListButton onPress={handleAllAlbumsClick}>All Albums</ListButton>
|
<ListButton onPress={handleAllAlbumsClick}>{t('all-albums')}</ListButton>
|
||||||
<ListButton onPress={handleSearchClick}>Search</ListButton>
|
<ListButton onPress={handleSearchClick}>{t('search')}</ListButton>
|
||||||
<Header style={colors.text}>Recent Albums</Header>
|
<Header style={colors.text}>{t('recent-albums')}</Header>
|
||||||
</ListContainer>
|
</ListContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useGetImage } from 'utility/JellyfinApi';
|
|||||||
import { NavigationProp } from '../types';
|
import { NavigationProp } from '../types';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { colors } from 'components/Colors';
|
import { colors } from 'components/Colors';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
const Container = styled.View`
|
const Container = styled.View`
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
@@ -88,8 +89,8 @@ export default function Search() {
|
|||||||
|
|
||||||
const HeaderComponent = React.useMemo(() => (
|
const HeaderComponent = React.useMemo(() => (
|
||||||
<Container>
|
<Container>
|
||||||
<Input value={searchTerm} onChangeText={setSearchTerm} style={colors.input} placeholder="Search..." />
|
<Input value={searchTerm} onChangeText={setSearchTerm} style={colors.input} placeholder={t('search') + '...'} />
|
||||||
{(searchTerm.length && !results.length) ? <Text>No results...</Text> : null}
|
{(searchTerm.length && !results.length) ? <Text>{t('no-results')}</Text> : null}
|
||||||
</Container>
|
</Container>
|
||||||
), [searchTerm, results, setSearchTerm]);
|
), [searchTerm, results, setSearchTerm]);
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ export default function Search() {
|
|||||||
<Text numberOfLines={1} ellipsizeMode="tail" style={colors.text}>
|
<Text numberOfLines={1} ellipsizeMode="tail" style={colors.text}>
|
||||||
{album.Name} - {album.AlbumArtist}
|
{album.Name} - {album.AlbumArtist}
|
||||||
</Text>
|
</Text>
|
||||||
<HalfOpacity style={colors.text}>Album</HalfOpacity>
|
<HalfOpacity style={colors.text}>{t('album')}</HalfOpacity>
|
||||||
</View>
|
</View>
|
||||||
</SearchResult>
|
</SearchResult>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { NavigationProp } from 'screens';
|
|||||||
import { useTypedSelector } from 'store';
|
import { useTypedSelector } from 'store';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setOnboardingStatus } from 'store/settings/actions';
|
import { setOnboardingStatus } from 'store/settings/actions';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
const Container = styled.SafeAreaView`
|
const Container = styled.SafeAreaView`
|
||||||
background-color: ${THEME_COLOR};
|
background-color: ${THEME_COLOR};
|
||||||
@@ -57,16 +58,16 @@ function Onboarding() {
|
|||||||
<TextContainer contentContainerStyle={{ flexGrow: 1, justifyContent: 'center' }}>
|
<TextContainer contentContainerStyle={{ flexGrow: 1, justifyContent: 'center' }}>
|
||||||
<Logo source={require('../../assets/app-icon-white.png')} />
|
<Logo source={require('../../assets/app-icon-white.png')} />
|
||||||
<Text >
|
<Text >
|
||||||
Welcome!
|
{t('onboarding-welcome')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
Jellyfin Audio Player will allow you to stream your music library from anywhere, with full support for background audio and casting.
|
{t('onboarding-intro')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
In order to get started, you need a Jellyfin server. Click the button below to enter your Jellyfin server address and login to it.
|
{t('onboarding-cta')}
|
||||||
</Text>
|
</Text>
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
<Button title="Set Jellyfin Server" color="#ffffff" onPress={handleClick} />
|
<Button title={t('set-jellyfin-server')} color="#ffffff" onPress={handleClick} />
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
</TextContainer>
|
</TextContainer>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import TouchableHandler from 'components/TouchableHandler';
|
|||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { colors } from 'components/Colors';
|
import { colors } from 'components/Colors';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean, isDark?: boolean }>`
|
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean, isDark?: boolean }>`
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@@ -52,7 +53,7 @@ export default function Queue() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Text style={{ marginTop: 20, marginBottom: 20 }}>Queue</Text>
|
<Text style={{ marginTop: 20, marginBottom: 20 }}>{t('queue')}</Text>
|
||||||
{queue.map((track, i) => (
|
{queue.map((track, i) => (
|
||||||
<TouchableHandler id={track.id} onPress={playTrack} key={i}>
|
<TouchableHandler id={track.id} onPress={playTrack} key={i}>
|
||||||
<QueueItem
|
<QueueItem
|
||||||
@@ -70,7 +71,7 @@ export default function Queue() {
|
|||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
))}
|
))}
|
||||||
<ClearQueue>
|
<ClearQueue>
|
||||||
<Button title="Clear Queue" color={THEME_COLOR} onPress={clearQueue} />
|
<Button title={t('clear-queue')} color={THEME_COLOR} onPress={clearQueue} />
|
||||||
</ClearQueue>
|
</ClearQueue>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Text, Button } from 'react-native';
|
|||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import music from 'store/music';
|
import music from 'store/music';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
export default function CacheSettings() {
|
export default function CacheSettings() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -18,9 +19,9 @@ export default function CacheSettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SubHeader>Cache</SubHeader>
|
<SubHeader>{t('setting-cache')}</SubHeader>
|
||||||
<Text>If you have updated your Jellyfin library, but the app is holding on to cached assets, you can forcefully clear the cache using this button. This will force the app to fetch the library from scratch.</Text>
|
<Text>{t('setting-cache-description')}</Text>
|
||||||
<Button title="Reset Cache" onPress={handleClearCache} color={THEME_COLOR} />
|
<Button title={t('reset-cache')} onPress={handleClearCache} color={THEME_COLOR} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import { SubHeader } from 'components/Typography';
|
|||||||
import { colors } from 'components/Colors';
|
import { colors } from 'components/Colors';
|
||||||
import { NavigationProp } from '../..';
|
import { NavigationProp } from '../..';
|
||||||
import { useTypedSelector } from 'store';
|
import { useTypedSelector } from 'store';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
const InputContainer = styled.View`
|
const InputContainer = styled.View`
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
@@ -25,20 +26,20 @@ export default function LibrarySettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SubHeader>Jellyfin Library</SubHeader>
|
<SubHeader>{t('jellyfin-library')}</SubHeader>
|
||||||
<InputContainer>
|
<InputContainer>
|
||||||
<Text style={colors.text}>Jellyfin Server URL</Text>
|
<Text style={colors.text}>{t('jellyfin-server-url')}</Text>
|
||||||
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} style={colors.input} />
|
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} style={colors.input} />
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
<InputContainer>
|
<InputContainer>
|
||||||
<Text style={colors.text}>Jellyfin Access Token</Text>
|
<Text style={colors.text}>{t('jellyfin-access-token')}</Text>
|
||||||
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} style={colors.input} />
|
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} style={colors.input} />
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
<InputContainer>
|
<InputContainer>
|
||||||
<Text style={colors.text}>Jellyfin User ID</Text>
|
<Text style={colors.text}>{t('jellyfin-user-id')}</Text>
|
||||||
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} style={colors.input} />
|
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} style={colors.input} />
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
<Button title="Set Jellyfin server" onPress={handleSetLibrary} color={THEME_COLOR} />
|
<Button title={t('set-jellyfin-server')} onPress={handleSetLibrary} color={THEME_COLOR} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,14 @@ import { Header } from 'components/Typography';
|
|||||||
import { colors } from 'components/Colors';
|
import { colors } from 'components/Colors';
|
||||||
import Library from './components/Library';
|
import Library from './components/Library';
|
||||||
import Cache from './components/Cache';
|
import Cache from './components/Cache';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
<View style={{ padding: 20 }}>
|
<View style={{ padding: 20 }}>
|
||||||
<Header style={colors.text}>Settings</Header>
|
<Header style={colors.text}>{t('settings')}</Header>
|
||||||
<Library />
|
<Library />
|
||||||
<Cache />
|
<Cache />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { useTypedSelector } from 'store';
|
|||||||
import Onboarding from './Onboarding';
|
import Onboarding from './Onboarding';
|
||||||
import TrackPopupMenu from './modals/TrackPopupMenu';
|
import TrackPopupMenu from './modals/TrackPopupMenu';
|
||||||
import { ModalStackParams } from './types';
|
import { ModalStackParams } from './types';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
const Stack = createStackNavigator<ModalStackParams>();
|
const Stack = createStackNavigator<ModalStackParams>();
|
||||||
const Tab = createBottomTabNavigator();
|
const Tab = createBottomTabNavigator();
|
||||||
@@ -64,9 +65,9 @@ function Screens() {
|
|||||||
inactiveTintColor: 'gray',
|
inactiveTintColor: 'gray',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tab.Screen name="NowPlaying" component={Player} options={{ tabBarLabel: 'Now Playing' }} />
|
<Tab.Screen name="NowPlaying" component={Player} options={{ tabBarLabel: t('now-playing') }} />
|
||||||
<Tab.Screen name="Music" component={Music} />
|
<Tab.Screen name="Music" component={Music} options={{ tabBarLabel: t('music') }} />
|
||||||
<Tab.Screen name="Settings" component={Settings} />
|
<Tab.Screen name="Settings" component={Settings} options={{ tabBarLabel: t('settings') }} />
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useNavigation, StackActions } from '@react-navigation/native';
|
|||||||
import CredentialGenerator from './components/CredentialGenerator';
|
import CredentialGenerator from './components/CredentialGenerator';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { colors } from 'components/Colors';
|
import { colors } from 'components/Colors';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
export default function SetJellyfinServer() {
|
export default function SetJellyfinServer() {
|
||||||
// State for first screen
|
// State for first screen
|
||||||
@@ -34,7 +35,7 @@ export default function SetJellyfinServer() {
|
|||||||
) : (
|
) : (
|
||||||
<View style={{ padding: 20, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
<View style={{ padding: 20, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||||
<Text style={colors.text}>
|
<Text style={colors.text}>
|
||||||
Please enter your Jellyfin server URL. Make sure to include the protocol and port
|
{t('set-jellyfin-server-instruction')}
|
||||||
</Text>
|
</Text>
|
||||||
<Input
|
<Input
|
||||||
placeholder="https://jellyfin.yourserver.io/"
|
placeholder="https://jellyfin.yourserver.io/"
|
||||||
@@ -46,7 +47,7 @@ export default function SetJellyfinServer() {
|
|||||||
style={{ ...colors.input, width: '100%' }}
|
style={{ ...colors.input, width: '100%' }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
title="Set server"
|
title={t('set-jellyfin-server')}
|
||||||
onPress={() => setIsLogginIn(true)}
|
onPress={() => setIsLogginIn(true)}
|
||||||
disabled={!serverUrl?.length}
|
disabled={!serverUrl?.length}
|
||||||
color={THEME_COLOR}
|
color={THEME_COLOR}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { THEME_COLOR } from 'CONSTANTS';
|
|||||||
import { SubHeader } from 'components/Typography';
|
import { SubHeader } from 'components/Typography';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import usePlayTrack from 'utility/usePlayTrack';
|
import usePlayTrack from 'utility/usePlayTrack';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
type Route = RouteProp<ModalStackParams, 'TrackPopupMenu'>;
|
type Route = RouteProp<ModalStackParams, 'TrackPopupMenu'>;
|
||||||
|
|
||||||
@@ -48,8 +49,8 @@ function TrackPopupMenu() {
|
|||||||
<SubHeader>{track?.Name}</SubHeader>
|
<SubHeader>{track?.Name}</SubHeader>
|
||||||
<Text>{track?.Album} - {track?.AlbumArtist}</Text>
|
<Text>{track?.Album} - {track?.AlbumArtist}</Text>
|
||||||
<Buttons>
|
<Buttons>
|
||||||
<Button title="Play Next" color={THEME_COLOR} onPress={handlePlayNext} />
|
<Button title={t('play-next')} color={THEME_COLOR} onPress={handlePlayNext} />
|
||||||
<Button title="Add to Queue" color={THEME_COLOR} onPress={handleAddToQueue} />
|
<Button title={t('add-to-queue')} color={THEME_COLOR} onPress={handleAddToQueue} />
|
||||||
</Buttons>
|
</Buttons>
|
||||||
</Container>
|
</Container>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
Reference in New Issue
Block a user