Basic functionality for locale change. Draft implementation of tools for working with i18n

This commit is contained in:
SleepWalker 2016-05-08 22:28:51 +03:00
parent 8db3c36261
commit 4f5f18d787
18 changed files with 491 additions and 108 deletions

View File

@ -8,10 +8,11 @@
"license": "UNLICENSED",
"repository": "git@bitbucket.org:ErickSkrauch/ely.by-account.git",
"scripts": {
"start": "webpack-dev-server --progress --colors",
"start": "rm -rf dist/ && webpack-dev-server --progress --colors",
"up": "npm install",
"test": "karma start ./karma.conf.js",
"lint": "eslint ./src",
"i18n": "cd ./scripts && ./node_modules/.bin/babel-node i18n-collect.js",
"build": "rm -rf dist/ && webpack --progress --colors -p"
},
"dependencies": {
@ -44,6 +45,7 @@
"babel-preset-react-hmre": "^1.0.1",
"babel-preset-stage-0": "^6.3.13",
"babel-runtime": "^5.6.15",
"bundle-loader": "^0.5.4",
"chai": "^3.0.0",
"chokidar": "^1.2.0",
"css-loader": "^0.23.0",

3
scripts/.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"breakConfig": true
}

147
scripts/i18n-collect.js Normal file
View File

@ -0,0 +1,147 @@
import fs from 'fs';
import {sync as globSync} from 'glob';
import {sync as mkdirpSync} from 'mkdirp';
import chalk from 'chalk';
import prompt from 'prompt';
const MESSAGES_PATTERN = `../dist/messages/**/*.json`;
const LANG_DIR = `../src/i18n`;
const DEFAULT_LOCALE = 'en';
const SUPPORTED_LANGS = [DEFAULT_LOCALE].concat('ru');
/**
* Aggregates the default messages that were extracted from the app's
* React components via the React Intl Babel plugin. An error will be thrown if
* there are messages in different components that use the same `id`. The result
* is a flat collection of `id: message` pairs for the app's default locale.
*/
let idToFileMap = {};
let duplicateIds = [];
let defaultMessages = globSync(MESSAGES_PATTERN)
.map((filename) => [filename, JSON.parse(fs.readFileSync(filename, 'utf8'))])
.reduce((collection, [file, descriptors]) => {
descriptors.forEach(({id, defaultMessage}) => {
if (collection.hasOwnProperty(id)) {
duplicateIds.push(id);
}
collection[id] = defaultMessage;
idToFileMap[id] = (idToFileMap[id] || []).concat(file);
});
return collection;
}, {});
if (duplicateIds.length) {
console.log('\nFound duplicated ids:');
duplicateIds.forEach((id) => console.log(`${chalk.yellow(id)}:\n - ${idToFileMap[id].join('\n - ')}\n`));
console.log(chalk.red('Please correct the errors above to proceed further!'));
return;
}
duplicateIds = null;
idToFileMap = null;
/**
* Making a diff with the previous DEFAULT_LOCALE version
*/
const defaultMessagesPath = `${LANG_DIR}/${DEFAULT_LOCALE}.json`;
let keysToUpdate = [];
let keysToAdd = [];
let keysToRemove = [];
const isNotMarked = (value) => value.slice(0, 2) !== '--';
try {
const prevMessages = JSON.parse(fs.readFileSync(defaultMessagesPath, 'utf8'));
keysToAdd = Object.keys(defaultMessages).filter((key) => !prevMessages[key]);
keysToRemove = Object.keys(prevMessages).filter((key) => !defaultMessages[key]).filter(isNotMarked);
keysToUpdate = Object.entries(prevMessages).reduce((acc, [key, message]) =>
acc.concat(defaultMessages[key] && defaultMessages[key] !== message ? key : [])
, []);
} catch(e) {
console.log(chalk.yellow(`Can not read ${defaultMessagesPath}. The new file will be created.`), e);
}
if (!keysToAdd.length && !keysToRemove.length && !keysToUpdate.length) {
return console.log(chalk.green('Everything is up to date!'));
}
console.log(chalk.magenta(`The diff relative to default locale (${DEFAULT_LOCALE}) is:`));
if (keysToRemove.length) {
console.log('The following keys will be removed:');
console.log(chalk.red('\n - ') + keysToRemove.join(chalk.red('\n - ')) + '\n');
}
if (keysToAdd.length) {
console.log('The following keys will be added:');
console.log(chalk.green('\n + ') + keysToAdd.join(chalk.green('\n + ')) + '\n');
}
if (keysToUpdate.length) {
console.log('The following keys will be updated:');
console.log(chalk.yellow('\n @ ') + keysToUpdate.join(chalk.yellow('\n @ ')) + '\n');
}
prompt.start();
prompt.get({
properties: {
apply: {
description: 'Apply changes? [Y/n]',
pattern: /^y|n$/i,
message: 'Please enter "y" or "n"',
default: 'y',
before: (value) => value.toLowerCase() === 'y'
}
}
}, (err, resp) => {
console.log('\n');
if (err || !resp.apply) {
return console.log(chalk.red('Aborted'));
}
buildLocales();
console.log(chalk.green('All locales was successfuly built'));
});
function buildLocales() {
mkdirpSync(LANG_DIR);
SUPPORTED_LANGS.map((lang) => {
const destPath = `${LANG_DIR}/${lang}.json`;
let newMessages = {};
try {
newMessages = JSON.parse(fs.readFileSync(defaultMessagesPath, 'utf8'));
} catch (e) {
console.log(chalk.yellow(`Can not read ${defaultMessagesPath}. The new file will be created.`), e);
}
keysToRemove.forEach((key) => {
delete newMessages[key];
});
keysToUpdate.forEach((key) => {
newMessages[`--${key}`] = newMessages[key];
});
keysToAdd.concat(keysToUpdate).forEach((key) => {
newMessages[key] = defaultMessages[key];
});
const sortedKeys = Object.keys(newMessages).sort((a, b) => {
a = a.replace(/^\-+/, '');
b = b.replace(/^\-+/, '');
return a < b || !isNotMarked(a) ? -1 : 1;
});
const sortedNewMessages = sortedKeys.reduce((acc, key) => {
acc[key] = newMessages[key];
return acc;
}, {});
fs.writeFileSync(destPath, JSON.stringify(sortedNewMessages, null, 4) + '\n');
});
}

15
scripts/package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "scripts",
"version": "1.0.0",
"description": "",
"main": "i18n-build.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^1.1.3"
}
}

View File

@ -8,7 +8,7 @@ import buttons from 'components/ui/buttons.scss';
import { Input } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
import passwordMessages from 'components/auth/password/Password.messages';
import passwordMessages from 'components/auth/password/Password.intl.json';
import messages from './Login.messages';
class Body extends BaseAuthBody {

View File

@ -0,0 +1,10 @@
{
"passwordTitle": "Enter password",
"signInButton": "Sign in",
"invalidPassword": "You entered wrong account password.",
"suggestResetPassword": "Are you have {link}?",
"forgotYourPassword": "forgot your password",
"forgotPassword": "Forgot password",
"accountPassword": "Account password",
"rememberMe": "Remember me on this device"
}

View File

@ -10,7 +10,7 @@ import { Input, Checkbox } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
import styles from './password.scss';
import messages from './Password.messages';
import messages from './Password.intl.json';
class Body extends BaseAuthBody {
static displayName = 'PasswordBody';

View File

@ -1,43 +0,0 @@
import { defineMessages } from 'react-intl';
export default defineMessages({
passwordTitle: {
id: 'passwordTitle',
defaultMessage: 'Enter password'
},
signInButton: {
id: 'signInButton',
defaultMessage: 'Sign in'
},
invalidPassword: {
id: 'invalidPassword',
defaultMessage: 'You entered wrong account password.'
},
suggestResetPassword: {
id: 'suggestResetPassword',
defaultMessage: 'Are you have {link}?'
},
forgotYourPassword: {
id: 'forgotYourPassword',
defaultMessage: 'forgot your password'
},
forgotPassword: {
id: 'forgotPassword',
defaultMessage: 'Forgot password'
},
accountPassword: {
id: 'accountPassword',
defaultMessage: 'Account password'
},
rememberMe: {
id: 'rememberMe',
defaultMessage: 'Remember me on this device'
}
});

View File

@ -7,6 +7,7 @@ import buttons from 'components/ui/buttons.scss';
import { Input, Checkbox } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
import passwordMessages from 'components/auth/password/Password.intl.json';
import activationMessages from 'components/auth/activation/Activation.messages';
import messages from './Register.messages';
@ -44,7 +45,7 @@ class Body extends BaseAuthBody {
color="blue"
type="password"
required
placeholder={messages.accountPassword}
placeholder={passwordMessages.accountPassword}
/>
<Input {...this.bindField('rePassword')}

View File

@ -0,0 +1,10 @@
{
"changePasswordTitle": "Change password",
"changePasswordDescription": "Please take a password, that will be different from your passwords on the other sites and will not be the same you are using to enter Minecraft game servers you are playing.",
"achievementLossWarning": "Are you cherish your game achievements, right?",
"passwordRequirements": "Password must contain at least 8 characters. It can be any symbols — do not limit yourself, create an unpredictable password!",
"changePasswordButton": "Change password",
"newPasswordLabel": "New password:",
"repeatNewPasswordLabel": "Repeat the password:",
"logoutOnAllDevices": "Logout on all devices"
}

View File

@ -7,7 +7,7 @@ import Helmet from 'react-helmet';
import { Input, Button, Checkbox, Form, FormModel } from 'components/ui/form';
import styles from 'components/profile/profileForm.scss';
import messages from './ChangePassword.messages';
import messages from './ChangePassword.intl.json';
export default class ChangePassword extends Component {
static displayName = 'ChangePassword';

View File

@ -1,43 +0,0 @@
import { defineMessages } from 'react-intl';
export default defineMessages({
changePasswordTitle: {
id: 'changePasswordTitle',
defaultMessage: 'Change password'
// defaultMessage: 'Смена пароля'
},
changePasswordDescription: {
id: 'changePasswordDescription',
defaultMessage: 'Please take a password, that will be different from your passwords on the other sites and will not be the same you are using to enter Minecraft game servers you are playing.'
// defaultMessage: 'Придумайте пароль, который будет отличаться от ваших паролей на других сайтах и не будет совпадаеть с тем паролем, который вы используете для входа на различные игровые сервера Minecraft.'
},
achievementLossWarning: {
id: 'achievementLossWarning',
defaultMessage: 'Are you cherish your game achievements, right?'
// defaultMessage: 'Вы ведь дорожите своими игровыми достижениями?'
},
passwordRequirements: {
id: 'passwordRequirements',
defaultMessage: 'Password must contain at least 8 characters. It can be any symbols — do not limit yourself, create an unpredictable password!'
// defaultMessage: 'Пароль должен содержать не менее 8 символов. Это могут быть любым символы — не ограничивайте себя, придумайте непредсказуемый пароль!'
},
changePasswordButton: {
id: 'changePasswordButton',
defaultMessage: 'Change password'
// defaultMessage: 'Сменить пароль'
},
newPasswordLabel: {
id: 'newPasswordLabel',
defaultMessage: 'New password:'
// defaultMessage: 'Новый пароль:'
},
repeatNewPasswordLabel: {
id: 'repeatNewPasswordLabel',
defaultMessage: 'Repeat the password:'
// defaultMessage: 'Повторите указанный пароль:'
},
logoutOnAllDevices: {
id: 'logoutOnAllDevices',
defaultMessage: 'Logout on all devices'
}
});

View File

@ -22,6 +22,7 @@ export default class User {
username: '',
email: '',
avatar: '',
lang: '',
goal: null, // the goal with wich user entered site
isGuest: true,
isActive: true,

111
src/i18n/en.json Normal file
View File

@ -0,0 +1,111 @@
{
"acceptRules": "I agree with {link}",
"accountActivationTitle": "Account activation",
"accountDescription": "Ely.by account allows you to get access to many Minecraft resources. Please, take care of your account safety. Use secure password and change it regularly.",
"accountEmail": "Enter account E-mail",
"accountPassword": "Account password",
"accountPreferencesTitle": "Ely.by account preferences",
"activationMailWasSent": "Please check {email} for the message with the last registration step",
"alreadyReceivedCode": "Already received code",
"approve": "Approve",
"authForAppFailed": "Authorization for {appName} was failed",
"authForAppSuccessful": "Authorization for {appName} was successfully completed",
"change": "Change",
"changeEmailButton": "Change E-mail",
"changeEmailDescription": "To change current account E-mail you must first verify that you own the current address and then confirm the new one.",
"changeEmailTitle": "Change E-mail",
"changePasswordTitle": "Change password",
"changeUsernameButton": "Change nickname",
"changeUsernameDescription": "You can change your nickname to any arbitrary value. Remember that it is not recommended to take a nickname of already existing Mojang account.",
"changeUsernameTitle": "Change nickname",
"changeUsernameWarning": "Be careful: if you playing on the server with nickname binding, then after changing nickname you may lose all your progress.",
"changedAt": "Changed {at}",
"codePlaceholder": "Paste the code here",
"components.auth.password.accountPassword": "Account password",
"components.auth.password.forgotPassword": "Forgot password",
"components.auth.password.forgotYourPassword": "forgot your password",
"components.auth.password.invalidPassword": "You entered wrong account password.",
"components.auth.password.passwordTitle": "Enter password",
"components.auth.password.rememberMe": "Remember me on this device",
"components.auth.password.signInButton": "Sign in",
"components.auth.password.suggestResetPassword": "Are you have {link}?",
"components.profile.changePassword.achievementLossWarning": "Are you cherish your game achievements, right?",
"components.profile.changePassword.changePasswordButton": "Change password",
"components.profile.changePassword.changePasswordDescription": "Please take a password, that will be different from your passwords on the other sites and will not be the same you are using to enter Minecraft game servers you are playing.",
"components.profile.changePassword.changePasswordTitle": "Change password",
"components.profile.changePassword.logoutOnAllDevices": "Logout on all devices",
"components.profile.changePassword.newPasswordLabel": "New password:",
"components.profile.changePassword.passwordRequirements": "Password must contain at least 8 characters. It can be any symbols — do not limit yourself, create an unpredictable password!",
"components.profile.changePassword.repeatNewPasswordLabel": "Repeat the password:",
"confirmEmail": "Confirm E-mail",
"contactSupport": "Contact support",
"copy": "Copy",
"currentAccountEmail": "Current account E-mail address:",
"currentPassword": "Enter current password",
"decline": "Decline",
"didNotReceivedEmail": "Did not received E-mail?",
"disabled": "Disabled",
"emailInvalid": "Email is invalid",
"emailIsTempmail": "Tempmail E-mail addresses is not allowed",
"emailNotAvailable": "This email is already registered.",
"emailOrUsername": "E-mail or username",
"emailRequired": "Email is required",
"enterFinalizationCode": "The E-mail change confirmation code was sent to {email}. Please enter the code received into the field below:",
"enterInitializationCode": "The E-mail with an initialization code for E-mail change procedure was sent to {email}. Please enter the code into the field below:",
"enterNewEmail": "Then provide your new E-mail address, that you want to use with this account. You will be mailed with confirmation code.",
"enterTheCode": "Enter the code from E-mail here",
"forgotPasswordMessage": "Specify the registration E-mail address for your account and we will send an email with instructions for further password recovery.",
"forgotPasswordTitle": "Forgot password",
"forgotYourPassword": "forgot your password",
"goToAuth": "Go to auth",
"invalidPassword": "You have entered wrong account password.",
"keyNotExists": "The key is incorrect",
"keyRequired": "Please, enter an activation key",
"loginNotExist": "Sorry, Ely doesn't recognise your login.",
"loginRequired": "Please enter email or username",
"loginTitle": "Sign in",
"logout": "Logout",
"mojangPriorityWarning": "A Mojang account with the same nickname was found. According to project rules, account owner has the right to demand the restoration of control over nickname.",
"newEmailPlaceholder": "Enter new E-mail",
"newPassword": "Enter new password",
"newPasswordRequired": "Please enter new password",
"newRePassword": "Repeat new password",
"newRePasswordRequired": "Please repeat new password",
"next": "Next",
"nickname": "Nickname",
"oldHashingAlgoWarning": "Your was hashed with an old hashing algorithm.<br />Please, change password.",
"passCodeToApp": "To complete authorization process, please, provide the following code to {appName}",
"password": "Password",
"passwordChangeMessage": "To enhance the security of your account, please change your password.",
"passwordRequired": "Please enter password",
"passwordTooShort": "Your password is too short",
"passwordsDoesNotMatch": "The passwords does not match",
"permissionsTitle": "Application permissions",
"personalData": "Personal data",
"pleaseEnterPassword": "Please, enter your current password",
"preferencesDescription": "Here you can change the key preferences of your account. Please note that all actions must be confirmed by entering a password.",
"pressButtonToStart": "Press the button below to send a message with the code for E-mail change initialization.",
"rePasswordRequired": "Please retype your password",
"register": "Join",
"registerTitle": "Sign Up",
"repeatPassword": "Repeat password",
"rulesAgreementRequired": "You must accept rules in order to create an account",
"scope_minecraft_server_session": "Authorization data for minecraft server",
"scope_offline_access": "Access to your profile data, when you offline",
"sendEmailButton": "Send E-mail",
"sendMail": "Send mail",
"signUpButton": "Register",
"skipThisStep": "Skip password changing",
"suggestResetPassword": "Are you have {link}?",
"termsOfService": "Terms of service",
"theAppNeedsAccess1": "This application needs access",
"theAppNeedsAccess2": "to your data",
"title": "Confirm your action",
"twoFactorAuth": "Two factor auth",
"usernameRequired": "Username is required",
"usernameUnavailable": "This username is already taken",
"waitAppReaction": "Please, wait till your application response",
"youAuthorizedAs": "You authorized as:",
"yourEmail": "Your E-mail",
"yourNickname": "Your nickname"
}

111
src/i18n/ru.json Normal file
View File

@ -0,0 +1,111 @@
{
"acceptRules": "I agree with {link}",
"accountActivationTitle": "Account activation",
"accountDescription": "Ely.by account allows you to get access to many Minecraft resources. Please, take care of your account safety. Use secure password and change it regularly.",
"accountEmail": "Enter account E-mail",
"accountPassword": "Account password",
"accountPreferencesTitle": "Ely.by account preferences",
"activationMailWasSent": "Please check {email} for the message with the last registration step",
"alreadyReceivedCode": "Already received code",
"approve": "Approve",
"authForAppFailed": "Authorization for {appName} was failed",
"authForAppSuccessful": "Authorization for {appName} was successfully completed",
"change": "Change",
"changeEmailButton": "Change E-mail",
"changeEmailDescription": "To change current account E-mail you must first verify that you own the current address and then confirm the new one.",
"changeEmailTitle": "Change E-mail",
"changePasswordTitle": "Change password",
"changeUsernameButton": "Change nickname",
"changeUsernameDescription": "You can change your nickname to any arbitrary value. Remember that it is not recommended to take a nickname of already existing Mojang account.",
"changeUsernameTitle": "Change nickname",
"changeUsernameWarning": "Be careful: if you playing on the server with nickname binding, then after changing nickname you may lose all your progress.",
"changedAt": "Changed {at}",
"codePlaceholder": "Paste the code here",
"components.auth.password.accountPassword": "Account password",
"components.auth.password.forgotPassword": "Forgot password",
"components.auth.password.forgotYourPassword": "forgot your password",
"components.auth.password.invalidPassword": "You entered wrong account password.",
"components.auth.password.passwordTitle": "Enter password",
"components.auth.password.rememberMe": "Remember me on this device",
"components.auth.password.signInButton": "Sign in",
"components.auth.password.suggestResetPassword": "Are you have {link}?",
"components.profile.changePassword.achievementLossWarning": "Вы ведь дорожите своими игровыми достижениями?",
"components.profile.changePassword.changePasswordButton": "Сменить пароль",
"components.profile.changePassword.changePasswordDescription": "Придумайте пароль, который будет отличаться от ваших паролей на других сайтах и не будет совпадаеть с тем паролем, который вы используете для входа на различные игровые сервера Minecraft.",
"components.profile.changePassword.changePasswordTitle": "Смена пароля",
"components.profile.changePassword.logoutOnAllDevices": "Вылогиниться на всех устройствах",
"components.profile.changePassword.newPasswordLabel": "Новый пароль:",
"components.profile.changePassword.passwordRequirements": "Пароль должен содержать не менее 8 символов. Это могут быть любым символы — не ограничивайте себя, придумайте непредсказуемый пароль!",
"components.profile.changePassword.repeatNewPasswordLabel": "Повторите указанный пароль:",
"confirmEmail": "Confirm E-mail",
"contactSupport": "Contact support",
"copy": "Copy",
"currentAccountEmail": "Current account E-mail address:",
"currentPassword": "Enter current password",
"decline": "Decline",
"didNotReceivedEmail": "Did not received E-mail?",
"disabled": "Disabled",
"emailInvalid": "Email is invalid",
"emailIsTempmail": "Tempmail E-mail addresses is not allowed",
"emailNotAvailable": "This email is already registered.",
"emailOrUsername": "E-mail or username",
"emailRequired": "Email is required",
"enterFinalizationCode": "The E-mail change confirmation code was sent to {email}. Please enter the code received into the field below:",
"enterInitializationCode": "The E-mail with an initialization code for E-mail change procedure was sent to {email}. Please enter the code into the field below:",
"enterNewEmail": "Then provide your new E-mail address, that you want to use with this account. You will be mailed with confirmation code.",
"enterTheCode": "Enter the code from E-mail here",
"forgotPasswordMessage": "Specify the registration E-mail address for your account and we will send an email with instructions for further password recovery.",
"forgotPasswordTitle": "Forgot password",
"forgotYourPassword": "forgot your password",
"goToAuth": "Go to auth",
"invalidPassword": "You have entered wrong account password.",
"keyNotExists": "The key is incorrect",
"keyRequired": "Please, enter an activation key",
"loginNotExist": "Sorry, Ely doesn't recognise your login.",
"loginRequired": "Please enter email or username",
"loginTitle": "Sign in",
"logout": "Logout",
"mojangPriorityWarning": "A Mojang account with the same nickname was found. According to project rules, account owner has the right to demand the restoration of control over nickname.",
"newEmailPlaceholder": "Enter new E-mail",
"newPassword": "Enter new password",
"newPasswordRequired": "Please enter new password",
"newRePassword": "Repeat new password",
"newRePasswordRequired": "Please repeat new password",
"next": "Next",
"nickname": "Nickname",
"oldHashingAlgoWarning": "Your was hashed with an old hashing algorithm.<br />Please, change password.",
"passCodeToApp": "To complete authorization process, please, provide the following code to {appName}",
"password": "Password",
"passwordChangeMessage": "To enhance the security of your account, please change your password.",
"passwordRequired": "Please enter password",
"passwordTooShort": "Your password is too short",
"passwordsDoesNotMatch": "The passwords does not match",
"permissionsTitle": "Application permissions",
"personalData": "Personal data",
"pleaseEnterPassword": "Please, enter your current password",
"preferencesDescription": "Here you can change the key preferences of your account. Please note that all actions must be confirmed by entering a password.",
"pressButtonToStart": "Press the button below to send a message with the code for E-mail change initialization.",
"rePasswordRequired": "Please retype your password",
"register": "Join",
"registerTitle": "Sign Up",
"repeatPassword": "Repeat password",
"rulesAgreementRequired": "You must accept rules in order to create an account",
"scope_minecraft_server_session": "Authorization data for minecraft server",
"scope_offline_access": "Access to your profile data, when you offline",
"sendEmailButton": "Send E-mail",
"sendMail": "Send mail",
"signUpButton": "Register",
"skipThisStep": "Skip password changing",
"suggestResetPassword": "Are you have {link}?",
"termsOfService": "Terms of service",
"theAppNeedsAccess1": "This application needs access",
"theAppNeedsAccess2": "to your data",
"title": "Confirm your action",
"twoFactorAuth": "Two factor auth",
"usernameRequired": "Username is required",
"usernameUnavailable": "This username is already taken",
"waitAppReaction": "Please, wait till your application response",
"youAuthorizedAs": "You authorized as:",
"yourEmail": "Your E-mail",
"yourNickname": "Your nickname"
}

View File

@ -15,7 +15,9 @@ import thunk from 'redux-thunk';
import { Router, browserHistory } from 'react-router';
import { syncHistory, routeReducer } from 'react-router-redux';
import { IntlProvider } from 'react-intl';
import { IntlProvider, addLocaleData } from 'react-intl';
import enLocaleData from 'react-intl/locale-data/en';
import ruLocaleData from 'react-intl/locale-data/ru';
import reducers from 'reducers';
import routesFactory from 'routes';
@ -32,23 +34,47 @@ const store = applyMiddleware(
thunk
)(createStore)(reducer);
if (process.env.NODE_ENV !== 'production') {
// some shortcuts for testing on localhost
addLocaleData(enLocaleData);
addLocaleData(ruLocaleData);
window.testOAuth = () => location.href = '/oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session';
// TODO: bind with user state
const SUPPORTED_LANGUAGES = ['ru', 'en'];
const DEFAULT_LANGUAGE = 'en';
const state = store.getState();
function getUserLanguages() {
return [].concat(state.user.lang || [])
.concat(navigator.languages || [])
.concat(navigator.language || []);
}
ReactDOM.render(
<IntlProvider locale="en" messages={{}}>
<ReduxProvider store={store}>
<Router history={browserHistory}>
{routesFactory(store)}
</Router>
</ReduxProvider>
</IntlProvider>,
document.getElementById('app')
);
function detectLanguage(userLanguages, availableLanguages, defaultLanguage) {
return (userLanguages || [])
.concat(defaultLanguage)
.map((lang) => lang.split('-').shift().toLowerCase())
.find((lang) => availableLanguages.indexOf(lang) !== -1);
}
setTimeout(() => {
document.getElementById('loader').classList.remove('is-active');
}, 50);
const locale = detectLanguage(getUserLanguages(), SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE);
new Promise(require(`bundle!i18n/${locale}.json`))
.then((messages) => {
ReactDOM.render(
<IntlProvider locale={locale} messages={messages}>
<ReduxProvider store={store}>
<Router history={browserHistory}>
{routesFactory(store)}
</Router>
</ReduxProvider>
</IntlProvider>,
document.getElementById('app')
);
document.getElementById('loader').classList.remove('is-active');
});
if (process.env.NODE_ENV !== 'production') {
// some shortcuts for testing on localhost
window.testOAuth = () => location.href = '/oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session';
}

View File

@ -156,15 +156,26 @@ var webpackConfig = {
},
{
test: /\.json$/,
exclude: /intl\.json/,
loader: 'json'
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.intl\.json$/,
loader: 'babel!intl-loader!json'
}
]
},
resolveLoader: {
alias: {
'intl-loader': path.resolve('./webpack/intl-loader')
}
},
sassLoader: {
importer: iconfontImporter({
test: /\.font.(js|json)$/,

21
webpack/intl-loader.js Normal file
View File

@ -0,0 +1,21 @@
module.exports = function() {
this.cacheable && this.cacheable();
var moduleId = this.context
.replace(this.options.resolve.root, '')
.replace(/^\/|\/$/g, '')
.replace(/\//g, '.');
var content = this.inputValue[0];
content = JSON.stringify(Object.keys(content).reduce(function(translations, key) {
translations[key] = {
id: moduleId + '.' + key,
defaultMessage: content[key]
};
return translations;
}, {}));
return 'import { defineMessages } from \'react-intl\';'
+ 'export default defineMessages(' + content + ')';
};