Log languages a post is tagged with after translating (#10014)
Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
+1
-1
@@ -85,7 +85,7 @@
|
|||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@bsky.app/alf": "^0.1.7",
|
"@bsky.app/alf": "^0.1.7",
|
||||||
"@bsky.app/expo-image-crop-tool": "^0.5.0",
|
"@bsky.app/expo-image-crop-tool": "^0.5.0",
|
||||||
"@bsky.app/expo-translate-text": "^0.2.7",
|
"@bsky.app/expo-translate-text": "^0.2.9",
|
||||||
"@bsky.app/react-native-mmkv": "2.12.5",
|
"@bsky.app/react-native-mmkv": "2.12.5",
|
||||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||||
"@emoji-mart/data": "^1.2.1",
|
"@emoji-mart/data": "^1.2.1",
|
||||||
|
|||||||
@@ -707,15 +707,20 @@ export type Events = {
|
|||||||
'reportDialog:failure': {}
|
'reportDialog:failure': {}
|
||||||
|
|
||||||
translate: {
|
translate: {
|
||||||
|
os: Platform['OS']
|
||||||
sourceLanguages: string[]
|
sourceLanguages: string[]
|
||||||
targetLanguage: string
|
targetLanguage: string
|
||||||
textLength: number
|
textLength: number
|
||||||
}
|
}
|
||||||
'translate:result': {
|
'translate:result': {
|
||||||
method: 'on-device' | 'google-translate' | 'fallback-alert'
|
method: 'on-device' | 'fallback-alert'
|
||||||
os: Platform['OS']
|
os: Platform['OS']
|
||||||
|
sourceSelection: 'automatic' | 'manual'
|
||||||
sourceLanguage: string | null
|
sourceLanguage: string | null
|
||||||
targetLanguage: string
|
targetLanguage: string
|
||||||
|
|
||||||
|
/* Only relevant to posts */
|
||||||
|
postLanguages?: string[]
|
||||||
}
|
}
|
||||||
'translate:override': {
|
'translate:override': {
|
||||||
os: Platform['OS']
|
os: Platform['OS']
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {useTranslate} from '#/lib/translation'
|
|||||||
import {type TranslationFunction} from '#/lib/translation'
|
import {type TranslationFunction} from '#/lib/translation'
|
||||||
import {
|
import {
|
||||||
codeToLanguageName,
|
codeToLanguageName,
|
||||||
|
getPostLanguageTags,
|
||||||
isPostInLanguage,
|
isPostInLanguage,
|
||||||
languageName,
|
languageName,
|
||||||
} from '#/locale/helpers'
|
} from '#/locale/helpers'
|
||||||
@@ -42,6 +43,7 @@ export function TranslatedPost({
|
|||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
const {clearTranslation, translate, translationState} = useTranslate({
|
const {clearTranslation, translate, translationState} = useTranslate({
|
||||||
key: post.uri,
|
key: post.uri,
|
||||||
|
postLangCodes: getPostLanguageTags(post),
|
||||||
})
|
})
|
||||||
|
|
||||||
const needsTranslation = useMemo(() => {
|
const needsTranslation = useMemo(() => {
|
||||||
@@ -122,6 +124,7 @@ function TranslationLink({
|
|||||||
})
|
})
|
||||||
|
|
||||||
ax.metric('translate', {
|
ax.metric('translate', {
|
||||||
|
os: Platform.OS,
|
||||||
sourceLanguages: [], // todo: get from post maybe?
|
sourceLanguages: [], // todo: get from post maybe?
|
||||||
targetLanguage: primaryLanguage,
|
targetLanguage: primaryLanguage,
|
||||||
textLength: postText.length,
|
textLength: postText.length,
|
||||||
@@ -405,6 +408,7 @@ function TranslationLanguageSelect({
|
|||||||
text: postText,
|
text: postText,
|
||||||
targetLangCode: langPrefs.primaryLanguage,
|
targetLangCode: langPrefs.primaryLanguage,
|
||||||
sourceLangCode,
|
sourceLangCode,
|
||||||
|
sourceSelection: 'manual',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||||
import {toShareUrl} from '#/lib/strings/url-helpers'
|
import {toShareUrl} from '#/lib/strings/url-helpers'
|
||||||
import {useTranslate} from '#/lib/translation'
|
import {useTranslate} from '#/lib/translation'
|
||||||
|
import {getPostLanguageTags} from '#/locale/helpers'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {type Shadow} from '#/state/cache/post-shadow'
|
import {type Shadow} from '#/state/cache/post-shadow'
|
||||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
@@ -137,6 +138,7 @@ let PostMenuItems = ({
|
|||||||
const openLink = useOpenLink()
|
const openLink = useOpenLink()
|
||||||
const {clearTranslation, translate, translationState} = useTranslate({
|
const {clearTranslation, translate, translationState} = useTranslate({
|
||||||
key: post.uri,
|
key: post.uri,
|
||||||
|
postLangCodes: getPostLanguageTags(post),
|
||||||
forceGoogleTranslate,
|
forceGoogleTranslate,
|
||||||
})
|
})
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
@@ -287,6 +289,7 @@ let PostMenuItems = ({
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
ax.metric('translate', {
|
ax.metric('translate', {
|
||||||
|
os: Platform.OS,
|
||||||
sourceLanguages: post.record.langs ?? [],
|
sourceLanguages: post.record.langs ?? [],
|
||||||
targetLanguage: langPrefs.primaryLanguage,
|
targetLanguage: langPrefs.primaryLanguage,
|
||||||
textLength: post.record.text.length,
|
textLength: post.record.text.length,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {memo, useCallback} from 'react'
|
import {memo, useCallback} from 'react'
|
||||||
import {LayoutAnimation} from 'react-native'
|
import {LayoutAnimation, Platform} from 'react-native'
|
||||||
import * as Clipboard from 'expo-clipboard'
|
import * as Clipboard from 'expo-clipboard'
|
||||||
import {type ChatBskyConvoDefs, RichText} from '@atproto/api'
|
import {type ChatBskyConvoDefs, RichText} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
@@ -67,6 +67,7 @@ export let MessageContextMenu = ({
|
|||||||
void translate(message.text, langPrefs.primaryLanguage)
|
void translate(message.text, langPrefs.primaryLanguage)
|
||||||
|
|
||||||
ax.metric('translate', {
|
ax.metric('translate', {
|
||||||
|
os: Platform.OS,
|
||||||
sourceLanguages: [],
|
sourceLanguages: [],
|
||||||
targetLanguage: langPrefs.primaryLanguage,
|
targetLanguage: langPrefs.primaryLanguage,
|
||||||
textLength: message.text.length,
|
textLength: message.text.length,
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
import {createContext} from 'react'
|
import {createContext} from 'react'
|
||||||
|
|
||||||
import {type TranslationFunctionParams, type TranslationState} from './types'
|
import {type ContextType} from './types'
|
||||||
|
|
||||||
export const Context = createContext<{
|
export const Context = createContext<ContextType | null>(null)
|
||||||
translationState: Record<string, TranslationState>
|
|
||||||
translate: (
|
|
||||||
parameters: TranslationFunctionParams & {
|
|
||||||
key: string
|
|
||||||
forceGoogleTranslate: boolean
|
|
||||||
},
|
|
||||||
) => Promise<void>
|
|
||||||
clearTranslation: (key: string) => void
|
|
||||||
acquireTranslation: (key: string) => () => void
|
|
||||||
} | null>(null)
|
|
||||||
Context.displayName = 'TranslationContext'
|
Context.displayName = 'TranslationContext'
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ import {logger} from '#/logger'
|
|||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {HAS_ON_DEVICE_TRANSLATION, IS_ANDROID, IS_IOS} from '#/env'
|
import {HAS_ON_DEVICE_TRANSLATION, IS_ANDROID, IS_IOS} from '#/env'
|
||||||
import {Context} from './context'
|
import {Context} from './context'
|
||||||
import {type TranslationFunctionParams, type TranslationState} from './types'
|
import {
|
||||||
|
type ContextType,
|
||||||
|
type TranslationFunctionParams,
|
||||||
|
type TranslationOptions,
|
||||||
|
type TranslationState,
|
||||||
|
} from './types'
|
||||||
import {guessLanguage} from './utils'
|
import {guessLanguage} from './utils'
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
@@ -98,10 +103,8 @@ async function attemptTranslation(
|
|||||||
export function useTranslate({
|
export function useTranslate({
|
||||||
key,
|
key,
|
||||||
forceGoogleTranslate = false,
|
forceGoogleTranslate = false,
|
||||||
}: {
|
postLangCodes,
|
||||||
key: string
|
}: TranslationOptions) {
|
||||||
forceGoogleTranslate?: boolean
|
|
||||||
}) {
|
|
||||||
const context = useContext(Context)
|
const context = useContext(Context)
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -118,9 +121,14 @@ export function useTranslate({
|
|||||||
|
|
||||||
const translate = useCallback(
|
const translate = useCallback(
|
||||||
async (params: TranslationFunctionParams) => {
|
async (params: TranslationFunctionParams) => {
|
||||||
return context.translate({...params, key, forceGoogleTranslate})
|
return context.translate({
|
||||||
|
...params,
|
||||||
|
key,
|
||||||
|
forceGoogleTranslate,
|
||||||
|
postLangCodes,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[context, forceGoogleTranslate, key],
|
[context, forceGoogleTranslate, key, postLangCodes],
|
||||||
)
|
)
|
||||||
|
|
||||||
const clearTranslation = useCallback(
|
const clearTranslation = useCallback(
|
||||||
@@ -199,27 +207,17 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const translate = useCallback(
|
const translate = useCallback<ContextType['translate']>(
|
||||||
async ({
|
async ({
|
||||||
key,
|
key,
|
||||||
text,
|
text,
|
||||||
targetLangCode,
|
targetLangCode,
|
||||||
sourceLangCode,
|
sourceLangCode,
|
||||||
|
sourceSelection = 'automatic',
|
||||||
|
postLangCodes,
|
||||||
...options
|
...options
|
||||||
}: {
|
|
||||||
key: string
|
|
||||||
text: string
|
|
||||||
targetLangCode: string
|
|
||||||
sourceLangCode?: string
|
|
||||||
forceGoogleTranslate?: boolean
|
|
||||||
}) => {
|
}) => {
|
||||||
if (options?.forceGoogleTranslate || !HAS_ON_DEVICE_TRANSLATION) {
|
if (options?.forceGoogleTranslate || !HAS_ON_DEVICE_TRANSLATION) {
|
||||||
ax.metric('translate:result', {
|
|
||||||
method: 'google-translate',
|
|
||||||
os: Platform.OS,
|
|
||||||
sourceLanguage: sourceLangCode ?? null,
|
|
||||||
targetLanguage: targetLangCode,
|
|
||||||
})
|
|
||||||
await googleTranslate(text, targetLangCode, sourceLangCode)
|
await googleTranslate(text, targetLangCode, sourceLangCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -240,8 +238,10 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
ax.metric('translate:result', {
|
ax.metric('translate:result', {
|
||||||
method: 'on-device',
|
method: 'on-device',
|
||||||
os: Platform.OS,
|
os: Platform.OS,
|
||||||
|
sourceSelection,
|
||||||
sourceLanguage: result.sourceLanguage,
|
sourceLanguage: result.sourceLanguage,
|
||||||
targetLanguage: result.targetLanguage,
|
targetLanguage: result.targetLanguage,
|
||||||
|
postLanguages: postLangCodes,
|
||||||
})
|
})
|
||||||
if (!IS_ANDROID) {
|
if (!IS_ANDROID) {
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||||
@@ -253,17 +253,20 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
translatedText: result.translatedText,
|
translatedText: result.translatedText,
|
||||||
sourceLanguage: result.sourceLanguage,
|
sourceLanguage: result.sourceLanguage,
|
||||||
targetLanguage: result.targetLanguage,
|
targetLanguage: result.targetLanguage,
|
||||||
|
postLanguages: postLangCodes,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Failed to translate post on device', {safeMessage: e})
|
logger.error('Failed to translate text on device', {safeMessage: e})
|
||||||
// On-device translation failed (language pack missing or user
|
// On-device translation failed (language pack missing or user
|
||||||
// dismissed the download prompt). Fall back to Google Translate.
|
// dismissed the download prompt).
|
||||||
ax.metric('translate:result', {
|
ax.metric('translate:result', {
|
||||||
method: 'fallback-alert',
|
method: 'fallback-alert',
|
||||||
os: Platform.OS,
|
os: Platform.OS,
|
||||||
|
sourceSelection,
|
||||||
sourceLanguage: sourceLangCode ?? null,
|
sourceLanguage: sourceLangCode ?? null,
|
||||||
targetLanguage: targetLangCode,
|
targetLanguage: targetLangCode,
|
||||||
|
postLanguages: postLangCodes,
|
||||||
})
|
})
|
||||||
let errorMessage = l`Device failed to translate :(`
|
let errorMessage = l`Device failed to translate :(`
|
||||||
if (!IS_ANDROID) {
|
if (!IS_ANDROID) {
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import {useCallback, useContext, useMemo} from 'react'
|
|||||||
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
|
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {Context} from './context'
|
import {Context} from './context'
|
||||||
import {type TranslationFunctionParams, type TranslationState} from './types'
|
import {
|
||||||
|
type ContextType,
|
||||||
|
type TranslationFunctionParams,
|
||||||
|
type TranslationOptions,
|
||||||
|
type TranslationState,
|
||||||
|
} from './types'
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
@@ -17,12 +22,7 @@ const clearTranslation = (_key: string) => {}
|
|||||||
/**
|
/**
|
||||||
* Web always opens Google Translate.
|
* Web always opens Google Translate.
|
||||||
*/
|
*/
|
||||||
export function useTranslate({
|
export function useTranslate({key, postLangCodes}: TranslationOptions) {
|
||||||
key,
|
|
||||||
}: {
|
|
||||||
key: string
|
|
||||||
forceGoogleTranslate?: boolean
|
|
||||||
}) {
|
|
||||||
const context = useContext(Context)
|
const context = useContext(Context)
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -33,9 +33,14 @@ export function useTranslate({
|
|||||||
// Always call hooks in consistent order
|
// Always call hooks in consistent order
|
||||||
const translate = useCallback(
|
const translate = useCallback(
|
||||||
async (params: TranslationFunctionParams) => {
|
async (params: TranslationFunctionParams) => {
|
||||||
return context.translate({...params, key, forceGoogleTranslate: true})
|
return context.translate({
|
||||||
|
...params,
|
||||||
|
key,
|
||||||
|
forceGoogleTranslate: true,
|
||||||
|
postLangCodes,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[key, context],
|
[key, context, postLangCodes],
|
||||||
)
|
)
|
||||||
|
|
||||||
const clearTranslation = useCallback(() => {
|
const clearTranslation = useCallback(() => {
|
||||||
@@ -55,23 +60,8 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const googleTranslate = useGoogleTranslate()
|
const googleTranslate = useGoogleTranslate()
|
||||||
|
|
||||||
const translate = useCallback(
|
const translate = useCallback<ContextType['translate']>(
|
||||||
async ({
|
async ({text, targetLangCode, sourceLangCode}) => {
|
||||||
text,
|
|
||||||
targetLangCode,
|
|
||||||
sourceLangCode,
|
|
||||||
}: {
|
|
||||||
key: string
|
|
||||||
text: string
|
|
||||||
targetLangCode: string
|
|
||||||
sourceLangCode?: string
|
|
||||||
}) => {
|
|
||||||
ax.metric('translate:result', {
|
|
||||||
method: 'google-translate',
|
|
||||||
os: 'web',
|
|
||||||
sourceLanguage: sourceLangCode ?? null,
|
|
||||||
targetLanguage: targetLangCode,
|
|
||||||
})
|
|
||||||
await googleTranslate(text, targetLangCode, sourceLangCode)
|
await googleTranslate(text, targetLangCode, sourceLangCode)
|
||||||
},
|
},
|
||||||
[ax, googleTranslate],
|
[ax, googleTranslate],
|
||||||
|
|||||||
@@ -27,8 +27,34 @@ export type TranslationFunctionParams = {
|
|||||||
* The source language of the text. Will auto-detect if not provided.
|
* The source language of the text. Will auto-detect if not provided.
|
||||||
*/
|
*/
|
||||||
sourceLangCode?: string
|
sourceLangCode?: string
|
||||||
|
/**
|
||||||
|
* Whether we auto-detected the language or it was selected manually. Defaults to 'automatic'.
|
||||||
|
*/
|
||||||
|
sourceSelection?: 'automatic' | 'manual'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TranslationOptions = {
|
||||||
|
key: string
|
||||||
|
forceGoogleTranslate?: boolean
|
||||||
|
/**
|
||||||
|
* The language(s) of the post being translated. Used for analytics purposes
|
||||||
|
* to understand translation usage patterns better. Optional because it may
|
||||||
|
* not always be available (e.g. if the post text is empty or if the
|
||||||
|
* translation is triggered from a non-post
|
||||||
|
* context).
|
||||||
|
*/
|
||||||
|
postLangCodes?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TranslationFunction = (
|
export type TranslationFunction = (
|
||||||
parameters: TranslationFunctionParams,
|
parameters: TranslationFunctionParams,
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
|
|
||||||
|
export type ContextType = {
|
||||||
|
translationState: Record<string, TranslationState>
|
||||||
|
translate: (
|
||||||
|
parameters: TranslationFunctionParams & TranslationOptions,
|
||||||
|
) => Promise<void>
|
||||||
|
clearTranslation: (key: string) => void
|
||||||
|
acquireTranslation: (key: string) => () => void
|
||||||
|
}
|
||||||
|
|||||||
+13
-14
@@ -61,6 +61,14 @@ function getLocalizedLanguage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPostLanguageTags(post: AppBskyFeedDefs.PostView) {
|
||||||
|
return AppBskyFeedPost.isRecord(post.record) &&
|
||||||
|
hasProp(post.record, 'langs') &&
|
||||||
|
Array.isArray(post.record.langs)
|
||||||
|
? post.record.langs
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
|
||||||
export function languageName(language: Language, appLang: string): string {
|
export function languageName(language: Language, appLang: string): string {
|
||||||
// if Intl.DisplayNames is unavailable on the target, display the English name
|
// if Intl.DisplayNames is unavailable on the target, display the English name
|
||||||
if (!Intl.DisplayNames) {
|
if (!Intl.DisplayNames) {
|
||||||
@@ -80,22 +88,14 @@ export function codeToLanguageName(lang2or3: string, appLang: string): string {
|
|||||||
export function getPostLanguage(
|
export function getPostLanguage(
|
||||||
post: AppBskyFeedDefs.PostView,
|
post: AppBskyFeedDefs.PostView,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
let candidates: string[] = []
|
let candidates: string[] = getPostLanguageTags(post)
|
||||||
let postText: string = ''
|
let postText: string = ''
|
||||||
if (hasProp(post.record, 'text') && typeof post.record.text === 'string') {
|
if (hasProp(post.record, 'text') && typeof post.record.text === 'string') {
|
||||||
postText = post.record.text
|
postText = post.record.text
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
AppBskyFeedPost.isRecord(post.record) &&
|
|
||||||
hasProp(post.record, 'langs') &&
|
|
||||||
Array.isArray(post.record.langs)
|
|
||||||
) {
|
|
||||||
candidates = post.record.langs
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there's only one declared language, use that
|
// if there's only one declared language, use that
|
||||||
if (candidates?.length === 1) {
|
if (candidates.length === 1) {
|
||||||
return candidates[0]
|
return candidates[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,11 +108,10 @@ export function getPostLanguage(
|
|||||||
let langsProbabilityMap = lande(postText)
|
let langsProbabilityMap = lande(postText)
|
||||||
|
|
||||||
// filter down using declared languages
|
// filter down using declared languages
|
||||||
if (candidates?.length) {
|
if (candidates.length) {
|
||||||
langsProbabilityMap = langsProbabilityMap.filter(
|
langsProbabilityMap = langsProbabilityMap.filter(
|
||||||
([lang, _probability]: [string, number]) => {
|
([lang, _probability]: [string, number]) =>
|
||||||
return candidates.includes(code3ToCode2(lang))
|
candidates.includes(code3ToCode2(lang)),
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2403,10 +2403,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@bsky.app/expo-image-crop-tool/-/expo-image-crop-tool-0.5.0.tgz#4308fbde5c15e6be9122601797bc3d9549c95e31"
|
resolved "https://registry.yarnpkg.com/@bsky.app/expo-image-crop-tool/-/expo-image-crop-tool-0.5.0.tgz#4308fbde5c15e6be9122601797bc3d9549c95e31"
|
||||||
integrity sha512-gmhQr2HWTRFyPO00fn5OmtiEVtikXusHMrN5Zoq26pu1VZX3zVE+aoc668etTqrvsQcm2Qu8fo96k5F3Wu+6wg==
|
integrity sha512-gmhQr2HWTRFyPO00fn5OmtiEVtikXusHMrN5Zoq26pu1VZX3zVE+aoc668etTqrvsQcm2Qu8fo96k5F3Wu+6wg==
|
||||||
|
|
||||||
"@bsky.app/expo-translate-text@^0.2.7":
|
"@bsky.app/expo-translate-text@^0.2.9":
|
||||||
version "0.2.7"
|
version "0.2.9"
|
||||||
resolved "https://registry.yarnpkg.com/@bsky.app/expo-translate-text/-/expo-translate-text-0.2.7.tgz#e34811d0f0300f8808762e5676aa50790ca5d5e8"
|
resolved "https://registry.yarnpkg.com/@bsky.app/expo-translate-text/-/expo-translate-text-0.2.9.tgz#4ed4552cd50bca7d02d14e706e419bd728d4ab51"
|
||||||
integrity sha512-J9zctP9hLxX0eustTKk5CBnCkk6cEdlu1s7GzUnpT65qkCSNbYqbbUCpcU2Z2S2dN/1+w6L/iHb+vmCEbZMOaQ==
|
integrity sha512-VmqMhc/YavjgkGhxT/fB8mGSi+VZHJET1tsbpTg8peqKRXFSju2F294NsRxH/4aaMQFlt5oRfPCRnLm1H5o3lA==
|
||||||
|
|
||||||
"@bsky.app/react-native-mmkv@2.12.5":
|
"@bsky.app/react-native-mmkv@2.12.5":
|
||||||
version "2.12.5"
|
version "2.12.5"
|
||||||
|
|||||||
Reference in New Issue
Block a user