summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
authorenahum <nahumhbl@gmail.com>2016-05-27 09:25:37 -0300
committerJoram Wilander <jwawilander@gmail.com>2016-05-27 08:25:37 -0400
commita2c183f401bdf8e9abddd35d4561d42034325046 (patch)
tree66565a9e21ad6b47b4dca18113ad9be48bd98492 /webapp/components
parent15e427e806716b372f7fbef12d922f847ccc71b5 (diff)
downloadchat-a2c183f401bdf8e9abddd35d4561d42034325046.tar.gz
chat-a2c183f401bdf8e9abddd35d4561d42034325046.tar.bz2
chat-a2c183f401bdf8e9abddd35d4561d42034325046.zip
PLT-1800 Load server side locale from the config.json (#3092)
* PLT-1800 Load server side locale from the config.json * Add support for locales with country specifics * Fix localization on served locale file as plain/text
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/admin_console/admin_sidebar.jsx9
-rw-r--r--webapp/components/admin_console/localization_settings.jsx145
-rw-r--r--webapp/components/admin_console/multiselect_settings.jsx80
-rw-r--r--webapp/components/root.jsx39
-rw-r--r--webapp/components/user_settings/user_settings_display.jsx11
5 files changed, 266 insertions, 18 deletions
diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx
index cdb7e29d5..9548a7763 100644
--- a/webapp/components/admin_console/admin_sidebar.jsx
+++ b/webapp/components/admin_console/admin_sidebar.jsx
@@ -293,6 +293,15 @@ export default class AdminSidebar extends React.Component {
}
/>
<AdminSidebarSection
+ name='localization'
+ title={
+ <FormattedMessage
+ id='admin.sidebar.localization'
+ defaultMessage='Localization'
+ />
+ }
+ />
+ <AdminSidebarSection
name='users_and_teams'
title={
<FormattedMessage
diff --git a/webapp/components/admin_console/localization_settings.jsx b/webapp/components/admin_console/localization_settings.jsx
new file mode 100644
index 000000000..6876e0c36
--- /dev/null
+++ b/webapp/components/admin_console/localization_settings.jsx
@@ -0,0 +1,145 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as I18n from 'i18n/i18n.jsx';
+
+import AdminSettings from './admin_settings.jsx';
+import {FormattedMessage} from 'react-intl';
+import SettingsGroup from './settings_group.jsx';
+import DropdownSetting from './dropdown_setting.jsx';
+import MultiSelectSetting from './multiselect_settings.jsx';
+
+export default class LocalizationSettings extends AdminSettings {
+ constructor(props) {
+ super(props);
+
+ this.getConfigFromState = this.getConfigFromState.bind(this);
+
+ this.renderSettings = this.renderSettings.bind(this);
+ this.canSave = this.canSave.bind(this);
+
+ const locales = I18n.getAllLanguages();
+
+ this.state = Object.assign(this.state, {
+ hasErrors: false,
+ defaultServerLocale: props.config.LocalizationSettings.DefaultServerLocale,
+ defaultClientLocale: props.config.LocalizationSettings.DefaultClientLocale,
+ availableLocales: props.config.LocalizationSettings.AvailableLocales.split(','),
+ languages: Object.keys(locales).map((l) => {
+ return {value: locales[l].value, text: locales[l].name};
+ })
+ });
+ }
+
+ canSave() {
+ return this.state.availableLocales.join(',').indexOf(this.state.defaultClientLocale) !== -1;
+ }
+
+ getConfigFromState(config) {
+ config.LocalizationSettings.DefaultServerLocale = this.state.defaultServerLocale;
+ config.LocalizationSettings.DefaultClientLocale = this.state.defaultClientLocale;
+ config.LocalizationSettings.AvailableLocales = this.state.availableLocales.join(',');
+
+ return config;
+ }
+
+ renderTitle() {
+ return (
+ <h3>
+ <FormattedMessage
+ id='admin.general.title'
+ defaultMessage='General Settings'
+ />
+ </h3>
+ );
+ }
+
+ renderSettings() {
+ return (
+ <SettingsGroup
+ header={
+ <FormattedMessage
+ id='admin.general.localization'
+ defaultMessage='Localization'
+ />
+ }
+ >
+ <DropdownSetting
+ id='defaultServerLocale'
+ values={this.state.languages}
+ label={
+ <FormattedMessage
+ id='admin.general.localization.serverLocaleTitle'
+ defaultMessage='Default Server Language:'
+ />
+ }
+ value={this.state.defaultServerLocale}
+ onChange={this.handleChange}
+ helpText={
+ <FormattedMessage
+ id='admin.general.localization.serverLocaleDescription'
+ defaultMessage='This setting sets the default language for the system messages and logs. (NEED SERVER RESTART)'
+ />
+ }
+ />
+ <DropdownSetting
+ id='defaultClientLocale'
+ values={this.state.languages}
+ label={
+ <FormattedMessage
+ id='admin.general.localization.clientLocaleTitle'
+ defaultMessage='Default Client Language:'
+ />
+ }
+ value={this.state.defaultClientLocale}
+ onChange={this.handleChange}
+ helpText={
+ <FormattedMessage
+ id='admin.general.localization.clientLocaleDescription'
+ defaultMessage="This setting sets the Default language for newly created users and for pages where the user hasn't loggged in."
+ />
+ }
+ />
+ <MultiSelectSetting
+ id='availableLocales'
+ values={this.state.languages}
+ label={
+ <FormattedMessage
+ id='admin.general.localization.availableLocalesTitle'
+ defaultMessage='Available Languages:'
+ />
+ }
+ selected={this.state.availableLocales}
+ mustBePresent={this.state.defaultClientLocale}
+ onChange={this.handleChange}
+ helpText={
+ <FormattedMessage
+ id='admin.general.localization.availableLocalesDescription'
+ defaultMessage='This setting determines the available languages that a user can set using the Account Settings.'
+ />
+ }
+ noResultText={
+ <FormattedMessage
+ id='admin.general.localization.availableLocalesNoResults'
+ defaultMessage='No results found'
+ />
+ }
+ errorText={
+ <FormattedMessage
+ id='admin.general.localization.availableLocalesError'
+ defaultMessage='There has to be at least one language available'
+ />
+ }
+ notPresent={
+ <FormattedMessage
+ id='admin.general.localization.availableLocalesNotPresent'
+ defaultMessage='The default client language must be included in the available list'
+ />
+ }
+ />
+ </SettingsGroup>
+ );
+ }
+} \ No newline at end of file
diff --git a/webapp/components/admin_console/multiselect_settings.jsx b/webapp/components/admin_console/multiselect_settings.jsx
new file mode 100644
index 000000000..deba983de
--- /dev/null
+++ b/webapp/components/admin_console/multiselect_settings.jsx
@@ -0,0 +1,80 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+import React from 'react';
+import ReactSelect from 'react-select';
+
+import Setting from './setting.jsx';
+import FormError from 'components/form_error.jsx';
+
+export default class MultiSelectSetting extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleChange = this.handleChange.bind(this);
+ this.state = {error: false};
+ }
+
+ handleChange(newValue) {
+ const values = newValue.map((n) => {
+ return n.value;
+ });
+
+ if (!newValue || newValue.length === 0) {
+ this.setState({error: this.props.errorText});
+ } else if (this.props.mustBePresent && values.join(',').indexOf(this.props.mustBePresent) === -1) {
+ this.setState({error: this.props.notPresent});
+ } else {
+ this.props.onChange(this.props.id, values);
+ this.setState({error: false});
+ }
+ }
+
+ componentWillReceiveProps(newProps) {
+ if (newProps.mustBePresent && newProps.selected.join(',').indexOf(newProps.mustBePresent) === -1) {
+ this.setState({error: this.props.notPresent});
+ } else {
+ this.setState({error: false});
+ }
+ }
+
+ render() {
+ return (
+ <Setting
+ label={this.props.label}
+ inputId={this.props.id}
+ helpText={this.props.helpText}
+ >
+ <ReactSelect
+ id={this.props.id}
+ multi={true}
+ labelKey='text'
+ options={this.props.values}
+ joinValues={true}
+ disabled={this.props.disabled}
+ noResultsText={this.props.noResultText}
+ onChange={this.handleChange}
+ value={this.props.selected}
+ />
+ <FormError error={this.state.error}/>
+ </Setting>
+ );
+ }
+}
+
+MultiSelectSetting.defaultProps = {
+ disabled: false
+};
+
+MultiSelectSetting.propTypes = {
+ id: React.PropTypes.string.isRequired,
+ values: React.PropTypes.array.isRequired,
+ label: React.PropTypes.node.isRequired,
+ selected: React.PropTypes.array.isRequired,
+ mustBePresent: React.PropTypes.string,
+ onChange: React.PropTypes.func.isRequired,
+ disabled: React.PropTypes.bool,
+ helpText: React.PropTypes.node,
+ noResultText: React.PropTypes.node,
+ errorText: React.PropTypes.node,
+ notPresent: React.PropTypes.node
+}; \ No newline at end of file
diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx
index f4656d45f..c8283cbf1 100644
--- a/webapp/components/root.jsx
+++ b/webapp/components/root.jsx
@@ -6,6 +6,7 @@
import * as GlobalActions from 'actions/global_actions.jsx';
import LocalizationStore from 'stores/localization_store.jsx';
+import Client from 'utils/web_client.jsx';
import {IntlProvider} from 'react-intl';
@@ -26,9 +27,25 @@ export default class Root extends React.Component {
this.localizationChanged = this.localizationChanged.bind(this);
this.redirectIfNecessary = this.redirectIfNecessary.bind(this);
+
+ // Ya....
+ /*eslint-disable */
+ if (window.mm_config.SegmentDeveloperKey != null && window.mm_config.SegmentDeveloperKey !== "") {
+ !function(){var analytics=global.window.analytics=global.window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
+ analytics.load(window.mm_config.SegmentDeveloperKey);
+ analytics.page();
+ }}();
+ }
+ /*eslint-enable */
+
+ // Fastclick
+ FastClick.attach(document.body);
}
localizationChanged() {
- this.setState({locale: LocalizationStore.getLocale(), translations: LocalizationStore.getTranslations()});
+ const locale = LocalizationStore.getLocale();
+
+ Client.setAcceptLanguage(locale);
+ this.setState({locale, translations: LocalizationStore.getTranslations()});
}
redirectIfNecessary(props) {
@@ -46,27 +63,15 @@ export default class Root extends React.Component {
this.redirectIfNecessary(newProps);
}
componentWillMount() {
+ // Redirect if Necessary
+ this.redirectIfNecessary(this.props);
+ }
+ componentDidMount() {
// Setup localization listener
LocalizationStore.addChangeListener(this.localizationChanged);
- // Ya....
- /*eslint-disable */
- if (window.mm_config.SegmentDeveloperKey != null && window.mm_config.SegmentDeveloperKey !== "") {
- !function(){var analytics=global.window.analytics=global.window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
- analytics.load(window.mm_config.SegmentDeveloperKey);
- analytics.page();
- }}();
- }
- /*eslint-enable */
-
- // Fastclick
- FastClick.attach(document.body);
-
// Get our localizaiton
GlobalActions.loadBrowserLocale();
-
- // Redirect if Necessary
- this.redirectIfNecessary(this.props);
}
componentWillUnmount() {
LocalizationStore.removeChangeListener(this.localizationChanged);
diff --git a/webapp/components/user_settings/user_settings_display.jsx b/webapp/components/user_settings/user_settings_display.jsx
index 16175d4de..98d4ed7da 100644
--- a/webapp/components/user_settings/user_settings_display.jsx
+++ b/webapp/components/user_settings/user_settings_display.jsx
@@ -641,7 +641,11 @@ export default class UserSettingsDisplay extends React.Component {
);
}
+ const userLocale = this.props.user.locale;
if (this.props.activeSection === 'languages') {
+ if (!I18n.isLanguageAvailable(userLocale)) {
+ this.props.user.locale = global.window.mm_config.DefaultClientLocale;
+ }
languagesSection = (
<ManageLanguages
user={this.props.user}
@@ -652,7 +656,12 @@ export default class UserSettingsDisplay extends React.Component {
/>
);
} else {
- var locale = I18n.getLanguageInfo(this.props.user.locale).name;
+ let locale;
+ if (I18n.isLanguageAvailable(userLocale)) {
+ locale = I18n.getLanguageInfo(userLocale).name;
+ } else {
+ locale = I18n.getLanguageInfo(global.window.mm_config.DefaultClientLocale).name;
+ }
languagesSection = (
<SettingItemMin