Fixes #3, ACCOUNTS-5CR, ACCOUNTS-5CD, ACCOUNTS-5DM, ACCOUNTS-5CV, ACCOUNTS-5GX, ACCOUNTS-5FK, ACCOUNTS-5EB. Handle cases when previously used language is missing. Add flow types for i18n related services and components.

This commit is contained in:
ErickSkrauch 2019-05-21 18:23:13 +03:00
parent b1dfcab7bb
commit b0a2923026
No known key found for this signature in database
GPG Key ID: 669339FCBB30EE0E
20 changed files with 113 additions and 44 deletions

View File

@ -1,26 +1,26 @@
// @flow
import i18n from 'services/i18n';
import captcha from 'services/captcha';
export const SET_LOCALE = 'i18n:setLocale';
export function setLocale(locale) {
return (dispatch) => i18n.require(
i18n.detectLanguage(locale)
).then(({locale, messages}) => {
dispatch(_setLocale({locale, messages}));
export function setLocale(desiredLocale: string) {
return async (dispatch: (action: Object) => any) => {
const { locale, messages } = await i18n.require(i18n.detectLanguage(desiredLocale));
dispatch(_setLocale(locale, messages));
// TODO: probably should be moved from here, because it is a side effect
captcha.setLang(locale);
return locale;
});
};
}
function _setLocale({locale, messages}) {
function _setLocale(locale: string, messages: { [string]: string }) {
return {
type: SET_LOCALE,
payload: {
locale,
messages
}
messages,
},
};
}

View File

@ -18,14 +18,14 @@ import type { LocalesMap } from './LanguageSwitcher';
const itemHeight = 51;
export default class LanguageList extends React.Component<{
userLang: string,
selectedLocale: string,
langs: LocalesMap,
onChangeLang: (lang: string) => void,
}> {
emptyListStateInner: ?HTMLDivElement;
render() {
const { userLang, langs } = this.props;
const { selectedLocale, langs } = this.props;
const isListEmpty = Object.keys(langs).length === 0;
const firstLocale = Object.keys(langs)[0] || null;
const emptyCaption = this.getEmptyCaption();
@ -67,7 +67,7 @@ export default class LanguageList extends React.Component<{
key={locale}
style={style}
className={classNames(styles.languageItem, {
[styles.activeLanguageItem]: locale === userLang,
[styles.activeLanguageItem]: locale === selectedLocale,
[styles.firstLanguageItem]: locale === firstLocale,
})}
onClick={this.onChangeLang(locale)}

View File

@ -11,7 +11,7 @@ import styles from './languageSwitcher.scss';
import messages from './languageSwitcher.intl.json';
import LanguageList from './LanguageList';
const translateUrl = 'https://translate.ely.by/project/elyby/invite';
const translateUrl = 'http://ely.by/translate';
export type LocaleData = {
code: string,
@ -25,7 +25,7 @@ export type LocalesMap = {[code: string]: LocaleData};
class LanguageSwitcher extends Component<{
onClose: Function,
userLang: string,
selectedLocale: string,
changeLang: (lang: string) => void,
langs: LocalesMap,
emptyCaptions: Array<{
@ -51,7 +51,7 @@ class LanguageSwitcher extends Component<{
};
render() {
const { userLang, onClose } = this.props;
const { selectedLocale, onClose } = this.props;
const { filteredLangs } = this.state;
return (
@ -80,7 +80,7 @@ class LanguageSwitcher extends Component<{
</div>
<LanguageList
userLang={userLang}
selectedLocale={selectedLocale}
langs={filteredLangs}
onChangeLang={this.onChangeLang}
/>
@ -157,7 +157,7 @@ import { connect } from 'react-redux';
import { changeLang } from 'components/user/actions';
export default connect((state) => ({
userLang: state.user.lang,
selectedLocale: state.i18n.locale,
}), {
changeLang,
})(LanguageSwitcher);

View File

@ -1,31 +1,43 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { create as createPopup } from 'components/ui/popup/actions';
import classNames from 'classnames';
import { localeFlags } from 'components/i18n';
import LANGS from 'i18n/index.json';
import LanguageSwitcher from '../LanguageSwitcher';
import styles from './link.scss';
function LanguageLink({
userLang,
interfaceLocale,
showLanguageSwitcherPopup,
}: {
userLang: string,
showLanguageSwitcherPopup: Function,
userLang: string;
interfaceLocale: string;
showLanguageSwitcherPopup: Function;
}) {
const localeDefinition = LANGS[userLang] || LANGS[interfaceLocale];
return (
<span className={styles.languageLink} onClick={showLanguageSwitcherPopup}>
<span
className={classNames(styles.languageLink, {
[styles.mark]: userLang !== interfaceLocale,
})}
onClick={showLanguageSwitcherPopup}
>
<span className={styles.languageIcon} style={{
backgroundImage: `url('${localeFlags.getIconUrl(userLang)}')`
backgroundImage: `url('${localeFlags.getIconUrl(localeDefinition.code)}')`,
}} />
{LANGS[userLang].name}
{localeDefinition.name}
</span>
);
}
import { connect } from 'react-redux';
import { create as createPopup } from 'components/ui/popup/actions';
import LanguageSwitcher from 'components/languageSwitcher';
export default connect((state) => ({
userLang: state.user.lang,
interfaceLocale: state.i18n.locale,
}), {
showLanguageSwitcherPopup: () => createPopup(LanguageSwitcher),
})(LanguageLink);

View File

@ -1,9 +1,23 @@
@import '~components/ui/colors.scss';
.languageLink {
position: relative;
color: #666;
border-bottom: 1px dotted #666;
text-decoration: none;
transition: .25s;
cursor: pointer;
&.mark {
&:after {
position: absolute;
content: '*';
color: $red;
font-size: 75%;
top: -0.20em;
}
}
}
.languageIcon {

View File

@ -12,6 +12,8 @@
"email": "Email:",
"password": "Password:",
"siteLanguage": "Site language:",
"languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"participateInTheTranslation": "participate in the translation",
"twoFactorAuth": "Twofactor auth:",
"uuid": "UUID:"
}

View File

@ -14,12 +14,13 @@ import RulesPage from 'pages/rules/RulesPage';
import type { User } from 'components/user';
class Profile extends Component<{
user: User
user: User;
interfaceLocale: string;
}> {
UUID: ?HTMLElement;
render() {
const { user } = this.props;
const { user, interfaceLocale } = this.props;
return (
<div>
@ -85,6 +86,16 @@ class Profile extends Component<{
<ProfileField
label={<Message {...messages.siteLanguage} />}
value={<ChangeLanguageLink />}
warningMessage={user.lang === interfaceLocale ? '' : (
<Message {...messages.languageIsUnavailableWarning} values={{
locale: user.lang,
participateInTheTranslation: (
<a href="http://ely.by/translate" target="_blank">
<Message {...messages.participateInTheTranslation} />
</a>
),
}}/>
)}
/>
<ProfileField
@ -139,8 +150,10 @@ class Profile extends Component<{
}
}
export default connect(({ user }): {
user: User,
export default connect(({ user, i18n }): {
user: User;
interfaceLocale: string;
} => ({
user,
interfaceLocale: i18n.locale,
}))(Profile);

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Не ўключана",
"components.profile.email": "Email:",
"components.profile.enabled": "Уключана",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Знойдзены акаўнт Mojang з такім жа нікам і, па {rules}, яго ўладальнік мае права патрабаваць аднаўленне кантролю над нікам.",
"components.profile.multiFactorAuth.codePlaceholder": "Увядзіце код сюды",
"components.profile.multiFactorAuth.disable": "Адключыць",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "Дадатак устаноўлены",
"components.profile.multiFactorAuth.whenKeyEntered": "Убачыўшы ў дадатку часовы код, перайдзіце да наступнага кроку.",
"components.profile.nickname": "Нік:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Пароль:",
"components.profile.passwordRequestForm.continue": "Працягнуць",
"components.profile.passwordRequestForm.description": "Каб завершыць, увядзіце пароль ад акаўнта",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Disabled",
"components.profile.email": "Email:",
"components.profile.enabled": "Enabled",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "A Mojang account with the same nickname was found. According to {rules}, account owner has the right to demand the restoration of control over nickname.",
"components.profile.multiFactorAuth.codePlaceholder": "Enter the code here",
"components.profile.multiFactorAuth.disable": "Disable",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "App has been installed",
"components.profile.multiFactorAuth.whenKeyEntered": "If a temporary code appears in your twofactor auth app, then you may proceed to the next step.",
"components.profile.nickname": "Nickname:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Password:",
"components.profile.passwordRequestForm.continue": "Continue",
"components.profile.passwordRequestForm.description": "To complete action enter the account password",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Désactivée",
"components.profile.email": "Email:",
"components.profile.enabled": "Activer",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Un compte Mojang avec le même pseudo a été trouvé. Selon les {rules}, le propriétaire du compte a le droit d'exiger la restauration du contrôle sur le surnom.",
"components.profile.multiFactorAuth.codePlaceholder": "Entrez le code ici",
"components.profile.multiFactorAuth.disable": "Désactiver",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "L'application a été installé",
"components.profile.multiFactorAuth.whenKeyEntered": "If a temporary code appears in your twofactor auth app, then you may proceed to the next step.",
"components.profile.nickname": "Surnom:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Mot de passe:",
"components.profile.passwordRequestForm.continue": "Continuer",
"components.profile.passwordRequestForm.description": "Pour compléter cette action, entrez le mot de passe du compte",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Dinonaktifkan",
"components.profile.email": "Email:",
"components.profile.enabled": "Diaktifkan",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Mojang akun dengan nickname yang sama di temukan. Sesuai dengan {rules}, pemilik akun dapat mengambil kontrol dengan nickname-nya.",
"components.profile.multiFactorAuth.codePlaceholder": "Masukkan kodenya disini",
"components.profile.multiFactorAuth.disable": "Nonaktifkan",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "Aplikasi telah diunduh",
"components.profile.multiFactorAuth.whenKeyEntered": "Jika kode sementara muncul di aplikasi otorisasi dua-faktor anda, anda boleh lanjut ke tahap berikutnya.",
"components.profile.nickname": "Nama panggilan:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Kata sandi:",
"components.profile.passwordRequestForm.continue": "Lanjut",
"components.profile.passwordRequestForm.description": "Untuk menyelesaikan, masukan kata sandi",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Išjungtas",
"components.profile.email": "El.Paštas:",
"components.profile.enabled": "Įjungtas",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Buvo rasta Mojang paskyra su tuo pačiu slapyvardžiu. Pagal {rules}, paskyros savininkas turi teisę reikalauti atkurti slapyvardžio kontrolę.",
"components.profile.multiFactorAuth.codePlaceholder": "Įveskite kodą čia",
"components.profile.multiFactorAuth.disable": "Išjungti",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "Programa įdiegta",
"components.profile.multiFactorAuth.whenKeyEntered": "Jei dviejų veiksmų programoje pasirodo laikinas kodas, galite pereiti prie kito žingsnio.",
"components.profile.nickname": "Slapyvardis:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Slaptažodis:",
"components.profile.passwordRequestForm.continue": "Tęsti",
"components.profile.passwordRequestForm.description": "Norėdami užbaigti veiksmą įveskite paskyros slaptažodį",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Wyłączone",
"components.profile.email": "Email:",
"components.profile.enabled": "Włączone",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Znaleziono konto Mojang z tym samym nickiem. Zgodnie z {rules} właściciel konta ma prawo żądać przywrócenia kontroli nad pseudonimem.",
"components.profile.multiFactorAuth.codePlaceholder": "Wprowadź tutaj kod",
"components.profile.multiFactorAuth.disable": "Wyłącz",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "Aplikacja została zainstalowana",
"components.profile.multiFactorAuth.whenKeyEntered": "Jeżeli kod tymczasowy pojawia się w twojej aplikacji uwierzytelniania dwupoziomowego, możesz przejść do następnego kroku.",
"components.profile.nickname": "Nick:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Hasło:",
"components.profile.passwordRequestForm.continue": "Kontynuuj",
"components.profile.passwordRequestForm.description": "Aby zakończyć działanie, wprowadź hasło konta",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Desativado",
"components.profile.email": "Email:",
"components.profile.enabled": "Habilitado",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Uma conta da Mojang com o mesmo nome foi encontrada. De acordo com as {rules}, o dono desta conta tem o poder de controlar este apelido.",
"components.profile.multiFactorAuth.codePlaceholder": "Digite o código aqui",
"components.profile.multiFactorAuth.disable": "Desabilitar",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "Aplicativo instalado",
"components.profile.multiFactorAuth.whenKeyEntered": "Se um código temporário aparece no seu app de aut. em dois fatores, então você pode prosseguir para a próxima etapa.",
"components.profile.nickname": "Apelido:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Senha:",
"components.profile.passwordRequestForm.continue": "Continuar",
"components.profile.passwordRequestForm.description": "Para completar a ação, coloque a senha da conta",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Не включена",
"components.profile.email": "Email:",
"components.profile.enabled": "Включена",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Найден аккаунт Mojang с таким же ником. Учтите, что согласно с {rules}, его владелец вправе потребовать восстановления контроля над ником.",
"components.profile.multiFactorAuth.codePlaceholder": "Введите сюда код",
"components.profile.multiFactorAuth.disable": "Отключить",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "Приложение установлено",
"components.profile.multiFactorAuth.whenKeyEntered": "После того, как вы увидели в приложении временный код, переходите к следующему шагу.",
"components.profile.nickname": "Ник:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Пароль:",
"components.profile.passwordRequestForm.continue": "Продолжить",
"components.profile.passwordRequestForm.description": "Для завершения, укажите пароль от аккаунта",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "онемогућена",
"components.profile.email": "Е-пошта:",
"components.profile.enabled": "омогућена",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Пронађен је Mojang налог са истим надимком. Према {rules}, власник налога има право да затражи враћање контроле над надимком.",
"components.profile.multiFactorAuth.codePlaceholder": "Овде унесите кôд",
"components.profile.multiFactorAuth.disable": "Онемогући",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "Апликација је инсталирана",
"components.profile.multiFactorAuth.whenKeyEntered": "Ако се привремени кôд појави у апликацији за потврду идентитета помоћу два фактора, можете прећи на следећи корак.",
"components.profile.nickname": "Надимак:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Лозинка:",
"components.profile.passwordRequestForm.continue": "Настави",
"components.profile.passwordRequestForm.description": "Да бисте довршили радњу, унесите лозинку налога",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Вимкнено",
"components.profile.email": "Email:",
"components.profile.enabled": "Увімкнена",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Знайдений акаунт Mojang з таким же ніком. Майте на увазі, що згідно з {rules}, його власник має право вимагати відновлення контролю над ніком.",
"components.profile.multiFactorAuth.codePlaceholder": "Вкажіть код сюди",
"components.profile.multiFactorAuth.disable": "Вимкнути",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "Додаток встановлено",
"components.profile.multiFactorAuth.whenKeyEntered": "Після того, як ви побачили в додатку тимчасовий код, перейдіть до наступного кроку.",
"components.profile.nickname": "Нік:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Пароль:",
"components.profile.passwordRequestForm.continue": "Продовжити",
"components.profile.passwordRequestForm.description": "Для завершення, вкажіть пароль від акаунту",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "Đã vô hiệu hóa",
"components.profile.email": "Email:",
"components.profile.enabled": "Đã kích hoạt",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "Một tài khoản Mojang với nickname này đã được tìm thấy. Theo {rules}, chủ của tài khoản có quyền được lấy lại nickname của mình.",
"components.profile.multiFactorAuth.codePlaceholder": "Nhập mã vào đây",
"components.profile.multiFactorAuth.disable": "Vô hiệu hoá",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "Ứng dụng đã được cài đặt",
"components.profile.multiFactorAuth.whenKeyEntered": "Nếu xuất hiện một mã tạm thời trong ứng dụng xác thực 2 bước thì bạn đã sẵn sàng để đến với bước kế tiếp.",
"components.profile.nickname": "Nickname:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "Mật khẩu:",
"components.profile.passwordRequestForm.continue": "Tiếp tục",
"components.profile.passwordRequestForm.description": "Để hoàn tất hành động hãy nhập mật khẩu tài khoản",

View File

@ -177,6 +177,7 @@
"components.profile.disabled": "禁用 ",
"components.profile.email": "Email",
"components.profile.enabled": "启用",
"components.profile.languageIsUnavailableWarning": "The locale \"{locale}\" you've used earlier isn't currently translated enough. If you want to continue using the selected language, please {participateInTheTranslation} of the project.",
"components.profile.mojangPriorityWarning": "找到一个具有相同昵称的Mojang帐户。根据{rules},账户拥有者有权要求恢复对昵称的控制权。",
"components.profile.multiFactorAuth.codePlaceholder": "在这里输入代码",
"components.profile.multiFactorAuth.disable": "禁用",
@ -198,6 +199,7 @@
"components.profile.multiFactorAuth.theAppIsInstalled": "应用程序已成功安装",
"components.profile.multiFactorAuth.whenKeyEntered": "如果临时代码出现在双重认证应用程序中,则可以继续下一步。",
"components.profile.nickname": "名称:",
"components.profile.participateInTheTranslation": "participate in the translation",
"components.profile.password": "密码:",
"components.profile.passwordRequestForm.continue": "继续",
"components.profile.passwordRequestForm.description": "要完成操作,请输入原帐户密码",

View File

@ -1,3 +1,4 @@
// @flow
import locales from 'i18n/index.json';
import { addLocaleData } from 'react-intl';
@ -7,37 +8,40 @@ const DEFAULT_LANGUAGE = 'en';
const needPolyfill = !window.Intl;
function getUserLanguages(userSelectedLang = []) {
return [].concat(userSelectedLang || [])
function getUserLanguages(userSelectedLang: string): Array<string> {
return [userSelectedLang]
.concat(navigator.languages || [])
.concat(navigator.language || []);
}
function detectLanguage(userLanguages, availableLanguages, defaultLanguage) {
return (userLanguages || [])
.concat(defaultLanguage)
function detectLanguage(
userLanguages: Array<string>,
availableLanguages: Array<string>,
defaultLanguage: string,
): string {
return userLanguages
.map((lang) => lang.split('-').shift().toLowerCase())
.find((lang) => availableLanguages.indexOf(lang) !== -1);
.find((lang) => availableLanguages.indexOf(lang) !== -1) || defaultLanguage;
}
export default {
detectLanguage(lang) {
detectLanguage(lang: string): string {
return detectLanguage(getUserLanguages(lang), SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE);
},
require(locale) {
const promises = [
require(locale: string): Promise<{
locale: string;
messages: { [string]: string };
}> {
const promises: Array<Promise<*>> = [
new Promise(require(`bundle?name=[name]!react-intl/locale-data/${locale}.js`)),
new Promise(require(`bundle?name=[name]!i18n/${locale}.json`))
];
if (needPolyfill) {
promises.push(
new Promise(require('bundle?name=intl!intl')),
);
promises.push(
new Promise(require(`bundle?name=[name]-polyfill-data!intl/locale-data/jsonp/${locale}.js`)),
);
// $FlowFixMe
promises.push(new Promise(require('bundle?name=intl!intl')));
promises.push(new Promise(require(`bundle?name=[name]-polyfill-data!intl/locale-data/jsonp/${locale}.js`)));
}
return Promise.all(promises)