Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork

feat: continue integration

ewancroft.uk f44e6dab 3bbf735c

verified
+6
src/Navigation.tsx
··· 158 158 159 159 /** 160 160 * These "common screens" are reused across stacks. 161 + * 162 + * Note: Navigation titles use i18n._() which evaluates at setup time, not render time. 163 + * This means they cannot dynamically respond to terminology preference changes. 164 + * All in-app content (buttons, dialogs, messages) uses the terminology system, 165 + * but these navigation bar titles remain static. This is a low-impact limitation 166 + * as navigation titles are rarely noticed by users. 161 167 */ 162 168 function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { 163 169 const title = (page: MessageDescriptor) =>
+23 -4
src/components/PostControls/BookmarkButton.tsx
··· 6 6 import type React from 'react' 7 7 8 8 import {useCleanError} from '#/lib/hooks/useCleanError' 9 + import {getTerminology} from '#/lib/strings/terminology' 10 + import {useTerminologyPreference} from '#/state/preferences' 9 11 import {logger} from '#/logger' 10 12 import {type Shadow} from '#/state/cache/post-shadow' 11 13 import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation' ··· 29 31 }): React.ReactNode { 30 32 const t = useTheme() 31 33 const {_} = useLingui() 34 + const terminologyPreference = useTerminologyPreference() 32 35 const {mutateAsync: bookmark} = useBookmarkMutation() 33 36 const cleanError = useCleanError() 34 37 const requireAuth = useRequireAuth() ··· 56 59 <toast.Outer> 57 60 <toast.Icon /> 58 61 <toast.Text> 59 - <Trans>Skeet saved</Trans> 62 + <Trans>{_(getTerminology(terminologyPreference, { 63 + skeet: msg`Skeet saved`, 64 + post: msg`Post saved`, 65 + spell: msg`Spell saved`, 66 + }))}</Trans> 60 67 </toast.Text> 61 68 {!disableUndo && ( 62 69 <toast.Action ··· 91 98 <toast.Outer> 92 99 <toast.Icon icon={TrashIcon} /> 93 100 <toast.Text> 94 - <Trans>Removed from saved skeets</Trans> 101 + <Trans>{_(getTerminology(terminologyPreference, { 102 + skeet: msg`Removed from saved skeets`, 103 + post: msg`Removed from saved posts`, 104 + spell: msg`Removed from saved spells`, 105 + }))}</Trans> 95 106 </toast.Text> 96 107 {!disableUndo && ( 97 108 <toast.Action ··· 125 136 big={big} 126 137 label={ 127 138 isBookmarked 128 - ? _(msg`Remove from saved skeets`) 129 - : _(msg`Add to saved skeets`) 139 + ? _(getTerminology(terminologyPreference, { 140 + skeet: msg`Remove from saved skeets`, 141 + post: msg`Remove from saved posts`, 142 + spell: msg`Remove from saved spells`, 143 + })) 144 + : _(getTerminology(terminologyPreference, { 145 + skeet: msg`Add to saved skeets`, 146 + post: msg`Add to saved posts`, 147 + spell: msg`Add to saved spells`, 148 + })) 130 149 } 131 150 onPress={onHandlePress} 132 151 hitSlop={hitSlop}>
+24 -22
src/components/PostControls/PostMenu/PostMenuItems.tsx
··· 47 47 import {useFeedFeedbackContext} from '#/state/feed-feedback' 48 48 import {useLanguagePrefs} from '#/state/preferences' 49 49 import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences' 50 + import {useTerminology} from '#/lib/hooks/useTerminology' 50 51 import {usePinnedPostMutation} from '#/state/queries/pinned-post' 51 52 import { 52 53 usePostDeleteMutation, ··· 188 189 postUri: post.uri, 189 190 rootPostUri: rootUri, 190 191 }) 192 + const terminology = useTerminology() 191 193 192 194 const href = useMemo(() => { 193 195 const urip = new AtUri(postUri) ··· 197 199 const onDeletePost = () => { 198 200 deletePostMutate({uri: postUri}).then( 199 201 () => { 200 - Toast.show(_(msg({message: 'Skeet deleted', context: 'toast'}))) 202 + Toast.show(_(msg({message: `${_(terminology.post)} deleted`, context: 'toast'}))) 201 203 202 204 const route = getCurrentRoute(navigation.getState()) 203 205 if (route.name === 'PostThread') { ··· 217 219 }, 218 220 e => { 219 221 logger.error('Failed to delete post', {message: e}) 220 - Toast.show(_(msg`Failed to delete skeet, please try again`), 'xmark') 222 + Toast.show(_(msg`Failed to delete ${_(terminology.post)}, please try again`), 'xmark') 221 223 }, 222 224 ) 223 225 } ··· 475 477 }) 476 478 Toast.show( 477 479 isDetach 478 - ? _(msg`Quote skeet was successfully detached`) 479 - : _(msg`Quote skeet was re-attached`), 480 + ? _(msg`Quote ${_(terminology.post)} was successfully detached`) 481 + : _(msg`Quote ${_(terminology.post)} was re-attached`), 480 482 ) 481 483 } catch (e: any) { 482 484 Toast.show( ··· 667 669 <> 668 670 <Prompt.Basic 669 671 control={redraftPromptControl} 670 - title={_(msg`Redraft this skeet?`)} 672 + title={_(msg`Redraft this ${_(terminology.post)}?`)} 671 673 description={_( 672 - msg`This will delete the original skeet and open the composer with its content.`, 674 + msg`This will delete the original ${_(terminology.post)} and open the composer with its content.`, 673 675 )} 674 676 onConfirm={onConfirmRedraft} 675 677 confirmButtonCta={_(msg`Redraft`)} ··· 753 755 754 756 <Menu.Item 755 757 testID="postDropdownCopyTextBtn" 756 - label={_(msg`Copy post text`)} 758 + label={_(msg`Copy ${_(terminology.post)} text`)} 757 759 onPress={onCopyPostText}> 758 - <Menu.ItemText>{_(msg`Copy skeet text`)}</Menu.ItemText> 760 + <Menu.ItemText>{_(msg`Copy ${_(terminology.post)} text`)}</Menu.ItemText> 759 761 <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 760 762 </Menu.Item> 761 763 </> 762 764 ) : ( 763 765 <Menu.Item 764 766 testID="postDropdownSignInBtn" 765 - label={_(msg`Sign in to view skeet`)} 767 + label={_(msg`Sign in to view ${_(terminology.post)}`)} 766 768 onPress={onSignIn}> 767 - <Menu.ItemText>{_(msg`Sign in to view skeet`)}</Menu.ItemText> 769 + <Menu.ItemText>{_(msg`Sign in to view ${_(terminology.post)}`)}</Menu.ItemText> 768 770 <Menu.ItemIcon icon={Eye} position="right" /> 769 771 </Menu.Item> 770 772 )} ··· 847 849 label={ 848 850 isReply 849 851 ? _(msg`Hide reply for me`) 850 - : _(msg`Hide skeet for me`) 852 + : _(msg`Hide ${_(terminology.post)} for me`) 851 853 } 852 854 onPress={() => hidePromptControl.open()}> 853 855 <Menu.ItemText> 854 856 {isReply 855 857 ? _(msg`Hide reply for me`) 856 - : _(msg`Hide skeet for me`)} 858 + : _(msg`Hide ${_(terminology.post)} for me`)} 857 859 </Menu.ItemText> 858 860 <Menu.ItemIcon icon={EyeSlash} position="right" /> 859 861 </Menu.Item> ··· 955 957 956 958 <Menu.Item 957 959 testID="postDropdownReportBtn" 958 - label={_(msg`Report skeet`)} 960 + label={_(msg`Report ${_(terminology.post)}`)} 959 961 onPress={() => reportDialogControl.open()}> 960 - <Menu.ItemText>{_(msg`Report skeet`)}</Menu.ItemText> 962 + <Menu.ItemText>{_(msg`Report ${_(terminology.post)}`)}</Menu.ItemText> 961 963 <Menu.ItemIcon icon={Warning} position="right" /> 962 964 </Menu.Item> 963 965 </> ··· 986 988 </Menu.Item> 987 989 <Menu.Item 988 990 testID="postDropdownDeleteBtn" 989 - label={_(msg`Delete post`)} 991 + label={_(msg`Delete ${_(terminology.post)}`)} 990 992 onPress={() => deletePromptControl.open()}> 991 - <Menu.ItemText>{_(msg`Delete skeet`)}</Menu.ItemText> 993 + <Menu.ItemText>{_(msg`Delete ${_(terminology.post)}`)}</Menu.ItemText> 992 994 <Menu.ItemIcon icon={Trash} position="right" /> 993 995 </Menu.Item> 994 996 </> ··· 1000 1002 1001 1003 <Prompt.Basic 1002 1004 control={deletePromptControl} 1003 - title={_(msg`Delete this skeet?`)} 1005 + title={_(msg`Delete this ${_(terminology.post)}?`)} 1004 1006 description={_( 1005 - msg`If you remove this skeet, you won't be able to recover it.`, 1007 + msg`If you remove this ${_(terminology.post)}, you won't be able to recover it.`, 1006 1008 )} 1007 1009 onConfirm={onDeletePost} 1008 1010 confirmButtonCta={_(msg`Delete`)} ··· 1011 1013 1012 1014 <Prompt.Basic 1013 1015 control={hidePromptControl} 1014 - title={isReply ? _(msg`Hide this reply?`) : _(msg`Hide this skeet?`)} 1016 + title={isReply ? _(msg`Hide this reply?`) : _(msg`Hide this ${_(terminology.post)}?`)} 1015 1017 description={_( 1016 - msg`This skeet will be hidden from feeds and threads. This cannot be undone.`, 1018 + msg`This ${_(terminology.post)} will be hidden from feeds and threads. This cannot be undone.`, 1017 1019 )} 1018 1020 onConfirm={onHidePost} 1019 1021 confirmButtonCta={_(msg`Hide`)} ··· 1036 1038 1037 1039 <Prompt.Basic 1038 1040 control={quotePostDetachConfirmControl} 1039 - title={_(msg`Detach quote skeet?`)} 1041 + title={_(msg`Detach quote ${_(terminology.post)}?`)} 1040 1042 description={_( 1041 - msg`This will remove your skeet from this quote skeet for all users, and replace it with a placeholder.`, 1043 + msg`This will remove your ${_(terminology.post)} from this quote ${_(terminology.post)} for all users, and replace it with a placeholder.`, 1042 1044 )} 1043 1045 onConfirm={onToggleQuotePostAttachment} 1044 1046 confirmButtonCta={_(msg`Yes, detach`)}
+3 -1
src/components/PostControls/PostMenu/index.tsx
··· 10 10 import {useLingui} from '@lingui/react' 11 11 12 12 import {type Shadow} from '#/state/cache/post-shadow' 13 + import {useTerminology} from '#/lib/hooks/useTerminology' 13 14 import {EventStopper} from '#/view/com/util/EventStopper' 14 15 import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' 15 16 import {useMenuControl} from '#/components/Menu' ··· 43 44 hitSlop?: Insets 44 45 }): React.ReactNode => { 45 46 const {_} = useLingui() 47 + const terminology = useTerminology() 46 48 47 49 const menuControl = useMenuControl() 48 50 const [hasBeenOpen, setHasBeenOpen] = useState(false) ··· 61 63 return ( 62 64 <EventStopper onKeyDown={false}> 63 65 <Menu.Root control={lazyMenuControl}> 64 - <Menu.Trigger label={_(msg`Open skeet options menu`)}> 66 + <Menu.Trigger label={_(msg`Open ${_(terminology.post)} options menu`)}> 65 67 {({props}) => { 66 68 return ( 67 69 <PostControlButton
+54 -10
src/components/PostControls/RepostButton.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {useHaptics} from '#/lib/haptics' 7 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 8 + import {useTerminologyPreference} from '#/state/preferences' 7 9 import {useRequireAuth} from '#/state/session' 8 10 import {atoms as a, useTheme} from '#/alf' 9 11 import {Button, ButtonText} from '#/components/Button' ··· 37 39 }: Props): React.ReactNode => { 38 40 const t = useTheme() 39 41 const {_} = useLingui() 42 + const terminologyPreference = useTerminologyPreference() 40 43 const requireAuth = useRequireAuth() 41 44 const dialogControl = Dialog.useDialogControl() 42 45 const formatPostStatCount = useFormatPostStatCount() ··· 121 124 }): React.ReactNode => { 122 125 const t = useTheme() 123 126 const {_} = useLingui() 127 + const terminologyPreference = useTerminologyPreference() 124 128 const playHaptic = useHaptics() 125 129 const control = Dialog.useDialogContext() 126 130 ··· 142 146 const onPressClose = useCallback(() => control.close(), [control]) 143 147 144 148 return ( 145 - <Dialog.ScrollableInner label={_(msg`Reskeet or quote skeet`)}> 149 + <Dialog.ScrollableInner label={_(getTerminology(terminologyPreference, { 150 + skeet: msg`Reskeet or quote skeet`, 151 + post: msg`Repost or quote post`, 152 + spell: msg`Respell or quote spell`, 153 + }))}> 146 154 <View style={a.gap_xl}> 147 155 <View style={a.gap_xs}> 148 156 <Button 149 157 style={[a.justify_start, a.px_md, a.gap_sm]} 150 158 label={ 151 159 isReposted 152 - ? _(msg`Remove reskeet`) 153 - : _(msg({message: `Reskeet`, context: 'action'})) 160 + ? _(getTerminology(terminologyPreference, { 161 + skeet: msg`Remove reskeet`, 162 + post: msg`Remove repost`, 163 + spell: msg`Remove respell`, 164 + })) 165 + : _(getTerminology(terminologyPreference, { 166 + skeet: msg({message: `Reskeet`, context: 'action'}), 167 + post: msg({message: `Repost`, context: 'action'}), 168 + spell: msg({message: `Respell`, context: 'action'}), 169 + })) 154 170 } 155 171 onPress={onPressRepost} 156 172 size="large" ··· 159 175 <RepostIcon size="lg" fill={t.palette.primary_500} /> 160 176 <Text style={[a.font_semi_bold, a.text_xl]}> 161 177 {isReposted ? ( 162 - <Trans>Remove reskeet</Trans> 178 + <Trans>{_(getTerminology(terminologyPreference, { 179 + skeet: msg`Remove reskeet`, 180 + post: msg`Remove repost`, 181 + spell: msg`Remove respell`, 182 + }))}</Trans> 163 183 ) : ( 164 - <Trans context="action">Reskeet</Trans> 184 + <Trans>{_(getTerminology(terminologyPreference, { 185 + skeet: msg({message: `Reskeet`, context: 'action'}), 186 + post: msg({message: `Repost`, context: 'action'}), 187 + spell: msg({message: `Respell`, context: 'action'}), 188 + }))}</Trans> 165 189 )} 166 190 </Text> 167 191 </Button> ··· 171 195 style={[a.justify_start, a.px_md, a.gap_sm]} 172 196 label={ 173 197 embeddingDisabled 174 - ? _(msg`Quote skeets disabled`) 175 - : _(msg`Quote skeet`) 198 + ? _(getTerminology(terminologyPreference, { 199 + skeet: msg`Quote skeets disabled`, 200 + post: msg`Quote posts disabled`, 201 + spell: msg`Quote spells disabled`, 202 + })) 203 + : _(getTerminology(terminologyPreference, { 204 + skeet: msg`Quote skeet`, 205 + post: msg`Quote post`, 206 + spell: msg`Quote spell`, 207 + })) 176 208 } 177 209 onPress={onPressQuote} 178 210 size="large" ··· 193 225 embeddingDisabled && t.atoms.text_contrast_low, 194 226 ]}> 195 227 {embeddingDisabled ? ( 196 - <Trans>Quote skeets disabled</Trans> 228 + <Trans>{_(getTerminology(terminologyPreference, { 229 + skeet: msg`Quote skeets disabled`, 230 + post: msg`Quote posts disabled`, 231 + spell: msg`Quote spells disabled`, 232 + }))}</Trans> 197 233 ) : ( 198 - <Trans>Quote skeet</Trans> 234 + <Trans>{_(getTerminology(terminologyPreference, { 235 + skeet: msg`Quote skeet`, 236 + post: msg`Quote post`, 237 + spell: msg`Quote spell`, 238 + }))}</Trans> 199 239 )} 200 240 </Text> 201 241 </Button> 202 242 </View> 203 243 <Button 204 - label={_(msg`Cancel quote skeet`)} 244 + label={_(getTerminology(terminologyPreference, { 245 + skeet: msg`Cancel quote skeet`, 246 + post: msg`Cancel quote post`, 247 + spell: msg`Cancel quote spell`, 248 + }))} 205 249 onPress={onPressClose} 206 250 size="large" 207 251 color="secondary">
+53 -10
src/components/PostControls/RepostButton.web.tsx
··· 1 1 import {msg} from '@lingui/macro' 2 2 import {useLingui} from '@lingui/react' 3 3 4 + import {getTerminology} from '#/lib/strings/terminology' 5 + import {useTerminologyPreference} from '#/state/preferences' 4 6 import {useRequireAuth} from '#/state/session' 5 7 import {useSession} from '#/state/session' 6 8 import {EventStopper} from '#/view/com/util/EventStopper' ··· 34 36 }: Props) => { 35 37 const t = useTheme() 36 38 const {_} = useLingui() 39 + const terminologyPreference = useTerminologyPreference() 37 40 const {hasSession} = useSession() 38 41 const requireAuth = useRequireAuth() 39 42 const formatPostStatCount = useFormatPostStatCount() ··· 41 44 return hasSession ? ( 42 45 <EventStopper onKeyDown={false}> 43 46 <Menu.Root> 44 - <Menu.Trigger label={_(msg`Repost or quote post`)}> 47 + <Menu.Trigger label={_(getTerminology(terminologyPreference, { 48 + skeet: msg`Reskeet or quote skeet`, 49 + post: msg`Repost or quote post`, 50 + spell: msg`Respell or quote spell`, 51 + }))}> 45 52 {({props}) => { 46 53 return ( 47 54 <PostControlButton ··· 65 72 <Menu.Item 66 73 label={ 67 74 isReposted 68 - ? _(msg`Undo reskeet`) 69 - : _(msg({message: `Reskeet`, context: `action`})) 75 + ? _(getTerminology(terminologyPreference, { 76 + skeet: msg`Undo reskeet`, 77 + post: msg`Undo repost`, 78 + spell: msg`Undo respell`, 79 + })) 80 + : _(getTerminology(terminologyPreference, { 81 + skeet: msg({message: `Reskeet`, context: `action`}), 82 + post: msg({message: `Repost`, context: `action`}), 83 + spell: msg({message: `Respell`, context: `action`}), 84 + })) 70 85 } 71 86 testID="repostDropdownRepostBtn" 72 87 onPress={onRepost}> 73 88 <Menu.ItemText> 74 89 {isReposted 75 - ? _(msg`Undo reskeet`) 76 - : _(msg({message: `Reskeet`, context: `action`}))} 90 + ? _(getTerminology(terminologyPreference, { 91 + skeet: msg`Undo reskeet`, 92 + post: msg`Undo repost`, 93 + spell: msg`Undo respell`, 94 + })) 95 + : _(getTerminology(terminologyPreference, { 96 + skeet: msg({message: `Reskeet`, context: `action`}), 97 + post: msg({message: `Repost`, context: `action`}), 98 + spell: msg({message: `Respell`, context: `action`}), 99 + }))} 77 100 </Menu.ItemText> 78 101 <Menu.ItemIcon icon={Repost} position="right" /> 79 102 </Menu.Item> ··· 81 104 disabled={embeddingDisabled} 82 105 label={ 83 106 embeddingDisabled 84 - ? _(msg`Quote skeets disabled`) 85 - : _(msg`Quote skeet`) 107 + ? _(getTerminology(terminologyPreference, { 108 + skeet: msg`Quote skeets disabled`, 109 + post: msg`Quote posts disabled`, 110 + spell: msg`Quote spells disabled`, 111 + })) 112 + : _(getTerminology(terminologyPreference, { 113 + skeet: msg`Quote skeet`, 114 + post: msg`Quote post`, 115 + spell: msg`Quote spell`, 116 + })) 86 117 } 87 118 testID="repostDropdownQuoteBtn" 88 119 onPress={onQuote}> 89 120 <Menu.ItemText> 90 121 {embeddingDisabled 91 - ? _(msg`Quote skeets disabled`) 92 - : _(msg`Quote skeet`)} 122 + ? _(getTerminology(terminologyPreference, { 123 + skeet: msg`Quote skeets disabled`, 124 + post: msg`Quote posts disabled`, 125 + spell: msg`Quote spells disabled`, 126 + })) 127 + : _(getTerminology(terminologyPreference, { 128 + skeet: msg`Quote skeet`, 129 + post: msg`Quote post`, 130 + spell: msg`Quote spell`, 131 + }))} 93 132 </Menu.ItemText> 94 133 <Menu.ItemIcon icon={Quote} position="right" /> 95 134 </Menu.Item> ··· 101 140 onPress={() => requireAuth(() => {})} 102 141 active={isReposted} 103 142 activeColor={t.palette.positive_500} 104 - label={_(msg`Reskeet or quote skeet`)} 143 + label={_(getTerminology(terminologyPreference, { 144 + skeet: msg`Reskeet or quote skeet`, 145 + post: msg`Repost or quote post`, 146 + spell: msg`Respell or quote spell`, 147 + }))} 105 148 big={big}> 106 149 <PostControlButtonIcon icon={Repost} /> 107 150 {typeof repostCount !== 'undefined' && repostCount > 0 && (
+48 -9
src/components/PostControls/ShareMenu/ShareMenuItems.tsx
··· 9 9 import {makeProfileLink} from '#/lib/routes/links' 10 10 import {type NavigationProp} from '#/lib/routes/types' 11 11 import {shareText, shareUrl} from '#/lib/sharing' 12 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 12 13 import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 13 14 import {logger} from '#/logger' 14 15 import {isIOS} from '#/platform/detection' 15 16 import {useProfileShadow} from '#/state/cache/profile-shadow' 17 + import {useTerminologyPreference} from '#/state/preferences' 16 18 import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' 17 19 import {useSession} from '#/state/session' 18 20 import * as Toast from '#/view/com/util/Toast' ··· 37 39 }: ShareMenuItemsProps): React.ReactNode => { 38 40 const {hasSession} = useSession() 39 41 const {_} = useLingui() 42 + const terminologyPreference = useTerminologyPreference() 40 43 const navigation = useNavigation<NavigationProp>() 41 44 const sendViaChatControl = useDialogControl() 42 45 const [devModeEnabled] = useDevMode() ··· 154 157 {isBridgedPost && ( 155 158 <Menu.Item 156 159 testID="postDropdownOpenOriginalPost" 157 - label={_(msg`Open original post`)} 160 + label={_(getTerminology(terminologyPreference, { 161 + skeet: msg`Open original skeet`, 162 + post: msg`Open original post`, 163 + spell: msg`Open original spell`, 164 + }))} 158 165 onPress={onOpenOriginalPost}> 159 166 <Menu.ItemText> 160 - <Trans>Open original skeet</Trans> 167 + <Trans>{_(getTerminology(terminologyPreference, { 168 + skeet: msg`Open original skeet`, 169 + post: msg`Open original post`, 170 + spell: msg`Open original spell`, 171 + }))}</Trans> 161 172 </Menu.ItemText> 162 173 <Menu.ItemIcon icon={ExternalIcon} position="right" /> 163 174 </Menu.Item> ··· 165 176 166 177 <Menu.Item 167 178 testID="postDropdownOpenInPdsls" 168 - label={_(msg`Open post in PDSls`)} 179 + label={_(getTerminology(terminologyPreference, { 180 + skeet: msg`Open skeet in PDSls`, 181 + post: msg`Open post in PDSls`, 182 + spell: msg`Open spell in PDSls`, 183 + }))} 169 184 onPress={onOpenPostInPdsls}> 170 185 <Menu.ItemText> 171 - <Trans>Open skeet in PDSls</Trans> 186 + <Trans>{_(getTerminology(terminologyPreference, { 187 + skeet: msg`Open skeet in PDSls`, 188 + post: msg`Open post in PDSls`, 189 + spell: msg`Open spell in PDSls`, 190 + }))}</Trans> 172 191 </Menu.ItemText> 173 192 <Menu.ItemIcon icon={ExternalIcon} position="right" /> 174 193 </Menu.Item> ··· 198 217 199 218 <Menu.Item 200 219 testID="postDropdownShareBtn" 201 - label={_(msg`Copy link to post`)} 220 + label={_(getTerminology(terminologyPreference, { 221 + skeet: msg`Copy link to skeet`, 222 + post: msg`Copy link to post`, 223 + spell: msg`Copy link to spell`, 224 + }))} 202 225 onPress={onCopyLink}> 203 226 <Menu.ItemText> 204 - <Trans>Copy link to skeet</Trans> 227 + <Trans>{_(getTerminology(terminologyPreference, { 228 + skeet: msg`Copy link to skeet`, 229 + post: msg`Copy link to post`, 230 + spell: msg`Copy link to spell`, 231 + }))}</Trans> 205 232 </Menu.ItemText> 206 233 <Menu.ItemIcon icon={ChainLinkIcon} position="right" /> 207 234 </Menu.Item> ··· 223 250 <Admonition 224 251 type="warning" 225 252 style={[a.flex_1, a.border_0, a.p_0, a.bg_transparent]}> 226 - <Trans>This skeet is only visible to logged-in users.</Trans> 253 + <Trans>{_(getTerminology(terminologyPreference, { 254 + skeet: msg`This skeet is only visible to logged-in users.`, 255 + post: msg`This post is only visible to logged-in users.`, 256 + spell: msg`This spell is only visible to logged-in users.`, 257 + }))}</Trans> 227 258 </Admonition> 228 259 </Menu.ContainerItem> 229 260 </Menu.Group> ··· 233 264 <Menu.Group> 234 265 <Menu.Item 235 266 testID="postAtUriShareBtn" 236 - label={_(msg`Share post at:// URI`)} 267 + label={_(getTerminology(terminologyPreference, { 268 + skeet: msg`Share skeet at:// URI`, 269 + post: msg`Share post at:// URI`, 270 + spell: msg`Share spell at:// URI`, 271 + }))} 237 272 onPress={onShareATURI}> 238 273 <Menu.ItemText> 239 - <Trans>Share skeet at:// URI</Trans> 274 + <Trans>{_(getTerminology(terminologyPreference, { 275 + skeet: msg`Share skeet at:// URI`, 276 + post: msg`Share post at:// URI`, 277 + spell: msg`Share spell at:// URI`, 278 + }))}</Trans> 240 279 </Menu.ItemText> 241 280 <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 242 281 </Menu.Item>
+58 -13
src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx
··· 8 8 import {makeProfileLink} from '#/lib/routes/links' 9 9 import {type NavigationProp} from '#/lib/routes/types' 10 10 import {shareText, shareUrl} from '#/lib/sharing' 11 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 11 12 import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 12 13 import {logger} from '#/logger' 13 14 import {isWeb} from '#/platform/detection' 14 15 import {useProfileShadow} from '#/state/cache/profile-shadow' 16 + import {useTerminologyPreference} from '#/state/preferences' 15 17 import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons' 16 18 import {useSession} from '#/state/session' 17 19 import {useBreakpoints} from '#/alf' ··· 37 39 const {hasSession} = useSession() 38 40 const {gtMobile} = useBreakpoints() 39 41 const {_} = useLingui() 42 + const terminologyPreference = useTerminologyPreference() 40 43 const navigation = useNavigation<NavigationProp>() 41 44 const embedPostControl = useDialogControl() 42 45 const sendViaChatControl = useDialogControl() ··· 109 112 <Menu.Group> 110 113 <Menu.Item 111 114 testID="postDropdownShareBtn" 112 - label={_(msg`Copy link to post`)} 115 + label={_(getTerminology(terminologyPreference, { 116 + skeet: msg`Copy link to skeet`, 117 + post: msg`Copy link to post`, 118 + spell: msg`Copy link to spell`, 119 + }))} 113 120 onPress={onCopyLink}> 114 121 <Menu.ItemText> 115 - <Trans>Copy link to skeet</Trans> 122 + <Trans>{_(getTerminology(terminologyPreference, { 123 + skeet: msg`Copy link to skeet`, 124 + post: msg`Copy link to post`, 125 + spell: msg`Copy link to spell`, 126 + }))}</Trans> 116 127 </Menu.ItemText> 117 128 <Menu.ItemIcon icon={ChainLinkIcon} position="right" /> 118 129 </Menu.Item> ··· 136 147 {showExternalShareButtons && isBridgedPost && ( 137 148 <Menu.Item 138 149 testID="postDropdownOpenOriginalPost" 139 - label={_(msg`Open original post`)} 150 + label={_(getTerminology(terminologyPreference, { 151 + skeet: msg`Open original skeet`, 152 + post: msg`Open original post`, 153 + spell: msg`Open original spell`, 154 + }))} 140 155 onPress={onOpenOriginalPost}> 141 156 <Menu.ItemText> 142 - <Trans>Open original skeet</Trans> 157 + <Trans>{_(getTerminology(terminologyPreference, { 158 + skeet: msg`Open original skeet`, 159 + post: msg`Open original post`, 160 + spell: msg`Open original spell`, 161 + }))}</Trans> 143 162 </Menu.ItemText> 144 163 <Menu.ItemIcon icon={ExternalIcon} position="right" /> 145 164 </Menu.Item> ··· 148 167 {showExternalShareButtons && ( 149 168 <Menu.Item 150 169 testID="postDropdownOpenInPdsls" 151 - label={_(msg`Open post in PDSls`)} 170 + label={_(getTerminology(terminologyPreference, { 171 + skeet: msg`Open skeet in PDSls`, 172 + post: msg`Open post in PDSls`, 173 + spell: msg`Open spell in PDSls`, 174 + }))} 152 175 onPress={onOpenPostInPdsls}> 153 176 <Menu.ItemText> 154 - <Trans>Open skeet in PDSls</Trans> 177 + <Trans>{_(getTerminology(terminologyPreference, { 178 + skeet: msg`Open skeet in PDSls`, 179 + post: msg`Open post in PDSls`, 180 + spell: msg`Open spell in PDSls`, 181 + }))}</Trans> 155 182 </Menu.ItemText> 156 183 <Menu.ItemIcon icon={ExternalIcon} position="right" /> 157 184 </Menu.Item> ··· 175 202 {canEmbed && ( 176 203 <Menu.Item 177 204 testID="postDropdownEmbedBtn" 178 - label={_(msg`Embed post`)} 205 + label={_(getTerminology(terminologyPreference, { 206 + skeet: msg`Embed skeet`, 207 + post: msg`Embed post`, 208 + spell: msg`Embed spell`, 209 + }))} 179 210 onPress={() => { 180 211 logger.metric('share:press:embed', {}, {statsig: true}) 181 212 embedPostControl.open() 182 213 }}> 183 - <Menu.ItemText>{_(msg`Embed skeet`)}</Menu.ItemText> 214 + <Menu.ItemText>{_(getTerminology(terminologyPreference, { 215 + skeet: msg`Embed skeet`, 216 + post: msg`Embed post`, 217 + spell: msg`Embed spell`, 218 + }))}</Menu.ItemText> 184 219 <Menu.ItemIcon icon={CodeBracketsIcon} position="right" /> 185 220 </Menu.Item> 186 221 )} ··· 190 225 {hasSession && <Menu.Divider />} 191 226 {copyLinkItem} 192 227 <Menu.LabelText style={{maxWidth: 220}}> 193 - <Trans> 194 - Note: This skeet is only visible to logged-in users. 195 - </Trans> 228 + <Trans>{_(getTerminology(terminologyPreference, { 229 + skeet: msg`Note: This skeet is only visible to logged-in users.`, 230 + post: msg`Note: This post is only visible to logged-in users.`, 231 + spell: msg`Note: This spell is only visible to logged-in users.`, 232 + }))}</Trans> 196 233 </Menu.LabelText> 197 234 </> 198 235 )} ··· 202 239 <Menu.Divider /> 203 240 <Menu.Item 204 241 testID="postAtUriShareBtn" 205 - label={_(msg`Copy post at:// URI`)} 242 + label={_(getTerminology(terminologyPreference, { 243 + skeet: msg`Copy skeet at:// URI`, 244 + post: msg`Copy post at:// URI`, 245 + spell: msg`Copy spell at:// URI`, 246 + }))} 206 247 onPress={onShareATURI}> 207 248 <Menu.ItemText> 208 - <Trans>Copy skeet at:// URI</Trans> 249 + <Trans>{_(getTerminology(terminologyPreference, { 250 + skeet: msg`Copy skeet at:// URI`, 251 + post: msg`Copy post at:// URI`, 252 + spell: msg`Copy spell at:// URI`, 253 + }))}</Trans> 209 254 </Menu.ItemText> 210 255 <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 211 256 </Menu.Item>
+30 -8
src/components/WhoCanReply.tsx
··· 17 17 18 18 import {HITSLOP_10} from '#/lib/constants' 19 19 import {makeListLink, makeProfileLink} from '#/lib/routes/links' 20 + import {getTerminology} from '#/lib/strings/terminology' 21 + import {useTerminologyPreference} from '#/state/preferences' 20 22 import {logger} from '#/logger' 21 23 import {isNative} from '#/platform/detection' 22 24 import { ··· 212 214 embeddingDisabled: boolean 213 215 }) { 214 216 const {_} = useLingui() 217 + const terminologyPreference = useTerminologyPreference() 215 218 216 219 return ( 217 220 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> ··· 221 224 style={web({maxWidth: 400})}> 222 225 <View style={[a.gap_sm]}> 223 226 <Text style={[a.font_semi_bold, a.text_xl, a.pb_sm]}> 224 - <Trans>Who can interact with this skeet?</Trans> 227 + <Trans>{_(getTerminology(terminologyPreference, { 228 + skeet: msg`Who can interact with this skeet?`, 229 + post: msg`Who can interact with this post?`, 230 + spell: msg`Who can interact with this spell?`, 231 + }))}</Trans> 225 232 </Text> 226 233 <Rules 227 234 post={post} ··· 258 265 embeddingDisabled: boolean 259 266 }) { 260 267 const t = useTheme() 268 + const {_} = useLingui() 269 + const terminologyPreference = useTerminologyPreference() 261 270 262 271 return ( 263 272 <> ··· 269 278 t.atoms.text_contrast_medium, 270 279 ]}> 271 280 {settings.length === 0 ? ( 272 - <Trans> 273 - This skeet has an unknown type of threadgate on it. Your app may be 274 - out of date. 275 - </Trans> 281 + <Trans>{_(getTerminology(terminologyPreference, { 282 + skeet: msg`This skeet has an unknown type of threadgate on it. Your app may be out of date.`, 283 + post: msg`This post has an unknown type of threadgate on it. Your app may be out of date.`, 284 + spell: msg`This spell has an unknown type of threadgate on it. Your app may be out of date.`, 285 + }))}</Trans> 276 286 ) : settings[0].type === 'everybody' ? ( 277 - <Trans>Everybody can reply to this skeet.</Trans> 287 + <Trans>{_(getTerminology(terminologyPreference, { 288 + skeet: msg`Everybody can reply to this skeet.`, 289 + post: msg`Everybody can reply to this post.`, 290 + spell: msg`Everybody can reply to this spell.`, 291 + }))}</Trans> 278 292 ) : settings[0].type === 'nobody' ? ( 279 - <Trans>Replies to this skeet are disabled.</Trans> 293 + <Trans>{_(getTerminology(terminologyPreference, { 294 + skeet: msg`Replies to this skeet are disabled.`, 295 + post: msg`Replies to this post are disabled.`, 296 + spell: msg`Replies to this spell are disabled.`, 297 + }))}</Trans> 280 298 ) : ( 281 299 <Trans> 282 300 Only{' '} ··· 298 316 a.flex_wrap, 299 317 t.atoms.text_contrast_medium, 300 318 ]}> 301 - <Trans>No one but the author can quote this skeet.</Trans> 319 + <Trans>{_(getTerminology(terminologyPreference, { 320 + skeet: msg`No one but the author can quote this skeet.`, 321 + post: msg`No one but the author can quote this post.`, 322 + spell: msg`No one but the author can quote this spell.`, 323 + }))}</Trans> 302 324 </Text> 303 325 )} 304 326 </>
+18 -6
src/components/dialogs/Embed.tsx
··· 6 6 7 7 import {EMBED_SCRIPT} from '#/lib/constants' 8 8 import {niceDate} from '#/lib/strings/time' 9 + import {getTerminology} from '#/lib/strings/terminology' 10 + import {useTerminologyPreference} from '#/state/preferences' 9 11 import {toShareUrl} from '#/lib/strings/url-helpers' 10 12 import {atoms as a, useTheme} from '#/alf' 11 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 51 53 }: Omit<EmbedDialogProps, 'control'>) { 52 54 const t = useTheme() 53 55 const {_, i18n} = useLingui() 56 + const terminologyPreference = useTerminologyPreference() 54 57 const [copied, setCopied] = useState(false) 55 58 const [showCustomisation, setShowCustomisation] = useState(false) 56 59 const [colorMode, setColorMode] = useState<ColorModeValues>('system') ··· 101 104 }, [i18n, postUri, postCid, record, timestamp, postAuthor, colorMode]) 102 105 103 106 return ( 104 - <Dialog.Inner label={_(msg`Embed post`)} style={[{maxWidth: 500}]}> 107 + <Dialog.Inner label={_(getTerminology(terminologyPreference, { 108 + skeet: msg`Embed skeet`, 109 + post: msg`Embed post`, 110 + spell: msg`Embed spell`, 111 + }))} style={[{maxWidth: 500}]}> 105 112 <View style={[a.gap_lg]}> 106 113 <View style={[a.gap_sm]}> 107 114 <Text style={[a.text_2xl, a.font_bold]}> 108 - <Trans>Embed skeet</Trans> 115 + <Trans>{_(getTerminology(terminologyPreference, { 116 + skeet: msg`Embed skeet`, 117 + post: msg`Embed post`, 118 + spell: msg`Embed spell`, 119 + }))}</Trans> 109 120 </Text> 110 121 <Text 111 122 style={[a.text_md, t.atoms.text_contrast_medium, a.leading_normal]}> 112 - <Trans> 113 - Embed this skeet in your website. Simply copy the following snippet 114 - and paste it into the HTML code of your website. 115 - </Trans> 123 + <Trans>{_(getTerminology(terminologyPreference, { 124 + skeet: msg`Embed this skeet in your website. Simply copy the following snippet and paste it into the HTML code of your website.`, 125 + post: msg`Embed this post in your website. Simply copy the following snippet and paste it into the HTML code of your website.`, 126 + spell: msg`Embed this spell in your website. Simply copy the following snippet and paste it into the HTML code of your website.`, 127 + }))}</Trans> 116 128 </Text> 117 129 </View> 118 130 <View
+3 -1
src/lib/api/index.ts
··· 24 24 25 25 import {isNetworkError} from '#/lib/strings/errors' 26 26 import {parseMarkdownLinks,shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' 27 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 27 28 import {logger} from '#/logger' 28 29 import {compressImage} from '#/state/gallery' 29 30 import { ··· 49 50 replyTo?: string 50 51 onStateChange?: (state: string) => void 51 52 langs?: string[] 53 + terminologyPreference?: string 52 54 } 53 55 54 56 export async function post( ··· 183 185 }) 184 186 if (isNetworkError(e)) { 185 187 throw new Error( 186 - t`Skeet failed to upload. Please check your Internet connection and try again.`, 188 + getTerminology(opts.terminologyPreference || 'skeet', TERMINOLOGY.uploadFailed), 187 189 ) 188 190 } else { 189 191 throw e
+62
src/lib/hooks/useTerminology.ts
··· 1 + import {useMemo} from 'react' 2 + import {defineMessage} from '@lingui/macro' 3 + 4 + import {useTerminologyPreference} from '#/state/preferences' 5 + 6 + export type TerminologyKey = 7 + | 'post' 8 + | 'posts' 9 + | 'repost' 10 + | 'reposts' 11 + | 'reposted' 12 + | 'reposting' 13 + 14 + const terminologyMap = { 15 + skeet: { 16 + post: defineMessage({message: 'skeet'}), 17 + posts: defineMessage({message: 'skeets'}), 18 + repost: defineMessage({message: 'reskeet'}), 19 + reposts: defineMessage({message: 'reskeets'}), 20 + reposted: defineMessage({message: 'reskeeted'}), 21 + reposting: defineMessage({message: 'reskeeting'}), 22 + }, 23 + post: { 24 + post: defineMessage({message: 'post'}), 25 + posts: defineMessage({message: 'posts'}), 26 + repost: defineMessage({message: 'repost'}), 27 + reposts: defineMessage({message: 'reposts'}), 28 + reposted: defineMessage({message: 'reposted'}), 29 + reposting: defineMessage({message: 'reposting'}), 30 + }, 31 + spell: { 32 + post: defineMessage({message: 'spell'}), 33 + posts: defineMessage({message: 'spells'}), 34 + repost: defineMessage({message: 'respell'}), 35 + reposts: defineMessage({message: 'respells'}), 36 + reposted: defineMessage({message: 'respelled'}), 37 + reposting: defineMessage({message: 'respelling'}), 38 + }, 39 + } 40 + 41 + /** 42 + * Hook to get the correct terminology based on user preference 43 + * @returns An object with all terminology variants 44 + */ 45 + export function useTerminology() { 46 + const preference = useTerminologyPreference() 47 + 48 + return useMemo(() => { 49 + const selectedTerminology = preference || 'skeet' 50 + return terminologyMap[selectedTerminology] 51 + }, [preference]) 52 + } 53 + 54 + /** 55 + * Hook to get a specific terminology term 56 + * @param key - The terminology key to retrieve 57 + * @returns The localized terminology message 58 + */ 59 + export function useTerm(key: TerminologyKey) { 60 + const terminology = useTerminology() 61 + return terminology[key] 62 + }
+441
src/lib/strings/terminology.ts
··· 1 + import {msg} from '@lingui/macro' 2 + 3 + export type TerminologyPreference = 'skeet' | 'post' | 'spell' 4 + 5 + /** 6 + * Returns the appropriate terminology based on the user's preference 7 + * @param preference - The user's terminology preference ('skeet', 'post', or 'spell') 8 + * @param variants - An object with message descriptors for each terminology option 9 + * @returns The appropriate message descriptor based on the preference 10 + */ 11 + export function getTerminology<T extends {skeet: any; post: any; spell: any}>( 12 + preference: TerminologyPreference | undefined, 13 + variants: T, 14 + ): T[keyof T] { 15 + const pref = preference ?? 'skeet' 16 + return variants[pref] 17 + } 18 + 19 + /** 20 + * Common terminology variants used throughout the app 21 + */ 22 + export const TERMINOLOGY = { 23 + // Single post 24 + singular: { 25 + skeet: msg`skeet`, 26 + post: msg`post`, 27 + spell: msg`spell`, 28 + }, 29 + // Multiple posts 30 + plural: { 31 + skeet: msg`skeets`, 32 + post: msg`posts`, 33 + spell: msg`spells`, 34 + }, 35 + // "Skeet by @handle" 36 + byHandle: (handle: string) => ({ 37 + skeet: msg`Skeet by @${handle}`, 38 + post: msg`Post by @${handle}`, 39 + spell: msg`Spell by @${handle}`, 40 + }), 41 + // Repost terminology 42 + repost: { 43 + singular: { 44 + skeet: msg`reskeet`, 45 + post: msg`repost`, 46 + spell: msg`respell`, 47 + }, 48 + plural: { 49 + skeet: msg`reskeets`, 50 + post: msg`reposts`, 51 + spell: msg`respells`, 52 + }, 53 + pastTense: { 54 + skeet: msg`reskeeted`, 55 + post: msg`reposted`, 56 + spell: msg`respelled`, 57 + }, 58 + byLine: { 59 + skeet: msg`Reskeeted By`, 60 + post: msg`Reposted By`, 61 + spell: msg`Respelled By`, 62 + }, 63 + action: { 64 + skeet: msg`reskeet`, 65 + post: msg`repost`, 66 + spell: msg`respell`, 67 + }, 68 + }, 69 + // For metrics/counts 70 + metrics: { 71 + skeet: msg`skeets metrics`, 72 + post: msg`posts metrics`, 73 + spell: msg`spells metrics`, 74 + }, 75 + // For carousel 76 + carousel: { 77 + skeet: msg`Combine reskeets into a horizontal carousel`, 78 + post: msg`Combine reposts into a horizontal carousel`, 79 + spell: msg`Combine respells into a horizontal carousel`, 80 + }, 81 + // For notifications 82 + viaRepostNotification: { 83 + skeet: msg`Disable "via reskeet" notifications`, 84 + post: msg`Disable "via repost" notifications`, 85 + spell: msg`Disable "via respell" notifications`, 86 + }, 87 + viaRepostPrivacy: { 88 + skeet: msg`Forcefully disables the notifications other people receive when you like/reskeet a skeet someone else has reskeeted for privacy.`, 89 + post: msg`Forcefully disables the notifications other people receive when you like/repost a post someone else has reposted for privacy.`, 90 + spell: msg`Forcefully disables the notifications other people receive when you like/respell a spell someone else has respelled for privacy.`, 91 + }, 92 + // For external share buttons 93 + externalShareButtons: { 94 + skeet: msg`Show "Open original skeet" and "Open skeet in PDSls" buttons`, 95 + post: msg`Show "Open original post" and "Open post in PDSls" buttons`, 96 + spell: msg`Show "Open original spell" and "Open spell in PDSls" buttons`, 97 + }, 98 + // For metrics labels 99 + repostMetrics: { 100 + skeet: msg`Disable reskeets metrics`, 101 + post: msg`Disable reposts metrics`, 102 + spell: msg`Disable respells metrics`, 103 + }, 104 + // For deletion messages 105 + deleted: { 106 + skeet: msg`This skeet was deleted by its author`, 107 + post: msg`This post was deleted by its author`, 108 + spell: msg`This spell was deleted by its author`, 109 + }, 110 + deletedShort: { 111 + skeet: msg`Skeet has been deleted`, 112 + post: msg`Post has been deleted`, 113 + spell: msg`Spell has been deleted`, 114 + }, 115 + // For error messages 116 + notFound: { 117 + skeet: msg`Skeet not found`, 118 + post: msg`Post not found`, 119 + spell: msg`Spell not found`, 120 + }, 121 + blocked: { 122 + skeet: msg`Skeet blocked`, 123 + post: msg`Post blocked`, 124 + spell: msg`Spell blocked`, 125 + }, 126 + errorLoading: { 127 + skeet: msg`Error loading skeet`, 128 + post: msg`Error loading post`, 129 + spell: msg`Error loading spell`, 130 + }, 131 + // For success messages 132 + sent: { 133 + skeet: msg`Your skeet was sent`, 134 + post: msg`Your post was sent`, 135 + spell: msg`Your spell was sent`, 136 + }, 137 + sentPlural: { 138 + skeet: msg`Your skeets were sent`, 139 + post: msg`Your posts were sent`, 140 + spell: msg`Your spells were sent`, 141 + }, 142 + pinned: { 143 + skeet: msg`Skeet pinned`, 144 + post: msg`Post pinned`, 145 + spell: msg`Spell pinned`, 146 + }, 147 + unpinned: { 148 + skeet: msg`Skeet unpinned`, 149 + post: msg`Post unpinned`, 150 + spell: msg`Spell unpinned`, 151 + }, 152 + failedToPin: { 153 + skeet: msg`Failed to pin skeet`, 154 + post: msg`Failed to pin post`, 155 + spell: msg`Failed to pin spell`, 156 + }, 157 + // For composer 158 + addAnother: { 159 + skeet: msg`Add another skeet`, 160 + post: msg`Add another post`, 161 + spell: msg`Add another spell`, 162 + }, 163 + anythingBut: { 164 + skeet: msg`Anything but skeet`, 165 + post: msg`Anything but post`, 166 + spell: msg`Anything but spell`, 167 + }, 168 + delete: { 169 + skeet: msg`Delete skeet`, 170 + post: msg`Delete post`, 171 + spell: msg`Delete spell`, 172 + }, 173 + discard: { 174 + skeet: msg`Discard skeet?`, 175 + post: msg`Discard post?`, 176 + spell: msg`Discard spell?`, 177 + }, 178 + discardConfirm: { 179 + skeet: msg`Are you sure you'd like to discard this skeet?`, 180 + post: msg`Are you sure you'd like to discard this post?`, 181 + spell: msg`Are you sure you'd like to discard this spell?`, 182 + }, 183 + view: { 184 + skeet: msg`View skeet`, 185 + post: msg`View post`, 186 + spell: msg`View spell`, 187 + }, 188 + // For composer actions 189 + composeNew: { 190 + skeet: msg`Compose new skeet`, 191 + post: msg`Compose new post`, 192 + spell: msg`Compose new spell`, 193 + }, 194 + newAction: { 195 + skeet: msg`New Skeet`, 196 + post: msg`New Post`, 197 + spell: msg`New Spell`, 198 + }, 199 + postAll: { 200 + skeet: msg`Skeet All`, 201 + post: msg`Post All`, 202 + spell: msg`Spell All`, 203 + }, 204 + postSingle: { 205 + skeet: msg`Skeet`, 206 + post: msg`Post`, 207 + spell: msg`Spell`, 208 + }, 209 + // For language selection 210 + selectLanguage: { 211 + skeet: msg`Select skeet language`, 212 + post: msg`Select post language`, 213 + spell: msg`Select spell language`, 214 + }, 215 + chooseLanguages: { 216 + skeet: msg`Choose Skeet Languages`, 217 + post: msg`Choose Post Languages`, 218 + spell: msg`Choose Spell Languages`, 219 + }, 220 + languageDescription: { 221 + skeet: msg`Select up to 3 languages used in this skeet`, 222 + post: msg`Select up to 3 languages used in this post`, 223 + spell: msg`Select up to 3 languages used in this spell`, 224 + }, 225 + replyingLanguage: (langs: string) => ({ 226 + skeet: msg`The skeet you're replying to was marked as being written in ${langs}`, 227 + post: msg`The post you're replying to was marked as being written in ${langs}`, 228 + spell: msg`The spell you're replying to was marked as being written in ${langs}`, 229 + }), 230 + // For interaction settings 231 + interactionSettings: { 232 + skeet: msg`Skeet interaction settings`, 233 + post: msg`Post interaction settings`, 234 + spell: msg`Spell interaction settings`, 235 + }, 236 + editInteractionSettings: { 237 + skeet: msg`Edit skeet interaction settings`, 238 + post: msg`Edit post interaction settings`, 239 + spell: msg`Edit spell interaction settings`, 240 + }, 241 + whoCanInteract: { 242 + skeet: msg`Who can interact with this skeet?`, 243 + post: msg`Who can interact with this post?`, 244 + spell: msg`Who can interact with this spell?`, 245 + }, 246 + // For embedding 247 + embed: { 248 + skeet: msg`Embed skeet`, 249 + post: msg`Embed post`, 250 + spell: msg`Embed spell`, 251 + }, 252 + embedDescription: { 253 + skeet: msg`Embed this skeet in your website. Simply copy the following snippet and paste it into the HTML code of your website.`, 254 + post: msg`Embed this post in your website. Simply copy the following snippet and paste it into the HTML code of your website.`, 255 + spell: msg`Embed this spell in your website. Simply copy the following snippet and paste it into the HTML code of your website.`, 256 + }, 257 + // For sharing 258 + copyLink: { 259 + skeet: msg`Copy link to skeet`, 260 + post: msg`Copy link to post`, 261 + spell: msg`Copy link to spell`, 262 + }, 263 + shareUri: { 264 + skeet: msg`Share skeet at:// URI`, 265 + post: msg`Share post at:// URI`, 266 + spell: msg`Share spell at:// URI`, 267 + }, 268 + copyUri: { 269 + skeet: msg`Copy skeet at:// URI`, 270 + post: msg`Copy post at:// URI`, 271 + spell: msg`Copy spell at:// URI`, 272 + }, 273 + sendTo: { 274 + skeet: msg`Send skeet to...`, 275 + post: msg`Send post to...`, 276 + spell: msg`Send spell to...`, 277 + }, 278 + openOriginal: { 279 + skeet: msg`Open original skeet`, 280 + post: msg`Open original post`, 281 + spell: msg`Open original spell`, 282 + }, 283 + openInPDSls: { 284 + skeet: msg`Open skeet in PDSls`, 285 + post: msg`Open post in PDSls`, 286 + spell: msg`Open spell in PDSls`, 287 + }, 288 + // For reporting 289 + report: { 290 + skeet: msg`Report this skeet`, 291 + post: msg`Report this post`, 292 + spell: msg`Report this spell`, 293 + }, 294 + reportWhy: { 295 + skeet: msg`Why should this skeet be reviewed?`, 296 + post: msg`Why should this post be reviewed?`, 297 + spell: msg`Why should this spell be reviewed?`, 298 + }, 299 + // For search and browse 300 + search: { 301 + skeet: msg`Search skeets`, 302 + post: msg`Search posts`, 303 + spell: msg`Search spells`, 304 + }, 305 + searchProfile: (handle: string) => ({ 306 + skeet: msg`Search @${handle}'s skeets`, 307 + post: msg`Search @${handle}'s posts`, 308 + spell: msg`Search @${handle}'s spells`, 309 + }), 310 + searchMy: { 311 + skeet: msg`Search my skeets`, 312 + post: msg`Search my posts`, 313 + spell: msg`Search my spells`, 314 + }, 315 + browse: (tag: string) => ({ 316 + skeet: msg`Browse skeets about ${tag}`, 317 + post: msg`Browse posts about ${tag}`, 318 + spell: msg`Browse spells about ${tag}`, 319 + }), 320 + browseTagged: (tag: string) => ({ 321 + skeet: msg`Browse skeets tagged with ${tag}`, 322 + post: msg`Browse posts tagged with ${tag}`, 323 + spell: msg`Browse spells tagged with ${tag}`, 324 + }), 325 + seeTag: (tag: string) => ({ 326 + skeet: msg`See ${tag} skeets`, 327 + post: msg`See ${tag} posts`, 328 + spell: msg`See ${tag} spells`, 329 + }), 330 + seeTagByUser: (tag: string) => ({ 331 + skeet: msg`See ${tag} skeets by user`, 332 + post: msg`See ${tag} posts by user`, 333 + spell: msg`See ${tag} spells by user`, 334 + }), 335 + // For loading and errors 336 + loadNew: { 337 + skeet: msg`Load new skeets`, 338 + post: msg`Load new posts`, 339 + spell: msg`Load new spells`, 340 + }, 341 + fetchError: { 342 + skeet: msg`There was an issue fetching skeets. Tap here to try again.`, 343 + post: msg`There was an issue fetching posts. Tap here to try again.`, 344 + spell: msg`There was an issue fetching spells. Tap here to try again.`, 345 + }, 346 + uploadFailed: { 347 + skeet: msg`Skeet failed to upload. Please check your Internet connection and try again.`, 348 + post: msg`Post failed to upload. Please check your Internet connection and try again.`, 349 + spell: msg`Spell failed to upload. Please check your Internet connection and try again.`, 350 + }, 351 + // For feeds and filtering 352 + ranOut: { 353 + skeet: msg`We ran out of skeets from your follows. Here's the latest from`, 354 + post: msg`We ran out of posts from your follows. Here's the latest from`, 355 + spell: msg`We ran out of spells from your follows. Here's the latest from`, 356 + }, 357 + // For quote posts 358 + quoteAction: { 359 + skeet: msg`Quote skeet`, 360 + post: msg`Quote post`, 361 + spell: msg`Quote spell`, 362 + }, 363 + quoteDisabled: { 364 + skeet: msg`Quote skeets disabled`, 365 + post: msg`Quote posts disabled`, 366 + spell: msg`Quote spells disabled`, 367 + }, 368 + allowQuote: { 369 + skeet: msg`Allow quote skeets`, 370 + post: msg`Allow quote posts`, 371 + spell: msg`Allow quote spells`, 372 + }, 373 + quoteAuthorDisabled: { 374 + skeet: msg`This skeet's author has disabled quote skeets.`, 375 + post: msg`This post's author has disabled quote posts.`, 376 + spell: msg`This spell's author has disabled quote spells.`, 377 + }, 378 + cancelQuote: { 379 + skeet: msg`Cancel quote skeet`, 380 + post: msg`Cancel quote post`, 381 + spell: msg`Cancel quote spell`, 382 + }, 383 + noQuoteYet: { 384 + skeet: msg`No one but the author can quote this skeet.`, 385 + post: msg`No one but the author can quote this post.`, 386 + spell: msg`No one but the author can quote this spell.`, 387 + }, 388 + // For reply settings 389 + repliesDisabled: { 390 + skeet: msg`Replies to this skeet are disabled.`, 391 + post: msg`Replies to this post are disabled.`, 392 + spell: msg`Replies to this spell are disabled.`, 393 + }, 394 + everyoneCanReply: { 395 + skeet: msg`Everybody can reply to this skeet.`, 396 + post: msg`Everybody can reply to this post.`, 397 + spell: msg`Everybody can reply to this spell.`, 398 + }, 399 + unknownThreadgate: { 400 + skeet: msg`This skeet has an unknown type of threadgate on it. Your app may be out of date.`, 401 + post: msg`This post has an unknown type of threadgate on it. Your app may be out of date.`, 402 + spell: msg`This spell has an unknown type of threadgate on it. Your app may be out of date.`, 403 + }, 404 + // For replies 405 + repliedTo: { 406 + skeet: msg`Replied to a skeet`, 407 + post: msg`Replied to a post`, 408 + spell: msg`Replied to a spell`, 409 + }, 410 + repliedToBlocked: { 411 + skeet: msg`Replied to a blocked skeet`, 412 + post: msg`Replied to a blocked post`, 413 + spell: msg`Replied to a blocked spell`, 414 + }, 415 + replyWasDeleted: { 416 + skeet: msg`We're sorry! The skeet you are replying to has been deleted.`, 417 + post: msg`We're sorry! The post you are replying to has been deleted.`, 418 + spell: msg`We're sorry! The spell you are replying to has been deleted.`, 419 + }, 420 + sortReplies: { 421 + skeet: msg`Sort replies to the same skeet by:`, 422 + post: msg`Sort replies to the same post by:`, 423 + spell: msg`Sort replies to the same spell by:`, 424 + }, 425 + showRepliesTree: { 426 + skeet: msg`Show skeet replies in a threaded tree view`, 427 + post: msg`Show post replies in a threaded tree view`, 428 + spell: msg`Show spell replies in a threaded tree view`, 429 + }, 430 + // For visibility 431 + onlyLoggedIn: { 432 + skeet: msg`This skeet is only visible to logged-in users.`, 433 + post: msg`This post is only visible to logged-in users.`, 434 + spell: msg`This spell is only visible to logged-in users.`, 435 + }, 436 + noteOnlyLoggedIn: { 437 + skeet: msg`Note: This skeet is only visible to logged-in users.`, 438 + post: msg`Note: This post is only visible to logged-in users.`, 439 + spell: msg`Note: This spell is only visible to logged-in users.`, 440 + }, 441 + }
+25 -4
src/screens/Bookmarks/index.tsx
··· 16 16 import {useCleanError} from '#/lib/hooks/useCleanError' 17 17 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 18 18 import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' 19 + import {getTerminology} from '#/lib/strings/terminology' 19 20 import { 20 21 type CommonNavigatorParams, 21 22 type NativeStackScreenProps, 22 23 } from '#/lib/routes/types' 23 24 import {logger} from '#/logger' 24 25 import {isIOS} from '#/platform/detection' 26 + import {useTerminologyPreference} from '#/state/preferences' 25 27 import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation' 26 28 import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery' 27 29 import {useSetMinimalShellMode} from '#/state/shell' ··· 42 44 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Bookmarks'> 43 45 44 46 export function BookmarksScreen({}: Props) { 47 + const {_} = useLingui() 48 + const terminologyPreference = useTerminologyPreference() 45 49 const setMinimalShellMode = useSetMinimalShellMode() 46 50 47 51 useFocusEffect( ··· 57 61 <Layout.Header.BackButton /> 58 62 <Layout.Header.Content> 59 63 <Layout.Header.TitleText> 60 - <Trans>Saved Skeets</Trans> 64 + <Trans>{_(getTerminology(terminologyPreference, { 65 + skeet: msg`Saved Skeets`, 66 + post: msg`Saved Posts`, 67 + spell: msg`Saved Spells`, 68 + }))}</Trans> 61 69 </Layout.Header.TitleText> 62 70 </Layout.Header.Content> 63 71 <Layout.Header.Slot /> ··· 209 217 }) { 210 218 const t = useTheme() 211 219 const {_} = useLingui() 220 + const terminologyPreference = useTerminologyPreference() 212 221 const {mutateAsync: bookmark} = useBookmarkMutation() 213 222 const cleanError = useCleanError() 214 223 215 224 const remove = async () => { 216 225 try { 217 226 await bookmark({action: 'delete', uri: post.uri}) 218 - toast.show(_(msg`Removed from saved skeets`), { 227 + toast.show(_(getTerminology(terminologyPreference, { 228 + skeet: msg`Removed from saved skeets`, 229 + post: msg`Removed from saved posts`, 230 + spell: msg`Removed from saved spells`, 231 + })), { 219 232 type: 'info', 220 233 }) 221 234 } catch (e: any) { ··· 253 266 a.italic, 254 267 t.atoms.text_contrast_medium, 255 268 ]}> 256 - <Trans>This skeet was deleted by its author</Trans> 269 + <Trans>{_(getTerminology(terminologyPreference, { 270 + skeet: msg`This skeet was deleted by its author`, 271 + post: msg`This post was deleted by its author`, 272 + spell: msg`This spell was deleted by its author`, 273 + }))}</Trans> 257 274 </Text> 258 275 </View> 259 276 <Button 260 - label={_(msg`Remove from saved skeets`)} 277 + label={_(getTerminology(terminologyPreference, { 278 + skeet: msg`Remove from saved skeets`, 279 + post: msg`Remove from saved posts`, 280 + spell: msg`Remove from saved spells`, 281 + }))} 261 282 size="tiny" 262 283 color="secondary" 263 284 onPress={remove}>
+5 -1
src/screens/Post/PostLikedBy.tsx
··· 4 4 import {useFocusEffect} from '@react-navigation/native' 5 5 6 6 import {useSetTitle} from '#/lib/hooks/useSetTitle' 7 + import {useTerminology} from '#/lib/hooks/useTerminology' 7 8 import { 8 9 type CommonNavigatorParams, 9 10 type NativeStackScreenProps, ··· 20 21 export const PostLikedByScreen = ({route}: Props) => { 21 22 const {_} = useLingui() 22 23 const setMinimalShellMode = useSetMinimalShellMode() 24 + const terminology = useTerminology() 23 25 const {name, rkey} = route.params 24 26 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) 25 27 const {data: post} = usePostQuery(uri) ··· 27 29 const {data: resolvedDid} = useResolveDidQuery(name) 28 30 const {data: profile} = useProfileQuery({did: resolvedDid}) 29 31 30 - useSetTitle(profile ? _(msg`Skeet by @${profile.handle}`) : undefined) 32 + useSetTitle( 33 + profile ? `${_(terminology.post)} ${_(msg`by`)} @${profile.handle}` : undefined, 34 + ) 31 35 32 36 let likeCount 33 37 if (post) {
+5 -1
src/screens/Post/PostQuotes.tsx
··· 4 4 import {useFocusEffect} from '@react-navigation/native' 5 5 6 6 import {useSetTitle} from '#/lib/hooks/useSetTitle' 7 + import {useTerminology} from '#/lib/hooks/useTerminology' 7 8 import { 8 9 type CommonNavigatorParams, 9 10 type NativeStackScreenProps, ··· 20 21 export const PostQuotesScreen = ({route}: Props) => { 21 22 const {_} = useLingui() 22 23 const setMinimalShellMode = useSetMinimalShellMode() 24 + const terminology = useTerminology() 23 25 const {name, rkey} = route.params 24 26 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) 25 27 const {data: post} = usePostQuery(uri) ··· 27 29 const {data: resolvedDid} = useResolveDidQuery(name) 28 30 const {data: profile} = useProfileQuery({did: resolvedDid}) 29 31 30 - useSetTitle(profile ? _(msg`Skeet by @${profile.handle}`) : undefined) 32 + useSetTitle( 33 + profile ? `${_(terminology.post)} ${_(msg`by`)} @${profile.handle}` : undefined, 34 + ) 31 35 32 36 let quoteCount 33 37 if (post) {
+8 -4
src/screens/Post/PostRepostedBy.tsx
··· 4 4 import {useFocusEffect} from '@react-navigation/native' 5 5 6 6 import {useSetTitle} from '#/lib/hooks/useSetTitle' 7 + import {useTerminology} from '#/lib/hooks/useTerminology' 7 8 import { 8 9 type CommonNavigatorParams, 9 10 type NativeStackScreenProps, ··· 19 20 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'> 20 21 export const PostRepostedByScreen = ({route}: Props) => { 21 22 const {_} = useLingui() 23 + const terminology = useTerminology() 22 24 const {name, rkey} = route.params 23 25 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) 24 26 const setMinimalShellMode = useSetMinimalShellMode() ··· 27 29 const {data: resolvedDid} = useResolveDidQuery(name) 28 30 const {data: profile} = useProfileQuery({did: resolvedDid}) 29 31 30 - useSetTitle(profile ? _(msg`Skeet by @${profile.handle}`) : undefined) 32 + useSetTitle( 33 + profile ? `${_(terminology.post)} ${_(msg`by`)} @${profile.handle}` : undefined, 34 + ) 31 35 32 36 let quoteCount 33 37 if (post) { ··· 48 52 {post && ( 49 53 <> 50 54 <Layout.Header.TitleText> 51 - <Trans>Reskeeted By</Trans> 55 + <Trans>{_(terminology.reposted)} By</Trans> 52 56 </Layout.Header.TitleText> 53 57 <Layout.Header.SubtitleText> 54 58 <Plural 55 59 value={quoteCount ?? 0} 56 - one="# reskeet" 57 - other="# reskeets" 60 + one={_(terminology.repost)} 61 + other={_(terminology.reposts)} 58 62 /> 59 63 </Layout.Header.SubtitleText> 60 64 </>
+6 -3
src/screens/PostThread/components/ThreadError.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {useCleanError} from '#/lib/hooks/useCleanError' 7 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 8 + import {useTerminologyPreference} from '#/state/preferences' 7 9 import {OUTER_SPACE} from '#/screens/PostThread/const' 8 10 import {atoms as a, useTheme} from '#/alf' 9 11 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 20 22 }) { 21 23 const t = useTheme() 22 24 const {_} = useLingui() 25 + const terminologyPreference = useTerminologyPreference() 23 26 const cleanError = useCleanError() 24 27 25 28 const {title, message} = useMemo(() => { 26 - let title = _(msg`Error loading skeet`) 29 + let title = _(getTerminology(terminologyPreference, TERMINOLOGY.errorLoading)) 27 30 let message = _(msg`Something went wrong. Please try again in a moment.`) 28 31 29 32 const {raw, clean} = cleanError(error) 30 33 31 34 if (error.message.startsWith('Post not found')) { 32 - title = _(msg`Skeet not found`) 35 + title = _(getTerminology(terminologyPreference, TERMINOLOGY.notFound)) 33 36 message = clean || raw || message 34 37 } 35 38 36 39 return {title, message} 37 - }, [_, error, cleanError]) 40 + }, [_, error, cleanError, terminologyPreference]) 38 41 39 42 return ( 40 43 <Layout.Center>
+14 -15
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 11 11 import {useLingui} from '@lingui/react' 12 12 13 13 import {useActorStatus} from '#/lib/actor-status' 14 + import {useTerminology} from '#/lib/hooks/useTerminology' 14 15 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 15 16 import {useTranslate} from '#/lib/hooks/useTranslate' 16 17 import {makeProfileLink} from '#/lib/routes/links' ··· 102 103 103 104 function ThreadItemAnchorDeleted({isRoot}: {isRoot: boolean}) { 104 105 const t = useTheme() 106 + const {_} = useLingui() 107 + const terminology = useTerminology() 105 108 106 109 return ( 107 110 <> ··· 136 139 </View> 137 140 <Text 138 141 style={[a.text_md, a.font_semi_bold, t.atoms.text_contrast_medium]}> 139 - <Trans>Skeet has been deleted</Trans> 142 + <Trans>{_(terminology.post)} has been deleted</Trans> 140 143 </Text> 141 144 </View> 142 145 </View> ··· 187 190 const {currentAccount, hasSession} = useSession() 188 191 const feedFeedback = useFeedFeedback(postSource?.feedSourceInfo, hasSession) 189 192 const formatPostStatCount = useFormatPostStatCount() 193 + const terminology = useTerminology() 190 194 191 195 const post = postShadow 192 196 const record = item.value.post.record ··· 446 450 {post.repostCount != null && 447 451 post.repostCount !== 0 && 448 452 !disableRepostsMetrics ? ( 449 - <Link to={repostsHref} label={_(msg`Reskeets of this skeet`)}> 453 + <Link to={repostsHref} label={_(msg`${_(terminology.reposts)} of this ${_(terminology.post)}`)}> 450 454 <Text 451 455 testID="repostCount-expanded" 452 456 style={[a.text_md, t.atoms.text_contrast_medium]}> 453 457 <Text style={[a.text_md, a.font_semi_bold, t.atoms.text]}> 454 458 {formatPostStatCount(post.repostCount)} 455 459 </Text>{' '} 456 - <Plural 457 - value={post.repostCount} 458 - one="reskeet" 459 - other="reskeets" 460 - /> 460 + {post.repostCount === 1 ? _(terminology.repost) : _(terminology.reposts)} 461 461 </Text> 462 462 </Link> 463 463 ) : null} ··· 465 465 post.quoteCount !== 0 && 466 466 !post.viewer?.embeddingDisabled && 467 467 !disableQuotesMetrics ? ( 468 - <Link to={quotesHref} label={_(msg`Quotes of this skeet`)}> 468 + <Link to={quotesHref} label={_(msg`Quotes of this ${_(terminology.post)}`)}> 469 469 <Text 470 470 testID="quoteCount-expanded" 471 471 style={[a.text_md, t.atoms.text_contrast_medium]}> ··· 483 483 {post.likeCount != null && 484 484 post.likeCount !== 0 && 485 485 !disableLikesMetrics ? ( 486 - <Link to={likesHref} label={_(msg`Likes on this skeet`)}> 486 + <Link to={likesHref} label={_(msg`Likes on this ${_(terminology.post)}`)}> 487 487 <Text 488 488 testID="likeCount-expanded" 489 489 style={[a.text_md, t.atoms.text_contrast_medium]}> ··· 623 623 const {_, i18n} = useLingui() 624 624 const control = Prompt.usePromptControl() 625 625 const enableSquareButtons = useEnableSquareButtons() 626 + const terminology = useTerminology() 626 627 627 628 const indexedAt = new Date(post.indexedAt) 628 629 const createdAt = bsky.dangerousIsType<AppBskyFeedPost.Record>( ··· 643 644 return ( 644 645 <> 645 646 <Button 646 - label={_(msg`Archived post`)} 647 - accessibilityHint={_( 648 - msg`Shows information about when this skeet was created`, 649 - )} 647 + label={`${_(msg`Archived`)} ${_(terminology.post)}`} 648 + accessibilityHint={`${_(msg`Shows information about when this`)} ${_(terminology.post)} ${_(msg`was created`)}`} 650 649 onPress={e => { 651 650 e.preventDefault() 652 651 e.stopPropagation() ··· 682 681 683 682 <Prompt.Outer control={control}> 684 683 <Prompt.TitleText> 685 - <Trans>Archived post</Trans> 684 + <Trans>Archived {_(terminology.post)}</Trans> 686 685 </Prompt.TitleText> 687 686 <Prompt.DescriptionText> 688 687 <Trans> 689 - This skeet claims to have been created on{' '} 688 + This {_(terminology.post)} claims to have been created on{' '} 690 689 <RNText style={[a.font_semi_bold]}> 691 690 {niceDate(i18n, createdAt)} 692 691 </RNText>
+6 -1
src/screens/PostThread/components/ThreadItemPost.tsx
··· 7 7 RichText as RichTextAPI, 8 8 } from '@atproto/api' 9 9 import {Trans} from '@lingui/macro' 10 + import {useLingui} from '@lingui/react' 10 11 11 12 import {useActorStatus} from '#/lib/actor-status' 12 13 import {MAX_POST_LINES} from '#/lib/constants' 13 14 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 14 15 import {makeProfileLink} from '#/lib/routes/links' 15 16 import {countLines} from '#/lib/strings/helpers' 17 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 18 + import {useTerminologyPreference} from '#/state/preferences' 16 19 import { 17 20 POST_TOMBSTONE, 18 21 type Shadow, ··· 83 86 overrides, 84 87 }: Pick<ThreadItemPostProps, 'item' | 'overrides'>) { 85 88 const t = useTheme() 89 + const {_} = useLingui() 90 + const terminologyPreference = useTerminologyPreference() 86 91 87 92 return ( 88 93 <ThreadItemPostOuterWrapper item={item} overrides={overrides}> ··· 109 114 </View> 110 115 <Text 111 116 style={[a.text_md, a.font_semi_bold, t.atoms.text_contrast_medium]}> 112 - <Trans>Skeet has been deleted</Trans> 117 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.deletedShort))}</Trans> 113 118 </Text> 114 119 </View> 115 120
+6 -3
src/screens/PostThread/components/ThreadItemPostTombstone.tsx
··· 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 7 + import {useTerminologyPreference} from '#/state/preferences' 6 8 import {LINEAR_AVI_WIDTH, OUTER_SPACE} from '#/screens/PostThread/const' 7 9 import {atoms as a, useTheme} from '#/alf' 8 10 import {PersonX_Stroke2_Corner0_Rounded as PersonXIcon} from '#/components/icons/Person' ··· 16 18 export function ThreadItemPostTombstone({type}: ThreadItemPostTombstoneProps) { 17 19 const t = useTheme() 18 20 const {_} = useLingui() 21 + const terminologyPreference = useTerminologyPreference() 19 22 const {copy, Icon} = useMemo(() => { 20 23 switch (type) { 21 24 case 'blocked': 22 - return {copy: _(msg`Skeet blocked`), Icon: PersonXIcon} 25 + return {copy: _(getTerminology(terminologyPreference, TERMINOLOGY.blocked)), Icon: PersonXIcon} 23 26 case 'not-found': 24 27 default: 25 - return {copy: _(msg`Skeet not found`), Icon: TrashIcon} 28 + return {copy: _(getTerminology(terminologyPreference, TERMINOLOGY.notFound)), Icon: TrashIcon} 26 29 } 27 - }, [_, type]) 30 + }, [_, type, terminologyPreference]) 28 31 29 32 return ( 30 33 <View
+8 -1
src/screens/Profile/Header/Metrics.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {makeProfileLink} from '#/lib/routes/links' 7 + import {getTerminology} from '#/lib/strings/terminology' 7 8 import {type Shadow} from '#/state/cache/types' 9 + import {useTerminologyPreference} from '#/state/preferences' 8 10 import {useDisableFollowersMetrics} from '#/state/preferences/disable-followers-metrics' 9 11 import {useDisableFollowingMetrics} from '#/state/preferences/disable-following-metrics' 10 12 import {useDisablePostsMetrics} from '#/state/preferences/disable-posts-metrics' ··· 20 22 }) { 21 23 const t = useTheme() 22 24 const {_, i18n} = useLingui() 25 + const terminologyPreference = useTerminologyPreference() 23 26 const following = formatCount(i18n, profile.followsCount || 0) 24 27 const followers = formatCount(i18n, profile.followersCount || 0) 25 28 const pluralizedFollowers = plural(profile.followersCount || 0, { ··· 70 73 <Text style={[a.font_semi_bold, t.atoms.text, a.text_md]}> 71 74 {formatCount(i18n, profile.postsCount || 0)}{' '} 72 75 <Text style={[t.atoms.text_contrast_medium, a.font_normal, a.text_md]}> 73 - {plural(profile.postsCount || 0, {one: 'skeet', other: 'skeets'})} 76 + {_(getTerminology(terminologyPreference, { 77 + skeet: msg`${plural(profile.postsCount || 0, {one: 'skeet', other: 'skeets'})}`, 78 + post: msg`${plural(profile.postsCount || 0, {one: 'post', other: 'posts'})}`, 79 + spell: msg`${plural(profile.postsCount || 0, {one: 'spell', other: 'spells'})}`, 80 + }))} 74 81 </Text> 75 82 </Text> 76 83 ) : null}
+4 -1
src/screens/Profile/ProfileFeed/index.tsx
··· 16 16 import {type CommonNavigatorParams} from '#/lib/routes/types' 17 17 import {type NavigationProp} from '#/lib/routes/types' 18 18 import {makeRecordUri} from '#/lib/strings/url-helpers' 19 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 19 20 import {s} from '#/lib/styles' 20 21 import {isNative} from '#/platform/detection' 21 22 import {listenSoftReset} from '#/state/events' 22 23 import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' 24 + import {useTerminologyPreference} from '#/state/preferences' 23 25 import { 24 26 type FeedSourceFeedInfo, 25 27 useFeedSourceInfoQuery, ··· 159 161 feedParams: FeedParams | undefined 160 162 }) { 161 163 const {_} = useLingui() 164 + const terminologyPreference = useTerminologyPreference() 162 165 const {hasSession} = useSession() 163 166 const {openComposer} = useOpenComposer() 164 167 const isScreenFocused = useIsFocused() ··· 228 231 {(isScrolledDown || hasNew) && ( 229 232 <LoadLatestBtn 230 233 onPress={onScrollToTop} 231 - label={_(msg`Load new skeets`)} 234 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.loadNew))} 232 235 showIndicator={hasNew} 233 236 /> 234 237 )}
+6 -3
src/screens/Profile/ProfileSearch.tsx
··· 3 3 import {useLingui} from '@lingui/react' 4 4 5 5 import {useSetTitle} from '#/lib/hooks/useSetTitle' 6 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 7 + import {useTerminologyPreference} from '#/state/preferences' 6 8 import { 7 9 type CommonNavigatorParams, 8 10 type NativeStackScreenProps, ··· 16 18 export const ProfileSearchScreen = ({route}: Props) => { 17 19 const {name, q: queryParam = ''} = route.params 18 20 const {_} = useLingui() 21 + const terminologyPreference = useTerminologyPreference() 19 22 const {currentAccount} = useSession() 20 23 21 24 const {data: resolvedDid} = useResolveDidQuery(name) 22 25 const {data: profile} = useProfileQuery({did: resolvedDid}) 23 26 24 - useSetTitle(profile ? _(msg`Search @${profile.handle}'s skeets`) : undefined) 27 + useSetTitle(profile ? _(getTerminology(terminologyPreference, TERMINOLOGY.searchProfile(profile.handle))) : undefined) 25 28 26 29 const fixedParams = useMemo( 27 30 () => ({ ··· 36 39 inputPlaceholder={ 37 40 profile 38 41 ? currentAccount?.did === profile.did 39 - ? _(msg`Search my skeets`) 40 - : _(msg`Search @${profile.handle}'s skeets`) 42 + ? _(getTerminology(terminologyPreference, TERMINOLOGY.searchMy)) 43 + : _(getTerminology(terminologyPreference, TERMINOLOGY.searchProfile(profile.handle))) 41 44 : _(msg`Search...`) 42 45 } 43 46 fixedParams={fixedParams}
+4 -1
src/screens/Profile/Sections/Feed.tsx
··· 5 5 import {useQueryClient} from '@tanstack/react-query' 6 6 7 7 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 8 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 8 9 import {isIOS, isNative} from '#/platform/detection' 10 + import {useTerminologyPreference} from '#/state/preferences' 9 11 import { 10 12 type FeedDescriptor, 11 13 RQKEY as FEED_RQKEY, ··· 49 51 emptyStateIcon, 50 52 }: FeedSectionProps) { 51 53 const {_} = useLingui() 54 + const terminologyPreference = useTerminologyPreference() 52 55 const queryClient = useQueryClient() 53 56 const [hasNew, setHasNew] = useState(false) 54 57 const [isScrolledDown, setIsScrolledDown] = useState(false) ··· 113 116 {(isScrolledDown || hasNew) && ( 114 117 <LoadLatestBtn 115 118 onPress={onScrollToTop} 116 - label={_(msg`Load new skeets`)} 119 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.loadNew))} 117 120 showIndicator={hasNew} 118 121 /> 119 122 )}
+12 -18
src/screens/Settings/DeerSettings.tsx
··· 9 9 import {APPVIEW_DID_PROXY} from '#/lib/constants' 10 10 import {usePalette} from '#/lib/hooks/usePalette' 11 11 import {type CommonNavigatorParams} from '#/lib/routes/types' 12 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 12 13 import {type Gate} from '#/lib/statsig/gates' 13 14 import { 14 15 resetDeerGateCache, ··· 559 560 </SettingsList.ItemText> 560 561 <Toggle.Item 561 562 name="external_share_buttons" 562 - label={_( 563 - msg`Show "Open original skeet" and "Open skeet in PDSls" buttons`, 564 - )} 563 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.externalShareButtons))} 565 564 value={showExternalShareButtons} 566 565 onChange={value => setShowExternalShareButtons(value)} 567 566 style={[a.w_full]}> 568 567 <Toggle.LabelText style={[a.flex_1]}> 569 - <Trans> 570 - Show "Open original skeet" and "Open skeet in PDSls" buttons 571 - </Trans> 568 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.externalShareButtons))}</Trans> 572 569 </Toggle.LabelText> 573 570 <Toggle.Platform /> 574 571 </Toggle.Item> ··· 741 738 742 739 <Toggle.Item 743 740 name="repost_carousel" 744 - label={_(msg`Combine reskeets into a horizontal carousel`)} 741 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.carousel))} 745 742 value={repostCarouselEnabled} 746 743 onChange={value => setRepostCarouselEnabled(value)} 747 744 style={[a.w_full]}> 748 745 <Toggle.LabelText style={[a.flex_1]}> 749 - <Trans>Combine reskeets into a horizontal carousel</Trans> 746 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.carousel))}</Trans> 750 747 </Toggle.LabelText> 751 748 <Toggle.Platform /> 752 749 </Toggle.Item> ··· 797 794 798 795 <Toggle.Item 799 796 name="disable_via_repost_notification" 800 - label={_(msg`Disable "via reskeet" notifications`)} 797 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.viaRepostNotification))} 801 798 value={disableViaRepostNotification} 802 799 onChange={value => setDisableViaRepostNotification(value)} 803 800 style={[a.w_full]}> 804 801 <Toggle.LabelText style={[a.flex_1]}> 805 - <Trans>Disable "via reskeet" notifications</Trans> 802 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.viaRepostNotification))}</Trans> 806 803 </Toggle.LabelText> 807 804 <Toggle.Platform /> 808 805 </Toggle.Item> 809 806 <Admonition type="info" style={[a.flex_1]}> 810 - <Trans> 811 - Forcefully disables the notifications other people receive when 812 - you like/reskeet a skeet someone else has reskeeted for privacy. 813 - </Trans> 807 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.viaRepostPrivacy))}</Trans> 814 808 </Admonition> 815 809 816 810 <Toggle.Item ··· 883 877 884 878 <Toggle.Item 885 879 name="disable_reposts_metrics" 886 - label={_(msg`Disable reskeets metrics`)} 880 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.repostMetrics))} 887 881 value={disableRepostsMetrics} 888 882 onChange={value => setDisableRepostsMetrics(value)} 889 883 style={[a.w_full]}> 890 884 <Toggle.LabelText style={[a.flex_1]}> 891 - <Trans>Disable reskeets metrics</Trans> 885 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.repostMetrics))}</Trans> 892 886 </Toggle.LabelText> 893 887 <Toggle.Platform /> 894 888 </Toggle.Item> ··· 967 961 968 962 <Toggle.Item 969 963 name="disable_posts_metrics" 970 - label={_(msg`Disable skeets metrics`)} 964 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.metrics))} 971 965 value={disablePostsMetrics} 972 966 onChange={value => setDisablePostsMetrics(value)} 973 967 style={[a.w_full]}> 974 968 <Toggle.LabelText style={[a.flex_1]}> 975 - <Trans>Disable skeets metrics</Trans> 969 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.metrics))}</Trans> 976 970 </Toggle.LabelText> 977 971 <Toggle.Platform /> 978 972 </Toggle.Item>
+23 -4
src/screens/Settings/FollowingFeedPreferences.tsx
··· 5 5 type CommonNavigatorParams, 6 6 type NativeStackScreenProps, 7 7 } from '#/lib/routes/types' 8 + import {getTerminology} from '#/lib/strings/terminology' 9 + import {useTerminologyPreference} from '#/state/preferences' 8 10 import { 9 11 usePreferencesQuery, 10 12 useSetFeedViewPreferencesMutation, ··· 25 27 > 26 28 export function FollowingFeedPreferencesScreen({}: Props) { 27 29 const {_} = useLingui() 30 + const terminologyPreference = useTerminologyPreference() 28 31 29 32 const {data: preferences} = usePreferencesQuery() 30 33 const {mutate: setFeedViewPref, variables} = ··· 86 89 <Toggle.Item 87 90 type="checkbox" 88 91 name="show-reposts" 89 - label={_(msg`Show reskeets`)} 92 + label={_(getTerminology(terminologyPreference, { 93 + skeet: msg`Show reskeets`, 94 + post: msg`Show reposts`, 95 + spell: msg`Show respells`, 96 + }))} 90 97 value={showReposts} 91 98 onChange={value => 92 99 setFeedViewPref({ ··· 96 103 <SettingsList.Item> 97 104 <SettingsList.ItemIcon icon={RepostIcon} /> 98 105 <SettingsList.ItemText> 99 - <Trans>Show reskeets</Trans> 106 + <Trans>{_(getTerminology(terminologyPreference, { 107 + skeet: msg`Show reskeets`, 108 + post: msg`Show reposts`, 109 + spell: msg`Show respells`, 110 + }))}</Trans> 100 111 </SettingsList.ItemText> 101 112 <Toggle.Platform /> 102 113 </SettingsList.Item> ··· 104 115 <Toggle.Item 105 116 type="checkbox" 106 117 name="show-quotes" 107 - label={_(msg`Show quote skeets`)} 118 + label={_(getTerminology(terminologyPreference, { 119 + skeet: msg`Show quote skeets`, 120 + post: msg`Show quote posts`, 121 + spell: msg`Show quote spells`, 122 + }))} 108 123 value={showQuotePosts} 109 124 onChange={value => 110 125 setFeedViewPref({ ··· 114 129 <SettingsList.Item> 115 130 <SettingsList.ItemIcon icon={QuoteIcon} /> 116 131 <SettingsList.ItemText> 117 - <Trans>Show quote skeets</Trans> 132 + <Trans>{_(getTerminology(terminologyPreference, { 133 + skeet: msg`Show quote skeets`, 134 + post: msg`Show quote posts`, 135 + spell: msg`Show quote spells`, 136 + }))}</Trans> 118 137 </SettingsList.ItemText> 119 138 <Toggle.Platform /> 120 139 </SettingsList.Item>
+16 -5
src/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings.tsx
··· 1 1 import {View} from 'react-native' 2 - import {Trans} from '@lingui/macro' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 3 4 4 5 import { 5 6 type AllNavigatorParams, 6 7 type NativeStackScreenProps, 7 8 } from '#/lib/routes/types' 9 + import {getTerminology} from '#/lib/strings/terminology' 10 + import {useTerminologyPreference} from '#/state/preferences' 8 11 import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' 9 12 import {atoms as a} from '#/alf' 10 13 import {Admonition} from '#/components/Admonition' ··· 19 22 'LikesOnRepostsNotificationSettings' 20 23 > 21 24 export function LikesOnRepostsNotificationSettingsScreen({}: Props) { 25 + const {_} = useLingui() 26 + const terminologyPreference = useTerminologyPreference() 22 27 const {data: preferences, isError} = useNotificationSettingsQuery() 23 28 24 29 return ( ··· 38 43 <SettingsList.ItemIcon icon={LikeRepostIcon} /> 39 44 <ItemTextWithSubtitle 40 45 bold 41 - titleText={<Trans>Likes of your reskeets</Trans>} 46 + titleText={<Trans>{_(getTerminology(terminologyPreference, { 47 + skeet: msg`Likes of your reskeets`, 48 + post: msg`Likes of your reposts`, 49 + spell: msg`Likes of your respells`, 50 + }))}</Trans>} 42 51 subtitleText={ 43 - <Trans> 44 - Get notifications when people like skeets that you've reskeeted. 45 - </Trans> 52 + <Trans>{_(getTerminology(terminologyPreference, { 53 + skeet: msg`Get notifications when people like skeets that you've reskeeted.`, 54 + post: msg`Get notifications when people like posts that you've reposted.`, 55 + spell: msg`Get notifications when people like spells that you've respelled.`, 56 + }))}</Trans> 46 57 } 47 58 /> 48 59 </SettingsList.Item>
+16 -3
src/screens/Settings/NotificationSettings/RepostNotificationSettings.tsx
··· 1 1 import {View} from 'react-native' 2 - import {Trans} from '@lingui/macro' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 3 4 4 5 import { 5 6 type AllNavigatorParams, 6 7 type NativeStackScreenProps, 7 8 } from '#/lib/routes/types' 9 + import {getTerminology} from '#/lib/strings/terminology' 10 + import {useTerminologyPreference} from '#/state/preferences' 8 11 import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' 9 12 import {atoms as a} from '#/alf' 10 13 import {Admonition} from '#/components/Admonition' ··· 19 22 'RepostNotificationSettings' 20 23 > 21 24 export function RepostNotificationSettingsScreen({}: Props) { 25 + const {_} = useLingui() 26 + const terminologyPreference = useTerminologyPreference() 22 27 const {data: preferences, isError} = useNotificationSettingsQuery() 23 28 24 29 return ( ··· 38 43 <SettingsList.ItemIcon icon={RepostIcon} /> 39 44 <ItemTextWithSubtitle 40 45 bold 41 - titleText={<Trans>Reskeets</Trans>} 46 + titleText={<Trans>{_(getTerminology(terminologyPreference, { 47 + skeet: msg`Reskeets`, 48 + post: msg`Reposts`, 49 + spell: msg`Respells`, 50 + }))}</Trans>} 42 51 subtitleText={ 43 - <Trans>Get notifications when people reskeet your skeets.</Trans> 52 + <Trans>{_(getTerminology(terminologyPreference, { 53 + skeet: msg`Get notifications when people reskeet your skeets.`, 54 + post: msg`Get notifications when people repost your posts.`, 55 + spell: msg`Get notifications when people respell your spells.`, 56 + }))}</Trans> 44 57 } 45 58 /> 46 59 </SettingsList.Item>
+16 -6
src/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings.tsx
··· 1 1 import {View} from 'react-native' 2 - import {Trans} from '@lingui/macro' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 3 4 4 5 import { 5 6 type AllNavigatorParams, 6 7 type NativeStackScreenProps, 7 8 } from '#/lib/routes/types' 9 + import {getTerminology} from '#/lib/strings/terminology' 10 + import {useTerminologyPreference} from '#/state/preferences' 8 11 import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' 9 12 import {atoms as a} from '#/alf' 10 13 import {Admonition} from '#/components/Admonition' ··· 19 22 'RepostsOnRepostsNotificationSettings' 20 23 > 21 24 export function RepostsOnRepostsNotificationSettingsScreen({}: Props) { 25 + const {_} = useLingui() 26 + const terminologyPreference = useTerminologyPreference() 22 27 const {data: preferences, isError} = useNotificationSettingsQuery() 23 28 24 29 return ( ··· 38 43 <SettingsList.ItemIcon icon={RepostRepostIcon} /> 39 44 <ItemTextWithSubtitle 40 45 bold 41 - titleText={<Trans>Reskeets of your reskeets</Trans>} 46 + titleText={<Trans>{_(getTerminology(terminologyPreference, { 47 + skeet: msg`Reskeets of your reskeets`, 48 + post: msg`Reposts of your reposts`, 49 + spell: msg`Respells of your respells`, 50 + }))}</Trans>} 42 51 subtitleText={ 43 - <Trans> 44 - Get notifications when people reskeet skeets that you've 45 - reskeeted. 46 - </Trans> 52 + <Trans>{_(getTerminology(terminologyPreference, { 53 + skeet: msg`Get notifications when people reskeet skeets that you've reskeeted.`, 54 + post: msg`Get notifications when people repost posts that you've reposted.`, 55 + spell: msg`Get notifications when people respell spells that you've respelled.`, 56 + }))}</Trans> 47 57 } 48 58 /> 49 59 </SettingsList.Item>
+6 -3
src/state/queries/pinned-post.ts
··· 3 3 import {useMutation, useQueryClient} from '@tanstack/react-query' 4 4 5 5 import {logger} from '#/logger' 6 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 7 + import {useTerminologyPreference} from '#/state/preferences' 6 8 import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' 7 9 import * as Toast from '#/view/com/util/Toast' 8 10 import {updatePostShadow} from '../cache/post-shadow' ··· 11 13 12 14 export function usePinnedPostMutation() { 13 15 const {_} = useLingui() 16 + const terminologyPreference = useTerminologyPreference() 14 17 const {currentAccount} = useSession() 15 18 const agent = useAgent() 16 19 const queryClient = useQueryClient() ··· 56 59 }) 57 60 58 61 if (pinCurrentPost) { 59 - Toast.show(_(msg({message: 'Skeet pinned', context: 'toast'}))) 62 + Toast.show(_(getTerminology(terminologyPreference, TERMINOLOGY.pinned))) 60 63 } else { 61 - Toast.show(_(msg({message: 'Skeet unpinned', context: 'toast'}))) 64 + Toast.show(_(getTerminology(terminologyPreference, TERMINOLOGY.unpinned))) 62 65 } 63 66 64 67 queryClient.invalidateQueries({ ··· 72 75 ), 73 76 }) 74 77 } catch (e: any) { 75 - Toast.show(_(msg`Failed to pin skeet`)) 78 + Toast.show(_(getTerminology(terminologyPreference, TERMINOLOGY.failedToPin))) 76 79 logger.error('Failed to pin post', {message: String(e)}) 77 80 // revert optimistic update 78 81 updatePostShadow(queryClient, postUri, {
+16 -15
src/view/com/composer/Composer.tsx
··· 74 74 import {type NavigationProp} from '#/lib/routes/types' 75 75 import {logEvent} from '#/lib/statsig/statsig' 76 76 import {cleanError} from '#/lib/strings/errors' 77 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 77 78 import {colors} from '#/lib/styles' 78 79 import {logger} from '#/logger' 79 80 import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection' ··· 85 86 pasteImage, 86 87 } from '#/state/gallery' 87 88 import {useModalControls} from '#/state/modals' 88 - import {useRequireAltTextEnabled} from '#/state/preferences' 89 + import {useRequireAltTextEnabled, useTerminologyPreference} from '#/state/preferences' 89 90 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 90 91 import { 91 92 fromPostLanguages, ··· 185 186 const currentDid = currentAccount!.did 186 187 const {closeComposer} = useComposerControls() 187 188 const {_} = useLingui() 189 + const terminologyPreference = useTerminologyPreference() 188 190 const requireAltTextEnabled = useRequireAltTextEnabled() 189 191 const langPrefs = useLanguagePrefs() 190 192 const setLangPrefs = useLanguagePrefsApi() ··· 456 458 replyTo: replyTo?.uri, 457 459 onStateChange: setPublishingStage, 458 460 langs: currentLanguages, 461 + terminologyPreference, 459 462 }) 460 463 ).uris[0] 461 464 ··· 509 512 510 513 let err = cleanError(e.message) 511 514 if (err.includes('not locate record')) { 512 - err = _( 513 - msg`We're sorry! The skeet you are replying to has been deleted.`, 514 - ) 515 + err = _(getTerminology(terminologyPreference, TERMINOLOGY.replyWasDeleted)) 515 516 } else if (e instanceof EmbeddingDisabledError) { 516 - err = _(msg`This skeet's author has disabled quote skeets.`) 517 + err = _(getTerminology(terminologyPreference, TERMINOLOGY.quoteAuthorDisabled)) 517 518 } 518 519 setError(err) 519 520 setIsPublishing(false) ··· 573 574 <Toast.Icon /> 574 575 <Toast.Text> 575 576 {thread.posts.length > 1 576 - ? _(msg`Your skeets were sent`) 577 + ? _(getTerminology(terminologyPreference, TERMINOLOGY.sentPlural)) 577 578 : replyTo 578 579 ? _(msg`Your reply was sent`) 579 - : _(msg`Your skeet was sent`)} 580 + : _(getTerminology(terminologyPreference, TERMINOLOGY.sent))} 580 581 </Toast.Text> 581 582 {postUri && ( 582 583 <Toast.Action 583 - label={_(msg`View skeet`)} 584 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.view))} 584 585 onPress={() => { 585 586 const {host: name, rkey} = new AtUri(postUri) 586 587 navigation.navigate('PostThread', {name, rkey}) ··· 857 858 const selectTextInputPlaceholder = isReply 858 859 ? isFirstPost 859 860 ? _(msg`Write your reply`) 860 - : _(msg`Add another skeet`) 861 - : _(msg`Anything but skeet`) 861 + : _(getTerminology(terminologyPreference, TERMINOLOGY.addAnother)) 862 + : _(getTerminology(terminologyPreference, TERMINOLOGY.anythingBut)) 862 863 const discardPromptControl = Prompt.usePromptControl() 863 864 864 865 const enableSquareButtons = useEnableSquareButtons() ··· 972 973 {canRemovePost && isActive && ( 973 974 <> 974 975 <Button 975 - label={_(msg`Delete skeet`)} 976 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.delete))} 976 977 size="small" 977 978 color="secondary" 978 979 variant="ghost" ··· 997 998 </Button> 998 999 <Prompt.Basic 999 1000 control={discardPromptControl} 1000 - title={_(msg`Discard skeet?`)} 1001 - description={_(msg`Are you sure you'd like to discard this skeet?`)} 1001 + title={_(getTerminology(terminologyPreference, TERMINOLOGY.discard))} 1002 + description={_(getTerminology(terminologyPreference, TERMINOLOGY.discardConfirm))} 1002 1003 onConfirm={() => { 1003 1004 dispatch({ 1004 1005 type: 'remove_post', ··· 1133 1134 {isReply ? ( 1134 1135 <Trans context="action">Reply</Trans> 1135 1136 ) : isThread ? ( 1136 - <Trans context="action">Skeet All</Trans> 1137 + <Trans context="action">{_(getTerminology(terminologyPreference, TERMINOLOGY.postAll))}</Trans> 1137 1138 ) : ( 1138 - <Trans context="action">Skeet</Trans> 1139 + <Trans context="action">{_(getTerminology(terminologyPreference, TERMINOLOGY.postSingle))}</Trans> 1139 1140 )} 1140 1141 </ButtonText> 1141 1142 </Button>
+6 -7
src/view/com/composer/select-language/PostLanguageSelect.tsx
··· 3 3 4 4 import {LANG_DROPDOWN_HITSLOP} from '#/lib/constants' 5 5 import {codeToLanguageName} from '#/locale/helpers' 6 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 7 + import {useTerminologyPreference} from '#/state/preferences' 6 8 import { 7 9 toPostLanguages, 8 10 useLanguagePrefs, ··· 25 27 onSelectLanguage?: (language: string) => void 26 28 }) { 27 29 const {_} = useLingui() 30 + const terminologyPreference = useTerminologyPreference() 28 31 const langPrefs = useLanguagePrefs() 29 32 const setLangPrefs = useLanguagePrefsApi() 30 33 const languageDialogControl = Dialog.useDialogControl() ··· 54 57 return ( 55 58 <> 56 59 <Menu.Root> 57 - <Menu.Trigger label={_(msg`Select skeet language`)}> 60 + <Menu.Trigger label={_(getTerminology(terminologyPreference, TERMINOLOGY.selectLanguage))}> 58 61 {({props}) => ( 59 62 <LanguageBtn currentLanguages={currentLanguages} {...props} /> 60 63 )} ··· 109 112 }, 110 113 ) { 111 114 const {_} = useLingui() 115 + const terminologyPreference = useTerminologyPreference() 112 116 const langPrefs = useLanguagePrefs() 113 117 const t = useTheme() 114 118 ··· 120 124 testID="selectLangBtn" 121 125 size="small" 122 126 hitSlop={LANG_DROPDOWN_HITSLOP} 123 - label={_( 124 - msg({ 125 - message: `Skeet language selection`, 126 - comment: `Accessibility label for button that opens dialog to choose post language settings`, 127 - }), 128 - )} 127 + label={_(getTerminology(terminologyPreference, TERMINOLOGY.selectLanguage))} 129 128 accessibilityHint={_(msg`Opens post language settings`)} 130 129 style={[a.mr_xs]} 131 130 {...props}>
+5 -2
src/view/com/composer/select-language/PostLanguageSelectDialog.tsx
··· 7 7 import {languageName} from '#/locale/helpers' 8 8 import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages' 9 9 import {isNative, isWeb} from '#/platform/detection' 10 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 11 + import {useTerminologyPreference} from '#/state/preferences' 10 12 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 11 13 import { 12 14 toPostLanguages, ··· 93 95 const setLangPrefs = useLanguagePrefsApi() 94 96 const t = useTheme() 95 97 const {_} = useLingui() 98 + const terminologyPreference = useTerminologyPreference() 96 99 97 100 const enableSquareButtons = useEnableSquareButtons() 98 101 ··· 184 187 a.text_xl, 185 188 a.mb_sm, 186 189 ]}> 187 - <Trans>Choose Skeet Languages</Trans> 190 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.chooseLanguages))}</Trans> 188 191 </Text> 189 192 <Text 190 193 nativeID="dialog-description" ··· 194 197 a.text_md, 195 198 a.mb_lg, 196 199 ]}> 197 - <Trans>Select up to 3 languages used in this skeet</Trans> 200 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.languageDescription))}</Trans> 198 201 </Text> 199 202 </View> 200 203
+13 -4
src/view/com/post-thread/PostRepostedBy.tsx
··· 5 5 6 6 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 7 7 import {cleanError} from '#/lib/strings/errors' 8 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 9 + import {useTerminologyPreference} from '#/state/preferences' 8 10 import {logger} from '#/logger' 9 11 import {usePostRepostedByQuery} from '#/state/queries/post-reposted-by' 10 12 import {useResolveUriQuery} from '#/state/queries/resolve-uri' ··· 34 36 35 37 export function PostRepostedBy({uri}: {uri: string}) { 36 38 const {_} = useLingui() 39 + const terminologyPreference = useTerminologyPreference() 37 40 const initialNumToRender = useInitialNumToRender() 38 41 39 42 const [isPTRing, setIsPTRing] = useState(false) ··· 87 90 isLoading={isLoadingUri || isLoadingRepostedBy} 88 91 isError={isError} 89 92 emptyType="results" 90 - emptyTitle={_(msg`No reskeets yet`)} 91 - emptyMessage={_( 92 - msg`Nobody has reskeeted this yet. Maybe you should be the first!`, 93 - )} 93 + emptyTitle={_(getTerminology(terminologyPreference, { 94 + skeet: msg`No reskeets yet`, 95 + post: msg`No reposts yet`, 96 + spell: msg`No respells yet`, 97 + }))} 98 + emptyMessage={_(getTerminology(terminologyPreference, { 99 + skeet: msg`Nobody has reskeeted this yet. Maybe you should be the first!`, 100 + post: msg`Nobody has reposted this yet. Maybe you should be the first!`, 101 + spell: msg`Nobody has respelled this yet. Maybe you should be the first!`, 102 + }))} 94 103 errorMessage={cleanError(resolveError || error)} 95 104 sideBorders={false} 96 105 />
+8 -3
src/view/com/posts/PostFeedReason.tsx
··· 6 6 import {isReasonFeedSource, type ReasonFeedSource} from '#/lib/api/feed/types' 7 7 import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' 8 8 import {makeProfileLink} from '#/lib/routes/links' 9 + import {getTerminology, TERMINOLOGY} from '#/lib/strings/terminology' 10 + import {useTerminologyPreference} from '#/state/preferences' 9 11 import {useSession} from '#/state/session' 10 12 import {atoms as a, useTheme} from '#/alf' 11 13 import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' ··· 30 32 }) { 31 33 const t = useTheme() 32 34 const {_} = useLingui() 35 + const terminologyPreference = useTerminologyPreference() 33 36 34 37 const {currentAccount} = useSession() 35 38 ··· 74 77 style={styles.includeReason} 75 78 to={makeProfileLink(reason.by)} 76 79 label={ 77 - isOwner ? _(msg`Reskeeted by you`) : _(msg`Reskeeted by ${reposter}`) 80 + isOwner 81 + ? _(getTerminology(terminologyPreference, TERMINOLOGY.repost.byLine)) 82 + : `${_(getTerminology(terminologyPreference, TERMINOLOGY.repost.byLine))} ${reposter}` 78 83 } 79 84 onPress={onOpenReposter}> 80 85 <RepostIcon ··· 91 96 ]} 92 97 numberOfLines={1}> 93 98 {isOwner ? ( 94 - <Trans>Reskeeted by you</Trans> 99 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.repost.byLine))}</Trans> 95 100 ) : ( 96 - <Trans>Reskeeted by {reposter}</Trans> 101 + <Trans>{_(getTerminology(terminologyPreference, TERMINOLOGY.repost.byLine))} {reposter}</Trans> 97 102 )} 98 103 </Text> 99 104 </ProfileHoverCard>
+13 -2
src/view/shell/desktop/LeftNav.tsx
··· 19 19 import {useGate} from '#/lib/statsig/statsig' 20 20 import {sanitizeDisplayName} from '#/lib/strings/display-names' 21 21 import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles' 22 + import {getTerminology} from '#/lib/strings/terminology' 22 23 import {emitSoftReset} from '#/state/events' 23 24 import {useHomeBadge} from '#/state/home-badge' 24 25 import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 26 + import {useTerminologyPreference} from '#/state/preferences' 25 27 import {useFetchHandle} from '#/state/queries/handle' 26 28 import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations' 27 29 import {useUnreadNotifications} from '#/state/queries/notifications/unread' ··· 530 532 const {getState} = useNavigation() 531 533 const {openComposer} = useOpenComposer() 532 534 const {_} = useLingui() 535 + const terminologyPreference = useTerminologyPreference() 533 536 const {leftNavMinimal} = useLayoutBreakpoints() 534 537 const [isFetchingHandle, setIsFetchingHandle] = useState(false) 535 538 const fetchHandle = useFetchHandle() ··· 580 583 <View style={[a.flex_row, a.pl_md, a.pt_xl]}> 581 584 <Button 582 585 disabled={isFetchingHandle} 583 - label={_(msg`Compose new post`)} 586 + label={_(getTerminology(terminologyPreference, { 587 + skeet: msg`Compose new skeet`, 588 + post: msg`Compose new post`, 589 + spell: msg`Compose new spell`, 590 + }))} 584 591 onPress={onPressCompose} 585 592 size="large" 586 593 variant="solid" ··· 588 595 style={enableSquareButtons ? [a.rounded_sm] : [a.rounded_full]}> 589 596 <ButtonIcon icon={EditBig} position="left" /> 590 597 <ButtonText> 591 - <Trans context="action">New Skeet</Trans> 598 + <Trans>{_(getTerminology(terminologyPreference, { 599 + skeet: msg({message: `New Skeet`, context: 'action'}), 600 + post: msg({message: `New Post`, context: 'action'}), 601 + spell: msg({message: `New Spell`, context: 'action'}), 602 + }))}</Trans> 592 603 </ButtonText> 593 604 </Button> 594 605 </View>