diff --git a/.gitignore b/.gitignore
index dc61fadfe..bae365060 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,8 +10,10 @@
/app/dist
+/app/locales
/app/node_modules
/build/venv
+/build/daemon.ver
/lbry-app-venv
/lbry-app
/lbry-venv
diff --git a/build/build.sh b/build/build.sh
index c0e60e79c..92c2c5e8f 100755
--- a/build/build.sh
+++ b/build/build.sh
@@ -82,9 +82,17 @@ fi
DAEMON_VER=$(node -e "console.log(require(\"$ROOT/app/package.json\").lbrySettings.lbrynetDaemonVersion)")
DAEMON_URL_TEMPLATE=$(node -e "console.log(require(\"$ROOT/app/package.json\").lbrySettings.lbrynetDaemonUrlTemplate)")
DAEMON_URL=$(echo ${DAEMON_URL_TEMPLATE//DAEMONVER/$DAEMON_VER} | sed "s/OSNAME/$OSNAME/g")
-wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip"
-unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/"
-rm "$BUILD_DIR/daemon.zip"
+DAEMON_VER_PATH="$BUILD_DIR/daemon.ver"
+echo "$DAEMON_VER_PATH"
+if [[ ! -f $DAEMON_VER_PATH || ! -f $ROOT/app/dist/lbrynet-daemon || "$(< "$DAEMON_VER_PATH")" != "$DAEMON_VER" ]]; then
+ wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip"
+ unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/"
+ rm "$BUILD_DIR/daemon.zip"
+ echo "$DAEMON_VER" > "$DAEMON_VER_PATH"
+else
+ echo "Already have daemon version $DAEMON_VER, skipping download"
+fi
+
diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js
index 7e41ccd52..144375b6e 100644
--- a/ui/js/actions/content.js
+++ b/ui/js/actions/content.js
@@ -231,14 +231,15 @@ export function doStartDownload(uri, outpoint) {
return function(dispatch, getState) {
const state = getState();
- if (!outpoint) { throw new Error("outpoint is required to begin a download"); }
+ if (!outpoint) {
+ throw new Error("outpoint is required to begin a download");
+ }
const { downloadingByOutpoint = {} } = state.fileInfo;
if (downloadingByOutpoint[outpoint]) return;
lbry.file_list({ outpoint, full_status: true }).then(([fileInfo]) => {
-
dispatch({
type: types.DOWNLOADING_STARTED,
data: {
@@ -282,29 +283,32 @@ export function doLoadVideo(uri) {
},
});
- lbry.get({ uri }).then(streamInfo => {
- const timeout =
- streamInfo === null ||
- typeof streamInfo !== "object" ||
- streamInfo.error == "Timeout";
+ lbry
+ .get({ uri })
+ .then(streamInfo => {
+ const timeout =
+ streamInfo === null ||
+ typeof streamInfo !== "object" ||
+ streamInfo.error == "Timeout";
- if (timeout) {
+ if (timeout) {
+ dispatch({
+ type: types.LOADING_VIDEO_FAILED,
+ data: { uri },
+ });
+
+ dispatch(doOpenModal("timedOut"));
+ } else {
+ dispatch(doDownloadFile(uri, streamInfo));
+ }
+ })
+ .catch(error => {
dispatch({
type: types.LOADING_VIDEO_FAILED,
data: { uri },
});
-
- dispatch(doOpenModal("timedOut"));
- } else {
- dispatch(doDownloadFile(uri, streamInfo));
- }
- }).catch(error => {
- dispatch({
- type: types.LOADING_VIDEO_FAILED,
- data: { uri },
+ dispatch(doAlertError(error));
});
- dispatch(doAlertError(error));
- });
};
}
diff --git a/ui/js/actions/settings.js b/ui/js/actions/settings.js
index be614c7f5..25731e135 100644
--- a/ui/js/actions/settings.js
+++ b/ui/js/actions/settings.js
@@ -1,6 +1,10 @@
import * as types from "constants/action_types";
import * as settings from "constants/settings";
+import batchActions from "util/batchActions";
+
import lbry from "lbry";
+import fs from "fs";
+import http from "http";
const { remote } = require("electron");
const { extname } = require("path");
@@ -84,5 +88,97 @@ export function doSetTheme(name) {
// update theme
dispatch(doSetClientSetting(settings.THEME, theme.name));
}
+}
+
+export function doDownloadLanguage(langFile) {
+ return function(dispatch, getState) {
+ const destinationPath = `app/locales/${langFile}`;
+ const language = langFile.replace(".json", "");
+ const req = http.get(
+ {
+ headers: {
+ "Content-Type": "text/html",
+ },
+ host: "i18n.lbry.io",
+ path: `/langs/${langFile}`,
+ },
+ response => {
+ if (response.statusCode === 200) {
+ const file = fs.createWriteStream(destinationPath);
+
+ file.on("error", errorHandler);
+ file.on("finish", () => {
+ file.close();
+
+ // push to our local list
+ dispatch({
+ type: types.DOWNLOAD_LANGUAGE_SUCCEEDED,
+ data: { language: language },
+ });
+ });
+
+ response.pipe(file);
+ } else {
+ errorHandler(new Error("Language request failed."));
+ }
+ }
+ );
+
+ const errorHandler = err => {
+ fs.unlink(destinationPath, () => {}); // Delete the file async. (But we don't check the result)
+
+ dispatch({
+ type: types.DOWNLOAD_LANGUAGE_FAILED,
+ data: { language },
+ });
+ };
+
+ req.setTimeout(30000, function() {
+ req.abort();
+ });
+
+ req.on("error", errorHandler);
+
+ req.end();
+ };
+}
+
+export function doDownloadLanguages() {
+ return function(dispatch, getState) {
+ if (!fs.existsSync(app.i18n.directory)) {
+ fs.mkdirSync(app.i18n.directory);
+ }
+
+ const xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState === XMLHttpRequest.DONE) {
+ if (xhr.status === 200) {
+ try {
+ const files = JSON.parse(xhr.responseText);
+ const actions = [];
+ files.forEach(file => {
+ actions.push(doDownloadLanguage(file));
+ });
+
+ dispatch(batchActions(...actions));
+ } catch (err) {
+ throw err;
+ }
+ } else {
+ throw new Error(
+ __("The list of available languages could not be retrieved.")
+ );
+ }
+ }
+ };
+ xhr.open("get", "http://i18n.lbry.io");
+ xhr.send();
+ };
+}
+
+export function doChangeLanguage(language) {
+ return function(dispatch, getState) {
+ lbry.setClientSetting(settings.LANGUAGE, language);
+ app.i18n.setLocale(language);
};
}
diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx
index f26eccd1d..eb29a1b2e 100644
--- a/ui/js/component/fileCard/view.jsx
+++ b/ui/js/component/fileCard/view.jsx
@@ -62,7 +62,8 @@ class FileCard extends React.PureComponent {
? metadata.thumbnail
: null;
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
- const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
+ const isRewardContent =
+ claim && rewardedContentClaimIds.includes(claim.claim_id);
let description = "";
if (isResolvingUri && !claim) {
@@ -95,8 +96,9 @@ class FileCard extends React.PureComponent {
{!hidePrice ?
: null}
- {isRewardContent &&
}
+ {isRewardContent &&
}
{uri}
{title}
diff --git a/ui/js/component/iconFeatured/view.jsx b/ui/js/component/iconFeatured/view.jsx
index 2a0b2b54f..651d82338 100644
--- a/ui/js/component/iconFeatured/view.jsx
+++ b/ui/js/component/iconFeatured/view.jsx
@@ -3,10 +3,11 @@ import { Icon } from "component/common.js";
const IconFeatured = props => {
return (
-
-
+
+
);
};
diff --git a/ui/js/component/userVerify/index.js b/ui/js/component/userVerify/index.js
index 1d48bfbbe..043b52c75 100644
--- a/ui/js/component/userVerify/index.js
+++ b/ui/js/component/userVerify/index.js
@@ -21,7 +21,7 @@ const select = (state, props) => {
};
const perform = dispatch => ({
- navigate: (uri) => dispatch(doNavigate(uri)),
+ navigate: uri => dispatch(doNavigate(uri)),
verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)),
});
diff --git a/ui/js/component/userVerify/view.jsx b/ui/js/component/userVerify/view.jsx
index ddfc18278..679052e53 100644
--- a/ui/js/component/userVerify/view.jsx
+++ b/ui/js/component/userVerify/view.jsx
@@ -29,20 +29,34 @@ class UserVerify extends React.PureComponent {
{__(
"To ensure you are a real person, we require a valid credit or debit card."
- ) + " " + __("There is no charge at all, now or in the future.") + " " }
-
+ ) +
+ " " +
+ __("There is no charge at all, now or in the future.") +
+ " "}
+
{errorMessage && {errorMessage}
}
-
- {__("You can continue without this step, but you will not be eligible to earn rewards.")}
+
- navigate("/discover")} button="alt" label={__("Skip Rewards")} />
+
+ {__(
+ "You can continue without this step, but you will not be eligible to earn rewards."
+ )}
+
+ navigate("/discover")}
+ button="alt"
+ label={__("Skip Rewards")}
+ />
);
}
diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx
index 3157c535d..3d10520a2 100644
--- a/ui/js/component/video/view.jsx
+++ b/ui/js/component/video/view.jsx
@@ -16,7 +16,11 @@ class Video extends React.PureComponent {
componentWillReceiveProps(nextProps) {
// reset playing state upon change path action
- if (!this.isMediaSame(nextProps) && this.props.fileInfo && this.state.isPlaying) {
+ if (
+ !this.isMediaSame(nextProps) &&
+ this.props.fileInfo &&
+ this.state.isPlaying
+ ) {
this.state.isPlaying = false;
}
}
diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js
index 78d12d2cf..7b2a7456d 100644
--- a/ui/js/constants/action_types.js
+++ b/ui/js/constants/action_types.js
@@ -113,3 +113,7 @@ export const CLAIM_REWARD_SUCCESS = "CLAIM_REWARD_SUCCESS";
export const CLAIM_REWARD_FAILURE = "CLAIM_REWARD_FAILURE";
export const CLAIM_REWARD_CLEAR_ERROR = "CLAIM_REWARD_CLEAR_ERROR";
export const FETCH_REWARD_CONTENT_COMPLETED = "FETCH_REWARD_CONTENT_COMPLETED";
+
+//Language
+export const DOWNLOAD_LANGUAGE_SUCCEEDED = "DOWNLOAD_LANGUAGE_SUCCEEDED";
+export const DOWNLOAD_LANGUAGE_FAILED = "DOWNLOAD_LANGUAGE_FAILED";
diff --git a/ui/js/constants/languages.js b/ui/js/constants/languages.js
new file mode 100644
index 000000000..f1879e904
--- /dev/null
+++ b/ui/js/constants/languages.js
@@ -0,0 +1,187 @@
+const LANGUAGES = {
+ aa: ["Afar", "Afar"],
+ ab: ["Abkhazian", "Аҧсуа"],
+ af: ["Afrikaans", "Afrikaans"],
+ ak: ["Akan", "Akana"],
+ am: ["Amharic", "አማርኛ"],
+ an: ["Aragonese", "Aragonés"],
+ ar: ["Arabic", "العربية"],
+ as: ["Assamese", "অসমীয়া"],
+ av: ["Avar", "Авар"],
+ ay: ["Aymara", "Aymar"],
+ az: ["Azerbaijani", "Azərbaycanca / آذربايجان"],
+ ba: ["Bashkir", "Башҡорт"],
+ be: ["Belarusian", "Беларуская"],
+ bg: ["Bulgarian", "Български"],
+ bh: ["Bihari", "भोजपुरी"],
+ bi: ["Bislama", "Bislama"],
+ bm: ["Bambara", "Bamanankan"],
+ bn: ["Bengali", "বাংলা"],
+ bo: ["Tibetan", "བོད་ཡིག / Bod skad"],
+ br: ["Breton", "Brezhoneg"],
+ bs: ["Bosnian", "Bosanski"],
+ ca: ["Catalan", "Català"],
+ ce: ["Chechen", "Нохчийн"],
+ ch: ["Chamorro", "Chamoru"],
+ co: ["Corsican", "Corsu"],
+ cr: ["Cree", "Nehiyaw"],
+ cs: ["Czech", "Česky"],
+ cu: ["Old Church Slavonic / Old Bulgarian", "словѣньскъ / slověnĭskŭ"],
+ cv: ["Chuvash", "Чăваш"],
+ cy: ["Welsh", "Cymraeg"],
+ da: ["Danish", "Dansk"],
+ de: ["German", "Deutsch"],
+ dv: ["Divehi", "ދިވެހިބަސް"],
+ dz: ["Dzongkha", "ཇོང་ཁ"],
+ ee: ["Ewe", "Ɛʋɛ"],
+ el: ["Greek", "Ελληνικά"],
+ en: ["English", "English"],
+ eo: ["Esperanto", "Esperanto"],
+ es: ["Spanish", "Español"],
+ et: ["Estonian", "Eesti"],
+ eu: ["Basque", "Euskara"],
+ fa: ["Persian", "فارسی"],
+ ff: ["Peul", "Fulfulde"],
+ fi: ["Finnish", "Suomi"],
+ fj: ["Fijian", "Na Vosa Vakaviti"],
+ fo: ["Faroese", "Føroyskt"],
+ fr: ["French", "Français"],
+ fy: ["West Frisian", "Frysk"],
+ ga: ["Irish", "Gaeilge"],
+ gd: ["Scottish Gaelic", "Gàidhlig"],
+ gl: ["Galician", "Galego"],
+ gn: ["Guarani", "Avañe'ẽ"],
+ gu: ["Gujarati", "ગુજરાતી"],
+ gv: ["Manx", "Gaelg"],
+ ha: ["Hausa", "هَوُسَ"],
+ he: ["Hebrew", "עברית"],
+ hi: ["Hindi", "हिन्दी"],
+ ho: ["Hiri Motu", "Hiri Motu"],
+ hr: ["Croatian", "Hrvatski"],
+ ht: ["Haitian", "Krèyol ayisyen"],
+ hu: ["Hungarian", "Magyar"],
+ hy: ["Armenian", "Հայերեն"],
+ hz: ["Herero", "Otsiherero"],
+ ia: ["Interlingua", "Interlingua"],
+ id: ["Indonesian", "Bahasa Indonesia"],
+ ie: ["Interlingue", "Interlingue"],
+ ig: ["Igbo", "Igbo"],
+ ii: ["Sichuan Yi", "ꆇꉙ / 四川彝语"],
+ ik: ["Inupiak", "Iñupiak"],
+ io: ["Ido", "Ido"],
+ is: ["Icelandic", "Íslenska"],
+ it: ["Italian", "Italiano"],
+ iu: ["Inuktitut", "ᐃᓄᒃᑎᑐᑦ"],
+ ja: ["Japanese", "日本語"],
+ jv: ["Javanese", "Basa Jawa"],
+ ka: ["Georgian", "ქართული"],
+ kg: ["Kongo", "KiKongo"],
+ ki: ["Kikuyu", "Gĩkũyũ"],
+ kj: ["Kuanyama", "Kuanyama"],
+ kk: ["Kazakh", "Қазақша"],
+ kl: ["Greenlandic", "Kalaallisut"],
+ km: ["Cambodian", "ភាសាខ្មែរ"],
+ kn: ["Kannada", "ಕನ್ನಡ"],
+ ko: ["Korean", "한국어"],
+ kr: ["Kanuri", "Kanuri"],
+ ks: ["Kashmiri", "कश्मीरी / كشميري"],
+ ku: ["Kurdish", "Kurdî / كوردی"],
+ kv: ["Komi", "Коми"],
+ kw: ["Cornish", "Kernewek"],
+ ky: ["Kirghiz", "Kırgızca / Кыргызча"],
+ la: ["Latin", "Latina"],
+ lb: ["Luxembourgish", "Lëtzebuergesch"],
+ lg: ["Ganda", "Luganda"],
+ li: ["Limburgian", "Limburgs"],
+ ln: ["Lingala", "Lingála"],
+ lo: ["Laotian", "ລາວ / Pha xa lao"],
+ lt: ["Lithuanian", "Lietuvių"],
+ lv: ["Latvian", "Latviešu"],
+ mg: ["Malagasy", "Malagasy"],
+ mh: ["Marshallese", "Kajin Majel / Ebon"],
+ mi: ["Maori", "Māori"],
+ mk: ["Macedonian", "Македонски"],
+ ml: ["Malayalam", "മലയാളം"],
+ mn: ["Mongolian", "Монгол"],
+ mo: ["Moldovan", "Moldovenească"],
+ mr: ["Marathi", "मराठी"],
+ ms: ["Malay", "Bahasa Melayu"],
+ mt: ["Maltese", "bil-Malti"],
+ my: ["Burmese", "Myanmasa"],
+ na: ["Nauruan", "Dorerin Naoero"],
+ nd: ["North Ndebele", "Sindebele"],
+ ne: ["Nepali", "नेपाली"],
+ ng: ["Ndonga", "Oshiwambo"],
+ nl: ["Dutch", "Nederlands"],
+ nn: ["Norwegian Nynorsk", "Norsk (nynorsk)"],
+ no: ["Norwegian", "Norsk (bokmål / riksmål)"],
+ nr: ["South Ndebele", "isiNdebele"],
+ nv: ["Navajo", "Diné bizaad"],
+ ny: ["Chichewa", "Chi-Chewa"],
+ oc: ["Occitan", "Occitan"],
+ oj: ["Ojibwa", "ᐊᓂᔑᓈᐯᒧᐎᓐ / Anishinaabemowin"],
+ om: ["Oromo", "Oromoo"],
+ or: ["Oriya", "ଓଡ଼ିଆ"],
+ os: ["Ossetian / Ossetic", "Иронау"],
+ pa: ["Panjabi / Punjabi", "ਪੰਜਾਬੀ / पंजाबी / پنجابي"],
+ pi: ["Pali", "Pāli / पाऴि"],
+ pl: ["Polish", "Polski"],
+ ps: ["Pashto", "پښتو"],
+ pt: ["Portuguese", "Português"],
+ qu: ["Quechua", "Runa Simi"],
+ rm: ["Raeto Romance", "Rumantsch"],
+ rn: ["Kirundi", "Kirundi"],
+ ro: ["Romanian", "Română"],
+ ru: ["Russian", "Русский"],
+ rw: ["Rwandi", "Kinyarwandi"],
+ sa: ["Sanskrit", "संस्कृतम्"],
+ sc: ["Sardinian", "Sardu"],
+ sd: ["Sindhi", "सिनधि"],
+ se: ["Northern Sami", "Sámegiella"],
+ sg: ["Sango", "Sängö"],
+ sh: ["Serbo-Croatian", "Srpskohrvatski / Српскохрватски"],
+ si: ["Sinhalese", "සිංහල"],
+ sk: ["Slovak", "Slovenčina"],
+ sl: ["Slovenian", "Slovenščina"],
+ sm: ["Samoan", "Gagana Samoa"],
+ sn: ["Shona", "chiShona"],
+ so: ["Somalia", "Soomaaliga"],
+ sq: ["Albanian", "Shqip"],
+ sr: ["Serbian", "Српски"],
+ ss: ["Swati", "SiSwati"],
+ st: ["Southern Sotho", "Sesotho"],
+ su: ["Sundanese", "Basa Sunda"],
+ sv: ["Swedish", "Svenska"],
+ sw: ["Swahili", "Kiswahili"],
+ ta: ["Tamil", "தமிழ்"],
+ te: ["Telugu", "తెలుగు"],
+ tg: ["Tajik", "Тоҷикӣ"],
+ th: ["Thai", "ไทย / Phasa Thai"],
+ ti: ["Tigrinya", "ትግርኛ"],
+ tk: ["Turkmen", "Туркмен / تركمن"],
+ tl: ["Tagalog / Filipino", "Tagalog"],
+ tn: ["Tswana", "Setswana"],
+ to: ["Tonga", "Lea Faka-Tonga"],
+ tr: ["Turkish", "Türkçe"],
+ ts: ["Tsonga", "Xitsonga"],
+ tt: ["Tatar", "Tatarça"],
+ tw: ["Twi", "Twi"],
+ ty: ["Tahitian", "Reo Mā`ohi"],
+ ug: ["Uyghur", "Uyƣurqə / ئۇيغۇرچە"],
+ uk: ["Ukrainian", "Українська"],
+ ur: ["Urdu", "اردو"],
+ uz: ["Uzbek", "Ўзбек"],
+ ve: ["Venda", "Tshivenḓa"],
+ vi: ["Vietnamese", "Tiếng Việt"],
+ vo: ["Volapük", "Volapük"],
+ wa: ["Walloon", "Walon"],
+ wo: ["Wolof", "Wollof"],
+ xh: ["Xhosa", "isiXhosa"],
+ yi: ["Yiddish", "ייִדיש"],
+ yo: ["Yoruba", "Yorùbá"],
+ za: ["Zhuang", "Cuengh / Tôô / 壮语"],
+ zh: ["Chinese", "中文"],
+ zu: ["Zulu", "isiZulu"],
+};
+
+export default LANGUAGES;
diff --git a/ui/js/jsonrpc.js b/ui/js/jsonrpc.js
index e9807bea7..4d1ed5bd3 100644
--- a/ui/js/jsonrpc.js
+++ b/ui/js/jsonrpc.js
@@ -27,7 +27,7 @@ jsonrpc.call = function(
xhr.addEventListener("load", function() {
var response = JSON.parse(xhr.responseText);
- let error = response.error || response.result && response.result.error;
+ let error = response.error || (response.result && response.result.error);
if (error) {
if (errorCallback) {
errorCallback(error);
diff --git a/ui/js/main.js b/ui/js/main.js
index aed81f1c8..42c40a3a2 100644
--- a/ui/js/main.js
+++ b/ui/js/main.js
@@ -7,6 +7,7 @@ import { Provider } from "react-redux";
import store from "store.js";
import SplashScreen from "component/splash";
import { doChangePath, doNavigate, doDaemonReady } from "actions/app";
+import { doDownloadLanguages } from "actions/settings";
import { toQueryString } from "util/query_params";
import * as types from "constants/action_types";
@@ -96,6 +97,8 @@ const updateProgress = () => {
const initialState = app.store.getState();
var init = function() {
+ app.store.dispatch(doDownloadLanguages());
+
function onDaemonReady() {
window.sessionStorage.setItem("loaded", "y"); //once we've made it here once per session, we don't need to show splash again
app.store.dispatch(doDaemonReady());
diff --git a/ui/js/page/backup/view.jsx b/ui/js/page/backup/view.jsx
index f3d930a17..6ce9de6b6 100644
--- a/ui/js/page/backup/view.jsx
+++ b/ui/js/page/backup/view.jsx
@@ -20,17 +20,22 @@ class BackupPage extends React.PureComponent {
-
{__("Backup Wallet")}
+ {__("Backup Your LBRY Credits")}
{__(
- "Currently, there is no automatic wallet backup, but it is fairly easy to back up manually."
+ "Your LBRY credits are controllable by you and only you, via wallet file(s) stored locally on your computer."
)}
{__(
- "To backup your wallet, make a copy of the folder listed below:"
+ "Currently, there is no automatic wallet backup. If you lose access to these files, you will lose your credits permanently."
+ )}
+
+
+ {__(
+ "However, it is fairly easy to back up manually. To backup your wallet, make a copy of the folder listed below:"
)}
@@ -45,6 +50,13 @@ class BackupPage extends React.PureComponent {
)}
+
+ For more details on backing up and best practices,{" "}
+ .
+
diff --git a/ui/js/page/filePage/view.jsx b/ui/js/page/filePage/view.jsx
index 3ca23aadc..8459ed01c 100644
--- a/ui/js/page/filePage/view.jsx
+++ b/ui/js/page/filePage/view.jsx
@@ -111,7 +111,7 @@ class FilePage extends React.PureComponent {
{!fileInfo || fileInfo.written_bytes <= 0
?
- {isRewardContent && {" "} }
+ {isRewardContent && {" "} }
: null}
{title}
diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx
index 8d97f33f6..1e7bba459 100644
--- a/ui/js/page/rewards/view.jsx
+++ b/ui/js/page/rewards/view.jsx
@@ -127,7 +127,9 @@ class RewardsPage extends React.PureComponent {
- {__("This application is unable to earn rewards due to an authentication failure.")}
+ {__(
+ "This application is unable to earn rewards due to an authentication failure."
+ )}
diff --git a/ui/js/page/settings/index.js b/ui/js/page/settings/index.js
index 8516f55f4..5fe86a7a2 100644
--- a/ui/js/page/settings/index.js
+++ b/ui/js/page/settings/index.js
@@ -6,13 +6,21 @@ import {
doSetClientSetting,
doGetThemes,
doSetTheme,
+ doChangeLanguage,
} from "actions/settings";
-import { selectDaemonSettings, selectShowNsfw } from "selectors/settings";
+import {
+ selectDaemonSettings,
+ selectShowNsfw,
+ selectLanguages,
+} from "selectors/settings";
+import { selectCurrentLanguage } from "selectors/app";
import SettingsPage from "./view";
const select = state => ({
daemonSettings: selectDaemonSettings(state),
showNsfw: selectShowNsfw(state),
+ language: selectCurrentLanguage(state),
+ languages: selectLanguages(state),
});
const perform = dispatch => ({
@@ -21,6 +29,7 @@ const perform = dispatch => ({
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
setTheme: name => dispatch(doSetTheme(name)),
getThemes: () => dispatch(doGetThemes),
+ changeLanguage: newLanguage => dispatch(doChangeLanguage(newLanguage)),
});
export default connect(select, perform)(SettingsPage);
diff --git a/ui/js/page/settings/view.jsx b/ui/js/page/settings/view.jsx
index 54dc969a0..cc030ffd8 100644
--- a/ui/js/page/settings/view.jsx
+++ b/ui/js/page/settings/view.jsx
@@ -110,11 +110,10 @@ class SettingsPage extends React.PureComponent {
this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked);
}
- // onLanguageChange(language) {
- // lbry.setClientSetting('language', language);
- // i18n.setLocale(language);
- // this.setState({language: language})
- // }
+ onLanguageChange(e) {
+ this.props.changeLanguage(e.target.value);
+ this.forceUpdate();
+ }
onShowUnavailableChange(event) {}
@@ -125,7 +124,7 @@ class SettingsPage extends React.PureComponent {
componentDidMount() {}
render() {
- const { daemonSettings } = this.props;
+ const { daemonSettings, language, languages } = this.props;
if (!daemonSettings || Object.keys(daemonSettings).length === 0) {
return (
@@ -137,6 +136,28 @@ class SettingsPage extends React.PureComponent {
return (
+
+
+
{__("Language")}
+
+
+
+
+ {__("English")}
+ {Object.keys(languages).map(dLang =>
+
+ {languages[dLang]}
+
+ )}
+
+
+
+
{__("Download Directory")}
diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js
index a503de7f1..62784fd0f 100644
--- a/ui/js/reducers/content.js
+++ b/ui/js/reducers/content.js
@@ -21,10 +21,7 @@ reducers[types.FETCH_FEATURED_CONTENT_COMPLETED] = function(state, action) {
});
};
-reducers[types.FETCH_REWARD_CONTENT_COMPLETED] = function(
- state,
- action
-) {
+reducers[types.FETCH_REWARD_CONTENT_COMPLETED] = function(state, action) {
const { claimIds, success } = action.data;
return Object.assign({}, state, {
diff --git a/ui/js/reducers/settings.js b/ui/js/reducers/settings.js
index 6dc59a068..14d04f450 100644
--- a/ui/js/reducers/settings.js
+++ b/ui/js/reducers/settings.js
@@ -1,5 +1,6 @@
import * as types from "constants/action_types";
import * as settings from "constants/settings";
+import LANGUAGES from "constants/languages";
import lbry from "lbry";
const reducers = {};
@@ -8,7 +9,9 @@ const defaultState = {
showNsfw: lbry.getClientSetting("showNsfw"),
theme: lbry.getClientSetting(settings.THEME),
themes: lbry.getClientSetting(settings.THEMES),
+ language: lbry.getClientSetting("language"),
},
+ languages: {},
};
reducers[types.DAEMON_SETTINGS_RECEIVED] = function(state, action) {
@@ -28,6 +31,28 @@ reducers[types.CLIENT_SETTING_CHANGED] = function(state, action) {
});
};
+reducers[types.DOWNLOAD_LANGUAGE_SUCCEEDED] = function(state, action) {
+ const languages = Object.assign({}, state.languages);
+ const language = action.data.language;
+
+ const langCode = language.substring(0, 2);
+
+ if (LANGUAGES[langCode]) {
+ languages[language] =
+ LANGUAGES[langCode][0] + " (" + LANGUAGES[langCode][1] + ")";
+ } else {
+ languages[langCode] = langCode;
+ }
+
+ return Object.assign({}, state, { languages });
+};
+
+reducers[types.DOWNLOAD_LANGUAGE_FAILED] = function(state, action) {
+ const languages = Object.assign({}, state.languages);
+ delete languages[action.data.language];
+ return Object.assign({}, state, { languages });
+};
+
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js
index 574494c7f..8d9f34741 100644
--- a/ui/js/selectors/app.js
+++ b/ui/js/selectors/app.js
@@ -218,6 +218,11 @@ export const selectBadgeNumber = createSelector(
state => state.badgeNumber
);
+export const selectCurrentLanguage = createSelector(
+ _selectState,
+ () => app.i18n.getLocale() || "en"
+);
+
export const selectPathAfterAuth = createSelector(
_selectState,
state => state.pathAfterAuth
diff --git a/ui/js/selectors/settings.js b/ui/js/selectors/settings.js
index d875ef360..61c7dc4f0 100644
--- a/ui/js/selectors/settings.js
+++ b/ui/js/selectors/settings.js
@@ -21,3 +21,8 @@ export const selectShowNsfw = createSelector(
selectClientSettings,
clientSettings => !!clientSettings.showNsfw
);
+
+export const selectLanguages = createSelector(
+ _selectState,
+ state => state.languages || {}
+);
diff --git a/ui/js/utils.js b/ui/js/utils.js
index 18e4ca21e..b41a2e0d4 100644
--- a/ui/js/utils.js
+++ b/ui/js/utils.js
@@ -1,5 +1,3 @@
-const { remote } = require("electron");
-
/**
* Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value
* is not set yet.