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)
+}