Comply w/ coding style

This commit is contained in:
Lyubo Marinov 2017-02-28 20:55:12 -06:00
parent c361e1e31a
commit 18368fefaa
32 changed files with 400 additions and 298 deletions

View File

@ -5,8 +5,14 @@ debian/
libs/ libs/
node_modules/ node_modules/
# The following are checked by ESLint which supersedes JSHint. # The following are checked by ESLint with the maximum configuration which
# supersedes JSHint.
flow-typed/ flow-typed/
react/ react/
# The following are checked by ESLint with the minimum configuration which does
# not supersede JSHint but take advantage of advanced language features such as
# Facebook Flow which are not supported by JSHint.
modules/translation/translation.js
analytics.js analytics.js

View File

@ -39,44 +39,44 @@
"videoMute": "Start or stop your camera" "videoMute": "Start or stop your camera"
}, },
"welcomepage":{ "welcomepage":{
"go": "GO",
"join": "JOIN",
"roomname": "Enter room name",
"roomnamePlaceHolder": "room name",
"disable": "Don't show this page again", "disable": "Don't show this page again",
"feature1": { "feature1": {
"title": "Simple to use", "content": "No downloads required. __app__ works directly within your browser. Simply share your conference URL with others to get started.",
"content": "No downloads required. __app__ works directly within your browser. Simply share your conference URL with others to get started." "title": "Simple to use"
}, },
"feature2": { "feature2": {
"title": "Low bandwidth", "content": "Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less.",
"content": "Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less." "title": "Low bandwidth"
}, },
"feature3": { "feature3": {
"title": "Open source", "content": "__app__ is licensed under the Apache License. You are free to download, use, modify, and share it as per this license.",
"content": "__app__ is licensed under the Apache License. You are free to download, use, modify, and share it as per this license." "title": "Open source"
}, },
"feature4": { "feature4": {
"title": "Unlimited users", "content": "There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors.",
"content": "There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors." "title": "Unlimited users"
}, },
"feature5": { "feature5": {
"title": "Screen sharing", "content": "It's easy to share your screen with others. __app__ is ideal for on-line presentations, lectures, and tech support sessions.",
"content": "It's easy to share your screen with others. __app__ is ideal for on-line presentations, lectures, and tech support sessions." "title": "Screen sharing"
}, },
"feature6": { "feature6": {
"title": "Secure rooms", "content": "Need some privacy? __app__ conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions.",
"content": "Need some privacy? __app__ conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions." "title": "Secure rooms"
}, },
"feature7": { "feature7": {
"title": "Shared notes", "content": "__app__ features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more.",
"content": "__app__ features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more." "title": "Shared notes"
}, },
"feature8": { "feature8": {
"title": "Usage statistics", "content": "Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems.",
"content": "Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems." "title": "Usage statistics"
}, },
"go": "GO",
"join": "JOIN",
"privacy": "Privacy", "privacy": "Privacy",
"roomname": "Enter room name",
"roomnamePlaceHolder": "room name",
"sendFeedback": "Send feedback", "sendFeedback": "Send feedback",
"terms": "Terms" "terms": "Terms"
}, },
@ -115,14 +115,12 @@
"profile": "Edit your profile", "profile": "Edit your profile",
"raiseHand": "Raise / Lower your hand" "raiseHand": "Raise / Lower your hand"
}, },
"unsupportedPage": { "unsupportedBrowser": {
"onlySupportedBy": "This application is currently only supported by", "appInstalled": "or if you already have it<br /><strong>then</strong>",
"download": "DOWNLOAD", "appNotInstalled": "You need <strong>__app__</strong> to join a conversation on your mobile",
"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", "downloadApp": "Download the App",
"availableApp": "or if you already have it<br /><strong>then</strong>" "joinConversation": "Join the conversation",
"startConference": "Start a conference"
}, },
"bottomtoolbar": { "bottomtoolbar": {
"chat": "Open / close chat", "chat": "Open / close chat",

View File

@ -1,8 +1,9 @@
/* global $, APP, AJS, interfaceConfig, JitsiMeetJS */ /* global $, APP, AJS, interfaceConfig, JitsiMeetJS */
import { LANGUAGES } from "../../../../react/features/base/i18n";
import UIUtil from "../../util/UIUtil"; import UIUtil from "../../util/UIUtil";
import UIEvents from "../../../../service/UI/UIEvents"; import UIEvents from "../../../../service/UI/UIEvents";
import languages from "../../../../service/translation/languages";
import Settings from '../../../settings/Settings'; import Settings from '../../../settings/Settings';
const sidePanelsContainerId = 'sideToolbarContainer'; const sidePanelsContainerId = 'sideToolbarContainer';
@ -145,7 +146,7 @@ export default {
let selectInput; let selectInput;
selectEl.html(generateLanguagesOptions( selectEl.html(generateLanguagesOptions(
languages.getLanguages(), LANGUAGES,
APP.translation.getCurrentLanguage() APP.translation.getCurrentLanguage()
)); ));
initSelect2(selectEl, () => { initSelect2(selectEl, () => {

View File

@ -1,46 +1,56 @@
/* global $ */ /* @flow */
import { i18n, DEFAULT_LANG } from '../../react/features/base/translation';
import jqueryI18next from 'jquery-i18next'; import jqueryI18next from 'jquery-i18next';
function initCompleted() { import { DEFAULT_LANGUAGE, i18next } from '../../react/features/base/i18n';
$("[data-i18n]").localize();
declare var $: Function;
/**
* Notifies that the {@link i18next} instance has finished its initialization.
*
* @returns {void}
* @private
*/
function _onI18nInitialized() {
$('[data-i18n]').localize();
} }
class Translation { class Translation {
init () { addLanguageChangedListener(listener: Function) {
if (i18n.isInitialized) i18next.on('languageChanged', listener);
initCompleted();
else
i18n.on('initialized', initCompleted);
jqueryI18next.init(i18n, $, {useOptionsAttr: true});
} }
setLanguage (lang) { generateTranslationHTML(key: string, options: Object) {
if(!lang) const optAttr
lang = DEFAULT_LANG; = options ? ` data-i18n-options='${JSON.stringify(options)}'` : '';
i18n.setLng(lang, {}, initCompleted);
}
getCurrentLanguage () { // XXX i18next expects undefined if options are missing.
return i18n.lng(); const text = i18next.t(key, options ? options : undefined);
}
translateElement (selector, options) {
// i18next expects undefined if options are missing, check if its null
selector.localize(
options === null ? undefined : options);
}
generateTranslationHTML (key, options) {
let optAttr = options
? ` data-i18n-options='${JSON.stringify(options)}'` : "";
let text = i18n.t(key, options === null ? undefined : options);
return `<span data-i18n="${key}"${optAttr}>${text}</span>`; return `<span data-i18n="${key}"${optAttr}>${text}</span>`;
} }
addLanguageChangedListener(listener) { getCurrentLanguage() {
i18n.on('languageChanged', listener); return i18next.lng();
}
init() {
if (i18next.isInitialized)
_onI18nInitialized();
else
i18next.on('initialized', _onI18nInitialized);
jqueryI18next.init(i18next, $, { useOptionsAttr: true });
}
setLanguage(language: string = DEFAULT_LANGUAGE) {
i18next.setLng(language, {}, _onI18nInitialized);
}
translateElement(selector: Object, options: Object) {
// XXX i18next expects undefined if options are missing.
selector.localize(options ? options : undefined);
} }
} }

View File

@ -40,7 +40,7 @@
"react-native-background-timer": "1.0.0", "react-native-background-timer": "1.0.0",
"react-native-immersive": "0.0.4", "react-native-immersive": "0.0.4",
"react-native-keep-awake": "^2.0.2", "react-native-keep-awake": "^2.0.2",
"react-native-locale-detector": "1.0.1 ", "react-native-locale-detector": "1.0.1",
"react-native-prompt": "^1.0.0", "react-native-prompt": "^1.0.0",
"react-native-vector-icons": "^4.0.0", "react-native-vector-icons": "^4.0.0",
"react-native-webrtc": "jitsi/react-native-webrtc", "react-native-webrtc": "jitsi/react-native-webrtc",

View File

@ -1,13 +1,12 @@
/* global APP */ /* global APP */
import React, { Component } from 'react'; import React, { Component } from 'react';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { compose, createStore } from 'redux'; import { compose, createStore } from 'redux';
import Thunk from 'redux-thunk'; import Thunk from 'redux-thunk';
import { I18nextProvider } from 'react-i18next'; import { i18next } from '../../base/i18n';
import { i18n } from '../../base/translation';
import { import {
localParticipantJoined, localParticipantJoined,
localParticipantLeft localParticipantLeft
@ -137,7 +136,7 @@ export class AbstractApp extends Component {
if (route) { if (route) {
return ( return (
<I18nextProvider i18n = { i18n }> <I18nextProvider i18n = { i18next }>
<Provider store = { this._getStore() }> <Provider store = { this._getStore() }>
{ {
this._createElement(route.component) this._createElement(route.component)

View File

@ -1,16 +1,20 @@
/* global config */ /* @flow */
declare var config: Object;
/** /**
* Custom language detection, just returns the config property if any. * Custom language detection, just returns the config property if any.
*/ */
export default { export default {
/** /**
* Name of the language detector. * Does not support caching.
*
* @returns {void}
*/ */
name: 'configLanguageDetector', cacheUserLanguage: Function.prototype,
/** /**
* The actual lookup. * Looks the language up in the config.
* *
* @returns {string} The default language if any. * @returns {string} The default language if any.
*/ */
@ -19,7 +23,7 @@ export default {
}, },
/** /**
* Doesn't support caching. * Name of the language detector.
*/ */
cacheUserLanguage: Function.prototype name: 'configLanguageDetector'
}; };

View File

@ -0,0 +1,36 @@
/**
* The available/supported languages.
*
* XXX The element at index zero is the default language.
*
* @public
* @type {Array<string>}
*/
export const LANGUAGES = [
'en', // XXX The default language.
'bg',
'de',
'es',
'fr',
'hy',
'it',
'oc',
'pl',
'ptBR',
'ru',
'sk',
'sl',
'sv',
'tr'
];
/**
* The default language.
*
* XXX The element at index zero of {@link LANGUAGES} is the default language.
*
* @public
* @type {string} The default language.
*/
export const DEFAULT_LANGUAGE = LANGUAGES[0];

View File

@ -0,0 +1,31 @@
import React from 'react';
import { translate as reactI18nextTranslate } from 'react-i18next';
/**
* Wraps a specific React Component in order to enable translations in it.
*
* @param {Component} component - The React Component to wrap.
* @returns {Component} The React Component which wraps {@link component} and
* enables translations in it.
*/
export function translate(component) {
// Use the default list of namespaces.
return (
reactI18nextTranslate([ 'main', 'languages' ], { wait: true })(
component));
}
/**
* Translates a specific key to text containing HTML via a specific translate
* function.
*
* @param {Function} t - The translate function.
* @param {string} key - The key to translate.
* @param {Array<*>} options - The options, if any, to pass to {@link t}.
* @returns {ReactElement} A ReactElement which depicts the translated HTML
* text.
*/
export function translateToHTML(t, key, options = {}) {
// eslint-disable-next-line react/no-danger
return <span dangerouslySetInnerHTML = {{ __html: t(key, options) }} />;
}

View File

@ -0,0 +1,63 @@
import i18next from 'i18next';
import I18nextXHRBackend from 'i18next-xhr-backend';
import LANGUAGES_RESOURCES from '../../../../lang/languages.json';
import MAIN_RESOURCES from '../../../../lang/main.json';
import { DEFAULT_LANGUAGE, LANGUAGES } from './constants';
import languageDetector from './languageDetector';
declare var interfaceConfig: Object;
/**
* The options to initialize i18next with.
*
* @type {Object}
*/
const options = {
app:
(typeof interfaceConfig !== 'undefined' && interfaceConfig.APP_NAME)
|| 'Jitsi Meet',
compatibilityAPI: 'v1',
compatibilityJSON: 'v1',
fallbackLng: DEFAULT_LANGUAGE,
fallbackOnEmpty: true,
fallbackOnNull: true,
// XXX i18next modifies the array lngWhitelist so make sure to clone
// LANGUAGES.
lngWhitelist: LANGUAGES.slice(),
load: 'unspecific',
ns: {
defaultNs: 'main',
namespaces: [ 'main', 'languages' ]
},
resGetPath: 'lang/__ns__-__lng__.json',
useDataAttrOptions: true
};
i18next
.use(I18nextXHRBackend)
.use(languageDetector)
.use({
name: 'resolveAppName',
process: (res, key) => i18next.t(key, { app: options.app }),
type: 'postProcessor'
})
.init(options);
// Add default language which is preloaded from the source code.
i18next.addResourceBundle(
DEFAULT_LANGUAGE,
'main',
MAIN_RESOURCES,
/* deep */ true,
/* overwrite */ true);
i18next.addResourceBundle(
DEFAULT_LANGUAGE,
'languages',
LANGUAGES_RESOURCES,
/* deep */ true,
/* overwrite */ true);
export default i18next;

View File

@ -0,0 +1,6 @@
export * from './constants';
export * from './functions';
// TODO Eventually (e.g. when the non-React Web app is rewritten into React), it
// should not be necessary to export i18next.
export { default as i18next } from './i18next';

View File

@ -0,0 +1,24 @@
/* @flow */
import locale from 'react-native-locale-detector';
/**
* The singleton language detector for React Native which uses the system-wide
* locale.
*/
export default {
/**
* Does not support caching.
*
* @returns {void}
*/
cacheUserLanguage: Function.prototype,
detect() {
return locale;
},
init: Function.prototype,
type: 'languageDetector'
};

View File

@ -0,0 +1,42 @@
/* @flow */
import BrowserLanguageDetector from 'i18next-browser-languagedetector';
import configLanguageDetector from './configLanguageDetector';
declare var interfaceConfig: Object;
/**
* The ordered list (by name) of language detectors to be utilized as backends
* by the singleton language detector for Web.
*
* @type {Array<string>}
*/
const order = [
'querystring',
'localStorage',
configLanguageDetector.name
];
// Allow i18next to detect the system language reported by the Web browser
// itself.
interfaceConfig.LANG_DETECTION && order.push('navigator');
/**
* The singleton language detector for Web.
*/
const languageDetector
= new BrowserLanguageDetector(
/* services */ null,
/* options */ {
caches: [ 'localStorage' ],
lookupLocalStorage: 'language',
lookupQuerystring: 'lang',
order
});
// Add the language detector which looks the language up in the config. Its
// order has already been established above.
languageDetector.addDetector(configLanguageDetector);
export default languageDetector;

View File

@ -1,7 +1,8 @@
/* @flow */ /* @flow */
import React, { Component } from 'react'; import React, { Component } from 'react';
import { translate } from '../../translation';
import { translate } from '../../i18n';
declare var APP: Object; declare var APP: Object;
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
@ -19,7 +20,7 @@ const _RIGHT_WATERMARK_STYLE = {
* A Web Component which renders watermarks such as Jits, brand, powered by, * A Web Component which renders watermarks such as Jits, brand, powered by,
* etc. * etc.
*/ */
class WatermarksComponent extends Component { class Watermarks extends Component {
state = { state = {
brandWatermarkLink: String, brandWatermarkLink: String,
jitsiWatermarkLink: String, jitsiWatermarkLink: String,
@ -147,7 +148,7 @@ class WatermarksComponent extends Component {
className = 'poweredby' className = 'poweredby'
href = 'http://jitsi.org' href = 'http://jitsi.org'
target = '_new'> target = '_new'>
<span>{t('poweredby')} jitsi.org</span> <span>{ t('poweredby') } jitsi.org</span>
</a> </a>
); );
} }
@ -156,4 +157,4 @@ class WatermarksComponent extends Component {
} }
} }
export const Watermarks = translate(WatermarksComponent); export default translate(Watermarks);

View File

@ -1,3 +1,3 @@
export * from './Container'; export * from './Container';
export * from './Link'; export * from './Link';
export * from './Watermarks'; export { default as Watermarks } from './Watermarks';

View File

@ -1,11 +0,0 @@
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
};

View File

@ -1,34 +0,0 @@
/* 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;

View File

@ -1,46 +0,0 @@
/* global interfaceConfig */
import i18n from 'i18next';
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 LanguageDetector from './LanguageDetector';
/**
* Default options to initialize i18next.
*
* @enum {string}
*/
const defaultOptions = {
compatibilityAPI: 'v1',
compatibilityJSON: 'v1',
fallbackLng: DEFAULT_LANG,
load: 'unspecific',
resGetPath: 'lang/__ns__-__lng__.json',
ns: {
namespaces: [ 'main', 'languages' ],
defaultNs: 'main'
},
lngWhitelist: languages.getLanguages(),
fallbackOnNull: true,
fallbackOnEmpty: true,
useDataAttrOptions: true,
app: typeof interfaceConfig === 'undefined'
? 'Jitsi Meet' : interfaceConfig.APP_NAME
};
i18n.use(XHR)
.use(LanguageDetector)
.use({
type: 'postProcessor',
name: 'resolveAppName',
process: (res, key) => i18n.t(key, { app: defaultOptions.app })
})
.init(defaultOptions);
// adds default language which is preloaded from code
i18n.addResourceBundle(DEFAULT_LANG, 'main', mainR, true, true);
i18n.addResourceBundle(DEFAULT_LANG, 'languages', languagesR, true, true);
export default i18n;

View File

@ -1,13 +0,0 @@
import languages from '../../../../service/translation/languages';
/**
* The default language globally for the project.
*
* @type {string} the default language globally for the project.
*/
export const DEFAULT_LANG = languages.EN;
/**
* Exports the list of languages currently supported.
*/
export { languages };

View File

@ -1,32 +0,0 @@
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.
*/
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 */
}

View File

@ -1,3 +0,0 @@
export { default as i18n } from './Translation';
export * from './constants';
export * from './functions';

View File

@ -3,8 +3,7 @@ import Prompt from 'react-native-prompt';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { setPassword } from '../../base/conference'; import { setPassword } from '../../base/conference';
import { translate } from '../../base/i18n';
import { translate } from '../../base/translation';
/** /**
* Implements a React Component which prompts the user when a password is * Implements a React Component which prompts the user when a password is
@ -24,6 +23,13 @@ class PasswordRequiredPrompt extends Component {
*/ */
conference: React.PropTypes.object, conference: React.PropTypes.object,
dispatch: React.PropTypes.func, dispatch: React.PropTypes.func,
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func t: React.PropTypes.func
} }

View File

@ -1,12 +1,11 @@
import React from 'react'; import React from 'react';
import { translate } from '../../base/i18n';
import { randomInt } from '../../base/util'; import { randomInt } from '../../base/util';
import AbstractOverlay from './AbstractOverlay'; import AbstractOverlay from './AbstractOverlay';
import ReloadTimer from './ReloadTimer'; import ReloadTimer from './ReloadTimer';
import { translate } from '../../base/translation';
declare var APP: Object; declare var APP: Object;
const logger = require('jitsi-meet-logger').getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { translate } from '../../base/translation'; import { translate } from '../../base/i18n';
declare var AJS: Object; declare var AJS: Object;
@ -57,10 +57,10 @@ class ReloadTimer extends Component {
step: React.PropTypes.number, step: React.PropTypes.number,
/** /**
* The function used to translate strings. * The function to translate human-readable text.
* *
* @public * @public
* @type {func} * @type {Function}
*/ */
t: React.PropTypes.func t: React.PropTypes.func
} }

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import AbstractOverlay from './AbstractOverlay'; import { translate } from '../../base/i18n';
import { translate } from '../../base/translation'; import AbstractOverlay from './AbstractOverlay';
/** /**
* Implements a React Component for suspended overlay. Shown when a suspend is * Implements a React Component for suspended overlay. Shown when a suspend is

View File

@ -2,9 +2,9 @@
import React from 'react'; import React from 'react';
import AbstractOverlay from './AbstractOverlay'; import { translate, translateToHTML } from '../../base/i18n';
import { translate, translateToHTML } from '../../base/translation'; import AbstractOverlay from './AbstractOverlay';
/** /**
* Implements a React Component for overlay with guidance how to proceed with * Implements a React Component for overlay with guidance how to proceed with
@ -56,7 +56,7 @@ class UserMediaPermissionsOverlay extends AbstractOverlay {
* @protected * @protected
*/ */
_renderOverlayContent() { _renderOverlayContent() {
const { t } = this.props; const { browser, t } = this.props;
return ( return (
<div> <div>
@ -64,12 +64,18 @@ class UserMediaPermissionsOverlay extends AbstractOverlay {
<span className = 'inlay__icon icon-microphone' /> <span className = 'inlay__icon icon-microphone' />
<span className = 'inlay__icon icon-camera' /> <span className = 'inlay__icon icon-camera' />
<h3 className = 'inlay__title'> <h3 className = 'inlay__title'>
{ t('startupoverlay.title', {
{ postProcess: 'resolveAppName' }) } t(
'startupoverlay.title',
{ postProcess: 'resolveAppName' })
}
</h3> </h3>
<span className = 'inlay__text'> <span className = 'inlay__text'>
{ translateToHTML(t, {
`userMedia.${this.props.browser}GrantPermissions`)} translateToHTML(
t,
`userMedia.${browser}GrantPermissions`)
}
</span> </span>
</div> </div>
<div className = 'policy overlay__policy'> <div className = 'policy overlay__policy'>

View File

@ -2,9 +2,9 @@ import React, { Component } from 'react';
import Prompt from 'react-native-prompt'; import Prompt from 'react-native-prompt';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { endRoomLockRequest } from '../actions'; import { translate } from '../../base/i18n';
import { translate } from '../../base/translation'; import { endRoomLockRequest } from '../actions';
/** /**
* Implements a React Component which prompts the user for a password to lock a * Implements a React Component which prompts the user for a password to lock a
@ -24,6 +24,13 @@ class RoomLockPrompt extends Component {
*/ */
conference: React.PropTypes.object, conference: React.PropTypes.object,
dispatch: React.PropTypes.func, dispatch: React.PropTypes.func,
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func t: React.PropTypes.func
} }

View File

@ -2,19 +2,19 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { translate } from '../../base/i18n';
import { Platform } from '../../base/react'; import { Platform } from '../../base/react';
import { translate } from '../../base/translation';
import { CHROME, FIREFOX, IE, SAFARI } from './browserLinks'; import { CHROME, FIREFOX, IE, SAFARI } from './browserLinks';
import HideNotificationBarStyle from './HideNotificationBarStyle'; import HideNotificationBarStyle from './HideNotificationBarStyle';
/** /**
* The CSS style namespace of UnsupportedDesktopBrowser. * The namespace of the CSS styles of UnsupportedDesktopBrowser.
* *
* @private * @private
* @type {string} * @type {string}
*/ */
const _NS = 'unsupported-desktop-browser'; const _SNS = 'unsupported-desktop-browser';
/** /**
* React component representing unsupported browser page. * React component representing unsupported browser page.
@ -28,6 +28,12 @@ class UnsupportedDesktopBrowser extends Component {
* @static * @static
*/ */
static propTypes = { static propTypes = {
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func t: React.PropTypes.func
} }
@ -38,17 +44,17 @@ class UnsupportedDesktopBrowser extends Component {
*/ */
render() { render() {
return ( return (
<div className = { _NS }> <div className = { _SNS }>
<h2 className = { `${_NS}__title` }> <h2 className = { `${_SNS}__title` }>
It looks like you're using a browser we don't support. It looks like you're using a browser we don't support.
</h2> </h2>
<p className = { `${_NS}__description` }> <p className = { `${_SNS}__description` }>
Please try again with the latest version of&nbsp; Please try again with the latest version of&nbsp;
<a <a
className = { `${_NS}__link` } className = { `${_SNS}__link` }
href = { CHROME } >Chrome</a>,&nbsp; href = { CHROME } >Chrome</a>,&nbsp;
<a <a
className = { `${_NS}__link` } className = { `${_SNS}__link` }
href = { FIREFOX }>Firefox</a> or&nbsp; href = { FIREFOX }>Firefox</a> or&nbsp;
{ {
this._renderOSSpecificBrowserDownloadLink() this._renderOSSpecificBrowserDownloadLink()
@ -84,7 +90,7 @@ class UnsupportedDesktopBrowser extends Component {
if (typeof link !== 'undefined') { if (typeof link !== 'undefined') {
return ( return (
<a <a
className = { `${_NS}__link` } className = { `${_SNS}__link` }
href = { link }> href = { link }>
{ {
text text

View File

@ -3,16 +3,33 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { translate, translateToHTML } from '../../base/i18n';
import { Platform } from '../../base/react'; import { Platform } from '../../base/react';
import { translate, translateToHTML } from '../../base/translation';
import HideNotificationBarStyle from './HideNotificationBarStyle'; import HideNotificationBarStyle from './HideNotificationBarStyle';
/**
* The namespace of the CSS styles of UnsupportedMobileBrowser.
*
* @private
* @type {string}
*/
const _SNS = 'unsupported-mobile-browser';
/**
* The namespace of the i18n/translation keys of UnsupportedMobileBrowser.
*
* @private
* @type {string}
*/
const _TNS = 'unsupportedBrowser';
/** /**
* The map of platforms to URLs at which the mobile app for the associated * The map of platforms to URLs at which the mobile app for the associated
* platform is available for download. * platform is available for download.
* *
* @private * @private
* @type {Array<string>}
*/ */
const _URLS = { const _URLS = {
android: 'https://play.google.com/store/apps/details?id=org.jitsi.meet', android: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
@ -41,6 +58,13 @@ class UnsupportedMobileBrowser extends Component {
* @type {string} * @type {string}
*/ */
_room: React.PropTypes.string, _room: React.PropTypes.string,
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func t: React.PropTypes.func
} }
@ -52,8 +76,7 @@ class UnsupportedMobileBrowser extends Component {
*/ */
componentWillMount() { componentWillMount() {
const joinText const joinText
= this.props._room ? 'unsupportedPage.joinConversation' = this.props._room ? 'joinConversation' : 'startConference';
: 'unsupportedPage.startConference';
// If the user installed the app while this Component was displayed // If the user installed the app while this Component was displayed
// (e.g. the user clicked the Download the App button), then we would // (e.g. the user clicked the Download the App button), then we would
@ -75,32 +98,36 @@ class UnsupportedMobileBrowser extends Component {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const ns = 'unsupported-mobile-browser';
const downloadButtonClassName = `${ns}__button ${ns}__button_primary`;
const { t } = this.props; const { t } = this.props;
const downloadButtonClassName
= `${_SNS}__button ${_SNS}__button_primary`;
return ( return (
<div className = { ns }> <div className = { _SNS }>
<div className = { `${ns}__body` }> <div className = { `${_SNS}__body` }>
<img <img
className = { `${ns}__logo` } className = { `${_SNS}__logo` }
src = 'images/logo-blue.svg' /> src = 'images/logo-blue.svg' />
<p className = { `${ns}__text` }> <p className = { `${_SNS}__text` }>
{ translateToHTML(t, {
'unsupportedPage.joinConversationMobile', translateToHTML(
{ postProcess: 'resolveAppName' }) } t,
`${_TNS}.appNotInstalled`,
{ postProcess: 'resolveAppName' })
}
</p> </p>
<a href = { _URLS[Platform.OS] }> <a href = { _URLS[Platform.OS] }>
<button className = { downloadButtonClassName }> <button className = { downloadButtonClassName }>
{ t('unsupportedPage.downloadApp') } { t(`${_TNS}.downloadApp`) }
</button> </button>
</a> </a>
<p className = { `${ns}__text ${ns}__text_small` }> <p className = { `${_SNS}__text ${_SNS}__text_small` }>
{ translateToHTML(t, 'unsupportedPage.availableApp') } { translateToHTML(t, `${_TNS}.appInstalled`) }
</p> </p>
<a href = { this.state.joinURL }> <a href = { this.state.joinURL }>
<button className = { `${ns}__button` }> <button className = { `${_SNS}__button` }>
{ t(this.state.joinText) } { t(`${_TNS}.${this.state.joinText}`) }
</button> </button>
</a> </a>
</div> </div>

View File

@ -2,14 +2,13 @@ import React from 'react';
import { Text, TextInput, TouchableHighlight, View } from 'react-native'; import { Text, TextInput, TouchableHighlight, View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { Link } from '../../base/react'; import { Link } from '../../base/react';
import { ColorPalette } from '../../base/styles'; import { ColorPalette } from '../../base/styles';
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage'; import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
import { styles } from './styles'; import { styles } from './styles';
import { translate } from '../../base/translation';
/** /**
* The URL at which the privacy policy is available to the user. * The URL at which the privacy policy is available to the user.
*/ */

View File

@ -3,12 +3,11 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { Watermarks } from '../../base/react'; import { Watermarks } from '../../base/react';
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage'; import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
import { translate } from '../../base/translation';
/* eslint-disable require-jsdoc */ /* eslint-disable require-jsdoc */
/** /**
@ -142,19 +141,17 @@ class WelcomePage extends AbstractWelcomePage {
*/ */
_renderFeature(index) { _renderFeature(index) {
const { t } = this.props; const { t } = this.props;
const tns = `welcomepage.feature${index}`;
return ( return (
<div <div
className = 'feature_holder' className = 'feature_holder'
key = { index } > key = { index } >
<div <div className = 'feature_icon'>
className = 'feature_icon'> { t(`${tns}.title`) }
{ t(`welcomepage.feature${index}.title`) }
</div> </div>
<div <div className = 'feature_description'>
className = 'feature_description'> { t(`${tns}.content`, { postProcess: 'resolveAppName' }) }
{ t(`welcomepage.feature${index}.content`,
{ postProcess: 'resolveAppName' }) }
</div> </div>
</div> </div>
); );

View File

@ -1,27 +0,0 @@
export default {
getLanguages : function () {
var languages = [];
for (var lang in this)
{
if (typeof this[lang] === "string")
languages.push(this[lang]);
}
return languages;
},
EN: "en",
BG: "bg",
DE: "de",
ES: "es",
FR: "fr",
HY: "hy",
IT: "it",
OC: "oc",
PL: "pl",
PTBR: "ptBR",
RU: "ru",
SK: "sk",
SL: "sl",
SV: "sv",
TR: "tr"
};