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/
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/
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

View File

@ -39,44 +39,44 @@
"videoMute": "Start or stop your camera"
},
"welcomepage":{
"go": "GO",
"join": "JOIN",
"roomname": "Enter room name",
"roomnamePlaceHolder": "room name",
"disable": "Don't show this page again",
"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": {
"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": {
"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": {
"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": {
"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": {
"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": {
"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": {
"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",
"roomname": "Enter room name",
"roomnamePlaceHolder": "room name",
"sendFeedback": "Send feedback",
"terms": "Terms"
},
@ -115,14 +115,12 @@
"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",
"unsupportedBrowser": {
"appInstalled": "or if you already have it<br /><strong>then</strong>",
"appNotInstalled": "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>"
"joinConversation": "Join the conversation",
"startConference": "Start a conference"
},
"bottomtoolbar": {
"chat": "Open / close chat",

View File

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

View File

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

View File

@ -1,13 +1,12 @@
/* global APP */
import React, { Component } from 'react';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { compose, createStore } from 'redux';
import Thunk from 'redux-thunk';
import { I18nextProvider } from 'react-i18next';
import { i18n } from '../../base/translation';
import { i18next } from '../../base/i18n';
import {
localParticipantJoined,
localParticipantLeft
@ -137,7 +136,7 @@ export class AbstractApp extends Component {
if (route) {
return (
<I18nextProvider i18n = { i18n }>
<I18nextProvider i18n = { i18next }>
<Provider store = { this._getStore() }>
{
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.
*/
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.
*/
@ -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 */
import React, { Component } from 'react';
import { translate } from '../../translation';
import { translate } from '../../i18n';
declare var APP: 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,
* etc.
*/
class WatermarksComponent extends Component {
class Watermarks extends Component {
state = {
brandWatermarkLink: String,
jitsiWatermarkLink: String,
@ -147,7 +148,7 @@ class WatermarksComponent extends Component {
className = 'poweredby'
href = 'http://jitsi.org'
target = '_new'>
<span>{t('poweredby')} jitsi.org</span>
<span>{ t('poweredby') } jitsi.org</span>
</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 './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 { setPassword } from '../../base/conference';
import { translate } from '../../base/translation';
import { translate } from '../../base/i18n';
/**
* Implements a React Component which prompts the user when a password is
@ -24,6 +23,13 @@ class PasswordRequiredPrompt extends Component {
*/
conference: React.PropTypes.object,
dispatch: React.PropTypes.func,
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func
}

View File

@ -1,12 +1,11 @@
import React from 'react';
import { translate } from '../../base/i18n';
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);

View File

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

View File

@ -1,8 +1,8 @@
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

View File

@ -2,9 +2,9 @@
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
@ -56,7 +56,7 @@ class UserMediaPermissionsOverlay extends AbstractOverlay {
* @protected
*/
_renderOverlayContent() {
const { t } = this.props;
const { browser, t } = this.props;
return (
<div>
@ -64,12 +64,18 @@ class UserMediaPermissionsOverlay extends AbstractOverlay {
<span className = 'inlay__icon icon-microphone' />
<span className = 'inlay__icon icon-camera' />
<h3 className = 'inlay__title'>
{ t('startupoverlay.title',
{ postProcess: 'resolveAppName' }) }
{
t(
'startupoverlay.title',
{ postProcess: 'resolveAppName' })
}
</h3>
<span className = 'inlay__text'>
{ translateToHTML(t,
`userMedia.${this.props.browser}GrantPermissions`)}
{
translateToHTML(
t,
`userMedia.${browser}GrantPermissions`)
}
</span>
</div>
<div className = 'policy overlay__policy'>

View File

@ -2,9 +2,9 @@ import React, { Component } from 'react';
import Prompt from 'react-native-prompt';
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
@ -24,6 +24,13 @@ class RoomLockPrompt extends Component {
*/
conference: React.PropTypes.object,
dispatch: React.PropTypes.func,
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func
}

View File

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

View File

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

View File

@ -2,14 +2,13 @@ import React from 'react';
import { Text, TextInput, TouchableHighlight, View } from 'react-native';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { Link } from '../../base/react';
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.
*/

View File

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