Upgrading (where practical) to I18n component, rather than i18n function so we can useSelector for language pref
This commit is contained in:
@ -1 +1 @@
|
||||
3.64.1
|
||||
3.65.0
|
||||
|
||||
28
babel.config.js
Executable file
28
babel.config.js
Executable file
@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript"
|
||||
],
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
],
|
||||
env: {
|
||||
development: {
|
||||
plugins: [
|
||||
"react-element-info"
|
||||
]
|
||||
},
|
||||
test: {
|
||||
plugins: [
|
||||
"react-element-info",
|
||||
"@babel/plugin-transform-runtime",
|
||||
["polyfill-corejs3", { "method": "usage-global" }]
|
||||
],
|
||||
presets: [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -3,7 +3,7 @@ import pathlib
|
||||
|
||||
from mopidy import config, ext
|
||||
|
||||
__version__ = "3.64.1"
|
||||
__version__ = "3.65.0"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
13653
package-lock.json
generated
13653
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = Mopidy-Iris
|
||||
version = 3.64.1
|
||||
version = 3.65.0
|
||||
url = https://github.com/jaedb/iris
|
||||
author = James Barnsley
|
||||
author_email = james@barnsley.nz
|
||||
|
||||
@ -87,8 +87,6 @@ const App = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const language = useSelector((state) => state.ui.language);
|
||||
window.language = language;
|
||||
const theme = useSelector((state) => state.ui.theme);
|
||||
const wide_scrollbar_enabled = useSelector((state) => state.ui.wide_scrollbar_enabled);
|
||||
const hide_scrollbars = useSelector((state) => state.ui.hide_scrollbars);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import URILink from './URILink';
|
||||
import { I18n, i18n } from '../locale';
|
||||
import { I18n } from '../locale';
|
||||
import {
|
||||
getFromUri,
|
||||
titleCase,
|
||||
@ -61,7 +61,7 @@ export default ({
|
||||
if (inline) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{i18n('specs.added_from')}
|
||||
<I18n path="specs.added_from" />
|
||||
{link}
|
||||
</div>
|
||||
);
|
||||
@ -71,7 +71,7 @@ export default ({
|
||||
<div className={`${className} tooltip`}>
|
||||
{link}
|
||||
<span className="tooltip__content">
|
||||
{i18n('specs.added_by', { by })}
|
||||
<I18n path="specs.added_by" params={{ by }} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { memo } from 'react';
|
||||
import { i18n } from '../locale';
|
||||
import React, { memo } from 'react';
|
||||
import { I18n } from '../locale';
|
||||
|
||||
/**
|
||||
* Format time duration
|
||||
@ -50,10 +50,10 @@ const durationSentence = (milliseconds = null) => {
|
||||
const totalMinutes = Math.floor(milliseconds / (1000 * 60));
|
||||
const totalHours = Math.floor(milliseconds / (1000 * 60 * 60));
|
||||
|
||||
if (totalHours > 1) return `${totalHours}+ ${i18n('time.hours.short')}`;
|
||||
if (totalMinutes > 1) return `${totalMinutes} ${i18n('time.minutes.short')}`;
|
||||
if (totalSeconds) return `${totalSeconds} ${i18n('time.seconds.short')}`;
|
||||
return `0 ${i18n('time.minutes.short')}`;
|
||||
if (totalHours > 1) return <>{totalHours} <I18n path="time.hours.short" /></>;
|
||||
if (totalMinutes > 1) return <>{totalMinutes} <I18n path="time.minutes.short" /></>;
|
||||
if (totalSeconds) return <>{totalSeconds} <I18n path="time.seconds.short" /></>;
|
||||
return <>0 <I18n path="time.minutes.short" /></>;
|
||||
};
|
||||
|
||||
const dater = (type, data) => {
|
||||
@ -97,17 +97,17 @@ const dater = (type, data) => {
|
||||
var years = Math.floor(diff / (1000 * 60 * 60 * 24 * 7 * 52));
|
||||
|
||||
if (seconds < 60) {
|
||||
return `${seconds} ${i18n(`time.seconds.${seconds > 1 ? 'plural' : 'singular'}`)}`;
|
||||
return <>{seconds} <I18n path={`time.seconds.${seconds > 1 ? 'plural' : 'singular'}`} /></>
|
||||
} if (minutes < 60) {
|
||||
return `${minutes} ${i18n(`time.minutes.${minutes > 1 ? 'plural' : 'singular'}`)}`;
|
||||
return <>{minutes} <I18n path={`time.minutes.${minutes > 1 ? 'plural' : 'singular'}`} /></>
|
||||
} if (hours < 24) {
|
||||
return `${hours} ${i18n(`time.hours.${hours > 1 ? 'plural' : 'singular'}`)}`;
|
||||
return <>{hours} <I18n path={`time.hours.${hours > 1 ? 'plural' : 'singular'}`} /></>
|
||||
} if (days < 7) {
|
||||
return `${days} ${i18n(`time.days.${days > 1 ? 'plural' : 'singular'}`)}`;
|
||||
return <>{days} <I18n path={`time.days.${days > 1 ? 'plural' : 'singular'}`} /></>
|
||||
} if (weeks < 54) {
|
||||
return `${weeks} ${i18n(`time.weeks.${weeks > 1 ? 'plural' : 'singular'}`)}`;
|
||||
return <>{weeks} <I18n path={`time.weeks.${weeks > 1 ? 'plural' : 'singular'}`} /></>
|
||||
}
|
||||
return `${years} ${i18n(`time.years.${years > 1 ? 'plural' : 'singular'}`)}`;
|
||||
return <>{years} <I18n path={`time.years.${years > 1 ? 'plural' : 'singular'}`} /></>
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React, { memo } from 'react';
|
||||
import { i18n } from '../locale';
|
||||
import { I18n } from '../locale';
|
||||
|
||||
export default memo(({ type, title, children }) => (
|
||||
<div className={`error-message${type ? ` error-message--${type}` : ''}`}>
|
||||
<i className="error-message__icon icon icon--material">error</i>
|
||||
<h4 className="error-message__title">
|
||||
{title || i18n('errors.unknown_error') }
|
||||
{title || <I18n path="errors.unknown_error" />}
|
||||
</h4>
|
||||
<div className="error-message__content">
|
||||
{children}
|
||||
|
||||
@ -10,7 +10,7 @@ import * as snapcastActions from '../services/snapcast/actions';
|
||||
import Icon from './Icon';
|
||||
import Loader from './Loader';
|
||||
import { indexToArray } from '../util/arrays';
|
||||
import { i18n, I18n } from '../locale';
|
||||
import { I18n } from '../locale';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import Button from './Button';
|
||||
|
||||
@ -135,7 +135,7 @@ const NotificationItems = () => {
|
||||
|
||||
dispatch(uiActions.removeNotification(notification_key, true));
|
||||
dispatch(uiActions.createNotification({
|
||||
content: i18n('modal.shared_config.imported'),
|
||||
content: <I18n path="modal.shared_config.imported" />
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import Snapcast from './Snapcast';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
import * as geniusActions from '../services/genius/actions';
|
||||
import { I18n, i18n } from '../locale';
|
||||
import { I18n } from '../locale';
|
||||
import Button from './Button';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
@ -242,7 +242,8 @@ const Spotify = () => {
|
||||
{me.name || me.id}
|
||||
{!authorization && (
|
||||
<span className="mid_grey-text">
|
||||
{` (${i18n('settings.services.limited_access')})`}
|
||||
{' '}
|
||||
<I18n path="settings.services.limited_access" />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
@ -4,7 +4,7 @@ import Link from './Link';
|
||||
import Icon from './Icon';
|
||||
import Dropzones from './Fields/Dropzones';
|
||||
import PinList from './Fields/PinList';
|
||||
import { I18n, i18n } from '../locale';
|
||||
import { I18n } from '../locale';
|
||||
import { toggleSidebar } from '../services/ui/actions';
|
||||
|
||||
const StatusIcon = () => {
|
||||
@ -42,19 +42,25 @@ const StatusIcon = () => {
|
||||
<Icon name="warning" className="red-text" />
|
||||
<span className="tooltip__content">
|
||||
{!mopidy_connected && (
|
||||
<I18n path="sidebar.not_connected" name={i18n('services.mopidy.title')} contentAfter>
|
||||
<>
|
||||
<I18n path="services.mopidy.title" />
|
||||
<I18n path="sidebar.not_connected" />
|
||||
<br />
|
||||
</I18n>
|
||||
</>
|
||||
)}
|
||||
{!pusher_connected && (
|
||||
<I18n path="sidebar.not_connected" name={i18n('services.pusher.title')} contentAfter>
|
||||
<>
|
||||
<I18n path="services.pusher.title" />
|
||||
<I18n path="sidebar.not_connected" />
|
||||
<br />
|
||||
</I18n>
|
||||
</>
|
||||
)}
|
||||
{!snapcast_connected && snapcast_enabled && (
|
||||
<I18n path="sidebar.not_connected" name={i18n('services.snapcast.title')} contentAfter>
|
||||
<>
|
||||
<I18n path="services.snapcast.title" />
|
||||
<I18n path="sidebar.not_connected" />
|
||||
<br />
|
||||
</I18n>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@ -4,7 +4,7 @@ import MuteControl from './Fields/MuteControl';
|
||||
import LatencyControl from './Fields/LatencyControl';
|
||||
import TextField from './Fields/TextField';
|
||||
import SelectField from './Fields/SelectField';
|
||||
import { I18n, i18n } from '../locale';
|
||||
import { I18n } from '../locale';
|
||||
import Link from './Link';
|
||||
|
||||
const SnapcastClients = ({
|
||||
@ -61,7 +61,7 @@ const SnapcastClients = ({
|
||||
{
|
||||
key: `client_${client.id}_new_group`,
|
||||
value: group.id,
|
||||
label: i18n('snapcast.new_group'),
|
||||
label: <I18n path="snapcast.new_group" />,
|
||||
},
|
||||
]}
|
||||
autosave
|
||||
|
||||
@ -7,7 +7,7 @@ import URILink from './URILink';
|
||||
import ContextMenuTrigger from './ContextMenu/ContextMenuTrigger';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import { isTouchDevice } from '../util/helpers';
|
||||
import { I18n, i18n } from '../locale';
|
||||
import { I18n } from '../locale';
|
||||
import AddedFrom from './AddedFrom';
|
||||
|
||||
const MiddleColumn = ({
|
||||
@ -181,8 +181,12 @@ const Track = ({
|
||||
{track_middle_column}
|
||||
<div className="list__item__column list__item__column--right">
|
||||
{drag_zone}
|
||||
{item.is_explicit && <span className="flag flag--dark">{i18n('track.explicit').toUpperCase()}</span>}
|
||||
{item.is_playable === false && <span className="flag flag--dark">{i18n('track.unplayable').toUpperCase()}</span>}
|
||||
{item.is_explicit && (
|
||||
<span className="flag flag--dark"><I18n path="track.explicit" /></span>
|
||||
)}
|
||||
{item.is_playable === false && (
|
||||
<span className="flag flag--dark"><I18n path="track.unplayable" /></span>
|
||||
)}
|
||||
{(context ?.type === 'album' || context ?.type === 'artist') && item.track_number && (
|
||||
<span className="mid_grey-text list__item__column__item list__item__column__item--track-number">
|
||||
<span>
|
||||
|
||||
@ -6,7 +6,6 @@ import { PersistGate } from 'redux-persist/integration/react';
|
||||
|
||||
import { store, persistor } from './store/index';
|
||||
import App from './App';
|
||||
import { _ } from 'core-js';
|
||||
|
||||
require('../scss/app.scss');
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import get from 'lodash/get';
|
||||
import dictionaries from './dictionaries';
|
||||
import { titleCase } from '../util/helpers';
|
||||
@ -7,8 +8,8 @@ const PARAMS_REG_EXP = '%{(.*?)}';
|
||||
const paramsRegExp = new RegExp(PARAMS_REG_EXP, 'g');
|
||||
const languagesAvailable = dictionaries.available;
|
||||
|
||||
const i18n = (path, params = {}, transform) => {
|
||||
const dictionary = dictionaries[window.language || 'en'] || dictionaries.en;
|
||||
const translate = (path, params = {}, transform, language) => {
|
||||
const dictionary = dictionaries[language || window.language || 'en'] || dictionaries.en;
|
||||
|
||||
let value = get((dictionary), path, '');
|
||||
value = value.replace(
|
||||
@ -40,18 +41,26 @@ const I18n = ({
|
||||
contentAfter,
|
||||
...params
|
||||
}) => {
|
||||
const language = useSelector((state) => state.ui.language);
|
||||
if (window.language !== language) window.language = language;
|
||||
|
||||
if (!children) {
|
||||
return i18n(path, params, transform);
|
||||
return translate(path, params, transform, language);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{!contentAfter && children}
|
||||
{i18n(path, params, transform)}
|
||||
{translate(path, params, transform, language)}
|
||||
{contentAfter && children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Prefer usage of I18n *component*, but sometimes you need a raw string, rather than an Object.
|
||||
// CAUTION: Directly calling i18n relies on window.language already being populated
|
||||
// TODO: Upgrade as much usage of i18n() to <I18n /> as possible.
|
||||
const i18n = translate;
|
||||
|
||||
export default {
|
||||
I18n,
|
||||
i18n,
|
||||
|
||||
@ -60,7 +60,7 @@ let initialState = {
|
||||
mopidy: {
|
||||
connected: false,
|
||||
host: window.location.hostname,
|
||||
port: (window.location.port ? window.location.port : (window.location.protocol === 'https:' ? '443' : '80')),
|
||||
port: window.location.port || (window.location.protocol === 'https:' ? '443' : '80'),
|
||||
ssl: window.location.protocol === 'https:',
|
||||
current_server: 'default',
|
||||
servers: {
|
||||
@ -68,7 +68,7 @@ let initialState = {
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
host: window.location.hostname,
|
||||
port: (window.location.port ? window.location.port : (window.location.protocol === 'https:' ? '443' : '80')),
|
||||
port: window.location.port || (window.location.protocol === 'https:' ? '443' : '80'),
|
||||
ssl: window.location.protocol === 'https:',
|
||||
},
|
||||
},
|
||||
@ -248,4 +248,4 @@ const store = createStore(
|
||||
const persistor = persistStore(store);
|
||||
|
||||
export default { store, persistor };
|
||||
export { store, persistor };
|
||||
export { store, persistor, initialState };
|
||||
|
||||
@ -27,6 +27,7 @@ import { arrayOf, indexToArray } from '../../util/arrays';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import Button from '../../components/Button';
|
||||
import { decodeUri, encodeUri } from '../../util/format';
|
||||
import { withRouter } from '../../util';
|
||||
|
||||
class Recommendations extends React.Component {
|
||||
constructor(props) {
|
||||
@ -158,11 +159,9 @@ class Recommendations extends React.Component {
|
||||
uiActions: {
|
||||
setWindowTitle,
|
||||
},
|
||||
match: {
|
||||
params: {
|
||||
uri,
|
||||
},
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
setWindowTitle(i18n('discover.recommendations.title'));
|
||||
@ -173,18 +172,14 @@ class Recommendations extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate = ({
|
||||
match: {
|
||||
params: {
|
||||
uri: prevUri,
|
||||
},
|
||||
},
|
||||
}) => {
|
||||
const {
|
||||
match: {
|
||||
params: {
|
||||
uri,
|
||||
},
|
||||
},
|
||||
} = this.props;
|
||||
if (prevUri !== uri) this.handleURLSeeds();
|
||||
}
|
||||
@ -220,11 +215,9 @@ class Recommendations extends React.Component {
|
||||
coreActions: {
|
||||
loadUris,
|
||||
},
|
||||
match: {
|
||||
params: {
|
||||
uri: uriProp,
|
||||
},
|
||||
},
|
||||
} = this.props;
|
||||
const uri = decodeUri(uriProp);
|
||||
if (!uri) return;
|
||||
@ -600,4 +593,4 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
coreActions: bindActionCreators(coreActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Recommendations);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Recommendations));
|
||||
|
||||
@ -163,7 +163,7 @@ class Artists extends React.Component {
|
||||
const view_options = [
|
||||
{
|
||||
value: 'thumbnails',
|
||||
label: i18n('fields.filters.thumbnails'),
|
||||
label: <I18n path="fields.filters.thumbnails" />,
|
||||
},
|
||||
{
|
||||
value: 'list',
|
||||
|
||||
@ -18,7 +18,7 @@ import * as spotifyActions from '../services/spotify/actions';
|
||||
import { isHosted } from '../util/helpers';
|
||||
import { i18n, I18n, languagesAvailable } from '../locale';
|
||||
import Button from '../components/Button';
|
||||
import { dater } from '../components/Dater';
|
||||
import Dater, { dater } from '../components/Dater';
|
||||
|
||||
const CheckboxSetting = ({
|
||||
name,
|
||||
@ -446,7 +446,8 @@ class Settings extends React.Component {
|
||||
{window.build}
|
||||
{window.build && (
|
||||
<span className="tooltip__content">
|
||||
{`${dater('ago', parseInt(window.build, 10) * 1000)} ago`}
|
||||
<Dater data={parseInt(window.build, 10) * 1000} type="ago" />
|
||||
{' ago'}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user