Translate react strings.
Split language detectors to be web/native dependent. Take care of strings that contain html.
This commit is contained in:
parent
e3d4152e32
commit
c361e1e31a
|
@ -40,7 +40,9 @@
|
|||
},
|
||||
"welcomepage":{
|
||||
"go": "GO",
|
||||
"join": "JOIN",
|
||||
"roomname": "Enter room name",
|
||||
"roomnamePlaceHolder": "room name",
|
||||
"disable": "Don't show this page again",
|
||||
"feature1": {
|
||||
"title": "Simple to use",
|
||||
|
@ -73,7 +75,10 @@
|
|||
"feature8": {
|
||||
"title": "Usage statistics",
|
||||
"content": "Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems."
|
||||
}
|
||||
},
|
||||
"privacy": "Privacy",
|
||||
"sendFeedback": "Send feedback",
|
||||
"terms": "Terms"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
|
@ -110,6 +115,15 @@
|
|||
"profile": "Edit your profile",
|
||||
"raiseHand": "Raise / Lower your hand"
|
||||
},
|
||||
"unsupportedPage": {
|
||||
"onlySupportedBy": "This application is currently only supported by",
|
||||
"download": "DOWNLOAD",
|
||||
"joinConversation": "Join the conversation",
|
||||
"startConference": "Start a conference",
|
||||
"joinConversationMobile": "You need <strong>__app__</strong> to join a conversation on your mobile",
|
||||
"downloadApp": "Download the App",
|
||||
"availableApp": "or if you already have it<br /><strong>then</strong>"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Open / close chat",
|
||||
"filmstrip": "Show / hide videos",
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"autosize": "^1.18.13",
|
||||
"bootstrap": "3.1.1",
|
||||
"i18next": "7.0.0",
|
||||
"i18next-browser-languagedetector": "*",
|
||||
"i18next-browser-languagedetector": "1.0.1",
|
||||
"i18next-xhr-backend": "1.3.0",
|
||||
"jitsi-meet-logger": "jitsi/jitsi-meet-logger",
|
||||
"jquery": "~2.1.1",
|
||||
|
@ -40,6 +40,7 @@
|
|||
"react-native-background-timer": "1.0.0",
|
||||
"react-native-immersive": "0.0.4",
|
||||
"react-native-keep-awake": "^2.0.2",
|
||||
"react-native-locale-detector": "1.0.1 ",
|
||||
"react-native-prompt": "^1.0.0",
|
||||
"react-native-vector-icons": "^4.0.0",
|
||||
"react-native-webrtc": "jitsi/react-native-webrtc",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { translate } from '../../translation';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
@ -18,7 +19,7 @@ const _RIGHT_WATERMARK_STYLE = {
|
|||
* A Web Component which renders watermarks such as Jits, brand, powered by,
|
||||
* etc.
|
||||
*/
|
||||
export class Watermarks extends Component {
|
||||
class WatermarksComponent extends Component {
|
||||
state = {
|
||||
brandWatermarkLink: String,
|
||||
jitsiWatermarkLink: String,
|
||||
|
@ -139,12 +140,14 @@ export class Watermarks extends Component {
|
|||
*/
|
||||
_renderPoweredBy() {
|
||||
if (this.state.showPoweredBy) {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<a
|
||||
className = 'poweredby'
|
||||
href = 'http://jitsi.org'
|
||||
target = '_new'>
|
||||
<span data-i18n = 'poweredby' /> jitsi.org
|
||||
<span>{t('poweredby')} jitsi.org</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
@ -152,3 +155,5 @@ export class Watermarks extends Component {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const Watermarks = translate(WatermarksComponent);
|
||||
|
|
|
@ -12,7 +12,7 @@ export default {
|
|||
/**
|
||||
* The actual lookup.
|
||||
*
|
||||
* @returns {string} the default language if any.
|
||||
* @returns {string} The default language if any.
|
||||
*/
|
||||
lookup() {
|
||||
return config.defaultLanguage;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import locale from 'react-native-locale-detector';
|
||||
|
||||
/**
|
||||
* A language detector that uses native locale.
|
||||
*/
|
||||
export default {
|
||||
init: Function.prototype,
|
||||
type: 'languageDetector',
|
||||
detect: () => locale,
|
||||
cacheUserLanguage: Function.prototype
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/* global interfaceConfig */
|
||||
import Browser from 'i18next-browser-languagedetector';
|
||||
import ConfigLanguageDetector from './ConfigLanguageDetector';
|
||||
|
||||
/**
|
||||
* List of detectors to use in their order.
|
||||
*
|
||||
* @type {[*]}
|
||||
*/
|
||||
const detectors = [ 'querystring', 'localStorage', 'configLanguageDetector' ];
|
||||
|
||||
/**
|
||||
* Allow i18n to detect the system language from the browser.
|
||||
*/
|
||||
if (interfaceConfig.LANG_DETECTION) {
|
||||
detectors.push('navigator');
|
||||
}
|
||||
|
||||
/**
|
||||
* The language detectors.
|
||||
*/
|
||||
const browser = new Browser(null, {
|
||||
order: detectors,
|
||||
lookupQuerystring: 'lang',
|
||||
lookupLocalStorage: 'language',
|
||||
caches: [ 'localStorage' ]
|
||||
});
|
||||
|
||||
/**
|
||||
* adds a language detector that just checks the config
|
||||
*/
|
||||
browser.addDetector(ConfigLanguageDetector);
|
||||
|
||||
export default browser;
|
|
@ -4,8 +4,8 @@ import XHR from 'i18next-xhr-backend';
|
|||
import { DEFAULT_LANG, languages } from './constants';
|
||||
import languagesR from '../../../../lang/languages.json';
|
||||
import mainR from '../../../../lang/main.json';
|
||||
import Browser from 'i18next-browser-languagedetector';
|
||||
import ConfigLanguageDetector from './ConfigLanguageDetector';
|
||||
|
||||
import LanguageDetector from './LanguageDetector';
|
||||
|
||||
/**
|
||||
* Default options to initialize i18next.
|
||||
|
@ -26,38 +26,12 @@ const defaultOptions = {
|
|||
fallbackOnNull: true,
|
||||
fallbackOnEmpty: true,
|
||||
useDataAttrOptions: true,
|
||||
app: interfaceConfig.APP_NAME
|
||||
app: typeof interfaceConfig === 'undefined'
|
||||
? 'Jitsi Meet' : interfaceConfig.APP_NAME
|
||||
};
|
||||
|
||||
/**
|
||||
* List of detectors to use in their order.
|
||||
*
|
||||
* @type {[*]}
|
||||
*/
|
||||
const detectors = [ 'querystring', 'localStorage', 'configLanguageDetector' ];
|
||||
|
||||
/**
|
||||
* Allow i18n to detect the system language from the browser.
|
||||
*/
|
||||
if (interfaceConfig.LANG_DETECTION) {
|
||||
detectors.push('navigator');
|
||||
}
|
||||
|
||||
/**
|
||||
* The language detectors.
|
||||
*/
|
||||
const browser = new Browser(null, {
|
||||
order: detectors,
|
||||
lookupQuerystring: 'lang',
|
||||
lookupLocalStorage: 'language',
|
||||
caches: [ 'localStorage' ]
|
||||
});
|
||||
|
||||
// adds a language detector that just checks the config
|
||||
browser.addDetector(ConfigLanguageDetector);
|
||||
|
||||
i18n.use(XHR)
|
||||
.use(browser)
|
||||
.use(LanguageDetector)
|
||||
.use({
|
||||
type: 'postProcessor',
|
||||
name: 'resolveAppName',
|
||||
|
|
|
@ -1,12 +1,32 @@
|
|||
import { translate as reactTranslate } from 'react-i18next';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Wrap a translatable component.
|
||||
*
|
||||
* @param {Component} component - the component to wrap
|
||||
* @returns {Component} the wrapped component.
|
||||
* @param {Component} component - The component to wrap.
|
||||
* @returns {Component} The wrapped component.
|
||||
*/
|
||||
export function translate(component) {
|
||||
// use the default list of namespaces
|
||||
return reactTranslate([ 'main', 'languages' ], { wait: true })(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates key and prepares data to be passed to dangerouslySetInnerHTML.
|
||||
* Used when translation text contains html.
|
||||
*
|
||||
* @param {func} t - Translate function.
|
||||
* @param {string} key - The key to translate.
|
||||
* @param {Array} options - Optional options.
|
||||
* @returns {XML} A span using dangerouslySetInnerHTML to insert html text.
|
||||
*/
|
||||
export function translateToHTML(t, key, options = {}) {
|
||||
/* eslint-disable react/no-danger */
|
||||
return (
|
||||
<span
|
||||
dangerouslySetInnerHTML = {{ __html: t(key, options) }} />
|
||||
);
|
||||
|
||||
/* eslint-enable react/no-danger */
|
||||
}
|
||||
|
|
|
@ -51,9 +51,6 @@ class Conference extends Component {
|
|||
APP.UI.registerListeners();
|
||||
APP.UI.bindEvents();
|
||||
|
||||
// XXX Temporary solution until we add React translation.
|
||||
APP.translation.translateElement($('#videoconference_page'));
|
||||
|
||||
this.props.dispatch(connect());
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { setPassword } from '../../base/conference';
|
||||
|
||||
import { translate } from '../../base/translation';
|
||||
|
||||
/**
|
||||
* Implements a React Component which prompts the user when a password is
|
||||
* required to join a conference.
|
||||
|
@ -21,7 +23,8 @@ class PasswordRequiredPrompt extends Component {
|
|||
* @type {JitsiConference}
|
||||
*/
|
||||
conference: React.PropTypes.object,
|
||||
dispatch: React.PropTypes.func
|
||||
dispatch: React.PropTypes.func,
|
||||
t: React.PropTypes.func
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,12 +48,14 @@ class PasswordRequiredPrompt extends Component {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<Prompt
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onSubmit }
|
||||
placeholder = 'Password'
|
||||
title = 'Password required'
|
||||
placeholder = { t('dialog.passwordLabel') }
|
||||
title = { t('dialog.passwordRequired') }
|
||||
visible = { true } />
|
||||
);
|
||||
}
|
||||
|
@ -84,4 +89,4 @@ class PasswordRequiredPrompt extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default connect()(PasswordRequiredPrompt);
|
||||
export default translate(connect()(PasswordRequiredPrompt));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global $, APP */
|
||||
/* global APP */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
|
@ -30,17 +30,6 @@ export default class AbstractOverlay extends Component {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is executed when comonent is mounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
// XXX Temporary solution until we add React translation.
|
||||
APP.translation.translateElement($('#overlay'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
|
|
@ -5,6 +5,8 @@ import { randomInt } from '../../base/util';
|
|||
import AbstractOverlay from './AbstractOverlay';
|
||||
import ReloadTimer from './ReloadTimer';
|
||||
|
||||
import { translate } from '../../base/translation';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
@ -14,7 +16,7 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|||
* conference is reloaded. Shows a warning message and counts down towards the
|
||||
* reload.
|
||||
*/
|
||||
export default class PageReloadOverlay extends AbstractOverlay {
|
||||
class PageReloadOverlay extends AbstractOverlay {
|
||||
/**
|
||||
* PageReloadOverlay component's property types.
|
||||
*
|
||||
|
@ -134,15 +136,17 @@ export default class PageReloadOverlay extends AbstractOverlay {
|
|||
if (this.props.isNetworkFailure) {
|
||||
const className
|
||||
= 'button-control button-control_primary button-control_center';
|
||||
const { t } = this.props;
|
||||
|
||||
/* eslint-disable react/jsx-handler-names */
|
||||
|
||||
return (
|
||||
<button
|
||||
className = { className }
|
||||
data-i18n = 'dialog.reconnectNow'
|
||||
id = 'reconnectNow'
|
||||
onClick = { this._reconnectNow } />
|
||||
onClick = { this._reconnectNow }>
|
||||
{ t('dialog.reconnectNow') }
|
||||
</button>
|
||||
);
|
||||
|
||||
|
||||
|
@ -161,17 +165,20 @@ export default class PageReloadOverlay extends AbstractOverlay {
|
|||
* @protected
|
||||
*/
|
||||
_renderOverlayContent() {
|
||||
const { t } = this.props;
|
||||
|
||||
/* eslint-disable react/jsx-handler-names */
|
||||
|
||||
return (
|
||||
<div className = 'inlay'>
|
||||
<span
|
||||
className = 'reload_overlay_title'
|
||||
data-i18n = { this.state.title } />
|
||||
className = 'reload_overlay_title'>
|
||||
{ t(this.state.title) }
|
||||
</span>
|
||||
<span
|
||||
className = 'reload_overlay_text'
|
||||
data-i18n = { this.state.message } />
|
||||
className = 'reload_overlay_text'>
|
||||
{ t(this.state.message) }
|
||||
</span>
|
||||
<ReloadTimer
|
||||
end = { 0 }
|
||||
interval = { 1 }
|
||||
|
@ -185,3 +192,5 @@ export default class PageReloadOverlay extends AbstractOverlay {
|
|||
/* eslint-enable react/jsx-handler-names */
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(PageReloadOverlay);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../base/translation';
|
||||
|
||||
declare var AJS: Object;
|
||||
|
||||
/**
|
||||
|
@ -8,7 +10,7 @@ declare var AJS: Object;
|
|||
* seconds until the current value reaches props.end. Also displays progress
|
||||
* bar.
|
||||
*/
|
||||
export default class ReloadTimer extends Component {
|
||||
class ReloadTimer extends Component {
|
||||
/**
|
||||
* ReloadTimer component's property types.
|
||||
*
|
||||
|
@ -52,7 +54,15 @@ export default class ReloadTimer extends Component {
|
|||
* @public
|
||||
* @type {number}
|
||||
*/
|
||||
step: React.PropTypes.number
|
||||
step: React.PropTypes.number,
|
||||
|
||||
/**
|
||||
* The function used to translate strings.
|
||||
*
|
||||
* @public
|
||||
* @type {func}
|
||||
*/
|
||||
t: React.PropTypes.func
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,6 +142,8 @@ export default class ReloadTimer extends Component {
|
|||
* @public
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
|
@ -143,9 +155,13 @@ export default class ReloadTimer extends Component {
|
|||
{
|
||||
this.state.current
|
||||
}
|
||||
<span data-i18n = 'dialog.conferenceReloadTimeLeft' />
|
||||
<span>
|
||||
{ t('dialog.conferenceReloadTimeLeft') }
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(ReloadTimer);
|
||||
|
|
|
@ -2,11 +2,13 @@ import React from 'react';
|
|||
|
||||
import AbstractOverlay from './AbstractOverlay';
|
||||
|
||||
import { translate } from '../../base/translation';
|
||||
|
||||
/**
|
||||
* Implements a React Component for suspended overlay. Shown when a suspend is
|
||||
* detected.
|
||||
*/
|
||||
export default class SuspendedOverlay extends AbstractOverlay {
|
||||
class SuspendedOverlay extends AbstractOverlay {
|
||||
/**
|
||||
* Constructs overlay body with the message and a button to rejoin.
|
||||
*
|
||||
|
@ -16,6 +18,7 @@ export default class SuspendedOverlay extends AbstractOverlay {
|
|||
*/
|
||||
_renderOverlayContent() {
|
||||
const btnClass = 'inlay__button button-control button-control_primary';
|
||||
const { t } = this.props;
|
||||
|
||||
/* eslint-disable react/jsx-handler-names */
|
||||
|
||||
|
@ -24,15 +27,19 @@ export default class SuspendedOverlay extends AbstractOverlay {
|
|||
<span className = 'inlay__icon icon-microphone' />
|
||||
<span className = 'inlay__icon icon-camera' />
|
||||
<h3
|
||||
className = 'inlay__title'
|
||||
data-i18n = 'suspendedoverlay.title' />
|
||||
className = 'inlay__title'>
|
||||
{ t('suspendedoverlay.title') }
|
||||
</h3>
|
||||
<button
|
||||
className = { btnClass }
|
||||
data-i18n = 'suspendedoverlay.rejoinKeyTitle'
|
||||
onClick = { this._reconnectNow } />
|
||||
onClick = { this._reconnectNow }>
|
||||
{ t('suspendedoverlay.rejoinKeyTitle') }
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
/* eslint-enable react/jsx-handler-names */
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(SuspendedOverlay);
|
||||
|
|
|
@ -4,11 +4,13 @@ import React from 'react';
|
|||
|
||||
import AbstractOverlay from './AbstractOverlay';
|
||||
|
||||
import { translate, translateToHTML } from '../../base/translation';
|
||||
|
||||
/**
|
||||
* Implements a React Component for overlay with guidance how to proceed with
|
||||
* gUM prompt.
|
||||
*/
|
||||
export default class UserMediaPermissionsOverlay extends AbstractOverlay {
|
||||
class UserMediaPermissionsOverlay extends AbstractOverlay {
|
||||
/**
|
||||
* UserMediaPermissionsOverlay component's property types.
|
||||
*
|
||||
|
@ -54,26 +56,26 @@ export default class UserMediaPermissionsOverlay extends AbstractOverlay {
|
|||
* @protected
|
||||
*/
|
||||
_renderOverlayContent() {
|
||||
const textKey = `userMedia.${this.props.browser}GrantPermissions`;
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className = 'inlay'>
|
||||
<span className = 'inlay__icon icon-microphone' />
|
||||
<span className = 'inlay__icon icon-camera' />
|
||||
<h3
|
||||
className = 'inlay__title'
|
||||
data-i18n = 'startupoverlay.title'
|
||||
data-i18n-options
|
||||
= '{"postProcess": "resolveAppName"}' />
|
||||
<span
|
||||
className = 'inlay__text'
|
||||
data-i18n = { `[html]${textKey}` } />
|
||||
<h3 className = 'inlay__title'>
|
||||
{ t('startupoverlay.title',
|
||||
{ postProcess: 'resolveAppName' }) }
|
||||
</h3>
|
||||
<span className = 'inlay__text'>
|
||||
{ translateToHTML(t,
|
||||
`userMedia.${this.props.browser}GrantPermissions`)}
|
||||
</span>
|
||||
</div>
|
||||
<div className = 'policy overlay__policy'>
|
||||
<p
|
||||
className = 'policy__text'
|
||||
data-i18n = '[html]startupoverlay.policyText' />
|
||||
<p className = 'policy__text'>
|
||||
{ t('startupoverlay.policyText') }
|
||||
</p>
|
||||
{
|
||||
this._renderPolicyLogo()
|
||||
}
|
||||
|
@ -102,3 +104,5 @@ export default class UserMediaPermissionsOverlay extends AbstractOverlay {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(UserMediaPermissionsOverlay);
|
||||
|
|
|
@ -4,6 +4,8 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { endRoomLockRequest } from '../actions';
|
||||
|
||||
import { translate } from '../../base/translation';
|
||||
|
||||
/**
|
||||
* Implements a React Component which prompts the user for a password to lock a
|
||||
* conference/room.
|
||||
|
@ -21,7 +23,8 @@ class RoomLockPrompt extends Component {
|
|||
* @type {JitsiConference}
|
||||
*/
|
||||
conference: React.PropTypes.object,
|
||||
dispatch: React.PropTypes.func
|
||||
dispatch: React.PropTypes.func,
|
||||
t: React.PropTypes.func
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,12 +48,14 @@ class RoomLockPrompt extends Component {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<Prompt
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onSubmit }
|
||||
placeholder = 'Password'
|
||||
title = 'Lock / Unlock room'
|
||||
placeholder = { t('dialog.passwordLabel') }
|
||||
title = { t('toolbar.lock') }
|
||||
visible = { true } />
|
||||
);
|
||||
}
|
||||
|
@ -80,4 +85,4 @@ class RoomLockPrompt extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default connect()(RoomLockPrompt);
|
||||
export default translate(connect()(RoomLockPrompt));
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import { Platform } from '../../base/react';
|
||||
import { translate } from '../../base/translation';
|
||||
|
||||
import { CHROME, FIREFOX, IE, SAFARI } from './browserLinks';
|
||||
import HideNotificationBarStyle from './HideNotificationBarStyle';
|
||||
|
@ -20,7 +21,16 @@ const _NS = 'unsupported-desktop-browser';
|
|||
*
|
||||
* @class UnsupportedDesktopBrowser
|
||||
*/
|
||||
export default class UnsupportedDesktopBrowser extends Component {
|
||||
class UnsupportedDesktopBrowser extends Component {
|
||||
/**
|
||||
* UnsupportedDesktopBrowser component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
t: React.PropTypes.func
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component.
|
||||
*
|
||||
|
@ -87,3 +97,4 @@ export default class UnsupportedDesktopBrowser extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default translate(UnsupportedDesktopBrowser);
|
||||
|
|
|
@ -4,6 +4,7 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { Platform } from '../../base/react';
|
||||
import { translate, translateToHTML } from '../../base/translation';
|
||||
|
||||
import HideNotificationBarStyle from './HideNotificationBarStyle';
|
||||
|
||||
|
@ -39,7 +40,8 @@ class UnsupportedMobileBrowser extends Component {
|
|||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
_room: React.PropTypes.string
|
||||
_room: React.PropTypes.string,
|
||||
t: React.PropTypes.func
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,7 +52,8 @@ class UnsupportedMobileBrowser extends Component {
|
|||
*/
|
||||
componentWillMount() {
|
||||
const joinText
|
||||
= this.props._room ? 'Join the conversation' : 'Start a conference';
|
||||
= this.props._room ? 'unsupportedPage.joinConversation'
|
||||
: 'unsupportedPage.startConference';
|
||||
|
||||
// If the user installed the app while this Component was displayed
|
||||
// (e.g. the user clicked the Download the App button), then we would
|
||||
|
@ -74,6 +77,7 @@ class UnsupportedMobileBrowser extends Component {
|
|||
render() {
|
||||
const ns = 'unsupported-mobile-browser';
|
||||
const downloadButtonClassName = `${ns}__button ${ns}__button_primary`;
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div className = { ns }>
|
||||
|
@ -82,24 +86,21 @@ class UnsupportedMobileBrowser extends Component {
|
|||
className = { `${ns}__logo` }
|
||||
src = 'images/logo-blue.svg' />
|
||||
<p className = { `${ns}__text` }>
|
||||
You need <strong>Jitsi Meet</strong> to join a
|
||||
conversation on mobile
|
||||
{ translateToHTML(t,
|
||||
'unsupportedPage.joinConversationMobile',
|
||||
{ postProcess: 'resolveAppName' }) }
|
||||
</p>
|
||||
<a href = { _URLS[Platform.OS] }>
|
||||
<button className = { downloadButtonClassName }>
|
||||
Download the App
|
||||
{ t('unsupportedPage.downloadApp') }
|
||||
</button>
|
||||
</a>
|
||||
<p className = { `${ns}__text ${ns}__text_small` }>
|
||||
or if you already have it
|
||||
<br />
|
||||
<strong>then</strong>
|
||||
{ translateToHTML(t, 'unsupportedPage.availableApp') }
|
||||
</p>
|
||||
<a href = { this.state.joinURL }>
|
||||
<button className = { `${ns}__button` }>
|
||||
{
|
||||
this.state.joinText
|
||||
}
|
||||
{ t(this.state.joinText) }
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -133,4 +134,4 @@ function _mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(UnsupportedMobileBrowser);
|
||||
export default translate(connect(_mapStateToProps)(UnsupportedMobileBrowser));
|
||||
|
|
|
@ -8,6 +8,8 @@ import { ColorPalette } from '../../base/styles';
|
|||
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
|
||||
import { styles } from './styles';
|
||||
|
||||
import { translate } from '../../base/translation';
|
||||
|
||||
/**
|
||||
* The URL at which the privacy policy is available to the user.
|
||||
*/
|
||||
|
@ -62,22 +64,24 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderLegalese() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.legaleseContainer }>
|
||||
<Link
|
||||
style = { styles.legaleseItem }
|
||||
url = { TERMS_URL }>
|
||||
Terms
|
||||
{ t('welcomepage.terms') }
|
||||
</Link>
|
||||
<Link
|
||||
style = { styles.legaleseItem }
|
||||
url = { PRIVACY_URL }>
|
||||
Privacy
|
||||
{ t('welcomepage.privacy') }
|
||||
</Link>
|
||||
<Link
|
||||
style = { styles.legaleseItem }
|
||||
url = { SEND_FEEDBACK_URL }>
|
||||
Send feedback
|
||||
{ t('welcomepage.sendFeedback') }
|
||||
</Link>
|
||||
</View>
|
||||
);
|
||||
|
@ -93,10 +97,14 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderLocalVideoOverlay() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.localVideoOverlay }>
|
||||
<View style = { styles.roomContainer }>
|
||||
<Text style = { styles.title }>Enter room name</Text>
|
||||
<Text style = { styles.title }>
|
||||
{ t('welcomepage.roomname') }
|
||||
</Text>
|
||||
<TextInput
|
||||
accessibilityLabel = { 'Input room name.' }
|
||||
autoCapitalize = 'none'
|
||||
|
@ -104,7 +112,7 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
autoCorrect = { false }
|
||||
autoFocus = { false }
|
||||
onChangeText = { this._onRoomChange }
|
||||
placeholder = 'room name'
|
||||
placeholder = { t('welcomepage.roomnamePlaceHolder') }
|
||||
style = { styles.textInput }
|
||||
underlineColorAndroid = 'transparent'
|
||||
value = { this.state.room } />
|
||||
|
@ -114,7 +122,9 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
onPress = { this._onJoin }
|
||||
style = { styles.button }
|
||||
underlayColor = { ColorPalette.white }>
|
||||
<Text style = { styles.buttonText }>JOIN</Text>
|
||||
<Text style = { styles.buttonText }>
|
||||
{ t('welcomepage.join') }
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
{
|
||||
|
@ -125,4 +135,4 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
}
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(WelcomePage);
|
||||
export default translate(connect(_mapStateToProps)(WelcomePage));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global $, APP, interfaceConfig */
|
||||
/* global APP, interfaceConfig */
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -7,6 +7,8 @@ import { Watermarks } from '../../base/react';
|
|||
|
||||
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
|
||||
|
||||
import { translate } from '../../base/translation';
|
||||
|
||||
/* eslint-disable require-jsdoc */
|
||||
|
||||
/**
|
||||
|
@ -51,9 +53,6 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
if (this.state.generateRoomnames) {
|
||||
this._updateRoomname();
|
||||
}
|
||||
|
||||
// XXX Temporary solution until we add React translation.
|
||||
APP.translation.translateElement($('#welcome_page'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -142,19 +141,21 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderFeature(index) {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'feature_holder'
|
||||
key = { index } >
|
||||
<div
|
||||
className = 'feature_icon'
|
||||
data-i18n = { `welcomepage.feature${index}.title` } />
|
||||
className = 'feature_icon'>
|
||||
{ t(`welcomepage.feature${index}.title`) }
|
||||
</div>
|
||||
<div
|
||||
className = 'feature_description'
|
||||
data-i18n = { `welcomepage.feature${index}.content` }
|
||||
data-i18n-options = { JSON.stringify({
|
||||
postProcess: 'resolveAppName'
|
||||
}) } />
|
||||
className = 'feature_description'>
|
||||
{ t(`welcomepage.feature${index}.content`,
|
||||
{ postProcess: 'resolveAppName' }) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -196,6 +197,7 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
_renderHeader() {
|
||||
|
||||
/* eslint-enable require-jsdoc */
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div id = 'welcome_page_header'>
|
||||
|
@ -229,10 +231,11 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
|
||||
<button
|
||||
className = 'enter-room__button'
|
||||
data-i18n = 'welcomepage.go'
|
||||
id = 'enter_room_button'
|
||||
onClick = { this._onJoin }
|
||||
type = 'button' />
|
||||
type = 'button'>
|
||||
{ t('welcomepage.go') }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -245,8 +248,9 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
type = 'checkbox' />
|
||||
<label
|
||||
className = 'disable_welcome_position'
|
||||
data-i18n = 'welcomepage.disable'
|
||||
htmlFor = 'disable_welcome' />
|
||||
htmlFor = 'disable_welcome'>
|
||||
{ t('welcomepage.disable') }
|
||||
</label>
|
||||
<div id = 'header_text' />
|
||||
</div>
|
||||
);
|
||||
|
@ -274,4 +278,4 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
}
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(WelcomePage);
|
||||
export default translate(connect(_mapStateToProps)(WelcomePage));
|
||||
|
|
Loading…
Reference in New Issue