diff --git a/README.md b/README.md index ec0b625a2..69ec19cdc 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ alt="Get it on Obtainium" height="54" /> --> - Embed player works with [stream.place](https://stream.place/) links! - Open skeets in PDSls and original pages of bridged posts - You can redraft skeets -- Better defaults (alt text required 😉) +- Better defaults (alt text required 😉 autoplay off 🫨) - More unique repost icons - Can download videos - 'Mutuals' in place of 'Following' when relevant @@ -50,6 +50,8 @@ These are all available as options in a sub-page of the app's settings. - Toggle similar account recommendations - Toggle to make all user avatars square (like labelers) - Toggle for more square-ish UI (still slightly rounded) +- Toggle to remove the composer prompt at the top of the Following & Discover feeds +- Change post translation provider (between Google, Kagi, Papago, and LibreTranslate) #### Metrics @@ -64,15 +66,6 @@ You can completely disable the visiblity of all metrics individually, including - following - & who someone's followed by -#### Gates - -- Toggle for an alternate share icon -- Toggle to show feed context for debugging -- Toggle to hide the 'show latest' button -- Toggle to make reply button open thread from feeds -- More may be available in developer mode? Often less 🤷 - - (Accessible by holding the version in the About settings screen) - ## Upcoming or wishful features - Better OpenGraph support for sharing profiles & skeets (including videos & fixing quotes) @@ -83,17 +76,16 @@ You can completely disable the visiblity of all metrics individually, including ### TODO: Xan - [ ] Setup App Linking for Android (.well-known w/ app package fingerprint) -- [ ] Automatic PDS detection like other social-app forks (fallback/email addresses to use witchsky.social) +- [ ] Fallback/email addresses to use witchsky.social in Automatic PDS detection - [ ] Change followed accounts [on onboarding](https://github.com/blacksky-algorithms/blacksky.community/commit/e36ee43efb4999f070860d7f70122e45b28c1e2b) -- [ ] Join date & switch accounts from composer from a fork like [deer.aylac.top](https://github.com/ayla6/deer-social-test) +- [ ] Join date & switch accounts in composer from a fork like [deer.aylac.top](https://github.com/ayla6/deer-social-test) - [ ] Visual replies indicator like the [Firmament userstyle](https://witchsky.app/profile/did:plc:jwhxcrf5uvl3vyw7nurecgt5/post/3m4rr3vzmak2a) (and likes?) -- [ ] Additional translation service providers + setting (Deepl, Kagi) - [ ] Put DeerSettings into separate subpages - [ ] After subpages for options, add [Outlinks page](https://witchsky.app/profile/did:plc:q7suwaz53ztc4mbiqyygbn43/post/3m5zjhhshic2g) & - [ ] ShareMenuItems.tsx, ShareMenuItems.web.tsx - [ ] For profile meatball button, Open profile in PDSls & Open bridged OG fedi account page - [ ] ProfileMenu.tsx -- [ ] Witchsky PDS and .social site (list good songs containing 'bitch' in their titles) +- [ ] Witchsky PDS and .social site (list good songs containing 'bitch' in their titles for related site) ### Even more wishful or far off @@ -101,7 +93,7 @@ You can completely disable the visiblity of all metrics individually, including - [ ] Submit releases to the Google Play Store and iOS App Store - [ ] Move from [Cloudflare Pages](https://pages.cloudflare.com/) to [wisp.place](https://wisp.place/) (needs serverless for embeds) - [ ] Toggle between handle and DID in share links -- [ ] Move TOS and privacy policy to Jollywhoppers website? +- [ ] Move TOS and privacy policy to Jollywhoppers website - [ ] Ignore `!no-unauthenticated` labels - [ ] Material 3 Expressive theming on Android (Liquid **ass on iOS) diff --git a/src/lib/hooks/useTranslate.ts b/src/lib/hooks/useTranslate.ts index 945c02183..f1e1044d0 100644 --- a/src/lib/hooks/useTranslate.ts +++ b/src/lib/hooks/useTranslate.ts @@ -1,7 +1,12 @@ import {useCallback} from 'react' import * as IntentLauncher from 'expo-intent-launcher' -import {getTranslatorLink, getTranslatorLinkKagi} from '#/locale/helpers' +import { + getTranslatorLink, + getTranslatorLinkKagi, + getTranslatorLinkLibreTranslate, + getTranslatorLinkPapago, +} from '#/locale/helpers' import {useTranslationServicePreference} from '#/state/preferences/translation-service-preference' import {IS_ANDROID} from '#/env' import {useOpenLink} from './useOpenLink' @@ -19,6 +24,10 @@ export function useTranslate() { // it is a mystery https://www.youtube.com/watch?v=fq3abPnEEGE if (translationServicePreference == 'kagi') { translateUrl = getTranslatorLinkKagi(text, language) + } else if (translationServicePreference == 'papago') { + translateUrl = getTranslatorLinkPapago(text, language) + } else if (translationServicePreference == 'libreTranslate') { + translateUrl = getTranslatorLinkLibreTranslate(text, language) } else { translateUrl = getTranslatorLink(text, language) } diff --git a/src/locale/helpers.ts b/src/locale/helpers.ts index 7b2c7186e..180994d28 100644 --- a/src/locale/helpers.ts +++ b/src/locale/helpers.ts @@ -3,6 +3,7 @@ import * as bcp47Match from 'bcp-47-match' import lande from 'lande' import {hasProp} from '#/lib/type-guards' +import * as persisted from '#/state/persisted' import { AppLanguage, type Language, @@ -140,6 +141,22 @@ export function getTranslatorLinkKagi(text: string, lang: string): string { )}` } +export function getTranslatorLinkPapago(text: string, lang: string): string { + return `https://papago.naver.com/?sk=auto&tk=${lang}&st=${encodeURIComponent( + text, + )}` +} + +export function getTranslatorLinkLibreTranslate( + text: string, + lang: string, +): string { + const instance = + persisted.get('libreTranslateInstance') ?? + persisted.defaults.libreTranslateInstance! + return `${instance}?source=auto&target=${lang}&q=${encodeURIComponent(text)}` +} + /** * Returns a valid `appLanguage` value from an arbitrary string. * diff --git a/src/screens/Settings/DeerSettings.tsx b/src/screens/Settings/DeerSettings.tsx index 1fd62164d..7dc8a4e5a 100644 --- a/src/screens/Settings/DeerSettings.tsx +++ b/src/screens/Settings/DeerSettings.tsx @@ -108,6 +108,8 @@ import { useShowLinkInHandle, } from '#/state/preferences/show-link-in-handle.tsx' import { + useLibreTranslateInstance, + useSetLibreTranslateInstance, useSetTranslationServicePreference, useTranslationServicePreference, } from '#/state/preferences/translation-service-preference' @@ -128,6 +130,7 @@ import {RaisingHand4Finger_Stroke2_Corner0_Rounded as RaisingHandIcon} from '#/c import {Star_Stroke2_Corner0_Rounded as StarIcon} from '#/components/icons/Star' import {Verified_Stroke2_Corner2_Rounded as VerifiedIcon} from '#/components/icons/Verified' import * as Layout from '#/components/Layout' +import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' import {IS_WEB} from '#/env' import {SearchProfileCard} from '../Search/components/SearchProfileCard' @@ -142,7 +145,8 @@ function ConstellationInstanceDialog({ const pal = usePalette('default') const {_} = useLingui() - const [url, setUrl] = useState('') + const constellationInstance = useConstellationInstance() + const [url, setUrl] = useState(constellationInstance ?? '') const setConstellationInstance = useSetConstellationInstance() const submit = () => { @@ -162,7 +166,7 @@ function ConstellationInstanceDialog({ setUrl('')}> + onClose={() => setUrl(constellationInstance ?? '')}> @@ -185,6 +189,83 @@ function ConstellationInstanceDialog({ accessibilityHint={_( msg`Input the url of the constellations instance to use`, )} + defaultValue={constellationInstance} + /> + + + + + + + + + + ) +} + +function LibreTranslateInstanceDialog({ + control, +}: { + control: Dialog.DialogControlProps +}) { + const pal = usePalette('default') + const {_} = useLingui() + + const libreTranslateInstance = useLibreTranslateInstance() + const [url, setUrl] = useState(libreTranslateInstance ?? '') + const setLibreTranslateInstance = useSetLibreTranslateInstance() + + const submit = () => { + setLibreTranslateInstance(url) + control.close() + } + + const shouldDisable = () => { + try { + return !new URL(url).hostname.includes('.') + } catch (e) { + return true + } + } + + return ( + setUrl(libreTranslateInstance ?? '')}> + + + + + LibreTranslate instance URL + + + + + { + setUrl(value) + }} + placeholder={persisted.defaults.libreTranslateInstance} + placeholderTextColor={pal.colors.textLight} + onSubmitEditing={submit} + accessibilityHint={_( + msg`Input the url of the LibreTranslate instance to use`, + )} + defaultValue={libreTranslateInstance} /> @@ -342,6 +423,8 @@ export function DeerSettingsScreen({}: Props) { const translationServicePreference = useTranslationServicePreference() const setTranslationServicePreference = useSetTranslationServicePreference() + const setLibreTranslateInstanceControl = Dialog.useDialogControl() + return ( @@ -480,11 +563,18 @@ export function DeerSettingsScreen({}: Props) { Constellation is used to supplement AppView responses for custom verifications and nuclear block bypass, via backlinks. Current - instance: {constellationInstance} + instance: + + {constellationInstance} + + + @@ -643,16 +733,14 @@ export function DeerSettingsScreen({}: Props) { + + - Translation Engine + Post Translation Engine - - Choose the engine to use when translating posts. - - + + setTranslationServicePreference('papago')} + style={[a.w_full]}> + + Use Naver Papago + + + + + setTranslationServicePreference('libreTranslate')} + style={[a.w_full]}> + + Use LibreTranslate + + + + {translationServicePreference === 'libreTranslate' && ( + + + + {`LibreTranslate Instance`} + + setLibreTranslateInstanceControl.open()} + /> + + )} + + + @@ -698,12 +825,12 @@ export function DeerSettingsScreen({}: Props) { setDisableRepostsMetrics(value)} style={[a.w_full]}> - Disable reskeets metrics + Disable reskeet metrics @@ -793,14 +920,7 @@ export function DeerSettingsScreen({}: Props) { - - - - These settings might summon nasal demons! Restart the app after - changing if anything breaks. - - - + @@ -847,6 +967,9 @@ export function DeerSettingsScreen({}: Props) { + ) } diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 5a045958b..4740052f3 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -172,7 +172,13 @@ const schema = z.object({ showExternalShareButtons: z.boolean().optional(), - translationServicePreference: z.enum(['google', 'kagi']), + translationServicePreference: z.enum([ + 'google', + 'kagi', + 'papago', + 'libreTranslate', + ]), + libreTranslateInstance: z.string().optional(), /** @deprecated */ mutedThreads: z.array(z.string()), @@ -284,6 +290,7 @@ export const defaults: Schema = { hideUnreplyablePosts: false, showExternalShareButtons: false, translationServicePreference: 'google', + libreTranslateInstance: 'https://libretranslate.com/', } export function tryParse(rawData: string): Schema | undefined { diff --git a/src/state/preferences/translation-service-preference.tsx b/src/state/preferences/translation-service-preference.tsx index 978dd24cd..cfa0d9be7 100644 --- a/src/state/preferences/translation-service-preference.tsx +++ b/src/state/preferences/translation-service-preference.tsx @@ -4,6 +4,10 @@ import * as persisted from '#/state/persisted' type StateContext = persisted.Schema['translationServicePreference'] type SetContext = (v: persisted.Schema['translationServicePreference']) => void +type InstanceStateContext = persisted.Schema['libreTranslateInstance'] +type SetInstanceContext = ( + v: persisted.Schema['libreTranslateInstance'], +) => void const stateContext = React.createContext( persisted.defaults.translationServicePreference, @@ -11,11 +15,20 @@ const stateContext = React.createContext( const setContext = React.createContext( (_: persisted.Schema['translationServicePreference']) => {}, ) +const instanceStateContext = React.createContext( + persisted.defaults.libreTranslateInstance, +) +const setInstanceContext = React.createContext( + (_: persisted.Schema['libreTranslateInstance']) => {}, +) export function Provider({children}: React.PropsWithChildren<{}>) { const [state, setState] = React.useState( persisted.get('translationServicePreference'), ) + const [instanceState, setInstanceState] = React.useState( + persisted.get('libreTranslateInstance'), + ) const setStateWrapped = React.useCallback( ( @@ -30,6 +43,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) { [setState], ) + const setInstanceStateWrapped = React.useCallback( + (libreTranslateInstance: persisted.Schema['libreTranslateInstance']) => { + setInstanceState(libreTranslateInstance) + persisted.write('libreTranslateInstance', libreTranslateInstance) + }, + [setInstanceState], + ) + React.useEffect(() => { return persisted.onUpdate( 'translationServicePreference', @@ -39,10 +60,20 @@ export function Provider({children}: React.PropsWithChildren<{}>) { ) }, [setStateWrapped]) + React.useEffect(() => { + return persisted.onUpdate('libreTranslateInstance', nextInstance => { + setInstanceState(nextInstance) + }) + }, [setInstanceStateWrapped]) + return ( - {children} + + + {children} + + ) @@ -55,3 +86,14 @@ export function useTranslationServicePreference() { export function useSetTranslationServicePreference() { return React.useContext(setContext) } + +export function useLibreTranslateInstance() { + return ( + React.useContext(instanceStateContext) ?? + persisted.defaults.libreTranslateInstance! + ) +} + +export function useSetLibreTranslateInstance() { + return React.useContext(setInstanceContext) +}