Merge branch 'i18n'

This commit is contained in:
Jeremy Kauffman 2017-08-24 13:43:16 -04:00
commit 3de3dd2544
22 changed files with 440 additions and 57 deletions

1
.gitignore vendored
View file

@ -10,6 +10,7 @@
/app/dist
/app/locales
/app/node_modules
/build/venv
/build/daemon.ver

View file

@ -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));
});
};
}

View file

@ -1,5 +1,9 @@
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";
export function doFetchDaemonSettings() {
return function(dispatch, getState) {
@ -41,3 +45,96 @@ export function doSetClientSetting(key, value) {
},
};
}
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);
};
}

View file

@ -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 {
<div className="card__subtitle">
<span style={{ float: "right" }}>
<FilePrice uri={uri} />
{isRewardContent && <span>{" "}<IconFeatured /></span> }
{fileInfo && <span>{" "}<Icon fixed icon="icon-folder" /></span> }
{isRewardContent && <span>{" "}<IconFeatured /></span>}
{fileInfo &&
<span>{" "}<Icon fixed icon="icon-folder" /></span>}
</span>
<UriIndicator uri={uri} />
</div>

View file

@ -71,7 +71,8 @@ class FileTile 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 onClick = () => navigate("/show", { uri });
@ -109,7 +110,7 @@ class FileTile extends React.PureComponent {
<div className="file-tile__content">
<div className="card__title-primary">
{!hidePrice ? <FilePrice uri={this.props.uri} /> : null}
{isRewardContent && <IconFeatured /> }
{isRewardContent && <IconFeatured />}
<div className="meta">{uri}</div>
<h3>
<TruncatedText lines={1}>{title}</TruncatedText>

View file

@ -3,10 +3,11 @@ import { Icon } from "component/common.js";
const IconFeatured = props => {
return (
<span className="icon-featured" title={ __("Watch content with this icon to earn weekly rewards.")}>
<Icon icon="icon-rocket"
fixed
className="card__icon-featured-content" />
<span
className="icon-featured"
title={__("Watch content with this icon to earn weekly rewards.")}
>
<Icon icon="icon-rocket" fixed className="card__icon-featured-content" />
</span>
);
};

View file

@ -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)),
});

View file

@ -29,20 +29,34 @@ class UserVerify extends React.PureComponent {
<p>
{__(
"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.") + " " }
<Link href="https://lbry.io/faq/identity-requirements" label={__("Read More")} />
) +
" " +
__("There is no charge at all, now or in the future.") +
" "}
<Link
href="https://lbry.io/faq/identity-requirements"
label={__("Read More")}
/>
</p>
{errorMessage && <p className="form-field__error">{errorMessage}</p>}
<p><CardVerify
label={__("Link Card and Finish")}
disabled={isPending}
token={this.onToken.bind(this)}
stripeKey="pk_live_e8M4dRNnCCbmpZzduEUZBgJO"
/></p>
<p>
{__("You can continue without this step, but you will not be eligible to earn rewards.")}
<CardVerify
label={__("Link Card and Finish")}
disabled={isPending}
token={this.onToken.bind(this)}
stripeKey="pk_live_e8M4dRNnCCbmpZzduEUZBgJO"
/>
</p>
<Link onClick={() => navigate("/discover")} button="alt" label={__("Skip Rewards")} />
<p>
{__(
"You can continue without this step, but you will not be eligible to earn rewards."
)}
</p>
<Link
onClick={() => navigate("/discover")}
button="alt"
label={__("Skip Rewards")}
/>
</div>
);
}

View file

@ -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;
}
}

View file

@ -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";

View file

@ -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;

View file

@ -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);

View file

@ -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());

View file

@ -111,7 +111,7 @@ class FilePage extends React.PureComponent {
{!fileInfo || fileInfo.written_bytes <= 0
? <span style={{ float: "right" }}>
<FilePrice uri={lbryuri.normalize(uri)} />
{isRewardContent && <span>{" "}<IconFeatured /></span> }
{isRewardContent && <span>{" "}<IconFeatured /></span>}
</span>
: null}
<h1>{title}</h1>

View file

@ -127,7 +127,9 @@ class RewardsPage extends React.PureComponent {
<div>
<div className="card__content empty">
<p>
{__("This application is unable to earn rewards due to an authentication failure.")}
{__(
"This application is unable to earn rewards due to an authentication failure."
)}
</p>
</div>
</div>

View file

@ -1,19 +1,31 @@
import React from "react";
import { connect } from "react-redux";
import { doClearCache } from "actions/app";
import { doSetDaemonSetting, doSetClientSetting } from "actions/settings";
import { selectDaemonSettings, selectShowNsfw } from "selectors/settings";
import {
doSetDaemonSetting,
doSetClientSetting,
doChangeLanguage,
} from "actions/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 => ({
setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
clearCache: () => dispatch(doClearCache()),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
changeLanguage: newLanguage => dispatch(doChangeLanguage(newLanguage)),
});
export default connect(select, perform)(SettingsPage);

View file

@ -96,16 +96,15 @@ 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) {}
render() {
const { daemonSettings } = this.props;
const { daemonSettings, language, languages } = this.props;
if (!daemonSettings || Object.keys(daemonSettings).length === 0) {
return (
@ -117,6 +116,28 @@ class SettingsPage extends React.PureComponent {
return (
<main className="main--single-column">
<SubHeader />
<section className="card">
<div className="card__content">
<h3>{__("Language")}</h3>
</div>
<div className="card__content">
<div className="form-row">
<FormField
type="select"
name="language"
defaultValue={language}
onChange={this.onLanguageChange.bind(this)}
>
<option value="en">{__("English")}</option>
{Object.keys(languages).map(dLang =>
<option key={dLang} value={dLang}>
{languages[dLang]}
</option>
)}
</FormField>
</div>
</div>
</section>
<section className="card">
<div className="card__content">
<h3>{__("Download Directory")}</h3>

View file

@ -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, {

View file

@ -1,11 +1,14 @@
import * as types from "constants/action_types";
import LANGUAGES from "constants/languages";
import lbry from "lbry";
const reducers = {};
const defaultState = {
clientSettings: {
showNsfw: lbry.getClientSetting("showNsfw"),
language: lbry.getClientSetting("language"),
},
languages: {},
};
reducers[types.DAEMON_SETTINGS_RECEIVED] = function(state, action) {
@ -25,6 +28,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);

View file

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

View file

@ -21,3 +21,8 @@ export const selectShowNsfw = createSelector(
selectClientSettings,
clientSettings => !!clientSettings.showNsfw
);
export const selectLanguages = createSelector(
_selectState,
state => state.languages || {}
);

View file

@ -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.