diff --git a/.jshintignore b/.jshintignore index b0e2ed3b7..6240b0092 100644 --- a/.jshintignore +++ b/.jshintignore @@ -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 diff --git a/lang/main.json b/lang/main.json index 14254122d..b93b873f1 100644 --- a/lang/main.json +++ b/lang/main.json @@ -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 __app__ to join a conversation on your mobile", + "unsupportedBrowser": { + "appInstalled": "or if you already have it
then", + "appNotInstalled": "You need __app__ to join a conversation on your mobile", "downloadApp": "Download the App", - "availableApp": "or if you already have it
then" + "joinConversation": "Join the conversation", + "startConference": "Start a conference" }, "bottomtoolbar": { "chat": "Open / close chat", diff --git a/modules/UI/side_pannels/settings/SettingsMenu.js b/modules/UI/side_pannels/settings/SettingsMenu.js index d5176661e..7e566e79f 100644 --- a/modules/UI/side_pannels/settings/SettingsMenu.js +++ b/modules/UI/side_pannels/settings/SettingsMenu.js @@ -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, () => { diff --git a/modules/translation/translation.js b/modules/translation/translation.js index 411c19b7b..27a29a51c 100644 --- a/modules/translation/translation.js +++ b/modules/translation/translation.js @@ -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 `${text}`; } - 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); } } diff --git a/package.json b/package.json index 71b71308d..2bf61ba1b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js index fc2791b4c..b3cb4f664 100644 --- a/react/features/app/components/AbstractApp.js +++ b/react/features/app/components/AbstractApp.js @@ -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 ( - + { this._createElement(route.component) diff --git a/react/features/base/translation/ConfigLanguageDetector.js b/react/features/base/i18n/configLanguageDetector.js similarity index 57% rename from react/features/base/translation/ConfigLanguageDetector.js rename to react/features/base/i18n/configLanguageDetector.js index 2369ed830..855203436 100644 --- a/react/features/base/translation/ConfigLanguageDetector.js +++ b/react/features/base/i18n/configLanguageDetector.js @@ -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' }; diff --git a/react/features/base/i18n/constants.js b/react/features/base/i18n/constants.js new file mode 100644 index 000000000..a5e692b72 --- /dev/null +++ b/react/features/base/i18n/constants.js @@ -0,0 +1,36 @@ +/** + * The available/supported languages. + * + * XXX The element at index zero is the default language. + * + * @public + * @type {Array} + */ +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]; diff --git a/react/features/base/i18n/functions.js b/react/features/base/i18n/functions.js new file mode 100644 index 000000000..8f9765b84 --- /dev/null +++ b/react/features/base/i18n/functions.js @@ -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 ; +} diff --git a/react/features/base/i18n/i18next.js b/react/features/base/i18n/i18next.js new file mode 100644 index 000000000..f1e571eb7 --- /dev/null +++ b/react/features/base/i18n/i18next.js @@ -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; diff --git a/react/features/base/i18n/index.js b/react/features/base/i18n/index.js new file mode 100644 index 000000000..4fb07bd59 --- /dev/null +++ b/react/features/base/i18n/index.js @@ -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'; diff --git a/react/features/base/i18n/languageDetector.native.js b/react/features/base/i18n/languageDetector.native.js new file mode 100644 index 000000000..ab407517f --- /dev/null +++ b/react/features/base/i18n/languageDetector.native.js @@ -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' +}; diff --git a/react/features/base/i18n/languageDetector.web.js b/react/features/base/i18n/languageDetector.web.js new file mode 100644 index 000000000..a45e701d4 --- /dev/null +++ b/react/features/base/i18n/languageDetector.web.js @@ -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} + */ +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; diff --git a/react/features/base/react/components/Watermarks.web.js b/react/features/base/react/components/Watermarks.web.js index 9f18a764d..18d7d3b8e 100644 --- a/react/features/base/react/components/Watermarks.web.js +++ b/react/features/base/react/components/Watermarks.web.js @@ -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'> - {t('poweredby')} jitsi.org + { t('poweredby') } jitsi.org ); } @@ -156,4 +157,4 @@ class WatermarksComponent extends Component { } } -export const Watermarks = translate(WatermarksComponent); +export default translate(Watermarks); diff --git a/react/features/base/react/components/index.js b/react/features/base/react/components/index.js index e3f8ed5bf..06bf02ab3 100644 --- a/react/features/base/react/components/index.js +++ b/react/features/base/react/components/index.js @@ -1,3 +1,3 @@ export * from './Container'; export * from './Link'; -export * from './Watermarks'; +export { default as Watermarks } from './Watermarks'; diff --git a/react/features/base/translation/LanguageDetector.native.js b/react/features/base/translation/LanguageDetector.native.js deleted file mode 100644 index e30fffeb4..000000000 --- a/react/features/base/translation/LanguageDetector.native.js +++ /dev/null @@ -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 -}; diff --git a/react/features/base/translation/LanguageDetector.web.js b/react/features/base/translation/LanguageDetector.web.js deleted file mode 100644 index c45eebcd5..000000000 --- a/react/features/base/translation/LanguageDetector.web.js +++ /dev/null @@ -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; diff --git a/react/features/base/translation/Translation.js b/react/features/base/translation/Translation.js deleted file mode 100644 index 4ec22b8fb..000000000 --- a/react/features/base/translation/Translation.js +++ /dev/null @@ -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; diff --git a/react/features/base/translation/constants.js b/react/features/base/translation/constants.js deleted file mode 100644 index 2300bca5f..000000000 --- a/react/features/base/translation/constants.js +++ /dev/null @@ -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 }; diff --git a/react/features/base/translation/functions.js b/react/features/base/translation/functions.js deleted file mode 100644 index 023b6e7e4..000000000 --- a/react/features/base/translation/functions.js +++ /dev/null @@ -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 ( - - ); - - /* eslint-enable react/no-danger */ -} diff --git a/react/features/base/translation/index.js b/react/features/base/translation/index.js deleted file mode 100644 index b3517eb35..000000000 --- a/react/features/base/translation/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { default as i18n } from './Translation'; -export * from './constants'; -export * from './functions'; diff --git a/react/features/conference/components/PasswordRequiredPrompt.native.js b/react/features/conference/components/PasswordRequiredPrompt.native.js index 7ad25da2a..e92498877 100644 --- a/react/features/conference/components/PasswordRequiredPrompt.native.js +++ b/react/features/conference/components/PasswordRequiredPrompt.native.js @@ -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 } diff --git a/react/features/overlay/components/PageReloadOverlay.js b/react/features/overlay/components/PageReloadOverlay.js index fb8460755..54f17d90d 100644 --- a/react/features/overlay/components/PageReloadOverlay.js +++ b/react/features/overlay/components/PageReloadOverlay.js @@ -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); diff --git a/react/features/overlay/components/ReloadTimer.js b/react/features/overlay/components/ReloadTimer.js index 8a0d1a9f0..1644d29da 100644 --- a/react/features/overlay/components/ReloadTimer.js +++ b/react/features/overlay/components/ReloadTimer.js @@ -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 } diff --git a/react/features/overlay/components/SuspendedOverlay.js b/react/features/overlay/components/SuspendedOverlay.js index bbc894858..1e417e1c3 100644 --- a/react/features/overlay/components/SuspendedOverlay.js +++ b/react/features/overlay/components/SuspendedOverlay.js @@ -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 diff --git a/react/features/overlay/components/UserMediaPermissionsOverlay.js b/react/features/overlay/components/UserMediaPermissionsOverlay.js index b304a600c..193563b98 100644 --- a/react/features/overlay/components/UserMediaPermissionsOverlay.js +++ b/react/features/overlay/components/UserMediaPermissionsOverlay.js @@ -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 (
@@ -64,12 +64,18 @@ class UserMediaPermissionsOverlay extends AbstractOverlay {

- { t('startupoverlay.title', - { postProcess: 'resolveAppName' }) } + { + t( + 'startupoverlay.title', + { postProcess: 'resolveAppName' }) + }

- { translateToHTML(t, - `userMedia.${this.props.browser}GrantPermissions`)} + { + translateToHTML( + t, + `userMedia.${browser}GrantPermissions`) + }
diff --git a/react/features/room-lock/components/RoomLockPrompt.native.js b/react/features/room-lock/components/RoomLockPrompt.native.js index 7a4a7ba73..6590e3efd 100644 --- a/react/features/room-lock/components/RoomLockPrompt.native.js +++ b/react/features/room-lock/components/RoomLockPrompt.native.js @@ -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 } diff --git a/react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js b/react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js index 1d9460170..8272cc555 100644 --- a/react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js +++ b/react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js @@ -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 ( -
-

+
+

It looks like you're using a browser we don't support.

-

+

Please try again with the latest version of  ChromeFirefox or  { this._renderOSSpecificBrowserDownloadLink() @@ -84,7 +90,7 @@ class UnsupportedDesktopBrowser extends Component { if (typeof link !== 'undefined') { return ( { text diff --git a/react/features/unsupported-browser/components/UnsupportedMobileBrowser.js b/react/features/unsupported-browser/components/UnsupportedMobileBrowser.js index e0e45300b..0ea047446 100644 --- a/react/features/unsupported-browser/components/UnsupportedMobileBrowser.js +++ b/react/features/unsupported-browser/components/UnsupportedMobileBrowser.js @@ -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} */ 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 ( -

-
+
+ diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js index 258aa1424..8a37dca8d 100644 --- a/react/features/welcome/components/WelcomePage.native.js +++ b/react/features/welcome/components/WelcomePage.native.js @@ -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. */ diff --git a/react/features/welcome/components/WelcomePage.web.js b/react/features/welcome/components/WelcomePage.web.js index 8fea1460a..f2b7717fa 100644 --- a/react/features/welcome/components/WelcomePage.web.js +++ b/react/features/welcome/components/WelcomePage.web.js @@ -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 (
-
- { t(`welcomepage.feature${index}.title`) } +
+ { t(`${tns}.title`) }
-
- { t(`welcomepage.feature${index}.content`, - { postProcess: 'resolveAppName' }) } +
+ { t(`${tns}.content`, { postProcess: 'resolveAppName' }) }
); diff --git a/service/translation/languages.js b/service/translation/languages.js deleted file mode 100644 index 20c2f134c..000000000 --- a/service/translation/languages.js +++ /dev/null @@ -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" -};