- {lang.t(stages[stage].description, lang.t('suspenduser.username'))}
+ {t(stages[stage].description, t('suspenduser.username'))}
-
{lang.t('suspenduser.write_message')}
+
{t('suspenduser.write_message')}
diff --git a/client/coral-admin/src/containers/Community/components/User.js b/client/coral-admin/src/containers/Community/components/User.js
index af4b72c0b..05224170b 100644
--- a/client/coral-admin/src/containers/Community/components/User.js
+++ b/client/coral-admin/src/containers/Community/components/User.js
@@ -3,10 +3,15 @@ import styles from '../Community.css';
import ActionButton from './ActionButton';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from '../../../translations.json';
+import t from 'coral-framework/services/i18n';
-const lang = new I18n(translations);
+const shortReasons = {
+ 'This comment is offensive': t('community.offensive'),
+ 'This looks like an ad/marketing': t('community.spam_ads'),
+ 'This user is impersonating': t('community.impersonating'),
+ 'I don\'t like this username': t('community.dont_like_username'),
+ 'Other': t('community.other')
+};
// Render a single user for the list
const User = (props) => {
@@ -35,11 +40,11 @@ const User = (props) => {
-
flag{lang.t('community.flags')}({ user.actions.length }):
+
flag{t('community.flags')}({ user.actions.length }):
{ user.action_summaries.map(
(action, i) => {
return
- {lang.t(`community.${action.reason}`)} ({action.count})
+ {shortReasons[action.reason]} ({action.count})
;
}
)}
@@ -49,7 +54,7 @@ const User = (props) => {
(action_sum, i) => {
return
- {lang.t(`community.${action_sum.reason}`)} ({action_sum.count})
+ {shortReasons[action_sum.reason]} ({action_sum.count})
{user.actions.map(
diff --git a/client/coral-admin/src/containers/Configure/Configure.js b/client/coral-admin/src/containers/Configure/Configure.js
index d22a19997..4eb046df4 100644
--- a/client/coral-admin/src/containers/Configure/Configure.js
+++ b/client/coral-admin/src/containers/Configure/Configure.js
@@ -10,11 +10,10 @@ import {
import {Button, List, Item, Card, Spinner} from 'coral-ui';
import styles from './Configure.css';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations.json';
import StreamSettings from './StreamSettings';
import ModerationSettings from './ModerationSettings';
import TechSettings from './TechSettings';
+import t from 'coral-framework/services/i18n';
import {can} from 'coral-framework/services/perms';
class Configure extends Component {
@@ -106,11 +105,11 @@ class Configure extends Component {
getPageTitle (section) {
switch(section) {
case 'stream':
- return lang.t('configure.stream-settings');
+ return t('configure.stream_settings');
case 'moderation':
- return lang.t('configure.moderation-settings');
+ return t('configure.moderation_settings');
case 'tech':
- return lang.t('configure.tech-settings');
+ return t('configure.tech_settings');
default:
return '';
}
@@ -133,13 +132,13 @@ class Configure extends Component {
-
- {lang.t('configure.stream-settings')}
+ {t('configure.stream_settings')}
-
- {lang.t('configure.moderation-settings')}
+ {t('configure.moderation_settings')}
-
- {lang.t('configure.tech-settings')}
+ {t('configure.tech_settings')}
@@ -152,7 +151,7 @@ class Configure extends Component {
icon='check'
full
>
- {lang.t('configure.save-changes')}
+ {t('configure.save_changes')}
:
}
@@ -182,5 +181,3 @@ const mapStateToProps = (state) => ({
settings: state.settings.toJS()
});
export default connect(mapStateToProps)(Configure);
-
-const lang = new I18n(translations);
diff --git a/client/coral-admin/src/containers/Configure/Domainlist.js b/client/coral-admin/src/containers/Configure/Domainlist.js
index 0ccc4158f..45790ec89 100644
--- a/client/coral-admin/src/containers/Configure/Domainlist.js
+++ b/client/coral-admin/src/containers/Configure/Domainlist.js
@@ -2,17 +2,14 @@ import React from 'react';
import {Card} from 'coral-ui';
import styles from './Configure.css';
import TagsInput from 'react-tagsinput';
-
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from '../../translations.json';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
const Domainlist = ({domains, onChangeDomainlist}) => {
return (
-
{lang.t('configure.domain-list-title')}
-
{lang.t('configure.domain-list-text')}
+
{t('configure.domain_list_title')}
+
{t('configure.domain_list_text')}
Embed Comment Stream
-
{lang.t('configure.copy-and-paste')}
+
{t('configure.copy_and_paste')}
-
{lang.t('configure.enable-premod-links')}
+
{t('configure.enable_premod_links')}
- {lang.t('configure.enable-premod-links-text')}
+ {t('configure.enable_premod_links_text')}
@@ -81,9 +79,9 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
{/* Edit Comment Timeframe */}
- {lang.t('configure.edit-comment-timeframe-heading')}
+ {t('configure.edit_comment_timeframe_heading')}
- {lang.t('configure.edit-comment-timeframe-text-pre')}
+ {t('configure.edit_comment_timeframe_text_pre')}
{
pattern='[0-9]+([\.][0-9]*)?'
/>
- {lang.t('configure.edit-comment-timeframe-text-post')}
+ {t('configure.edit_comment_timeframe_text_post')}
-
+
);
};
diff --git a/client/coral-admin/src/containers/Configure/StreamSettings.js b/client/coral-admin/src/containers/Configure/StreamSettings.js
index 341c47ae9..41c62c153 100644
--- a/client/coral-admin/src/containers/Configure/StreamSettings.js
+++ b/client/coral-admin/src/containers/Configure/StreamSettings.js
@@ -1,7 +1,6 @@
import React from 'react';
import {SelectField, Option} from 'react-mdl-selectfield';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from '../../translations.json';
+import t from 'coral-framework/services/i18n';
import styles from './Configure.css';
import {Checkbox, Textfield} from 'react-mdl';
import {Card, Icon, TextArea} from 'coral-ui';
@@ -77,9 +76,9 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
checked={settings.charCountEnable} />
-
{lang.t('configure.comment-count-header')}
+
{t('configure.comment_count_header')}
- {lang.t('configure.comment-count-text-pre')}
+ {t('configure.comment_count_text_pre')}
{
value={settings.charCount}
disabled={settings.charCountEnable ? '' : 'disabled'}
/>
- {lang.t('configure.comment-count-text-post')}
+ {t('configure.comment_count_text_post')}
{
errors.charCount &&
- {lang.t('configure.comment-count-error')}
+ {t('configure.comment_count_error')}
}
@@ -107,10 +106,10 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
- {lang.t('configure.include-comment-stream')}
+ {t('configure.include_comment_stream')}
- {lang.t('configure.include-comment-stream-desc')}
+ {t('configure.include_comment_stream_desc')}
{
-
{lang.t('configure.closed-stream-settings')}
-
{lang.t('configure.closed-comments-desc')}
+
{t('configure.closed_stream_settings')}
+
{t('configure.closed_comments_desc')}
- {lang.t('configure.close-after')}
+ {t('configure.close_after')}
{
style={{width: 50}}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout)}
value={getTimeoutAmount(settings.closedTimeout)}
- label={lang.t('configure.closed-comments-label')} />
+ label={t('configure.closed_comments_label')} />
-
-
-
+
+
+
@@ -183,5 +182,3 @@ const getTimeoutMeasure = (ts) => {
// Dividing the amount by it's measure (hours, days, weeks) we
// obtain the amount of time
const getTimeoutAmount = (ts) => ts / TIMESTAMPS[getTimeoutMeasure(ts)];
-
-const lang = new I18n(translations);
diff --git a/client/coral-admin/src/containers/Configure/TechSettings.js b/client/coral-admin/src/containers/Configure/TechSettings.js
index 6fbc302a0..2dca7b207 100644
--- a/client/coral-admin/src/containers/Configure/TechSettings.js
+++ b/client/coral-admin/src/containers/Configure/TechSettings.js
@@ -3,9 +3,7 @@ import {Card} from 'coral-ui';
import Domainlist from './Domainlist';
import EmbedLink from './EmbedLink';
import styles from './Configure.css';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from '../../translations.json';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
const updateCustomCssUrl = (updateSettings) => (event) => {
const customCssUrl = event.target.value;
@@ -21,8 +19,8 @@ const TechSettings = ({settings, onChangeDomainlist, updateSettings}) => {
-
{lang.t('configure.custom-css-url')}
-
{lang.t('configure.custom-css-url-desc')}
+
{t('configure.custom_css_url')}
+
{t('configure.custom_css_url_desc')}
(
- {lang.t('configure.banned-words-title')}
- {lang.t('configure.banned-word-text')}
+ {t('configure.banned_words_title')}
+ {t('configure.banned_word_text')}
(
- {lang.t('configure.suspect-words-title')}
- {lang.t('configure.suspect-word-text')}
+ {t('configure.suspect_word_title')}
+ {t('configure.suspect_word_text')}
(
);
export default Wordlist;
-
-const lang = new I18n(translations);
diff --git a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js
index d6cbb8edd..5f974971c 100644
--- a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js
+++ b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js
@@ -1,18 +1,15 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './Widget.css';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations';
-
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
const ActivityWidget = ({assets}) => {
return (
-
Articles with the most conversations
+
{t('dashboard.most_conversations')}
-
{lang.t('streams.article')}
-
{lang.t('dashboard.comment_count')}
+
{t('streams.article')}
+
{t('dashboard.comment_count')}
{
@@ -29,7 +26,7 @@ const ActivityWidget = ({assets}) => {
);
})
- :
{lang.t('dashboard.no_activity')}
+ :
{t('dashboard.no_activity')}
}
diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js
index 6a50223bc..ef0974fbd 100644
--- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js
+++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js
@@ -1,19 +1,17 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './Widget.css';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
const FlagWidget = ({assets}) => {
return (
-
{lang.t('dashboard.most_flags')}
+
{t('dashboard.most_flags')}
-
{lang.t('streams.article')}
-
{lang.t('dashboard.flags')}
+
{t('streams.article')}
+
{t('dashboard.flags')}
{
@@ -35,7 +33,7 @@ const FlagWidget = ({assets}) => {
);
})
- :
{lang.t('dashboard.no_flags')}
+ :
{t('dashboard.no_flags')}
}
diff --git a/client/coral-admin/src/containers/Dashboard/LikeWidget.js b/client/coral-admin/src/containers/Dashboard/LikeWidget.js
index 4d7483667..2188e38dd 100644
--- a/client/coral-admin/src/containers/Dashboard/LikeWidget.js
+++ b/client/coral-admin/src/containers/Dashboard/LikeWidget.js
@@ -1,10 +1,7 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './Widget.css';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations';
-
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
const LikeWidget = ({assets}) => {
@@ -12,8 +9,8 @@ const LikeWidget = ({assets}) => {
Articles with the most likes
-
{lang.t('streams.article')}
-
{lang.t('modqueue.likes')}
+
{t('streams.article')}
+
{t('modqueue.likes')}
{
@@ -31,7 +28,7 @@ const LikeWidget = ({assets}) => {
);
})
- :
{lang.t('dashboard.no_likes')}
+ :
{t('dashboard.no_likes')}
}
diff --git a/client/coral-admin/src/containers/Dashboard/MostLikedCommentsWidget.js b/client/coral-admin/src/containers/Dashboard/MostLikedCommentsWidget.js
index c29c9ad43..6bbb67c50 100644
--- a/client/coral-admin/src/containers/Dashboard/MostLikedCommentsWidget.js
+++ b/client/coral-admin/src/containers/Dashboard/MostLikedCommentsWidget.js
@@ -1,11 +1,9 @@
import React from 'react';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations';
+
import ModerationQueue from 'coral-admin/src/containers/ModerationQueue/ModerationQueue';
import styles from './Widget.css';
import BanUserDialog from 'coral-admin/src/components/BanUserDialog';
-
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
const MostLikedCommentsWidget = (props) => {
const {
@@ -21,7 +19,7 @@ const MostLikedCommentsWidget = (props) => {
return (
-
{lang.t('most_liked_comments')}
+
{t('most_liked_comments')}
{
const {handleSettingsChange, handleSettingsSubmit, install} = props;
return (
-
{lang.t('ADD_ORGANIZATION.DESCRIPTION')}
+
{t('install.add_organization.description')}
diff --git a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js
index e46f013a8..c5d334610 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js
@@ -2,9 +2,7 @@ import React from 'react';
import styles from './style.css';
import {TextField, Button, Spinner} from 'coral-ui';
-const lang = new I18n(translations);
-import translations from '../../translations.json';
-import I18n from 'coral-framework/modules/i18n/i18n';
+import t from 'coral-framework/services/i18n';
const InitialStep = (props) => {
const {handleUserChange, handleUserSubmit, install} = props;
@@ -16,7 +14,7 @@ const InitialStep = (props) => {
className={styles.textField}
id="email"
type="email"
- label={lang.t('CREATE.EMAIL')}
+ label={t('install.create.email')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.email}
@@ -27,7 +25,7 @@ const InitialStep = (props) => {
className={styles.textField}
id="username"
type="text"
- label={lang.t('CREATE.USERNAME')}
+ label={t('install.create.username')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.username}
@@ -37,7 +35,7 @@ const InitialStep = (props) => {
className={styles.textField}
id="password"
type="password"
- label={lang.t('CREATE.PASSWORD')}
+ label={t('install.create.password')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.password}
@@ -47,15 +45,15 @@ const InitialStep = (props) => {
className={styles.textField}
id="confirmPassword"
type="password"
- label={lang.t('CREATE.CONFIRM_PASSWORD')}
+ label={t('install.create.confirm_password')}
onChange={handleUserChange}
showErrors={install.showErrors}
- errorMsg={install.errors.confirmPassword}
+ errorMsg={install.errors.confirm_password}
/>
{
!props.install.isLoading ?
-
+
:
}
diff --git a/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js b/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js
index 0ad918487..dbfd33461 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js
@@ -3,16 +3,14 @@ import styles from './style.css';
import {Button} from 'coral-ui';
import {Link} from 'react-router';
-const lang = new I18n(translations);
-import translations from '../../translations.json';
-import I18n from 'coral-framework/modules/i18n/i18n';
+import t from 'coral-framework/services/i18n';
const InitialStep = () => {
return (
);
};
diff --git a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
index 18b49ce64..aceb0d965 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
@@ -2,16 +2,14 @@ import React from 'react';
import styles from './style.css';
import {Button} from 'coral-ui';
-const lang = new I18n(translations);
-import translations from '../../translations.json';
-import I18n from 'coral-framework/modules/i18n/i18n';
+import t from 'coral-framework/services/i18n';
const InitialStep = (props) => {
const {nextStep} = props;
return (
-
{lang.t('INITIAL.DESCRIPTION')}
-
+
{t('install.initial.description')}
+
);
};
diff --git a/client/coral-admin/src/containers/Install/components/Steps/PermittedDomainsStep.js b/client/coral-admin/src/containers/Install/components/Steps/PermittedDomainsStep.js
index 9058db4c2..fde78779d 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/PermittedDomainsStep.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/PermittedDomainsStep.js
@@ -3,18 +3,16 @@ import styles from './style.css';
import {Button, Card} from 'coral-ui';
import TagsInput from 'react-tagsinput';
-const lang = new I18n(translations);
-import translations from '../../translations.json';
-import I18n from 'coral-framework/modules/i18n/i18n';
+import t from 'coral-framework/services/i18n';
const PermittedDomainsStep = (props) => {
const {finishInstall, install, handleDomainsChange} = props;
const domains = install.data.settings.domains.whitelist;
return (
-
{lang.t('PERMITTED_DOMAINS.TITLE')}
+
{t('install.permitted_domains.title')}
- {lang.t('PERMITTED_DOMAINS.DESCRIPTION')}
+ {t('install.permitted_domains.description')}
{
onChange={(tags) => handleDomainsChange(tags)}
/>
-
+
);
};
diff --git a/client/coral-admin/src/containers/Install/translations.json b/client/coral-admin/src/containers/Install/translations.json
deleted file mode 100644
index e5b9f1542..000000000
--- a/client/coral-admin/src/containers/Install/translations.json
+++ /dev/null
@@ -1,58 +0,0 @@
-{
- "en": {
- "INITIAL" : {
- "DESCRIPTION": "Let's set up your Talk community in just a few short steps.",
- "SUBMIT": "Get Started"
- },
- "ADD_ORGANIZATION": {
- "DESCRIPTION": "Please tell us the name of your organization. This will appear in emails when inviting new team members.",
- "LABEL": "Organization Name",
- "SAVE": "Save"
- },
- "CREATE": {
- "EMAIL": "Email address",
- "USERNAME": "Username",
- "PASSWORD": "Password",
- "CONFIRM_PASSWORD": "Confirm Password",
- "SAVE": "Save"
- },
- "PERMITTED_DOMAINS": {
- "TITLE": "Permitted domains",
- "DESCRIPTION": "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com).",
- "SUBMIT": "Finish install"
- },
- "FINAL": {
- "DESCRIPTION": "Thanks for installing Talk! We sent an email to verify your email address. While you finish setting up the account, you can start engaging with your readers now.",
- "LAUNCH": "Launch Talk",
- "CLOSE": "Close this Installer"
- }
- },
- "es": {
- "INITIAL" : {
- "DESCRIPTION": "Configuremos tu comunidad de Talk en sólo algunos pasos.",
- "SUBMIT": "Empezá!"
- },
- "ADD_ORGANIZATION": {
- "DESCRIPTION": "Por favor, dinos el nombre de tu organización. Este aparecerá en los emails cuando invites nuevos miembros.",
- "LABEL": "Nombre de la Organización",
- "SAVE": "Guardar"
- },
- "CREATE": {
- "EMAIL": "Dirección de E-Mail",
- "USERNAME": "Usuario",
- "PASSWORD": "Contraseña",
- "CONFIRM_PASSWORD": "Confirmar contraseña",
- "SAVE": "Guardar"
- },
- "PERMITTED_DOMAINS": {
- "TITLE": "Dominios Permitidos",
- "DESCRIPTION": "Agrega dominios permitidos a Talk, e.g. tu localhost, staging y ambientes de production (ex. localhost:3000, staging.domain.com, domain.com).",
- "SUBMIT": "Finalizar instalación"
- },
- "FINAL": {
- "DESCRIPTION": "Gracias por instalar Talk! Te enviamos un email para verificar tu identidad. Mientras se termina de configurar la cuenta, ya puedes empezar a interactuar con tus lectores",
- "LAUNCH": "Lanzar Talk",
- "CLOSE": "Cerrar este instalador"
- }
- }
-}
diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
index 237059103..152121a63 100644
--- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
+++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
@@ -6,8 +6,7 @@ import * as notification from 'coral-admin/src/services/notification';
import key from 'keymaster';
import isEqual from 'lodash/isEqual';
import styles from './components/styles.css';
-import translations from 'coral-admin/src/translations';
-import I18n from 'coral-framework/modules/i18n/i18n';
+import t, {timeago} from 'coral-framework/services/i18n';
import {modQueueQuery, getQueueCounts} from '../../graphql/queries';
import {banUser, setCommentStatus, suspendUser} from '../../graphql/mutations';
@@ -36,8 +35,6 @@ import NotFoundAsset from './components/NotFoundAsset';
import ModerationKeysModal from '../../components/ModerationKeysModal';
import UserDetail from './UserDetail';
-const lang = new I18n(translations);
-
class ModerationContainer extends Component {
state = {
selectedIndex: 0,
@@ -108,9 +105,9 @@ class ModerationContainer extends Component {
throw result.data.suspendUser.errors;
}
notification.success(
- lang.t('suspenduser.notify_suspend_until',
+ t('suspenduser.notify_suspend_until',
this.props.moderation.suspendUserDialog.username,
- lang.timeago(args.until)),
+ timeago(args.until)),
);
const {commentStatus, commentId} = this.props.moderation.suspendUserDialog;
if (commentStatus !== 'REJECTED') {
diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js
index 043173549..bdcf82eef 100644
--- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js
+++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js
@@ -4,11 +4,10 @@ import Comment from './components/Comment';
import styles from './components/styles.css';
import EmptyCard from '../../components/EmptyCard';
import {actionsMap} from './helpers/moderationQueueActionsMap';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations';
+import t from 'coral-framework/services/i18n';
+
import LoadMore from './components/LoadMore';
-const lang = new I18n(translations);
class ModerationQueue extends React.Component {
static propTypes = {
@@ -71,7 +70,7 @@ class ModerationQueue extends React.Component {
currentUserId={this.props.currentUserId}
/>;
})
- : {lang.t('modqueue.emptyqueue')}
+ : {t('modqueue.empty_queue')}
}
(e) => {
e.preventDefault();
@@ -22,22 +19,22 @@ const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, c
open={open}
onClose={handleClose}
onCancel={handleClose}
- title={lang.t('bandialog.ban_user')}>
+ title={t('bandialog.ban_user')}>
×
-
{lang.t('bandialog.ban_user')}
+ {t('bandialog.ban_user')}
-
{lang.t('bandialog.are_you_sure', user.name)}
- {showRejectedNote && lang.t('bandialog.note')}
+ {t('bandialog.are_you_sure', user.name)}
+ {showRejectedNote && t('bandialog.note')}
diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js
index 6246dd5b9..bf6b050a1 100644
--- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js
+++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js
@@ -1,5 +1,4 @@
import React, {PropTypes} from 'react';
-import timeago from 'timeago.js';
import {Link} from 'react-router';
import Linkify from 'react-linkify';
@@ -16,9 +15,7 @@ import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
const linkify = new Linkify();
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations.json';
-const lang = new I18n(translations);
+import t, {timeago} from 'coral-framework/services/i18n';
const Comment = ({
actions = [],
@@ -62,10 +59,7 @@ const Comment = ({
{comment.user.name}
- {timeago().format(
- comment.created_at || Date.now() - props.index * 60 * 1000,
- lang.getLocale().replace('-', '_')
- )}
+ {timeago(comment.created_at || Date.now() - props.index * 60 * 1000)}
{props.currentUserId !== comment.user.id &&
@@ -85,7 +79,7 @@ const Comment = ({
{comment.user.status === 'banned'
?
- {lang.t('comment.banned_user')}
+ {t('comment.banned_user')}
: null}
@@ -93,7 +87,7 @@ const Comment = ({
Story: {comment.asset.title}
{!props.currentAsset &&
- Moderate →}
+ {t('modqueue.moderate')}}
@@ -107,7 +101,7 @@ const Comment = ({
href={`${comment.asset.url}#${comment.id}`}
target="_blank"
>
- {lang.t('comment.view_context')}
+ {t('comment.view_context')}
diff --git a/client/coral-admin/src/containers/ModerationQueue/components/CommentCount.js b/client/coral-admin/src/containers/ModerationQueue/components/CommentCount.js
index 0517caf0f..75af89584 100644
--- a/client/coral-admin/src/containers/ModerationQueue/components/CommentCount.js
+++ b/client/coral-admin/src/containers/ModerationQueue/components/CommentCount.js
@@ -1,19 +1,18 @@
import React, {PropTypes} from 'react';
import styles from './CommentCount.css';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations.json';
-const lang = new I18n(translations);
+
+import t from 'coral-framework/services/i18n';
const CommentCount = ({count}) => {
let number = count;
// shorten large counts to abbreviations
if (number / 1e9 > 1) {
- number = `${(number / 1e9).toFixed(1)}${lang.t('modqueue.billion')}`;
+ number = `${(number / 1e9).toFixed(1)}${t('modqueue.billion')}`;
} else if (number / 1e6 > 1) {
- number = `${(number / 1e6).toFixed(1)}${lang.t('modqueue.million')}`;
+ number = `${(number / 1e6).toFixed(1)}${t('modqueue.million')}`;
} else if (number / 1e3 > 1) {
- number = `${(number / 1e3).toFixed(1)}${lang.t('modqueue.thousand')}`;
+ number = `${(number / 1e3).toFixed(1)}${t('modqueue.thousand')}`;
}
return (
diff --git a/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js b/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js
index f2f52e75e..7dff51fdb 100644
--- a/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js
+++ b/client/coral-admin/src/containers/ModerationQueue/components/FlagBox.js
@@ -1,16 +1,14 @@
import React, {Component, PropTypes} from 'react';
import {Icon} from 'coral-ui';
import styles from './FlagBox.css';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations.json';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
const shortReasons = {
- 'This comment is offensive': lang.t('modqueue.offensive'),
- 'This looks like an ad/marketing': lang.t('modqueue.spam/ads'),
- 'This user is impersonating': lang.t('modqueue.impersonating'),
- 'I don\'t like this username': lang.t('modqueue.dont-like-username'),
- 'Other': lang.t('modqueue.other')
+ 'This comment is offensive': t('modqueue.offensive'),
+ 'This looks like an ad/marketing': t('modqueue.spam_ads'),
+ 'This user is impersonating': t('modqueue.impersonating'),
+ 'I don\'t like this username': t('modqueue.dont_like_username'),
+ 'Other': t('modqueue.other')
};
class FlagBox extends Component {
@@ -42,13 +40,13 @@ class FlagBox extends Component {
{showDetail && (
diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js
index 1a982a68c..c6909226b 100644
--- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js
+++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js
@@ -2,6 +2,7 @@ import React from 'react';
import {Link} from 'react-router';
import {Icon} from 'coral-ui';
import styles from './styles.css';
+import t from 'coral-framework/services/i18n';
const ModerationHeader = (props) => (
@@ -9,7 +10,7 @@ const ModerationHeader = (props) => (
{
props.asset ?
diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js
index 10f6732fd..65498fa15 100644
--- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js
+++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js
@@ -2,12 +2,10 @@ import React, {PropTypes} from 'react';
import CommentCount from './CommentCount';
import styles from './styles.css';
import {SelectField, Option} from 'react-mdl-selectfield';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations.json';
import {Icon} from 'coral-ui';
import {Link} from 'react-router';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
const ModerationMenu = (
{asset, allCount, acceptedCount, premodCount, rejectedCount, flaggedCount, selectSort, sort}
@@ -26,31 +24,31 @@ const ModerationMenu = (
to={getPath('all')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
-
{lang.t('modqueue.all')}
+
{t('modqueue.all')}
-
{lang.t('modqueue.premod')}
+
{t('modqueue.premod')}
-
{lang.t('modqueue.flagged')}
+
{t('modqueue.flagged')}
-
{lang.t('modqueue.approved')}
+
{t('modqueue.approved')}
-
{lang.t('modqueue.rejected')}
+
{t('modqueue.rejected')}
selectSort(sort)}>
-
-
+
+
diff --git a/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.js b/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.js
index f257bcfb9..212757f20 100644
--- a/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.js
+++ b/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.js
@@ -5,10 +5,8 @@ import styles from './SuspendUserDialog.css';
import Button from 'coral-ui/components/Button';
-import I18n from 'coral-framework/modules/i18n/i18n';
+import t, {timeago} from 'coral-framework/services/i18n';
import {dateAdd} from 'coral-framework/utils';
-import translations from '../../../translations';
-const lang = new I18n(translations);
const initialState = {step: 0, duration: '3'};
@@ -39,11 +37,11 @@ class SuspendUserDialog extends React.Component {
goToStep1 = () => {
this.setState({
step: 1,
- message: lang.t(
+ message: t(
'suspenduser.email_message_suspend',
this.props.username,
this.props.organizationName,
- lang.timeago(durationsToDate(this.state.duration)),
+ timeago(durationsToDate(this.state.duration)),
),
});
}
@@ -65,13 +63,13 @@ class SuspendUserDialog extends React.Component {
return (
- {lang.t('suspenduser.title_suspend')}
+ {t('suspenduser.title_suspend')}
- {lang.t('suspenduser.description_suspend', username)}
+ {t('suspenduser.description_suspend', username)}
@@ -103,13 +101,13 @@ class SuspendUserDialog extends React.Component {
return (
- {lang.t('suspenduser.title_notify')}
+ {t('suspenduser.title_notify')}
- {lang.t('suspenduser.description_notify', username)}
+ {t('suspenduser.description_notify', username)}
diff --git a/client/coral-admin/src/containers/ModerationQueue/helpers/moderationQueueActionsMap.js b/client/coral-admin/src/containers/ModerationQueue/helpers/moderationQueueActionsMap.js
index 09066831f..32b79a35c 100644
--- a/client/coral-admin/src/containers/ModerationQueue/helpers/moderationQueueActionsMap.js
+++ b/client/coral-admin/src/containers/ModerationQueue/helpers/moderationQueueActionsMap.js
@@ -5,10 +5,10 @@ export const actionsMap = {
};
export const menuActionsMap = {
- 'REJECT': {status: 'REJECTED', text: 'Reject', icon: 'close', key: 'r'},
- 'REJECTED': {status: 'REJECTED', text: 'Rejected', icon: 'close'},
- 'APPROVE': {status: 'ACCEPTED', text: 'Approve', icon: 'done', key: 't'},
- 'FLAGGED': {status: 'FLAGGED', text: 'Flag', icon: 'flag', filter: 'Untouched'},
- 'BAN': {status: 'BANNED', text: 'Ban User', icon: 'not interested'},
+ 'REJECT': {status: 'REJECTED', text: 'reject', icon: 'close', key: 'r'},
+ 'REJECTED': {status: 'REJECTED', text: 'rejected', icon: 'close'},
+ 'APPROVE': {status: 'ACCEPTED', text: 'approve', icon: 'done', key: 't'},
+ 'FLAGGED': {status: 'FLAGGED', text: 'flag', icon: 'flag', filter: 'Untouched'},
+ 'BAN': {status: 'BANNED', text: 'ban_user', icon: 'not interested'},
'': {icon: 'done'}
};
diff --git a/client/coral-admin/src/containers/Stories/Stories.js b/client/coral-admin/src/containers/Stories/Stories.js
index 62393cb04..292eaeaba 100644
--- a/client/coral-admin/src/containers/Stories/Stories.js
+++ b/client/coral-admin/src/containers/Stories/Stories.js
@@ -1,9 +1,8 @@
import React, {Component} from 'react';
import styles from './Stories.css';
import {connect} from 'react-redux';
-import I18n from 'coral-framework/modules/i18n/i18n';
+import t from 'coral-framework/services/i18n';
import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets';
-import translations from 'coral-admin/src/translations.json';
import {Link} from 'react-router';
import {Pager, Icon} from 'coral-ui';
@@ -82,14 +81,14 @@ class Stories extends Component {
className={closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(closed, id, statusMenuOpen)}>
{!statusMenuOpen &&
}
- {closed ? lang.t('streams.closed') : lang.t('streams.open')}
+ {closed ? t('streams.closed') : t('streams.open')}
{
statusMenuOpen &&
- {!closed ? lang.t('streams.closed') : lang.t('streams.open')}
+ {!closed ? t('streams.closed') : t('streams.open')}
}
;
@@ -121,10 +120,10 @@ class Stories extends Component {
value={search}
className={styles.searchBoxInput}
onChange={this.onSearchChange}
- placeholder={lang.t('streams.search')}/>
+ placeholder={t('streams.search')}/>
- {lang.t('streams.filter-streams')}
- {lang.t('streams.stream-status')}
+ {t('streams.filter_streams')}
+ {t('streams.stream_status')}
- {lang.t('streams.all')}
- {lang.t('streams.open')}
- {lang.t('streams.closed')}
+ {t('streams.all')}
+ {t('streams.open')}
+ {t('streams.closed')}
- {lang.t('streams.sort-by')}
+ {t('streams.sort_by')}
- {lang.t('streams.newest')}
- {lang.t('streams.oldest')}
+ {t('streams.newest')}
+ {t('streams.oldest')}
{
assetsIds.length
?
- {lang.t('streams.article')}
+ {t('streams.article')}
- {lang.t('streams.pubdate')}
+ {t('streams.pubdate')}
- {lang.t('streams.status')}
+ {t('streams.status')}
- : {lang.t('streams.empty_result')}
+ : {t('streams.empty_result')}
}
);
@@ -188,5 +187,3 @@ const mapDispatchToProps = (dispatch) => {
};
export default connect(mapStateToProps, mapDispatchToProps)(Stories);
-
-const lang = new I18n(translations);
diff --git a/client/coral-admin/src/containers/Streams/Stories.js b/client/coral-admin/src/containers/Streams/Stories.js
index 7c7a65389..86db633b2 100644
--- a/client/coral-admin/src/containers/Streams/Stories.js
+++ b/client/coral-admin/src/containers/Streams/Stories.js
@@ -1,9 +1,9 @@
import React, {Component} from 'react';
import styles from './Stories.css';
import {connect} from 'react-redux';
-import I18n from 'coral-framework/modules/i18n/i18n';
+import t from 'coral-framework/services/i18n';
import {fetchAssets, updateAssetState} from '../../actions/assets';
-import translations from '../../translations.json';
+
import {Link} from 'react-router';
import {Pager, Icon} from 'coral-ui';
@@ -81,14 +81,14 @@ class Stories extends Component {
className={closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(closed, id, statusMenuOpen)}>
{!statusMenuOpen &&
}
- {closed ? lang.t('streams.closed') : lang.t('streams.open')}
+ {closed ? t('streams.closed') : t('streams.open')}
{
statusMenuOpen &&
- {!closed ? lang.t('streams.closed') : lang.t('streams.open')}
+ {!closed ? t('streams.closed') : t('streams.open')}
}
;
@@ -116,10 +116,10 @@ class Stories extends Component {
value={search}
className={styles.searchBoxInput}
onChange={this.onSearchChange}
- placeholder={lang.t('streams.search')}/>
+ placeholder={t('streams.search')}/>
-
{lang.t('streams.filter-streams')}
-
{lang.t('streams.stream-status')}
+
{t('streams.filter_streams')}
+
{t('streams.stream_status')}
- {lang.t('streams.all')}
- {lang.t('streams.open')}
- {lang.t('streams.closed')}
+ {t('streams.all')}
+ {t('streams.open')}
+ {t('streams.closed')}
-
{lang.t('streams.sort-by')}
+
{t('streams.sort_by')}
- {lang.t('streams.newest')}
- {lang.t('streams.oldest')}
+ {t('streams.newest')}
+ {t('streams.oldest')}
{
assetsIds.length
?
- {lang.t('streams.article')}
+ {t('streams.article')}
- {lang.t('streams.pubdate')}
+ {t('streams.pubdate')}
- {lang.t('streams.status')}
+ {t('streams.status')}
- :
{lang.t('streams.empty_result')}
+ :
{t('streams.empty_result')}
}
);
@@ -183,5 +183,3 @@ const mapDispatchToProps = (dispatch) => {
};
export default connect(mapStateToProps, mapDispatchToProps)(Stories);
-
-const lang = new I18n(translations);
diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js
index 9a532856e..0af9a4a5a 100644
--- a/client/coral-admin/src/index.js
+++ b/client/coral-admin/src/index.js
@@ -8,6 +8,9 @@ import store from './services/store';
import App from './components/App';
import 'react-mdl/extra/material.js';
+import {loadPluginsTranslations} from 'coral-framework/helpers/plugins';
+
+loadPluginsTranslations();
render(
diff --git a/client/coral-admin/src/services/notification.js b/client/coral-admin/src/services/notification.js
index 01b256df0..cc25b0756 100644
--- a/client/coral-admin/src/services/notification.js
+++ b/client/coral-admin/src/services/notification.js
@@ -1,9 +1,6 @@
-import translations from 'coral-admin/src/translations';
-import I18n from 'coral-framework/modules/i18n/i18n';
+import t from 'coral-framework/services/i18n';
import {toast} from 'react-toastify';
-const lang = new I18n(translations);
-
export function success(msg) {
return toast(msg, {type: 'success'});
}
@@ -21,7 +18,7 @@ export function showMutationErrors(err) {
errors.forEach((err) => {
console.error(err);
toast(
- err.translation_key ? lang.t(`errors.${err.translation_key}`) : err,
+ err.translation_key ? t(`error.${err.translation_key}`) : err,
{type: 'error'}
);
});
diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json
deleted file mode 100644
index 68072f649..000000000
--- a/client/coral-admin/src/translations.json
+++ /dev/null
@@ -1,382 +0,0 @@
-{
- "en": {
- "errors": {
- "NOT_AUTHORIZED": "You are not authorized to perform this action.",
- "LOGIN_MAXIMUM_EXCEEDED": "You have made too many unsuccessful password attempts. Please wait."
- },
- "community": {
- "username_and_email": "Username and Email",
- "account_creation_date": "Account Creation Date",
- "newsroom_role": "Newsroom Role",
- "admin": "Administrator",
- "moderator": "Moderator",
- "staff": "Staff",
- "role": "Select role...",
- "no-results": "No users found with that user name or email address. They're hiding!",
- "status": "Status",
- "select-status": "Select status...",
- "active": "Active",
- "banned": "Banned",
- "banned-user": "Banned User",
- "loading": "Loading results",
- "flags": "Flags",
- "flaggedaccounts": "Flagged Usernames",
- "people": "People",
- "no-flagged-accounts": "The Account Flags queue is currently empty.",
- "I don't like this username": "I don't like this username",
- "This user is impersonating": "Impersonation",
- "This looks like an ad/marketing": "Spam/Ads",
- "This username is offensive": "Offensive",
- "Other": "Other",
- "ban_user": "Ban User?",
- "are_you_sure": "Are you sure you would like to ban {0}?",
- "note": "Note: Banning this user will not let them edit, comment or remove anything.",
- "cancel": "Cancel",
- "yes_ban_user": "Yes, Ban User"
- },
- "modqueue": {
- "likes": "likes",
- "all": "all",
- "approved": "approved",
- "premod": "pre-mod",
- "rejected": "rejected",
- "flagged": "flagged",
- "account": "account flags",
- "shortcuts": "Shortcuts",
- "close": "Close",
- "actions": "Actions",
- "navigation": "Navigation",
- "mod-faster": "Moderate faster with keyboard shortcuts",
- "try-these": "Try these",
- "view-more-shortcuts": "View more shortcuts",
- "shift-key": "⇧",
- "approve": "Approve comment",
- "reject": "Reject comment",
- "nextcomment": "Go to the next comment",
- "prevcomment": "Go to the previous comment",
- "singleview": "Toggle single comment edit view",
- "thismenu": "Open this menu",
- "emptyqueue": "No more comments to moderate! You're all caught up. Go have some ☕️",
- "showshortcuts": "Show Shortcuts",
- "more-detail": "More detail",
- "less-detail": "Less detail",
- "dont-like-username": "Don't like username",
- "impersonating": "Impersonating",
- "offensive": "Offensive",
- "spam/ads": "Spam/Ads",
- "other": "Other",
- "thousand": "k",
- "million": "M",
- "billion": "B"
- },
- "comment": {
- "flagged": "flagged",
- "anon": "Anonymous",
- "ban_user": "Ban User",
- "view_context": "View context",
- "banned_user": "Banned User"
- },
- "user": {
- "user_bio": "User Bio",
- "bio_flags": "flags for this bio",
- "username_flags": "flags for this username"
- },
- "embedlink": {
- "copy": "Copy to Clipboard"
- },
- "configure": {
- "sign-out": "Sign Out",
- "shortcuts": "Shortcuts",
- "closed-stream-settings": "Closed Stream Message",
- "open-stream-configuration": "This comment stream is currently open. By closing this comment stream, no new comments may be submitted and all previous comments will still be displayed.",
- "close-stream-configuration": "This comment stream is currently closed. By opening this comment stream, new comments may be submitted and displayed",
- "close-stream": "Close Stream",
- "open-stream": "Open Stream",
- "stream-settings": "Stream Settings",
- "moderation-settings": "Moderation Settings",
- "tech-settings": "Tech Settings",
- "custom-css-url": "Custom CSS URL",
- "custom-css-url-desc": "URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.",
- "dashboard": "Dashboard",
- "enable-pre-moderation": "Enable pre-moderation",
- "enable-pre-moderation-text": "Moderators must approve any comment before it is published.",
- "require-email-verification": "Require Email Verification",
- "require-email-verification-text": "New Users must verify their email before commenting",
- "include-comment-stream": "Include Comment Stream Description for Readers",
- "include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.",
- "include-text": "Include your text here.",
- "enable-premod-links": "Pre-Moderate Comments Containing Links",
- "enable-premod-links-text": "Moderators must approve any comment containing a link before its published.",
- "edit-comment-timeframe-heading": "Edit Comment Timeframe",
- "edit-comment-timeframe-text-pre": "Commenters will have",
- "edit-comment-timeframe-text-post": "seconds to edit their comments.",
- "comment-settings": "Settings",
- "embed-comment-stream": "Embed Stream",
- "banned-word-text": "Comments which contain these words or phrases (not case-sensitive) will be automatically removed from the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list.",
- "suspect-word-text": "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list.",
- "wordlist": "Banned Words",
- "banned-words-title": "Banned words list",
- "suspect-words-title": "Suspect words list",
- "save-changes": "Save Changes",
- "copy-and-paste": "Copy and paste code below into your CMS to embed your comment box in your articles",
- "moderate": "Moderate",
- "configure": "Configure",
- "community": "Community",
- "stories": "Stories",
- "closed-comments-desc": "Write a message to be displayed when when your comment stream is closed and no longer accepting comments.",
- "closed-comments-label": "Write a message...",
- "hours": "Hours",
- "days": "Days",
- "weeks": "Weeks",
- "close-after": "Close comments after",
- "comment-count-header": "Limit Comment Length",
- "comment-count-text-pre": "Comments will be limited to ",
- "comment-count-text-post": " characters",
- "comment-count-error": "Please enter a valid number.",
- "domain-list-title": "Permitted Domains",
- "domain-list-text": "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com)."
- },
- "bandialog": {
- "ban_user": "Ban User?",
- "are_you_sure": "Are you sure you would like to ban {0}?",
- "note": "Note: Banning this user will also place this comment in the Rejected queue.",
- "cancel": "Cancel",
- "yes_ban_user": "Yes, Ban User"
- },
- "suspenduser": {
- "title_suspend": "Suspend User",
- "description_suspend": "You are suspending {0}. This comment will go to the Rejected queue, and {0} will not be allowed to like, report, reply or post until the suspension time is complete.",
- "select_duration": "Select suspension duration",
- "title_reject": "We noticed you rejected a username",
- "description_reject": "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily hide their comments until they rewrite their {0}.",
-
- "title_notify": "Notify the user of their temporary suspension",
- "description_notify": "Suspending this user will temporarily disable their account and hide all of their comments on the site.",
- "no_cancel": "No, cancel",
- "yes_suspend": "Yes, suspend",
- "send": "Send",
- "bio": "bio",
- "username": "username",
- "email_subject": "Your account has been suspended",
- "email_message_reject": "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns.",
- "email_message_suspend": "Dear {0},\n\nIn accordance with {1}’s community guidelines, your account has been temporarily suspended. During the suspension, you will be unable to comment, flag or engage with fellow commenters. Please rejoin the conversation {2}.",
- "write_message": "Write a message",
- "one_hour": "1 hour",
- "hours": "{0} hours",
- "days": "{0} days",
- "suspend_user": "Suspend User",
- "cancel": "Cancel",
- "error_email_message_empty": "You must specify an E-Mail message.",
- "notify_suspend_until": "User {0} has been temporarily suspended. This suspension will automatically end {1}."
- },
- "dashboard": {
- "next-update": "{0} minutes until next update.",
- "auto-update": "Data automatically updates every five minutes or when you Reload.",
- "no_flags": "There have been no flags in the last 5 minutes! Hooray!",
- "no_likes": "There have been no likes in the last 5 minutes. All quiet.",
- "flags": "Flags",
- "no_activity": "There haven't been any comments anywhere in the last five minutes.",
- "comment_count": "comments",
- "most_flags": "Articles with the most flags"
- },
- "streams": {
- "empty_result": "No assets match this search. Maybe try widening your search?",
- "search": "Search",
- "filter-streams": "Filter Streams",
- "stream-status": "Stream Status",
- "all": "All",
- "open": "Open",
- "closed": "Closed",
- "newest": "Newest",
- "oldest": "Oldest",
- "sort-by": "Sort By",
- "open": "Open",
- "closed": "Closed",
- "article": "Story",
- "pubdate": "Publication Date",
- "status": "Stream Status"
- }
- },
- "es": {
- "errors": {
- "NOT_AUTHORIZED": "Acción no autorizada.",
- "LOGIN_MAXIMUM_EXCEEDED": "Ha realizado demasiados intentos fallidos de colocar la contraseña. Por favor espere."
- },
- "community": {
- "username_and_email": "Usuario y E-mail",
- "account_creation_date": "Fecha de creación de la cuenta",
- "newsroom_role": "Rol en la redacción",
- "admin": "Administradora",
- "moderator": "Moderadora",
- "staff": "Miembro",
- "role": "Seleccionar rol...",
- "no-results": "No se encontraron usuarixs con ese nombre de usuario o e-mail.",
- "status": "Estado",
- "select-status": "Seleccionar estado...",
- "active": "Activa",
- "banned": "Suspendido",
- "banned-user": "Usuario Suspendido",
- "loading": "Cargando resultados",
- "flags": "Reporte",
- "flaggedaccounts": "Nombres de Usuario Reportados",
- "people": "Gente",
- "no-flagged-accounts": "No hay ninguna cuenta reportada.",
- "I don't like this username": "No me gusta ese nombre de usuario",
- "This user is impersonating": "Suplantación",
- "This looks like an ad/marketing": "Spam/Propaganda",
- "This username is offensive": "Ofensivo",
- "Other": "Otros",
- "ban_user": "Quieres suspender al Usuario?",
- "are_you_sure": "Estas segura que quieres suspender a {0}?",
- "note": "Nota: Suspender a este usuario no le va a permitir (al usuario) borrar ni editar ni comentar.",
- "cancel": "Cancelar",
- "yes_ban_user": "Si, Suspendan el usuario"
- },
- "suspenduser": {
- "title_suspend": "Suspender Usuario",
- "title_reject": "Esta queriendo suspender un usuario?",
- "description_reject": "Le gustaria suspender a esta usuaria temporarianmente por su nombre de usuario? Si lo hace sus comentarios serán escondidos temporariamente hasta que puedan reescribir su nombre de usuario.",
- "title_notify": "Enviarle una nota al usuario sobre su cuenta suspendida",
- "description_notify": "Si suspende a este usuario, su cuenta va a ser deshabilitada y todos sus comentarios escondidos del sitio.",
- "no_cancel": "No, cancelar",
- "yes_suspend": "Si, suspender",
- "send": "Enviar",
- "username": "nombre de usuario",
- "email_subject": "Su cuenta ha sido suspendida temporariamente",
- "email_message_reject": "Otra persona de la comunidad recientemente marcó su nombre de usuario para ser revisado. Por su contenido, el nombre de usuario ha sido rechazado. Esto quiere decir que no puede comentar, gustar o marcar contenido hasta que modifique su nombre de usuario. Por favor, envienos un correo a moderator@newsorg.com si tiene alguna pregunta o preocupación",
- "write_message": "Escribir un mensaje",
- "loading": "Cargando resultados",
- "hour": "una hora",
- "hours": "{0} horas",
- "days": "{0} días",
- "suspend_user": "Suspender",
- "cancel": "Cancelar"
- },
- "modqueue": {
- "all": "todos",
- "approved": "aprobado",
- "likes": "gustos",
- "premod": "pre-mod",
- "rejected": "rechazado",
- "flagged": "marcado",
- "shortcuts": "Atajos de teclado",
- "mod-faster": "Moderar más rápido con atajos del teclado",
- "try-these": "Intenta estos",
- "view-more-shortcuts": "Ver más atajos",
- "shift-key": "⇧",
- "close": "Cerrar",
- "emptyqueue": "No se encontro ningún usuario. Están escondidos.",
- "showshortcuts": "Mostrar atajos",
- "more-detail": "Mas detalle",
- "less-detail": "Menos detalle",
- "dont-like-username": "No me gusta ese nombre de usuario",
- "impersonating": "Suplantación",
- "offensive": "Ofensivo",
- "spam/ads": "Spam/Propaganda",
- "other": "Otros",
- "thousand": "m",
- "million": "M",
- "billion": "B"
- },
- "comment": {
- "flagged": "marcado",
- "anon": "Anónimo",
- "view_context": "Ver contexto",
- "ban_user": "Suspender Usuario",
- "banned_user": "Usuario Suspendido"
- },
- "user": {
- "user_bio": "marcas para este usuario",
- "bio_flags": "marcas para esta biografia",
- "username_flags": "marcas para este nombre de usuario"
- },
- "configure": {
- "sign-out": "Desconectar",
- "shortcuts": "Atajos",
- "closed-stream-settings": "Mensaje a enviar cuando los comentarios están cerrados en el artículo",
- "open-stream-configuration": "Este hilo de comentarios esta abierto. Al cerrarlo, ningún nuevo comentario será publicado y todos los comentarios anteriores serán mostrados.",
- "close-stream-configuration": "Este hilo de comentario está en este momento cerrado. Al abrirlo, nuevos comentarios serán publicaods y mostrados.",
- "close-stream": "Cerrar Comentarios",
- "open-stream": "Abrir Comentarios",
- "stream-settings": "Configuración de Comentarios",
- "moderation-settings": "Configuración de Moderación",
- "tech-settings": "Configuración Técnica",
- "custom-css-url": "URL CSS a medida",
- "custom-css-url-desc": "URL de una hoja de estilo que va a sobrescribir los estilos por defecto del hilo de comentarios. Puede ser interna o externa.",
- "dashboard": "Panel",
- "enable-pre-moderation": "Habilitar pre-moderación",
- "enable-pre-moderation-text": "Los moderadores deben aprobar cada comentario antes de que sea publicado.",
- "require-email-verification": "Necesita confirmación de e-mail",
- "require-email-verification-text": "Nuevos usuarios deben verificar sus e-mails antes de comentar",
- "include-comment-stream": "Incluir la Descripción a un Hilo de Comentario para los y las Lectoras.",
- "include-comment-stream-desc": "Escribir un mensaje que será agregado a la parte de arriba del tu hilo de comentarios. Por ejemplo, un tema, guias de comunidad, etc.",
- "include-text": "Incluir tu texto aqui.",
- "comment-settings": "Configuración de Comentarios",
- "embed-comment-stream": "Colocar Hilo de Comentarios",
- "enable-premod-links": "Pre-Moderar Commentarios que contienen Enlaces",
- "enable-premod-links-text": "Los y las Moderadoras deben aprobar cualquier comentario que contengan links antes de su publicación.",
- "edit-comment-timeframe-heading": "Editar Tiempo de Comentario",
- "edit-comment-timeframe-text-pre": "Los comentaristas tendrán",
- "edit-comment-timeframe-text-post": "segundos para editar sus comentarios.",
- "wordlist": "Palabras Suspendidas y Sospechosas",
- "banned-word-text": "Comentarios que contengan estas palabras o frases, no separadas por comas y en mayusculas o minusuculas, serán automaticamente marcadas para separar los comentarios publicados.",
- "suspect-word-text": "Comentarios que contengan estas palabras o frases, considerando mayusculas y minusculas, serán automaticamente destacadas en los comentarios publicados. Escribir una palabra y apretar Enter o Tabulador para agergarla. Opcionalmente pegar una lista separada por coma.",
- "banned-words-title": "Lista de palabras prohibidas",
- "suspect-words-title": "Lista de palabras sospechosas",
- "save-changes": "Guardar Cambios",
- "copy-and-paste": "Copiar y pegar el código de más abajo en tu CMS para colocar la caja de comentarios en tus articulos",
- "moderate": "Moderar",
- "configure": "Configurar",
- "community": "Comunidad",
- "stories": "Artículos",
- "closed-comments-desc": "Escribe un mensaje que será mostrado cuando los comentarios estén cerrados y no se acepten más comentarios.",
- "closed-comments-label": "Escribe un mensaje...",
- "never": "Nunca",
- "hours": "Horas",
- "days": "Días",
- "weeks": "Semanas",
- "close-after": "Cerrar comentarios luego de",
- "comment-count-header": "Limitar el largo del comentario",
- "comment-count-text-pre": "El largo de comentarios será ",
- "comment-count-text-post": " caracteres",
- "comment-count-error": "Por favor escribe un número válido.",
- "domain-list-title": "Lista de Dominios Permitidos",
- "domain-list-text": "Agrega dominios permitidos a Talk, por ejemplo tu localhost, staging y ambientes de producción (ej. localhost:3000, staging.domain.com, domain.com)."
- },
- "embedlink": {
- "copy": "Copiar"
- },
- "bandialog": {
- "ban_user": "¿Quieres suspender el Usuario?",
- "are_you_sure": "¿Estás segura que quieres suspender a {0}?",
- "note": "Nota: Suspender a este usuario también va a colocar este comentario en la cola de Rechazados.",
- "cancel": "Cancelar",
- "yes_ban_user": "Si, Suspendan el usuario"
- },
- "dashboard": {
- "next-update": "{0} minutos hasta la siguiente actualización.",
- "auto-update": "Los datos se actualizan automaticamente cada 5 minutos o cuando recargas.",
- "no_flags": "¡Nadie ha marcado nada en los últimos 5 minutos! ¡Bravo!",
- "no_likes": "A nadie le ha gustado algún comentario en los últimos 5 minutos. Todo tranquilo.",
- "flags": "Marcados",
- "no_activity": "No hubo comentarios en los ultimos 5 minutos",
- "comment_count": "comentarios",
- "most_flags": "Articulos con más reportes"
- },
- "streams": {
- "empty_result": "No se encuentro articulo con esta busqueda. ¿Tal vez puedas extender la busqueda?",
- "search": "buscar",
- "filter-streams": "Filtrar Hilos de Comentarios",
- "stream-status": "Estado del Hilo de Comentarios",
- "all": "todxs",
- "open": "abrir",
- "closed": "cerrado",
- "newest": "más nuevoß",
- "oldest": "más viejo",
- "sort-by": "ordenar por",
- "article": "artículo",
- "pubdate": "Fecha de Pblicación",
- "status": "Estado"
- }
- }
-}
diff --git a/client/coral-configure/components/CloseCommentsInfo.js b/client/coral-configure/components/CloseCommentsInfo.js
index fae7bc5e5..b9c6371c4 100644
--- a/client/coral-configure/components/CloseCommentsInfo.js
+++ b/client/coral-configure/components/CloseCommentsInfo.js
@@ -1,25 +1,22 @@
import React from 'react';
import {Button} from 'coral-ui';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-admin/src/translations';
-
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
export default ({status, onClick}) => (
status === 'open' ? (
- {lang.t('configure.open-stream-configuration')}
+ {t('configure.open_stream_configuration')}
-
+
) : (
- {lang.t('configure.close-stream-configuration')}
+ {t('configure.close_stream_configuration')}
-
+
)
);
diff --git a/client/coral-configure/components/ConfigureCommentStream.js b/client/coral-configure/components/ConfigureCommentStream.js
index 6c00b5996..5ee9b706f 100644
--- a/client/coral-configure/components/ConfigureCommentStream.js
+++ b/client/coral-configure/components/ConfigureCommentStream.js
@@ -3,23 +3,21 @@ import {Button, Checkbox, TextField} from 'coral-ui';
import styles from './ConfigureCommentStream.css';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from '../translations.json';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
export default ({handleChange, handleApply, changed, ...props}) => (
: null
diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js
index 66ed9ad56..5e7c66555 100644
--- a/client/coral-embed-stream/src/index.js
+++ b/client/coral-embed-stream/src/index.js
@@ -11,7 +11,9 @@ import reducers from './reducers';
import store, {injectReducers} from 'coral-framework/services/store';
import AppRouter from './AppRouter';
import {pym} from 'coral-framework';
+import {loadPluginsTranslations} from 'coral-framework/helpers/plugins';
+loadPluginsTranslations();
injectReducers(reducers);
// Don't run this in the popup.
diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js
index 9c0081470..d252bb59f 100644
--- a/client/coral-framework/actions/asset.js
+++ b/client/coral-framework/actions/asset.js
@@ -2,9 +2,7 @@ import * as actions from '../constants/asset';
import coralApi from '../helpers/request';
import {addNotification} from '../actions/notification';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from './../translations';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
export const fetchAssetRequest = () => ({type: actions.FETCH_ASSET_REQUEST});
export const fetchAssetSuccess = (asset) => ({type: actions.FETCH_ASSET_SUCCESS, asset});
@@ -19,7 +17,7 @@ export const updateConfiguration = (newConfig) => (dispatch, getState) => {
dispatch(updateAssetSettingsRequest());
coralApi(`/assets/${assetId}/settings`, {method: 'PUT', body: newConfig})
.then(() => {
- dispatch(addNotification('success', lang.t('successUpdateSettings')));
+ dispatch(addNotification('success', t('framework.success_update_settings')));
dispatch(updateAssetSettingsSuccess(newConfig));
})
.catch((error) => dispatch(updateAssetSettingsFailure(error)));
@@ -30,7 +28,7 @@ export const updateOpenStream = (closedBody) => (dispatch, getState) => {
dispatch(fetchAssetRequest());
coralApi(`/assets/${assetId}/status`, {method: 'PUT', body: closedBody})
.then(() => {
- dispatch(addNotification('success', lang.t('successUpdateSettings')));
+ dispatch(addNotification('success', t('framework.success_update_settings')));
dispatch(fetchAssetSuccess(closedBody));
})
.catch((error) => dispatch(fetchAssetFailure(error)));
@@ -48,4 +46,3 @@ export const updateOpenStatus = (status) => (dispatch) => {
dispatch(updateOpenStream({closedAt: new Date().getTime()}));
}
};
-
diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js
index ee799c8cc..c9219fb67 100644
--- a/client/coral-framework/actions/auth.js
+++ b/client/coral-framework/actions/auth.js
@@ -5,9 +5,7 @@ import * as actions from '../constants/auth';
import * as Storage from '../helpers/storage';
import coralApi, {base} from '../helpers/request';
-const lang = new I18n(translations);
-import translations from './../translations';
-import I18n from '../../coral-framework/modules/i18n/i18n';
+import t from 'coral-framework/services/i18n';
export const showSignInDialog = () => (dispatch, getState) => {
const signInPopUp = window.open(
@@ -74,7 +72,7 @@ export const createUsername = (userId, formData) => (dispatch) => {
dispatch(updateUsername(formData));
})
.catch((error) => {
- dispatch(createUsernameFailure(lang.t(`error.${error.translation_key}`)));
+ dispatch(createUsernameFailure(t(`error.${error.translation_key}`)));
});
};
@@ -146,12 +144,12 @@ export const fetchSignIn = (formData) => {
// the user might not have a valid email. prompt the user user re-request the confirmation email
dispatch(
- signInFailure(lang.t('error.emailNotVerified', error.metadata))
+ signInFailure(t('error.email_not_verified', error.metadata))
);
} else {
// invalid credentials
- dispatch(signInFailure(lang.t('error.emailPasswordError')));
+ dispatch(signInFailure(t('error.email_password')));
}
});
};
@@ -236,7 +234,7 @@ export const fetchSignUp = (formData, redirectUri) => (dispatch) => {
dispatch(signUpSuccess(user));
})
.catch((error) => {
- let errorMessage = lang.t(`error.${error.message}`);
+ let errorMessage = t(`error.${error.message}`);
// if there is no translation defined, just show the error string
if (errorMessage === `error.${error.message}`) {
diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js
index 95d4116a6..b978f686d 100644
--- a/client/coral-framework/actions/user.js
+++ b/client/coral-framework/actions/user.js
@@ -2,9 +2,7 @@ import {addNotification} from '../actions/notification';
import coralApi from '../helpers/request';
import * as actions from '../constants/auth';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from './../translations';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
const editUsernameFailure = (error) => ({type: actions.EDIT_USERNAME_FAILURE, error});
const editUsernameSuccess = () => ({type: actions.EDIT_USERNAME_SUCCESS});
@@ -13,9 +11,9 @@ export const editName = (username) => (dispatch) => {
return coralApi('/account/username', {method: 'PUT', body: {username}})
.then(() => {
dispatch(editUsernameSuccess());
- dispatch(addNotification('success', lang.t('successNameUpdate')));
+ dispatch(addNotification('success', t('framework.success_name_update')));
})
.catch((error) => {
- dispatch(editUsernameFailure(lang.t(`error.${error.translation_key}`)));
+ dispatch(editUsernameFailure(t(`error.${error.translation_key}`)));
});
};
diff --git a/client/coral-framework/components/RestrictedContent.js b/client/coral-framework/components/RestrictedContent.js
index d3406ca40..01c5ddd93 100644
--- a/client/coral-framework/components/RestrictedContent.js
+++ b/client/coral-framework/components/RestrictedContent.js
@@ -1,11 +1,9 @@
import React from 'react';
+import t from 'coral-framework/services/i18n';
import RestrictedMessageBox from './RestrictedMessageBox';
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from 'coral-framework/translations.json';
-const lang = new I18n(translations);
-export default ({children, restricted, message = lang.t('contentNotAvailable'), restrictedComp}) => {
+export default ({children, restricted, message = t('framework.content_not_available'), restrictedComp}) => {
if (restricted) {
return restrictedComp ? restrictedComp :
;
} else {
@@ -16,4 +14,3 @@ export default ({children, restricted, message = lang.t('contentNotAvailable'),
);
}
};
-
diff --git a/client/coral-framework/helpers/error.js b/client/coral-framework/helpers/error.js
index 89f20c928..18c365de1 100644
--- a/client/coral-framework/helpers/error.js
+++ b/client/coral-framework/helpers/error.js
@@ -1,11 +1,9 @@
-import I18n from 'coral-framework/modules/i18n/i18n';
-import translations from './../translations';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
export default {
- email: lang.t('error.email'),
- password: lang.t('error.password'),
- username: lang.t('error.username'),
- confirmPassword: lang.t('error.confirmPassword'),
- organizationName: lang.t('error.organizationName'),
+ email: t('error.email'),
+ password: t('error.password'),
+ username: t('error.username'),
+ confirmPassword: t('error.confirm_password'),
+ organizationName: t('error.organization_name'),
};
diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js
index f14963833..02ee8c3a7 100644
--- a/client/coral-framework/helpers/plugins.js
+++ b/client/coral-framework/helpers/plugins.js
@@ -6,6 +6,7 @@ import uniq from 'lodash/uniq';
import pick from 'lodash/pick';
import plugins from 'pluginsConfig';
import {getDefinitionName, mergeDocuments} from 'coral-framework/utils';
+import {loadTranslations} from 'coral-framework/services/i18n';
export const pluginReducers = merge(
...plugins
@@ -88,3 +89,12 @@ export function getGraphQLExtensions() {
.filter((o) => o);
}
+function getTranslations() {
+ return plugins
+ .map((o) => o.module.translations)
+ .filter((o) => o);
+}
+
+export function loadPluginsTranslations() {
+ getTranslations().forEach((t) => loadTranslations(t));
+}
diff --git a/client/coral-framework/index.js b/client/coral-framework/index.js
index b85b69bcd..5cd9d6174 100644
--- a/client/coral-framework/index.js
+++ b/client/coral-framework/index.js
@@ -1,12 +1,10 @@
import pym from './services/PymConnection';
-import I18n from './modules/i18n/i18n';
import actions from './actions';
// TODO (bc): Deprecate old actions. Spreading actions is now needed.
export default {
pym,
- I18n,
actions,
...actions
};
diff --git a/client/coral-framework/modules/i18n/i18n.js b/client/coral-framework/modules/i18n/i18n.js
deleted file mode 100644
index 08fc1c451..000000000
--- a/client/coral-framework/modules/i18n/i18n.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import timeago from 'timeago.js';
-import esTA from '../../../../node_modules/timeago.js/locales/es';
-import has from 'lodash/has';
-import get from 'lodash/get';
-
-/**
- * Default locales, this should be overriden by config file
- */
-
-class i18n {
- constructor (translations) {
-
- /**
- * Register locales
- */
-
- this.locales = {'en': 'en', 'es': 'es'};
- timeago.register('es_ES', esTA);
- this.timeagoInstance = new timeago();
-
- /**
- * Load translations
- */
- let trans = translations || {en: {}};
-
- try {
- const locale = localStorage.getItem('locale') || navigator.language;
- localStorage.setItem('locale', locale);
- const lang = this.locales[locale.split('-')[0]] || 'en';
- this.translations = trans[lang];
- } catch (err) {
- this.translations = trans['en'];
- }
-
- this.setLocale = (locale) => {
- try {
- localStorage.setItem('locale', locale);
- } catch (err) {
- console.error(err);
- }
- };
-
- this.getLocale = () => (
- localStorage.getItem('locale') || navigator.locale || 'en-US'
- );
-
- /**
- * Expose the translation function
- *
- * it takes a string with the translation key and returns
- * the translation value or the key itself if not found
- * it works with nested translations (my.page.title)
- *
- * any extra parameters are optional and replace a variable marked by {0}, {1}, etc in the translation.
- */
-
- this.t = (key, ...replacements) => {
- if (has(this.translations, key)) {
- let translation = get(this.translations, key);
-
- // replace any {n} with the arguments passed to this method
- replacements.forEach((str, i) => {
- translation = translation.replace(new RegExp(`\\{${i}\\}`, 'g'), str);
- });
- return translation;
- } else {
- console.warn(`${key} language key not set`);
- return key;
- }
- };
-
- this.timeago = (time) => {
- return this.timeagoInstance.format(new Date(time));
- };
- }
-}
-
-export default i18n;
diff --git a/client/coral-framework/services/i18n.js b/client/coral-framework/services/i18n.js
new file mode 100644
index 000000000..6a9b07d05
--- /dev/null
+++ b/client/coral-framework/services/i18n.js
@@ -0,0 +1,81 @@
+import ta from 'timeago.js';
+import has from 'lodash/has';
+import get from 'lodash/get';
+import merge from 'lodash/merge';
+
+import esTA from '../../../node_modules/timeago.js/locales/es';
+import en from '../../../locales/en.yml';
+import es from '../../../locales/es.yml';
+
+// Translations are happening at https://www.transifex.com/the-coral-project/talk-1/dashboard/.
+
+const defaultLanguage = 'en';
+const translations = {...en, ...es};
+
+let lang;
+let timeagoInstance;
+
+function setLocale(locale) {
+ try {
+ localStorage.setItem('locale', locale);
+ } catch (err) {
+ console.error(err);
+ }
+}
+
+function getLocale() {
+ return (localStorage.getItem('locale') || navigator.language || defaultLanguage).split('-')[0];
+}
+
+function init() {
+ const locale = getLocale();
+ setLocale(locale);
+
+ // Extract language key.
+ lang = locale.split('-')[0];
+
+ // Check if we have a translation in this language.
+ if (!(lang in translations)) {
+ lang = defaultLanguage;
+ }
+
+ ta.register('es', esTA);
+ timeagoInstance = ta();
+}
+
+export function loadTranslations(newTranslations) {
+ merge(translations, newTranslations);
+}
+
+export function timeago(time) {
+ return timeagoInstance.format(new Date(time), lang);
+}
+
+/**
+ * Expose the translation function
+ *
+ * it takes a string with the translation key and returns
+ * the translation value or the key itself if not found
+ * it works with nested translations (my.page.title)
+ *
+ * any extra parameters are optional and replace a variable marked by {0}, {1}, etc in the translation.
+ */
+export function t(key, ...replacements) {
+ const fullKey = `${lang}.${key}`;
+ if (has(translations, fullKey)) {
+ let translation = get(translations, fullKey);
+
+ // replace any {n} with the arguments passed to this method
+ replacements.forEach((str, i) => {
+ translation = translation.replace(new RegExp(`\\{${i}\\}`, 'g'), str);
+ });
+ return translation;
+ } else {
+ console.warn(`${fullKey} language key not set`);
+ return key;
+ }
+}
+
+export default t;
+
+init();
diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json
deleted file mode 100644
index 2ea58e159..000000000
--- a/client/coral-framework/translations.json
+++ /dev/null
@@ -1,112 +0,0 @@
-{
- "en": {
- "MY_COMMENTS": "My Comments",
- "profile": "Profile",
- "myProfile": "My profile",
- "successUpdateSettings": "The changes you have made have been applied to the comment stream on this article",
- "successNameUpdate": "Your username has been updated",
- "contentNotAvailable": "This content is not available",
- "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Report, or write comments. Please contact us if you have any questions.",
-
- "temporarilySuspended": "In accordance with {0}'s community guidlines, your account has been temporarily suspended. Please rejoin the conversation {1}.",
- "editName": {
- "msg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. Please contact us if you have any questions.",
- "label": "New Username",
- "button": "Submit",
- "error": "Usernames can contain letters, numbers and _ only"
- },
- "viewMoreComments": "view more comments",
- "showAllComments": "Show all comments",
- "viewReply": "view reply",
- "viewAllRepliesInitial": "view all {0} replies",
- "viewAllReplies": "view {0} replies",
- "newCount": "View {0} new {1}",
- "comment": "comment",
- "comments": "comments",
- "commentIsIgnored": "This comment is hidden because you ignored this user.",
- "editComment": {
- "bodyInputLabel": "Edit this comment",
- "saveButton": "Save changes",
- "editWindowExpired": "You can no longer edit this comment. The time window to do so has expired. Why not post another one?",
- "editWindowExpiredClose": "Close",
- "editWindowTimerPrefix": "Edit Window: ",
- "second": "second",
- "secondsPlural": "seconds",
- "unexpectedError": "Unexpected error while saving changes. Sorry!"
- },
- "error": {
- "COMMENT_TOO_SHORT": "Your comment must have something in it",
- "EDIT_WINDOW_ENDED": "You can no longer edit this comment. The time window to do so has expired.",
- "emailNotVerified": "Email address {0} not verified.",
- "email": "Not a valid E-Mail",
- "networkError": "Failed to connect to server. Check your internet connection and try again.",
- "password": "Password must be at least 8 characters",
- "username": "Usernames can contain letters, numbers and _ only",
- "confirmPassword": "Passwords don't match. Please, check again",
- "organizationName": "Organization name must only contain letters or numbers.",
- "emailPasswordError": "Email and/or password combination incorrect.",
- "EMAIL_REQUIRED": "An email address is required",
- "PASSWORD_REQUIRED": "Must input a password",
- "PASSWORD_LENGTH": "Password is too short",
- "EMAIL_IN_USE": "Email address already in use",
- "EMAIL_USERNAME_IN_USE": "Email address or username already in use",
- "USERNAME_IN_USE": "Username already in use",
- "USERNAME_REQUIRED": "Must input a username",
- "NO_SPECIAL_CHARACTERS": "Usernames can contain letters, numbers and _ only",
- "PROFANITY_ERROR": "Usernames must not contain profanity. Please contact the administrator if you believe this to be in error.",
- "NOT_AUTHORIZED": "Not authorized.",
- "EDIT_USERNAME_NOT_AUTHORIZED": "You do not have permission to update your username."
- }
- },
- "es": {
- "profile": "Pérfil",
- "MY_COMMENTS": "Mis Comentarios",
- "myProfile": "Mi pérfil",
- "successUpdateSettings": "La configuración de este articulo fue actualizada",
- "successBioUpdate": "Tu biografia fue actualizada",
- "contentNotAvailable": "El contenido no se encuentra disponible",
- "bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes gustar, marcar o escribir commentarios.",
- "editNameMsg": "",
- "viewMoreComments": "Ver commentarios más",
- "viewReply": "ver respuesta",
- "viewAllRepliesInitial": "ver todas las {0} respuestas",
- "viewAllReplies": "ver {0} respuestas",
- "newCount": "Ver {0} {1} más",
- "comment": "commentario",
- "comments": "commentarios",
- "commentIsIgnored": "Este comentario está escondido porque has ignorado al usuario.",
- "showAllComments": "Mostrar todos los comentarios",
- "editComment": {
- "bodyInputLabel": "Editar este comentario",
- "saveButton": "Guardar cambios",
- "editWindowExpired": "Ya no puedes editar este comentario. La ventana de tiempo para hacerlo ha caducado. ¿Por qué no publicar otro?",
- "editWindowExpiredClose": "Cerca",
- "editWindowTimerPrefix": "Ventana de edición: ",
- "second": "segundo",
- "secondsPlural": "segundos",
- "unexpectedError": "Unexpected error while saving changes. Sorry!"
- },
- "error": {
- "editWindowExpired": "Ya no puedes editar este comentario. La ventana de tiempo para hacerlo ha caducado.",
- "emailNotVerified": "E-mail {0} no verificado.",
- "email": "No es un e-mail válido",
- "networkError": "Error al conectar con el servidor. Compruebe su conexión a Internet y vuelva a intentarlo.",
- "password": "La contraseña debe tener por lo menos 8 caracteres",
- "username": "Los nombres pueden contener letras, números y _",
- "organizationName": "El nombre de la organización debe contener letras y/o números.",
- "confirmPassword": "Las contraseñas no coinciden",
- "emailPasswordError": "E-mail y/o contraseña incorrecta.",
- "EMAIL_REQUIRED": "Se requiere un e-mail",
- "PASSWORD_REQUIRED": "Debe ingresar una contraseña",
- "PASSWORD_LENGTH": "La contraseña es muy corta",
- "EMAIL_IN_USE": "El e-mail se encuentra en uso",
- "EMAIL_USERNAME_IN_USE": "E-mail o Nombre en uso.",
- "USERNAME_IN_USE": "Nombre en uso.",
- "USERNAME_REQUIRED": "Debe ingresar un nombre",
- "NO_SPECIAL_CHARACTERS": "Los nombres pueden contener letras, números y _",
- "PROFANITY_ERROR": "Los nombres no pueden contener blasfemias. Por favor contacte al o la administradora si cree que esto es un error",
- "NOT_AUTHORIZED": "Acción no autorizada.",
- "EDIT_USERNAME_NOT_AUTHORIZED": "No tiene permiso para editar el nombre de usuario."
- }
- }
-}
diff --git a/client/coral-plugin-best/BestButton.js b/client/coral-plugin-best/BestButton.js
index 639d496cf..febf26991 100644
--- a/client/coral-plugin-best/BestButton.js
+++ b/client/coral-plugin-best/BestButton.js
@@ -1,6 +1,7 @@
import React, {Component, PropTypes} from 'react';
-import {I18n} from '../coral-framework';
-import translations from './translations.json';
+
+import t from 'coral-framework/services/i18n';
+
import {Icon} from 'coral-ui';
import classnames from 'classnames';
@@ -12,7 +13,6 @@ export const commentIsBest = ({tags} = {}) => {
};
const name = 'coral-plugin-best';
-const lang = new I18n(translations);
// It would be best if the backend/api held this business logic
const canModifyBestTag = ({roles = []} = {}) => roles && ['ADMIN', 'MODERATOR'].some((role) => roles.includes(role));
@@ -20,7 +20,7 @@ const canModifyBestTag = ({roles = []} = {}) => roles && ['ADMIN', 'MODERATOR'].
// Put this on a comment to show that it is best
export const BestIndicator = ({children =
}) => (
-
+
{ children }
);
@@ -98,7 +98,7 @@ export class BestButton extends Component {
);
diff --git a/client/coral-plugin-best/translations.json b/client/coral-plugin-best/translations.json
deleted file mode 100644
index 6eb4b0572..000000000
--- a/client/coral-plugin-best/translations.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "en": {
- "setBest": "Tag as Best",
- "unsetBest": "Untag as Best",
- "commentIsBest": "This comment is one of the best"
- },
- "es": {
- "setBest": "Etiquetar como el mejor",
- "unsetBest": "Desetiquetar como el mejor",
- "commentIsBest": "Este comentario es uno de los mejores"
- }
-}
diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js
index 764984634..8a2517387 100644
--- a/client/coral-plugin-comment-count/CommentCount.js
+++ b/client/coral-plugin-comment-count/CommentCount.js
@@ -1,11 +1,12 @@
import React, {PropTypes} from 'react';
-import {I18n} from '../coral-framework';
-import translations from './translations.json';
+
+import t from 'coral-framework/services/i18n';
+
const name = 'coral-plugin-comment-count';
const CommentCount = ({count}) => {
return
- {`${count} ${count === 1 ? lang.t('comment') : lang.t('comment-plural')}`}
+ {`${count} ${count === 1 ? t('comment.comment') : t('comment_plural')}`}
;
};
@@ -14,5 +15,3 @@ CommentCount.propTypes = {
};
export default CommentCount;
-
-const lang = new I18n(translations);
diff --git a/client/coral-plugin-comment-count/translations.json b/client/coral-plugin-comment-count/translations.json
deleted file mode 100644
index f212810e9..000000000
--- a/client/coral-plugin-comment-count/translations.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "en": {
- "comment": "Comment",
- "comment-plural": "Comments"
- },
- "es": {
- "comment": "Comentario",
- "comment-plural": "Comentarios"
- }
-}
diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js
index 984e6a9ad..ab318be13 100644
--- a/client/coral-plugin-commentbox/CommentBox.js
+++ b/client/coral-plugin-commentbox/CommentBox.js
@@ -1,6 +1,7 @@
import React, {PropTypes} from 'react';
-import {I18n} from '../coral-framework';
-import translations from './translations.json';
+
+import t from 'coral-framework/services/i18n';
+
import Slot from 'coral-framework/components/Slot';
import {connect} from 'react-redux';
import {CommentForm} from './CommentForm';
@@ -11,9 +12,9 @@ export const name = 'coral-plugin-commentbox';
// if needed
export const notifyForNewCommentStatus = (addNotification, status) => {
if (status === 'REJECTED') {
- addNotification('error', lang.t('comment-post-banned-word'));
+ addNotification('error', t('comment_box.comment_post_banned_word'));
} else if (status === 'PREMOD') {
- addNotification('success', lang.t('comment-post-notif-premod'));
+ addNotification('success', t('comment_box.comment_post_notif_premod'));
}
};
@@ -156,10 +157,10 @@ class CommentBox extends React.Component {
key={this.state.postedCount}
defaultValue={this.props.defaultValue}
bodyInputId={isReply ? 'replyText' : 'commentText'}
- bodyLabel={isReply ? lang.t('reply') : lang.t('comment')}
+ bodyLabel={isReply ? t('comment_box.reply') : t('comment.comment')}
maxCharCount={maxCharCount}
charCountEnable={this.props.charCountEnable}
- bodyPlaceholder={lang.t('comment')}
+ bodyPlaceholder={t('comment.comment')}
bodyInputId={isReply ? 'replyText' : 'commentText'}
saveComment={authorId && this.postComment}
buttonContainerStart={ ({commentBox});
export default connect(mapStateToProps, null)(CommentBox);
-
-const lang = new I18n(translations);
diff --git a/client/coral-plugin-commentbox/CommentForm.js b/client/coral-plugin-commentbox/CommentForm.js
index 5dc52af4f..4ce32c11f 100644
--- a/client/coral-plugin-commentbox/CommentForm.js
+++ b/client/coral-plugin-commentbox/CommentForm.js
@@ -1,13 +1,11 @@
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
import classnames from 'classnames';
-import {I18n} from '../coral-framework';
-import translations from './translations.json';
import Slot from 'coral-framework/components/Slot';
import {name} from './CommentBox';
-const lang = new I18n(translations);
+import t from 'coral-framework/services/i18n';
/**
* Common UI for Creating or Editing a Comment
@@ -56,9 +54,9 @@ export class CommentForm extends React.Component {
}
static get defaultProps() {
return {
- bodyLabel: lang.t('comment'),
- bodyPlaceholder: lang.t('comment'),
- submitText: lang.t('post'),
+ bodyLabel: t('comment_box.comment'),
+ bodyPlaceholder: t('comment_box.comment'),
+ submitText: t('comment_box.post'),
saveButtonCStyle: 'darkGrey',
saveCommentEnabled: () => true,
};
@@ -109,7 +107,7 @@ export class CommentForm extends React.Component {
{
this.props.charCountEnable &&
maxCharCount ? `${name}-char-max` : ''}`}>
- {maxCharCount && `${maxCharCount - length} ${lang.t('characters-remaining')}`}
+ {maxCharCount && `${maxCharCount - length} ${t('comment_box.characters_remaining')}`}
}
@@ -120,7 +118,7 @@ export class CommentForm extends React.Component {
cStyle='darkGrey'
className={classnames(`${name}-cancel-button`, buttonClass)}
onClick={this.props.cancelButtonClicked}>
- {lang.t('cancel')}
+ {t('comment_box.cancel')}
)
}
diff --git a/client/coral-plugin-commentbox/translations.json b/client/coral-plugin-commentbox/translations.json
deleted file mode 100644
index 4110df75d..000000000
--- a/client/coral-plugin-commentbox/translations.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "en": {
- "post": "Post",
- "cancel": "Cancel",
- "reply": "Reply",
- "comment": "Post a comment",
- "name": "Name",
- "comment-post-notif": "Your comment has been posted.",
- "comment-post-notif-premod": "Thank you for posting. Our moderation team will review your comment shortly.",
- "comment-post-banned-word": "Your comment contains one or more words that are not permitted, so it will not be published. If you think this message is incorrect, please contact our moderation team.",
- "characters-remaining": " characters remaining"
- },
- "es": {
- "post": "Publicar",
- "cancel": "Cancelar",
- "reply": "Responder",
- "comment": "Publicar un comentario",
- "name": "Nombre",
- "comment-post-notif": "Tu comentario ha sido publicado.",
- "comment-post-notif-premod": "Gracias por el comentario. Nuestro equipo de moderación va a revisarlo muy pronto.",
- "comment-post-banned-word": "Tu comentario contiene una o más palabras que no estan permitidas en nuestro espacio, por lo que no será publicado. Si crees que es un error, por favor contacta a nuestro equipo de moderación.",
- "characters-remaining": "carácteres restantes"
- }
-}
diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js
index 1613d5aa2..9a772f102 100644
--- a/client/coral-plugin-flags/FlagButton.js
+++ b/client/coral-plugin-flags/FlagButton.js
@@ -1,6 +1,7 @@
import React, {Component} from 'react';
-import {I18n} from '../coral-framework';
-import translations from './translations.json';
+
+import t from 'coral-framework/services/i18n';
+
import {PopupMenu, Button} from 'coral-ui';
import onClickOutside from 'react-onclickoutside';
@@ -141,8 +142,8 @@ class FlagButton extends Component {
className={`${name}-button`}>
{
flagged
- ?
{lang.t('reported')}
- :
{lang.t('report')}
+ ?
{t('reported')}
+ :
{t('report')}
}