diff --git a/package.json b/package.json index dbeed3b91..8022b92a1 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "imagesloaded": "^4.1.4", "json-loader": "^0.5.4", "lbry-format": "https://github.com/lbryio/lbry-format.git", - "lbry-redux": "lbryio/lbry-redux#1fc5afa0c45cfb4126539513088b580db9c4aca1", + "lbry-redux": "lbryio/lbry-redux#6828dc2b86818a00d762c1b883572ea17f0bf6b9", "lbryinc": "lbryio/lbryinc#517c28a183d6ab69a357227809bc7c3c12d8411e", "lint-staged": "^7.0.2", "localforage": "^1.7.1", diff --git a/static/app-strings.json b/static/app-strings.json index c77c4b104..0f13d81ae 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -1457,6 +1457,12 @@ "Pin": "Pin", "Unpin": "Unpin", "LBRY leveled up": "LBRY leveled up", + "Post": "Post", + "Primary Language": "Primary Language", + "Your main content language": "Your main content language", + "None selected": "None selected", + "Secondary Language": "Secondary Language", + "Your other content language": "Your other content language", "This link leads to an external website.": "This link leads to an external website.", "Hold on, we are setting up your account": "Hold on, we are setting up your account", "No Content Found": "No Content Found", diff --git a/ui/component/channelAbout/index.js b/ui/component/channelAbout/index.js index 98ab9e935..40678c8c7 100644 --- a/ui/component/channelAbout/index.js +++ b/ui/component/channelAbout/index.js @@ -7,6 +7,7 @@ const select = (state, props) => ({ description: makeSelectMetadataItemForUri(props.uri, 'description')(state), website: makeSelectMetadataItemForUri(props.uri, 'website_url')(state), email: makeSelectMetadataItemForUri(props.uri, 'email')(state), + languages: makeSelectMetadataItemForUri(props.uri, 'languages')(state), }); export default connect(select, null)(ChannelAbout); diff --git a/ui/component/channelAbout/view.jsx b/ui/component/channelAbout/view.jsx index b9537d661..8c7d9ddbb 100644 --- a/ui/component/channelAbout/view.jsx +++ b/ui/component/channelAbout/view.jsx @@ -8,6 +8,7 @@ import Button from 'component/button'; import * as PAGES from 'constants/pages'; import DateTime from 'component/dateTime'; import YoutubeBadge from 'component/youtubeBadge'; +import SUPPORTED_LANGUAGES from 'constants/supported_languages'; type Props = { claim: ChannelClaim, @@ -15,6 +16,7 @@ type Props = { description: ?string, email: ?string, website: ?string, + languages: Array, }; const formatEmail = (email: string) => { @@ -27,7 +29,7 @@ const formatEmail = (email: string) => { }; function ChannelAbout(props: Props) { - const { claim, uri, description, email, website } = props; + const { claim, uri, description, email, website, languages } = props; const claimId = claim && claim.claim_id; return ( @@ -64,6 +66,12 @@ function ChannelAbout(props: Props) { + +
+ {/* this could use some nice 'tags' styling */} + {`${SUPPORTED_LANGUAGES[languages[0]]}${languages[1] ? ', ' + SUPPORTED_LANGUAGES[languages[1]] : ''}`} +
+
{claim.meta.claims_in_channel}
diff --git a/ui/component/channelEdit/view.jsx b/ui/component/channelEdit/view.jsx index 047b83dc6..cd6df1ddb 100644 --- a/ui/component/channelEdit/view.jsx +++ b/ui/component/channelEdit/view.jsx @@ -18,6 +18,9 @@ import Card from 'component/common/card'; import * as PAGES from 'constants/pages'; import analytics from 'analytics'; import LbcSymbol from 'component/common/lbc-symbol'; +import SUPPORTED_LANGUAGES from 'constants/supported_languages'; +const LANG_NONE = 'none'; + const MAX_TAG_SELECT = 5; type Props = { @@ -63,7 +66,7 @@ function ChannelForm(props: Props) { coverUrl, tags, locations, - languages, + languages = [], onDone, updateChannel, updateError, @@ -83,6 +86,9 @@ function ChannelForm(props: Props) { const name = params.name; const isNewChannel = !uri; const { replace } = useHistory(); + const languageParam = params.languages; + const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0]; + const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1]; function getChannelParams() { // fill this in with sdk data @@ -141,6 +147,25 @@ function ChannelForm(props: Props) { } } + function handleLanguageChange(index, code) { + let langs = [...languageParam]; + if (index === 0) { + if (code === LANG_NONE) { + // clear all + langs = []; + } else { + langs[0] = code; + } + } else { + if (code === LANG_NONE || code === langs[0]) { + langs.splice(1, 1); + } else { + langs[index] = code; + } + } + setParams({ ...params, languages: langs }); + } + function handleThumbnailChange(thumbnailUrl: string) { setParams({ ...params, thumbnailUrl }); } @@ -356,6 +381,43 @@ function ChannelForm(props: Props) { value={params.email} onChange={e => setParams({ ...params, email: e.target.value })} /> + handleLanguageChange(0, event.target.value)} + value={primaryLanguage} + helper={__('Your main content language')} + > + + {Object.keys(SUPPORTED_LANGUAGES).map(language => ( + + ))} + + handleLanguageChange(1, event.target.value)} + value={secondaryLanguage} + disabled={!languageParam[0]} + helper={__('Your other content language')} + > + + {Object.keys(SUPPORTED_LANGUAGES) + .filter(lang => lang !== languageParam[0]) + .map(language => ( + + ))} + } /> diff --git a/ui/component/claimListDiscover/index.js b/ui/component/claimListDiscover/index.js index 5fa38b2f0..988947a8b 100644 --- a/ui/component/claimListDiscover/index.js +++ b/ui/component/claimListDiscover/index.js @@ -19,7 +19,9 @@ const select = state => ({ loading: selectFetchingClaimSearch(state), showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state), hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state), + languageSetting: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), hiddenUris: selectBlockedChannels(state), + searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state), }); const perform = { diff --git a/ui/component/claimListDiscover/view.jsx b/ui/component/claimListDiscover/view.jsx index 20f03f27b..9a9112116 100644 --- a/ui/component/claimListDiscover/view.jsx +++ b/ui/component/claimListDiscover/view.jsx @@ -61,6 +61,8 @@ type Props = { hideFilters?: boolean, maxPages?: number, forceShowReposts?: boolean, + languageSetting: string, + searchInLanguage: boolean, }; function ClaimListDiscover(props: Props) { @@ -106,6 +108,8 @@ function ClaimListDiscover(props: Props) { claimIds, maxPages, forceShowReposts = false, + languageSetting, + searchInLanguage, } = props; const didNavigateForward = history.action === 'PUSH'; const { search } = location; @@ -121,6 +125,20 @@ function ClaimListDiscover(props: Props) { (urlParams.get(CS.TAGS_KEY) !== null && urlParams.get(CS.TAGS_KEY)) || (defaultTags && getParamFromTags(defaultTags)); const freshnessParam = freshness || urlParams.get(CS.FRESH_KEY) || defaultFreshness; + + const langParam = urlParams.get(CS.LANGUAGE_KEY) || null; + const languageParam = searchInLanguage + ? langParam === null + ? languageSetting + : langParam === 'any' + ? null + : langParam + : langParam === null + ? null + : langParam === 'any' + ? null + : langParam; + const contentTypeParam = urlParams.get(CS.CONTENT_KEY); const claimTypeParam = claimType || (CS.CLAIM_TYPES.includes(contentTypeParam) && contentTypeParam) || defaultClaimType || null; @@ -159,6 +177,7 @@ function ClaimListDiscover(props: Props) { page: number, no_totals: boolean, any_tags?: Array, + any_languages?: Array, not_tags: Array, channel_ids?: Array, claim_ids?: Array, @@ -280,6 +299,24 @@ function ClaimListDiscover(props: Props) { } } + if (languageParam) { + if (languageParam !== CS.LANGUAGES_ALL) { + options.any_languages = [languageParam]; + } + } + + if (tagsParam) { + if (tagsParam !== CS.TAGS_ALL && tagsParam !== '') { + if (tagsParam === CS.TAGS_FOLLOWED) { + options.any_tags = followed; + } else if (Array.isArray(tagsParam)) { + options.any_tags = tagsParam; + } else { + options.any_tags = tagsParam.split(','); + } + } + } + if (hideReposts && !options.reposted_claim_id && !forceShowReposts) { if (Array.isArray(options.claim_type)) { if (options.claim_type.length > 1) { diff --git a/ui/component/claimListHeader/index.js b/ui/component/claimListHeader/index.js index 54e319089..b163182b7 100644 --- a/ui/component/claimListHeader/index.js +++ b/ui/component/claimListHeader/index.js @@ -10,6 +10,8 @@ const select = state => ({ followedTags: selectFollowedTags(state), loading: selectFetchingClaimSearch(state), showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state), + searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state), + languageSetting: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), }); const perform = { diff --git a/ui/component/claimListHeader/view.jsx b/ui/component/claimListHeader/view.jsx index d76d52ca9..ee7d86fe9 100644 --- a/ui/component/claimListHeader/view.jsx +++ b/ui/component/claimListHeader/view.jsx @@ -11,6 +11,7 @@ import { SETTINGS } from 'lbry-redux'; import { FormField } from 'component/common/form'; import Button from 'component/button'; import { toCapitalCase } from 'util/string'; +import SEARCHABLE_LANGUAGES from 'constants/searchable_languages'; type Props = { defaultTags: string, @@ -32,6 +33,8 @@ type Props = { doSetClientSetting: (string, boolean, ?boolean) => void, setPage: number => void, hideFilters: boolean, + searchInLanguage: boolean, + languageSetting: string, }; function ClaimListHeader(props: Props) { @@ -55,6 +58,8 @@ function ClaimListHeader(props: Props) { doSetClientSetting, setPage, hideFilters, + searchInLanguage, + languageSetting, } = props; const { action, push, location } = useHistory(); const { search } = location; @@ -72,6 +77,7 @@ function ClaimListHeader(props: Props) { const streamTypeParam = streamType || (CS.FILE_TYPES.includes(contentTypeParam) && contentTypeParam) || defaultStreamType || null; const durationParam = urlParams.get(CS.DURATION_KEY) || null; + const languageParam = urlParams.get(CS.LANGUAGE_KEY) || null; const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY); const channelIdsParam = channelIdsInUrl ? channelIdsInUrl.split(',') : channelIds; const feeAmountParam = urlParams.get('fee_amount') || feeAmount || CS.FEE_AMOUNT_ANY; @@ -82,9 +88,22 @@ function ClaimListHeader(props: Props) { urlParams.get(CS.CONTENT_KEY) || urlParams.get(CS.DURATION_KEY) || urlParams.get(CS.TAGS_KEY) || - urlParams.get(CS.FEE_AMOUNT_KEY) + urlParams.get(CS.FEE_AMOUNT_KEY) || + urlParams.get(CS.LANGUAGE_KEY) ); + const languageValue = searchInLanguage + ? languageParam === null + ? languageSetting + : languageParam + : languageParam === null + ? CS.LANGUAGES_ALL + : languageParam; + + const shouldHighlight = searchInLanguage + ? languageParam !== languageSetting && languageParam !== null + : languageParam !== CS.LANGUAGES_ALL && languageParam !== null; + React.useEffect(() => { if (action !== 'POP' && isFiltered()) { setExpanded(true); @@ -172,6 +191,9 @@ function ClaimListHeader(props: Props) { newUrlParams.set(CS.DURATION_KEY, delta.value); } break; + case CS.LANGUAGE_KEY: + newUrlParams.set(CS.LANGUAGE_KEY, delta.value); + break; case CS.TAGS_KEY: if (delta.value === CS.TAGS_ALL) { if (defaultTags === CS.TAGS_ALL) { @@ -334,6 +356,43 @@ function ClaimListHeader(props: Props) { )} + {/* LANGUAGE FIELD */} + {!claimType && ( +
+ + handleChange({ + key: CS.LANGUAGE_KEY, + value: e.target.value, + }) + } + > + + {Object.entries(SEARCHABLE_LANGUAGES).map(([code, label]) => { + return ( + + ); + })} + +
+ )} + {/* DURATIONS FIELD */} {showDuration && (
diff --git a/ui/component/settingLanguage/index.js b/ui/component/settingLanguage/index.js index d173530ab..7fee4e9c8 100644 --- a/ui/component/settingLanguage/index.js +++ b/ui/component/settingLanguage/index.js @@ -1,15 +1,17 @@ import { connect } from 'react-redux'; import { SETTINGS } from 'lbry-redux'; -import { doSetLanguage } from 'redux/actions/settings'; +import { doSetLanguage, doSetClientSetting } from 'redux/actions/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings'; import SettingLanguage from './view'; const select = state => ({ language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), + searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state), }); const perform = dispatch => ({ setLanguage: value => dispatch(doSetLanguage(value)), + setSearchInLanguage: value => dispatch(doSetClientSetting(SETTINGS.SEARCH_IN_LANGUAGE, value)), }); export default connect(select, perform)(SettingLanguage); diff --git a/ui/component/settingLanguage/view.jsx b/ui/component/settingLanguage/view.jsx index 8ef9b9707..e1f651326 100644 --- a/ui/component/settingLanguage/view.jsx +++ b/ui/component/settingLanguage/view.jsx @@ -3,15 +3,17 @@ import React, { useState } from 'react'; import { FormField } from 'component/common/form'; import Spinner from 'component/spinner'; -import SUPPORTED_LANGUAGES from '../../constants/supported_languages'; +import SUPPORTED_LANGUAGES from 'constants/supported_languages'; type Props = { language: string, setLanguage: string => void, + searchInLanguage: boolean, + setSearchInLanguage: boolean => void, }; function SettingLanguage(props: Props) { - const { language, setLanguage } = props; + const { language, setLanguage, searchInLanguage, setSearchInLanguage } = props; const [previousLanguage, setPreviousLanguage] = useState(null); const languages = SUPPORTED_LANGUAGES; @@ -45,6 +47,13 @@ function SettingLanguage(props: Props) { ))} {previousLanguage && } + setSearchInLanguage(!searchInLanguage)} + /> ); } diff --git a/ui/constants/claim_search.js b/ui/constants/claim_search.js index a17405d33..0807581d5 100644 --- a/ui/constants/claim_search.js +++ b/ui/constants/claim_search.js @@ -3,6 +3,7 @@ export const PAGE_SIZE = 20; export const FRESH_KEY = 'fresh'; export const ORDER_BY_KEY = 'order'; export const DURATION_KEY = 'duration'; +export const LANGUAGE_KEY = 'language'; export const TAGS_KEY = 't'; export const CONTENT_KEY = 'content'; export const REPOSTED_URI_KEY = 'reposted_uri'; @@ -15,6 +16,8 @@ export const FEE_AMOUNT_ANY = '>=0'; export const FEE_AMOUNT_ONLY_PAID = '>0'; export const FEE_AMOUNT_ONLY_FREE = '<=0'; +export const LANGUAGES_ALL = 'all'; + export const FRESH_DAY = 'day'; export const FRESH_WEEK = 'week'; export const FRESH_MONTH = 'month'; diff --git a/ui/constants/searchable_languages.js b/ui/constants/searchable_languages.js new file mode 100644 index 000000000..7e9406c13 --- /dev/null +++ b/ui/constants/searchable_languages.js @@ -0,0 +1,20 @@ +import LANGUAGES from './languages'; + +const SEARCHABLE_LANGUAGES = { + en: LANGUAGES.en[1], + hr: LANGUAGES.hr[1], + nl: LANGUAGES.nl[1], + fr: LANGUAGES.fr[1], + de: LANGUAGES.de[1], + it: LANGUAGES.it[1], + pl: LANGUAGES.pl[1], + pt: LANGUAGES.pt[1], + ru: LANGUAGES.ru[1], + es: LANGUAGES.es[1], + tr: LANGUAGES.tr[1], + cs: LANGUAGES.cs[1], +}; + +// Properties: language code (e.g. 'ja') +// Values: name of the language in native form (e.g. '日本語') +export default SEARCHABLE_LANGUAGES; diff --git a/ui/redux/reducers/settings.js b/ui/redux/reducers/settings.js index 1962d9529..ea107148b 100644 --- a/ui/redux/reducers/settings.js +++ b/ui/redux/reducers/settings.js @@ -37,6 +37,7 @@ const defaultState = { // UI [SETTINGS.LANGUAGE]: settingLanguage.find(language => SUPPORTED_LANGUAGES[language]), + [SETTINGS.SEARCH_IN_LANGUAGE]: false, [SETTINGS.THEME]: __('light'), [SETTINGS.THEMES]: [__('light'), __('dark')], [SETTINGS.HIDE_SPLASH_ANIMATION]: false, diff --git a/yarn.lock b/yarn.lock index 35fcbc15b..55fe93cb5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7391,9 +7391,9 @@ lazy-val@^1.0.4: yargs "^13.2.2" zstd-codec "^0.1.1" -lbry-redux@lbryio/lbry-redux#1fc5afa0c45cfb4126539513088b580db9c4aca1: +lbry-redux@lbryio/lbry-redux#6828dc2b86818a00d762c1b883572ea17f0bf6b9: version "0.0.1" - resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/1fc5afa0c45cfb4126539513088b580db9c4aca1" + resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/6828dc2b86818a00d762c1b883572ea17f0bf6b9" dependencies: proxy-polyfill "0.1.6" reselect "^3.0.0"