From 0517d2fae2d0944ce229b7a2ef43302bf232e020 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Wed, 19 May 2021 23:28:01 +0200 Subject: [PATCH] Local translation works --- .envrc.example | 2 + app.config.ts | 3 +- src/@types/untyped.d.ts | 1 + src/components/Menu/Row.tsx | 6 +- src/components/Parse/Emojis.tsx | 2 - src/components/Parse/HTML.tsx | 4 +- src/components/Timeline/Default.tsx | 7 +- src/components/Timeline/Shared/Actions.tsx | 2 +- .../Timeline/Shared/ActionsUsers.tsx | 9 +- src/components/Timeline/Shared/Content.tsx | 2 + .../Timeline/Shared/FullConversation.tsx | 2 +- src/components/Timeline/Shared/Translate.tsx | 128 +++++++++++++++--- src/i18n/en/components/timeline.json | 6 +- src/utils/queryHooks/translate.ts | 26 +++- tsconfig.json | 1 + 15 files changed, 159 insertions(+), 42 deletions(-) diff --git a/.envrc.example b/.envrc.example index 0cf681c7..70045951 100644 --- a/.envrc.example +++ b/.envrc.example @@ -5,6 +5,8 @@ export SENTRY_PROJECT="" export SENTRY_AUTH_TOKEN="" export SENTRY_DSN="" +export TRANSLATE_KEY="" + # Fastlane start export LC_ALL="" export LANG="" diff --git a/app.config.ts b/app.config.ts index 692a247b..7882a256 100644 --- a/app.config.ts +++ b/app.config.ts @@ -13,7 +13,8 @@ export default (): ExpoConfig => ({ privacy: 'hidden', assetBundlePatterns: ['assets/*'], extra: { - sentryDSN: process.env.SENTRY_DSN + sentryDSN: process.env.SENTRY_DSN, + translateKey: process.env.TRANSLATE_KEY }, hooks: { postPublish: [ diff --git a/src/@types/untyped.d.ts b/src/@types/untyped.d.ts index 24592684..9220fed3 100644 --- a/src/@types/untyped.d.ts +++ b/src/@types/untyped.d.ts @@ -1,4 +1,5 @@ declare module 'gl-react-blurhash' +declare module 'htmlparser2-without-node-native' declare module 'li' declare module 'react-native-feather' declare module 'react-native-htmlview' diff --git a/src/components/Menu/Row.tsx b/src/components/Menu/Row.tsx index ffb62118..ed8a4d35 100644 --- a/src/components/Menu/Row.tsx +++ b/src/components/Menu/Row.tsx @@ -162,12 +162,12 @@ const MenuRow: React.FC = ({ const styles = StyleSheet.create({ base: { - minHeight: 46, - paddingVertical: StyleConstants.Spacing.S + minHeight: 50 }, core: { flex: 1, - flexDirection: 'row' + flexDirection: 'row', + paddingVertical: StyleConstants.Spacing.S }, front: { flex: 2, diff --git a/src/components/Parse/Emojis.tsx b/src/components/Parse/Emojis.tsx index 34c1066f..d98f4ba1 100644 --- a/src/components/Parse/Emojis.tsx +++ b/src/components/Parse/Emojis.tsx @@ -4,7 +4,6 @@ import { StyleConstants } from '@utils/styles/constants' import { adaptiveScale } from '@utils/styles/scaling' import { useTheme } from '@utils/styles/ThemeManager' import React, { useMemo } from 'react' -import { useTranslation } from 'react-i18next' import { StyleSheet, Text } from 'react-native' import FastImage from 'react-native-fast-image' import { useSelector } from 'react-redux' @@ -28,7 +27,6 @@ const ParseEmojis = React.memo( adaptiveSize = false, fontBold = false }: Props) => { - const { t } = useTranslation('componentParse') const { reduceMotionEnabled } = useAccessibility() const adaptiveFontsize = useSelector(getSettingsFontsize) diff --git a/src/components/Parse/HTML.tsx b/src/components/Parse/HTML.tsx index 4d0589be..dc42c6d7 100644 --- a/src/components/Parse/HTML.tsx +++ b/src/components/Parse/HTML.tsx @@ -162,6 +162,7 @@ export interface Props { showFullLink?: boolean numberOfLines?: number expandHint?: string + highlighted?: boolean disableDetails?: boolean } @@ -176,6 +177,7 @@ const ParseHTML = React.memo( showFullLink = false, numberOfLines = 10, expandHint, + highlighted = false, disableDetails = false }: Props) => { const adaptiveFontsize = useSelector(getSettingsFontsize) @@ -234,7 +236,7 @@ const ParseHTML = React.memo( const { t } = useTranslation('componentParse') const [expandAllow, setExpandAllow] = useState(false) - const [expanded, setExpanded] = useState(false) + const [expanded, setExpanded] = useState(highlighted) const onTextLayout = useCallback(({ nativeEvent }) => { if ( diff --git a/src/components/Timeline/Default.tsx b/src/components/Timeline/Default.tsx index 3cfe4451..89eb759d 100644 --- a/src/components/Timeline/Default.tsx +++ b/src/components/Timeline/Default.tsx @@ -129,12 +129,13 @@ const TimelineDefault: React.FC = ({ {!disableDetails && actualStatus.card && ( )} - + {!disableDetails ? ( + + ) : null} + - - {queryKey && !disableDetails && ( { analytics('timeline_shared_actionsusers_press_boosted', { count: status.reblogs_count @@ -68,7 +68,7 @@ const TimelineActionsUsers = React.memo( 'shared.actionsUsers.favourited_by.accessibilityHint' )} accessibilityRole='button' - style={[styles.text, { color: theme.secondary }]} + style={[styles.text, { color: theme.blue }]} onPress={() => { analytics('timeline_shared_actionsusers_press_boosted', { count: status.favourites_count @@ -98,10 +98,9 @@ const styles = StyleSheet.create({ base: { flexDirection: 'row' }, - pressable: { margin: StyleConstants.Spacing.M }, text: { - ...StyleConstants.FontStyle.S, - padding: StyleConstants.Spacing.S * 1.5, + ...StyleConstants.FontStyle.M, + padding: StyleConstants.Spacing.S, paddingLeft: 0, marginRight: StyleConstants.Spacing.S } diff --git a/src/components/Timeline/Shared/Content.tsx b/src/components/Timeline/Shared/Content.tsx index c23bdeba..d76b546b 100644 --- a/src/components/Timeline/Shared/Content.tsx +++ b/src/components/Timeline/Shared/Content.tsx @@ -30,6 +30,7 @@ const TimelineContent = React.memo( mentions={status.mentions} tags={status.tags} numberOfLines={999} + highlighted={highlighted} disableDetails={disableDetails} /> diff --git a/src/components/Timeline/Shared/FullConversation.tsx b/src/components/Timeline/Shared/FullConversation.tsx index 79b65c15..7cf2208e 100644 --- a/src/components/Timeline/Shared/FullConversation.tsx +++ b/src/components/Timeline/Shared/FullConversation.tsx @@ -26,7 +26,7 @@ const TimelineFullConversation = React.memo( style={{ ...StyleConstants.FontStyle.S, color: theme.blue, - marginTop: StyleConstants.Font.Size.S + marginTop: StyleConstants.Spacing.S }} > {t('shared.fullConversation')} diff --git a/src/components/Timeline/Shared/Translate.tsx b/src/components/Timeline/Shared/Translate.tsx index cca8b9ae..e7183e88 100644 --- a/src/components/Timeline/Shared/Translate.tsx +++ b/src/components/Timeline/Shared/Translate.tsx @@ -1,13 +1,32 @@ -import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { useTranslateQuery } from '@utils/queryHooks/translate' import { getSettingsLanguage } from '@utils/slices/settingsSlice' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' -import React from 'react' +import htmlparser2 from 'htmlparser2-without-node-native' +import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import { Text } from 'react-native' +import { Pressable, StyleSheet, Text } from 'react-native' +import { Circle } from 'react-native-animated-spinkit' import { useSelector } from 'react-redux' +const availableLanguages = [ + 'en', + 'ar', + 'zh', + 'fr', + 'de', + 'hi', + 'ga', + 'it', + 'ja', + 'ko', + 'pl', + 'pt', + 'ru', + 'es', + 'tr' +] + export interface Props { highlighted: boolean status: Mastodon.Status @@ -22,38 +41,113 @@ const TimelineTranslate = React.memo( return null } - const settingsLanguage = useSelector(getSettingsLanguage, () => true) + const { t } = useTranslation('componentTimeline') + const { theme } = useTheme() + const tootLanguage = status.language.slice(0, 2) + if (!availableLanguages.includes(tootLanguage)) { + return ( + + {t('shared.translate.unavailable')} + + ) + } + + const settingsLanguage = useSelector(getSettingsLanguage, () => true) if (settingsLanguage.includes(tootLanguage)) { return null } - const { t } = useTranslation('componentTimeline') - const { theme } = useTheme() + let emojisRemoved = status.spoiler_text + ? status.spoiler_text.concat(status.content) + : status.content + if (status.emojis) { + for (const emoji of status.emojis) { + emojisRemoved = emojisRemoved.replaceAll(`:${emoji.shortcode}:`, '') + } + } - const { data } = useTranslateQuery({ - toot: status.content, + let cleaned = '' + const parser = new htmlparser2.Parser({ + ontext (text: string) { + cleaned = cleaned.concat(text) + } + }) + parser.write(emojisRemoved) + parser.end() + + const [enabled, setEnabled] = useState(false) + const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({ + toot: cleaned, source: status.language, - target: settingsLanguage.slice(0, 2) + target: settingsLanguage.slice(0, 2), + options: { enabled } }) return ( <> - { + if (enabled) { + if (!isSuccess) { + refetch() + } + } else { + setEnabled(true) + } }} > - {t('shared.translate')} - - {data ? : null} + + {isError + ? t('shared.translate.error') + : t('shared.translate.default')} + + {isLoading ? ( + + ) : null} + + {data ? ( + + ) : null} ) }, () => true ) +const styles = StyleSheet.create({ + button: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: StyleConstants.Spacing.S + } +}) + export default TimelineTranslate diff --git a/src/i18n/en/components/timeline.json b/src/i18n/en/components/timeline.json index 79d94370..21582d52 100644 --- a/src/i18n/en/components/timeline.json +++ b/src/i18n/en/components/timeline.json @@ -74,7 +74,11 @@ "expandHint": "hidden content" }, "fullConversation": "Read conversations", - "translate": "Translate", + "translate": { + "default": "Translate", + "error": "Try to translate again", + "unavailable": "Language not supported" + }, "header": { "shared": { "account": { diff --git a/src/utils/queryHooks/translate.ts b/src/utils/queryHooks/translate.ts index 3b73b60d..571a19e0 100644 --- a/src/utils/queryHooks/translate.ts +++ b/src/utils/queryHooks/translate.ts @@ -1,30 +1,42 @@ import apiGeneral from '@api/general' -import { useQuery } from 'react-query' +import { AxiosError } from 'axios' +import { Constants } from 'react-native-unimodules' +import { useQuery, UseQueryOptions } from 'react-query' export type QueryKeyTranslate = [ 'Translate', { toot: string; source: string; target: string } ] -const queryFunction = ({ queryKey }: { queryKey: QueryKeyTranslate }) => { +const queryFunction = async ({ queryKey }: { queryKey: QueryKeyTranslate }) => { const { toot, source, target } = queryKey[1] - return apiGeneral({ + const res = await apiGeneral({ domain: 'translate.tooot.app', method: 'post', url: 'translate', params: { - api_key: '65180371-1ddb-4ec0-9aa3-ac47d371c41a', + api_key: Constants.manifest?.extra?.translateKey, q: toot, source, target } - }).then(res => res.body.translatedText) + }) + return res.body.translatedText } -const useTranslateQuery = (queryKeyParams: QueryKeyTranslate[1]) => { +const useTranslateQuery = ({ + options, + ...queryKeyParams +}: QueryKeyTranslate[1] & { + options?: UseQueryOptions< + Translate.Translate['translatedText'], + AxiosError, + Translate.Translate['translatedText'] + > +}) => { const queryKey: QueryKeyTranslate = ['Translate', { ...queryKeyParams }] - return useQuery(queryKey, queryFunction) + return useQuery(queryKey, queryFunction, options) } export { useTranslateQuery } diff --git a/tsconfig.json b/tsconfig.json index 1cb03104..86fced90 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "skipLibCheck": true, "resolveJsonModule": true, "strict": true, + "strictFunctionTypes": false, "baseUrl": "./", "paths": { "@api/*": ["./src/api/*"],