mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 05:09:43 +02:00
parent
a1cb04ea8e
commit
1329da3fe5
31 changed files with 277 additions and 40 deletions
|
@ -41,6 +41,13 @@ export const handleUserUpdate = (user) => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const updateCurrentUserLanguage = (language) => ({
|
||||||
|
type: EntryActionTypes.CURRENT_USER_LANGUAGE_UPDATE,
|
||||||
|
payload: {
|
||||||
|
language,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const updateUserEmail = (id, data) => ({
|
export const updateUserEmail = (id, data) => ({
|
||||||
type: EntryActionTypes.USER_EMAIL_UPDATE,
|
type: EntryActionTypes.USER_EMAIL_UPDATE,
|
||||||
payload: {
|
payload: {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Divider, Header, Tab } from 'semantic-ui-react';
|
import { Button, Divider, Dropdown, Header, Tab } from 'semantic-ui-react';
|
||||||
|
|
||||||
|
import locales from '../../../locales';
|
||||||
import AvatarEditPopup from './AvatarEditPopup';
|
import AvatarEditPopup from './AvatarEditPopup';
|
||||||
import User from '../../User';
|
import User from '../../User';
|
||||||
import UserInformationEdit from '../../UserInformationEdit';
|
import UserInformationEdit from '../../UserInformationEdit';
|
||||||
|
@ -20,12 +21,14 @@ const AccountPane = React.memo(
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
phone,
|
phone,
|
||||||
organization,
|
organization,
|
||||||
|
language,
|
||||||
isAvatarUpdating,
|
isAvatarUpdating,
|
||||||
usernameUpdateForm,
|
usernameUpdateForm,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
passwordUpdateForm,
|
passwordUpdateForm,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onAvatarUpdate,
|
onAvatarUpdate,
|
||||||
|
onLanguageUpdate,
|
||||||
onUsernameUpdate,
|
onUsernameUpdate,
|
||||||
onUsernameUpdateMessageDismiss,
|
onUsernameUpdateMessageDismiss,
|
||||||
onEmailUpdate,
|
onEmailUpdate,
|
||||||
|
@ -41,6 +44,13 @@ const AccountPane = React.memo(
|
||||||
});
|
});
|
||||||
}, [onUpdate]);
|
}, [onUpdate]);
|
||||||
|
|
||||||
|
const handleLanguageChange = useCallback(
|
||||||
|
(_, { value }) => {
|
||||||
|
onLanguageUpdate(value === 'auto' ? null : value); // FIXME: hack
|
||||||
|
},
|
||||||
|
[onLanguageUpdate],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab.Pane attached={false} className={styles.wrapper}>
|
<Tab.Pane attached={false} className={styles.wrapper}>
|
||||||
<AvatarEditPopup
|
<AvatarEditPopup
|
||||||
|
@ -60,6 +70,32 @@ const AccountPane = React.memo(
|
||||||
}}
|
}}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
/>
|
/>
|
||||||
|
<Divider horizontal section>
|
||||||
|
<Header as="h4">
|
||||||
|
{t('common.language', {
|
||||||
|
context: 'title',
|
||||||
|
})}
|
||||||
|
</Header>
|
||||||
|
</Divider>
|
||||||
|
<Dropdown
|
||||||
|
fluid
|
||||||
|
selection
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
key: 'auto',
|
||||||
|
value: 'auto',
|
||||||
|
text: t('common.detectAutomatically'),
|
||||||
|
},
|
||||||
|
...locales.map((locale) => ({
|
||||||
|
key: locale.language,
|
||||||
|
value: locale.language,
|
||||||
|
flag: locale.country,
|
||||||
|
text: locale.name,
|
||||||
|
})),
|
||||||
|
]}
|
||||||
|
value={language || 'auto'}
|
||||||
|
onChange={handleLanguageChange}
|
||||||
|
/>
|
||||||
<Divider horizontal section>
|
<Divider horizontal section>
|
||||||
<Header as="h4">
|
<Header as="h4">
|
||||||
{t('common.authentication', {
|
{t('common.authentication', {
|
||||||
|
@ -129,6 +165,7 @@ AccountPane.propTypes = {
|
||||||
avatarUrl: PropTypes.string,
|
avatarUrl: PropTypes.string,
|
||||||
phone: PropTypes.string,
|
phone: PropTypes.string,
|
||||||
organization: PropTypes.string,
|
organization: PropTypes.string,
|
||||||
|
language: PropTypes.string,
|
||||||
isAvatarUpdating: PropTypes.bool.isRequired,
|
isAvatarUpdating: PropTypes.bool.isRequired,
|
||||||
/* eslint-disable react/forbid-prop-types */
|
/* eslint-disable react/forbid-prop-types */
|
||||||
usernameUpdateForm: PropTypes.object.isRequired,
|
usernameUpdateForm: PropTypes.object.isRequired,
|
||||||
|
@ -137,6 +174,7 @@ AccountPane.propTypes = {
|
||||||
/* eslint-enable react/forbid-prop-types */
|
/* eslint-enable react/forbid-prop-types */
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
onAvatarUpdate: PropTypes.func.isRequired,
|
onAvatarUpdate: PropTypes.func.isRequired,
|
||||||
|
onLanguageUpdate: PropTypes.func.isRequired,
|
||||||
onUsernameUpdate: PropTypes.func.isRequired,
|
onUsernameUpdate: PropTypes.func.isRequired,
|
||||||
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
|
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
|
||||||
onEmailUpdate: PropTypes.func.isRequired,
|
onEmailUpdate: PropTypes.func.isRequired,
|
||||||
|
@ -150,6 +188,7 @@ AccountPane.defaultProps = {
|
||||||
avatarUrl: undefined,
|
avatarUrl: undefined,
|
||||||
phone: undefined,
|
phone: undefined,
|
||||||
organization: undefined,
|
organization: undefined,
|
||||||
|
language: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AccountPane;
|
export default AccountPane;
|
||||||
|
|
|
@ -14,6 +14,7 @@ const UserSettingsModal = React.memo(
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
phone,
|
phone,
|
||||||
organization,
|
organization,
|
||||||
|
language,
|
||||||
subscribeToOwnCards,
|
subscribeToOwnCards,
|
||||||
isAvatarUpdating,
|
isAvatarUpdating,
|
||||||
usernameUpdateForm,
|
usernameUpdateForm,
|
||||||
|
@ -21,6 +22,7 @@ const UserSettingsModal = React.memo(
|
||||||
passwordUpdateForm,
|
passwordUpdateForm,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onAvatarUpdate,
|
onAvatarUpdate,
|
||||||
|
onLanguageUpdate,
|
||||||
onUsernameUpdate,
|
onUsernameUpdate,
|
||||||
onUsernameUpdateMessageDismiss,
|
onUsernameUpdateMessageDismiss,
|
||||||
onEmailUpdate,
|
onEmailUpdate,
|
||||||
|
@ -44,12 +46,14 @@ const UserSettingsModal = React.memo(
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
phone={phone}
|
phone={phone}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
|
language={language}
|
||||||
isAvatarUpdating={isAvatarUpdating}
|
isAvatarUpdating={isAvatarUpdating}
|
||||||
usernameUpdateForm={usernameUpdateForm}
|
usernameUpdateForm={usernameUpdateForm}
|
||||||
emailUpdateForm={emailUpdateForm}
|
emailUpdateForm={emailUpdateForm}
|
||||||
passwordUpdateForm={passwordUpdateForm}
|
passwordUpdateForm={passwordUpdateForm}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
onAvatarUpdate={onAvatarUpdate}
|
onAvatarUpdate={onAvatarUpdate}
|
||||||
|
onLanguageUpdate={onLanguageUpdate}
|
||||||
onUsernameUpdate={onUsernameUpdate}
|
onUsernameUpdate={onUsernameUpdate}
|
||||||
onUsernameUpdateMessageDismiss={onUsernameUpdateMessageDismiss}
|
onUsernameUpdateMessageDismiss={onUsernameUpdateMessageDismiss}
|
||||||
onEmailUpdate={onEmailUpdate}
|
onEmailUpdate={onEmailUpdate}
|
||||||
|
@ -92,6 +96,7 @@ UserSettingsModal.propTypes = {
|
||||||
avatarUrl: PropTypes.string,
|
avatarUrl: PropTypes.string,
|
||||||
phone: PropTypes.string,
|
phone: PropTypes.string,
|
||||||
organization: PropTypes.string,
|
organization: PropTypes.string,
|
||||||
|
language: PropTypes.string,
|
||||||
subscribeToOwnCards: PropTypes.bool.isRequired,
|
subscribeToOwnCards: PropTypes.bool.isRequired,
|
||||||
isAvatarUpdating: PropTypes.bool.isRequired,
|
isAvatarUpdating: PropTypes.bool.isRequired,
|
||||||
/* eslint-disable react/forbid-prop-types */
|
/* eslint-disable react/forbid-prop-types */
|
||||||
|
@ -101,6 +106,7 @@ UserSettingsModal.propTypes = {
|
||||||
/* eslint-enable react/forbid-prop-types */
|
/* eslint-enable react/forbid-prop-types */
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
onAvatarUpdate: PropTypes.func.isRequired,
|
onAvatarUpdate: PropTypes.func.isRequired,
|
||||||
|
onLanguageUpdate: PropTypes.func.isRequired,
|
||||||
onUsernameUpdate: PropTypes.func.isRequired,
|
onUsernameUpdate: PropTypes.func.isRequired,
|
||||||
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
|
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
|
||||||
onEmailUpdate: PropTypes.func.isRequired,
|
onEmailUpdate: PropTypes.func.isRequired,
|
||||||
|
@ -115,6 +121,7 @@ UserSettingsModal.defaultProps = {
|
||||||
avatarUrl: undefined,
|
avatarUrl: undefined,
|
||||||
phone: undefined,
|
phone: undefined,
|
||||||
organization: undefined,
|
organization: undefined,
|
||||||
|
language: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserSettingsModal;
|
export default UserSettingsModal;
|
||||||
|
|
|
@ -31,6 +31,7 @@ export default {
|
||||||
USER_UPDATE: `${PREFIX}/USER_UPDATE`,
|
USER_UPDATE: `${PREFIX}/USER_UPDATE`,
|
||||||
CURRENT_USER_UPDATE: `${PREFIX}/CURRENT_USER_UPDATE`,
|
CURRENT_USER_UPDATE: `${PREFIX}/CURRENT_USER_UPDATE`,
|
||||||
USER_UPDATE_HANDLE: `${PREFIX}/USER_UPDATE_HANDLE`,
|
USER_UPDATE_HANDLE: `${PREFIX}/USER_UPDATE_HANDLE`,
|
||||||
|
CURRENT_USER_LANGUAGE_UPDATE: `${PREFIX}/CURRENT_USER_LANGUAGE_UPDATE`,
|
||||||
USER_EMAIL_UPDATE: `${PREFIX}/USER_EMAIL_UPDATE`,
|
USER_EMAIL_UPDATE: `${PREFIX}/USER_EMAIL_UPDATE`,
|
||||||
CURRENT_USER_EMAIL_UPDATE: `${PREFIX}/CURRENT_USER_EMAIL_UPDATE`,
|
CURRENT_USER_EMAIL_UPDATE: `${PREFIX}/CURRENT_USER_EMAIL_UPDATE`,
|
||||||
USER_EMAIL_UPDATE_ERROR_CLEAR: `${PREFIX}/USER_EMAIL_UPDATE_ERROR_CLEAR`,
|
USER_EMAIL_UPDATE_ERROR_CLEAR: `${PREFIX}/USER_EMAIL_UPDATE_ERROR_CLEAR`,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
updateCurrentUser,
|
updateCurrentUser,
|
||||||
updateCurrentUserAvatar,
|
updateCurrentUserAvatar,
|
||||||
updateCurrentUserEmail,
|
updateCurrentUserEmail,
|
||||||
|
updateCurrentUserLanguage,
|
||||||
updateCurrentUserPassword,
|
updateCurrentUserPassword,
|
||||||
updateCurrentUserUsername,
|
updateCurrentUserUsername,
|
||||||
} from '../actions/entry';
|
} from '../actions/entry';
|
||||||
|
@ -23,6 +24,7 @@ const mapStateToProps = (state) => {
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
phone,
|
phone,
|
||||||
organization,
|
organization,
|
||||||
|
language,
|
||||||
subscribeToOwnCards,
|
subscribeToOwnCards,
|
||||||
isAvatarUpdating,
|
isAvatarUpdating,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
|
@ -37,6 +39,7 @@ const mapStateToProps = (state) => {
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
phone,
|
phone,
|
||||||
organization,
|
organization,
|
||||||
|
language,
|
||||||
subscribeToOwnCards,
|
subscribeToOwnCards,
|
||||||
isAvatarUpdating,
|
isAvatarUpdating,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
|
@ -50,6 +53,7 @@ const mapDispatchToProps = (dispatch) =>
|
||||||
{
|
{
|
||||||
onUpdate: updateCurrentUser,
|
onUpdate: updateCurrentUser,
|
||||||
onAvatarUpdate: updateCurrentUserAvatar,
|
onAvatarUpdate: updateCurrentUserAvatar,
|
||||||
|
onLanguageUpdate: updateCurrentUserLanguage,
|
||||||
onUsernameUpdate: updateCurrentUserUsername,
|
onUsernameUpdate: updateCurrentUserUsername,
|
||||||
onUsernameUpdateMessageDismiss: clearCurrentUserUsernameUpdateError,
|
onUsernameUpdateMessageDismiss: clearCurrentUserUsernameUpdateError,
|
||||||
onEmailUpdate: updateCurrentUserEmail,
|
onEmailUpdate: updateCurrentUserEmail,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import languageDetector from 'i18next-browser-languagedetector';
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
import formatDate from 'date-fns/format';
|
import formatDate from 'date-fns/format';
|
||||||
import parseDate from 'date-fns/parse';
|
import parseDate from 'date-fns/parse';
|
||||||
import { registerLocale, setDefaultLocale } from 'react-datepicker';
|
import { registerLocale, setDefaultLocale } from 'react-datepicker';
|
||||||
|
|
||||||
import { embedLocales, languages } from './locales';
|
import { embeddedLocales, languages } from './locales';
|
||||||
|
|
||||||
i18n.dateFns = {
|
i18n.dateFns = {
|
||||||
locales: {},
|
locales: {},
|
||||||
|
@ -52,12 +52,12 @@ const parseDatePostProcessor = {
|
||||||
};
|
};
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(languageDetector)
|
.use(LanguageDetector)
|
||||||
.use(formatDatePostProcessor)
|
.use(formatDatePostProcessor)
|
||||||
.use(parseDatePostProcessor)
|
.use(parseDatePostProcessor)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources: embedLocales,
|
resources: embeddedLocales,
|
||||||
fallbackLng: 'en',
|
fallbackLng: 'en',
|
||||||
supportedLngs: languages,
|
supportedLngs: languages,
|
||||||
load: 'languageOnly',
|
load: 'languageOnly',
|
||||||
|
@ -74,7 +74,7 @@ i18n
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
react: {
|
react: {
|
||||||
useSuspense: false,
|
useSuspense: true,
|
||||||
},
|
},
|
||||||
debug: process.env.NODE_ENV !== 'production',
|
debug: process.env.NODE_ENV !== 'production',
|
||||||
});
|
});
|
||||||
|
@ -95,4 +95,20 @@ i18n.loadCoreLocale = async (language = i18n.resolvedLanguage) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
i18n.detectLanguage = () => {
|
||||||
|
const {
|
||||||
|
services: { languageDetector, languageUtils },
|
||||||
|
} = i18n;
|
||||||
|
|
||||||
|
localStorage.removeItem(languageDetector.options.lookupLocalStorage);
|
||||||
|
|
||||||
|
const detectedLanguages = languageDetector.detect();
|
||||||
|
|
||||||
|
i18n.language = languageUtils.getBestMatchFromCodes(detectedLanguages);
|
||||||
|
i18n.languages = languageUtils.toResolveHierarchy(i18n.language);
|
||||||
|
|
||||||
|
i18n.resolvedLanguage = undefined;
|
||||||
|
i18n.setResolvedLanguage(i18n.language);
|
||||||
|
};
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|
8
client/src/locales/cs/index.js
Normal file
8
client/src/locales/cs/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'cs',
|
||||||
|
country: 'cz',
|
||||||
|
name: 'Čeština',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
8
client/src/locales/da/index.js
Normal file
8
client/src/locales/da/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'da',
|
||||||
|
country: 'dk',
|
||||||
|
name: 'Dansk',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
|
@ -72,6 +72,7 @@ export default {
|
||||||
deleteTask_title: 'Aufgabe löschen',
|
deleteTask_title: 'Aufgabe löschen',
|
||||||
deleteUser_title: 'Benutzer löschen',
|
deleteUser_title: 'Benutzer löschen',
|
||||||
description: 'Beschreibung',
|
description: 'Beschreibung',
|
||||||
|
detectAutomatically: 'Automatische Erkennung',
|
||||||
dropFileToUpload: 'Datei ablegen, um hochzuladen',
|
dropFileToUpload: 'Datei ablegen, um hochzuladen',
|
||||||
editAttachment_title: 'Anhang bearbieten',
|
editAttachment_title: 'Anhang bearbieten',
|
||||||
editAvatar_title: 'Avatar bearbeiten',
|
editAvatar_title: 'Avatar bearbeiten',
|
||||||
|
@ -97,6 +98,7 @@ export default {
|
||||||
hours: 'Stunden',
|
hours: 'Stunden',
|
||||||
invalidCurrentPassword: 'Das aktuelle Passwort ist falsch',
|
invalidCurrentPassword: 'Das aktuelle Passwort ist falsch',
|
||||||
labels: 'Labels',
|
labels: 'Labels',
|
||||||
|
language: 'Sprache',
|
||||||
leaveBoard_title: 'Board verlassen',
|
leaveBoard_title: 'Board verlassen',
|
||||||
leaveProject_title: 'Projekt verlassen',
|
leaveProject_title: 'Projekt verlassen',
|
||||||
list: 'Listen',
|
list: 'Listen',
|
||||||
|
|
8
client/src/locales/de/index.js
Normal file
8
client/src/locales/de/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'de',
|
||||||
|
country: 'de',
|
||||||
|
name: 'Deutsch',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
|
@ -64,6 +64,7 @@ export default {
|
||||||
deleteTask_title: 'Delete Task',
|
deleteTask_title: 'Delete Task',
|
||||||
deleteUser_title: 'Delete User',
|
deleteUser_title: 'Delete User',
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
|
detectAutomatically: 'Detect automatically',
|
||||||
dropFileToUpload: 'Drop file to upload',
|
dropFileToUpload: 'Drop file to upload',
|
||||||
editAttachment_title: 'Edit Attachment',
|
editAttachment_title: 'Edit Attachment',
|
||||||
editAvatar_title: 'Edit Avatar',
|
editAvatar_title: 'Edit Avatar',
|
||||||
|
@ -90,6 +91,7 @@ export default {
|
||||||
hours: 'Hours',
|
hours: 'Hours',
|
||||||
invalidCurrentPassword: 'Invalid current password',
|
invalidCurrentPassword: 'Invalid current password',
|
||||||
labels: 'Labels',
|
labels: 'Labels',
|
||||||
|
language: 'Language',
|
||||||
leaveBoard_title: 'Leave Board',
|
leaveBoard_title: 'Leave Board',
|
||||||
leaveProject_title: 'Leave Project',
|
leaveProject_title: 'Leave Project',
|
||||||
list: 'List',
|
list: 'List',
|
||||||
|
|
11
client/src/locales/en/index.js
Normal file
11
client/src/locales/en/index.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import merge from 'lodash/merge';
|
||||||
|
|
||||||
|
import login from './login';
|
||||||
|
import core from './core';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'en',
|
||||||
|
country: 'us',
|
||||||
|
name: 'English',
|
||||||
|
embeddedLocale: merge(login, core),
|
||||||
|
};
|
8
client/src/locales/es/index.js
Normal file
8
client/src/locales/es/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'es',
|
||||||
|
country: 'es',
|
||||||
|
name: 'Español',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
8
client/src/locales/fr/index.js
Normal file
8
client/src/locales/fr/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'fr',
|
||||||
|
country: 'fr',
|
||||||
|
name: 'Français',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
|
@ -1,37 +1,27 @@
|
||||||
import merge from 'lodash/merge';
|
import cs from './cs';
|
||||||
import fromPairs from 'lodash/fromPairs';
|
import da from './da';
|
||||||
|
import de from './de';
|
||||||
|
import en from './en';
|
||||||
|
import es from './es';
|
||||||
|
import fr from './fr';
|
||||||
|
import ja from './ja';
|
||||||
|
import pl from './pl';
|
||||||
|
import ru from './ru';
|
||||||
|
import sk from './sk';
|
||||||
|
import sv from './sv';
|
||||||
|
import uz from './uz';
|
||||||
|
import zh from './zh';
|
||||||
|
|
||||||
import csLogin from './cs/login';
|
const locales = [cs, da, de, en, es, fr, ja, pl, ru, sk, sv, uz, zh];
|
||||||
import daLogin from './da/login';
|
|
||||||
import deLogin from './de/login';
|
|
||||||
import enLogin from './en/login';
|
|
||||||
import enCore from './en/core';
|
|
||||||
import esLogin from './es/login';
|
|
||||||
import frLogin from './fr/login';
|
|
||||||
import jaLogin from './ja/login';
|
|
||||||
import plLogin from './pl/login';
|
|
||||||
import ruLogin from './ru/login';
|
|
||||||
import skLogin from './sk/login';
|
|
||||||
import svLogin from './sv/login';
|
|
||||||
import uzLogin from './uz/login';
|
|
||||||
import zhLogin from './zh/login';
|
|
||||||
|
|
||||||
const localePairs = [
|
export default locales;
|
||||||
['cs', csLogin],
|
|
||||||
['da', daLogin],
|
|
||||||
['de', deLogin],
|
|
||||||
['en', merge(enLogin, enCore)],
|
|
||||||
['es', esLogin],
|
|
||||||
['fr', frLogin],
|
|
||||||
['ja', jaLogin],
|
|
||||||
['pl', plLogin],
|
|
||||||
['ru', ruLogin],
|
|
||||||
['sk', skLogin],
|
|
||||||
['sv', svLogin],
|
|
||||||
['uz', uzLogin],
|
|
||||||
['zh', zhLogin],
|
|
||||||
];
|
|
||||||
|
|
||||||
export const languages = localePairs.map((locale) => locale[0]);
|
export const languages = locales.map((locale) => locale.language);
|
||||||
|
|
||||||
export const embedLocales = fromPairs(localePairs);
|
export const embeddedLocales = locales.reduce(
|
||||||
|
(result, locale) => ({
|
||||||
|
...result,
|
||||||
|
[locale.language]: locale.embeddedLocale,
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
8
client/src/locales/ja/index.js
Normal file
8
client/src/locales/ja/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'ja',
|
||||||
|
country: 'jp',
|
||||||
|
name: '日本語',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
8
client/src/locales/pl/index.js
Normal file
8
client/src/locales/pl/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'pl',
|
||||||
|
country: 'pl',
|
||||||
|
name: 'Polski',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
|
@ -62,6 +62,7 @@ export default {
|
||||||
deleteTask: 'Удаление задачи',
|
deleteTask: 'Удаление задачи',
|
||||||
deleteUser: 'Удаление пользователя',
|
deleteUser: 'Удаление пользователя',
|
||||||
description: 'Описание',
|
description: 'Описание',
|
||||||
|
detectAutomatically: 'Определить автоматически',
|
||||||
dropFileToUpload: 'Перетяните файл, чтобы загрузить',
|
dropFileToUpload: 'Перетяните файл, чтобы загрузить',
|
||||||
editAttachment: 'Изменение вложения',
|
editAttachment: 'Изменение вложения',
|
||||||
editAvatar: 'Изменение аватара',
|
editAvatar: 'Изменение аватара',
|
||||||
|
@ -88,6 +89,7 @@ export default {
|
||||||
hours: 'Часы',
|
hours: 'Часы',
|
||||||
invalidCurrentPassword: 'Неверный текущий пароль',
|
invalidCurrentPassword: 'Неверный текущий пароль',
|
||||||
labels: 'Метки',
|
labels: 'Метки',
|
||||||
|
language: 'Язык',
|
||||||
list: 'Список',
|
list: 'Список',
|
||||||
listActions: 'Действия со списком',
|
listActions: 'Действия со списком',
|
||||||
members: 'Участники',
|
members: 'Участники',
|
||||||
|
|
8
client/src/locales/ru/index.js
Normal file
8
client/src/locales/ru/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'ru',
|
||||||
|
country: 'ru',
|
||||||
|
name: 'Русский',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
8
client/src/locales/sk/index.js
Normal file
8
client/src/locales/sk/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'sk',
|
||||||
|
country: 'sk',
|
||||||
|
name: 'Slovenčina',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
8
client/src/locales/sv/index.js
Normal file
8
client/src/locales/sv/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'sv',
|
||||||
|
country: 'se',
|
||||||
|
name: 'Svenska',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
8
client/src/locales/uz/index.js
Normal file
8
client/src/locales/uz/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'uz',
|
||||||
|
country: 'uz',
|
||||||
|
name: "O'zbek",
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
8
client/src/locales/zh/index.js
Normal file
8
client/src/locales/zh/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import login from './login';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
language: 'zh',
|
||||||
|
country: 'cn',
|
||||||
|
name: '中文',
|
||||||
|
embeddedLocale: login,
|
||||||
|
};
|
|
@ -40,6 +40,7 @@ export default class extends Model {
|
||||||
avatarUrl: attr(),
|
avatarUrl: attr(),
|
||||||
phone: attr(),
|
phone: attr(),
|
||||||
organization: attr(),
|
organization: attr(),
|
||||||
|
language: attr(),
|
||||||
subscribeToOwnCards: attr(),
|
subscribeToOwnCards: attr(),
|
||||||
deletedAt: attr(),
|
deletedAt: attr(),
|
||||||
isAdmin: attr({
|
isAdmin: attr({
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { fetchCoreRequest } from '../requests';
|
||||||
import { initializeCore } from '../../../actions';
|
import { initializeCore } from '../../../actions';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
|
||||||
export function* initializeCoreService() {
|
export function* initializeCoreService() {
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
|
@ -25,6 +24,7 @@ export function* initializeCoreService() {
|
||||||
notifications,
|
notifications,
|
||||||
} = yield call(fetchCoreRequest); // TODO: handle error
|
} = yield call(fetchCoreRequest); // TODO: handle error
|
||||||
|
|
||||||
|
yield call(i18n.changeLanguage, user.language);
|
||||||
yield call(i18n.loadCoreLocale);
|
yield call(i18n.loadCoreLocale);
|
||||||
|
|
||||||
yield put(
|
yield put(
|
||||||
|
@ -48,3 +48,14 @@ export function* initializeCoreService() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* changeCoreLanguageService(language) {
|
||||||
|
if (language === null) {
|
||||||
|
yield call(i18n.detectLanguage);
|
||||||
|
yield call(i18n.loadCoreLocale);
|
||||||
|
yield call(i18n.changeLanguage, i18n.resolvedLanguage);
|
||||||
|
} else {
|
||||||
|
yield call(i18n.loadCoreLocale, language);
|
||||||
|
yield call(i18n.changeLanguage, language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { call, put, select } from 'redux-saga/effects';
|
import { call, put, select } from 'redux-saga/effects';
|
||||||
|
|
||||||
import { logoutService } from './login';
|
import { logoutService } from './login';
|
||||||
|
import { changeCoreLanguageService } from './core';
|
||||||
import request from '../request';
|
import request from '../request';
|
||||||
import { currentUserIdSelector, currentUserSelector, pathSelector } from '../../../selectors';
|
import { currentUserIdSelector, currentUserSelector, pathSelector } from '../../../selectors';
|
||||||
import {
|
import {
|
||||||
|
@ -81,6 +82,21 @@ export function* handleUserUpdateService(user) {
|
||||||
yield put(handleUserUpdate(user, users, isCurrent));
|
yield put(handleUserUpdate(user, users, isCurrent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add loading state
|
||||||
|
export function* updateUserLanguageService(id, language) {
|
||||||
|
yield call(changeCoreLanguageService, language);
|
||||||
|
|
||||||
|
yield call(updateUserService, id, {
|
||||||
|
language,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* updateCurrentUserLanguageService(language) {
|
||||||
|
const id = yield select(currentUserIdSelector);
|
||||||
|
|
||||||
|
yield call(updateUserLanguageService, id, language);
|
||||||
|
}
|
||||||
|
|
||||||
export function* updateUserEmailService(id, data) {
|
export function* updateUserEmailService(id, data) {
|
||||||
yield put(updateUserEmail(id, data));
|
yield put(updateUserEmail(id, data));
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
updateUserService,
|
updateUserService,
|
||||||
updateCurrentUserAvatarService,
|
updateCurrentUserAvatarService,
|
||||||
updateCurrentUserEmailService,
|
updateCurrentUserEmailService,
|
||||||
|
updateCurrentUserLanguageService,
|
||||||
updateCurrentUserPasswordService,
|
updateCurrentUserPasswordService,
|
||||||
updateCurrentUserService,
|
updateCurrentUserService,
|
||||||
updateCurrentUserUsernameService,
|
updateCurrentUserUsernameService,
|
||||||
|
@ -49,6 +50,9 @@ export default function* userWatchers() {
|
||||||
takeEvery(EntryActionTypes.USER_UPDATE_HANDLE, ({ payload: { user } }) =>
|
takeEvery(EntryActionTypes.USER_UPDATE_HANDLE, ({ payload: { user } }) =>
|
||||||
handleUserUpdateService(user),
|
handleUserUpdateService(user),
|
||||||
),
|
),
|
||||||
|
takeEvery(EntryActionTypes.CURRENT_USER_LANGUAGE_UPDATE, ({ payload: { language } }) =>
|
||||||
|
updateCurrentUserLanguageService(language),
|
||||||
|
),
|
||||||
takeEvery(EntryActionTypes.USER_EMAIL_UPDATE, ({ payload: { id, data } }) =>
|
takeEvery(EntryActionTypes.USER_EMAIL_UPDATE, ({ payload: { id, data } }) =>
|
||||||
updateUserEmailService(id, data),
|
updateUserEmailService(id, data),
|
||||||
),
|
),
|
||||||
|
|
|
@ -40,6 +40,11 @@ module.exports = {
|
||||||
isNotEmptyString: true,
|
isNotEmptyString: true,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
|
language: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
subscribeToOwnCards: {
|
subscribeToOwnCards: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
|
@ -62,6 +67,7 @@ module.exports = {
|
||||||
'username',
|
'username',
|
||||||
'phone',
|
'phone',
|
||||||
'organization',
|
'organization',
|
||||||
|
'language',
|
||||||
'subscribeToOwnCards',
|
'subscribeToOwnCards',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,11 @@ module.exports = {
|
||||||
isNotEmptyString: true,
|
isNotEmptyString: true,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
|
language: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
subscribeToOwnCards: {
|
subscribeToOwnCards: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
|
@ -66,6 +71,7 @@ module.exports = {
|
||||||
'avatarUrl',
|
'avatarUrl',
|
||||||
'phone',
|
'phone',
|
||||||
'organization',
|
'organization',
|
||||||
|
'language',
|
||||||
'subscribeToOwnCards',
|
'subscribeToOwnCards',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,11 @@ module.exports = {
|
||||||
isNotEmptyString: true,
|
isNotEmptyString: true,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
|
language: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
subscribeToOwnCards: {
|
subscribeToOwnCards: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
defaultsTo: false,
|
defaultsTo: false,
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports.up = async (knex) =>
|
||||||
|
knex.schema.table('user_account', (table) => {
|
||||||
|
/* Columns */
|
||||||
|
|
||||||
|
table.text('language');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.down = async (knex) =>
|
||||||
|
knex.schema.table('user_account', (table) => {
|
||||||
|
table.dropColumn('language');
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue