diff --git a/lang/main.json b/lang/main.json
index 78a8249d1a..14254122dd 100644
--- a/lang/main.json
+++ b/lang/main.json
@@ -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 __app__ to join a conversation on your mobile",
+ "downloadApp": "Download the App",
+ "availableApp": "or if you already have it
then"
+ },
"bottomtoolbar": {
"chat": "Open / close chat",
"filmstrip": "Show / hide videos",
diff --git a/package.json b/package.json
index 9d7e0840d0..71b71308df 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/react/features/base/react/components/Watermarks.web.js b/react/features/base/react/components/Watermarks.web.js
index 60b674e455..9f18a764d4 100644
--- a/react/features/base/react/components/Watermarks.web.js
+++ b/react/features/base/react/components/Watermarks.web.js
@@ -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 (
- jitsi.org
+ {t('poweredby')} jitsi.org
);
}
@@ -152,3 +155,5 @@ export class Watermarks extends Component {
return null;
}
}
+
+export const Watermarks = translate(WatermarksComponent);
diff --git a/react/features/base/translation/ConfigLanguageDetector.js b/react/features/base/translation/ConfigLanguageDetector.js
index 427cf0850b..2369ed8305 100644
--- a/react/features/base/translation/ConfigLanguageDetector.js
+++ b/react/features/base/translation/ConfigLanguageDetector.js
@@ -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;
diff --git a/react/features/base/translation/LanguageDetector.native.js b/react/features/base/translation/LanguageDetector.native.js
new file mode 100644
index 0000000000..e30fffeb4e
--- /dev/null
+++ b/react/features/base/translation/LanguageDetector.native.js
@@ -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
+};
diff --git a/react/features/base/translation/LanguageDetector.web.js b/react/features/base/translation/LanguageDetector.web.js
new file mode 100644
index 0000000000..c45eebcd5d
--- /dev/null
+++ b/react/features/base/translation/LanguageDetector.web.js
@@ -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;
diff --git a/react/features/base/translation/Translation.js b/react/features/base/translation/Translation.js
index 3af4b17456..4ec22b8fb5 100644
--- a/react/features/base/translation/Translation.js
+++ b/react/features/base/translation/Translation.js
@@ -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',
diff --git a/react/features/base/translation/functions.js b/react/features/base/translation/functions.js
index 25bee46068..023b6e7e42 100644
--- a/react/features/base/translation/functions.js
+++ b/react/features/base/translation/functions.js
@@ -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 (
+
+ );
+
+ /* eslint-enable react/no-danger */
+}
diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js
index 1175d477b1..be7b366e7e 100644
--- a/react/features/conference/components/Conference.web.js
+++ b/react/features/conference/components/Conference.web.js
@@ -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());
}
diff --git a/react/features/conference/components/PasswordRequiredPrompt.native.js b/react/features/conference/components/PasswordRequiredPrompt.native.js
index 7944cb5131..7ad25da2a2 100644
--- a/react/features/conference/components/PasswordRequiredPrompt.native.js
+++ b/react/features/conference/components/PasswordRequiredPrompt.native.js
@@ -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 (
+ { t('startupoverlay.policyText') } +
{ this._renderPolicyLogo() } @@ -102,3 +104,5 @@ export default class UserMediaPermissionsOverlay extends AbstractOverlay { return null; } } + +export default translate(UserMediaPermissionsOverlay); diff --git a/react/features/room-lock/components/RoomLockPrompt.native.js b/react/features/room-lock/components/RoomLockPrompt.native.js index b2c5e73d96..7a4a7ba734 100644 --- a/react/features/room-lock/components/RoomLockPrompt.native.js +++ b/react/features/room-lock/components/RoomLockPrompt.native.js @@ -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 (- You need Jitsi Meet to join a - conversation on mobile + { translateToHTML(t, + 'unsupportedPage.joinConversationMobile', + { postProcess: 'resolveAppName' }) }
- or if you already have it
-
- then
+ { translateToHTML(t, 'unsupportedPage.availableApp') }