Upgrading (where practical) to I18n component, rather than i18n function so we can useSelector for language pref

This commit is contained in:
James Barnsley
2022-09-29 10:39:19 +13:00
parent 6d6a2b4f24
commit 875072dea8
20 changed files with 105 additions and 13719 deletions

View File

@ -1 +1 @@
3.64.1
3.65.0

28
babel.config.js Executable file
View 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"
]
}
}
};

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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);

View File

@ -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>
);

View File

@ -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;
}

View File

@ -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}

View File

@ -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" />
}));
}

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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');

View File

@ -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,

View File

@ -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 };

View File

@ -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));

View File

@ -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',

View File

@ -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>