auto-detect composer language from keyboard
adds a new expo-keyboard-language native module that reads the current keyboard language on iOS (via first responder's textInputMode) and Android (via InputMethodManager). iOS also gets real-time updates when the keyboard language changes. users opt in via Settings > Languages > "Automatically detect post language from keyboard" (native only, marked experimental). when enabled, the composer language tracks the keyboard language. users can override per-session by selecting a language manually, and re-enable auto mode via the "Automatic" item in the language menu. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
group = 'expo.modules.keyboardlanguage'
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||||
|
apply from: expoModulesCorePlugin
|
||||||
|
applyKotlinExpoModulesCorePlugin()
|
||||||
|
useCoreDependencies()
|
||||||
|
useExpoPublishing()
|
||||||
|
|
||||||
|
def useManagedAndroidSdkVersions = false
|
||||||
|
if (useManagedAndroidSdkVersions) {
|
||||||
|
useDefaultAndroidSdkVersions()
|
||||||
|
} else {
|
||||||
|
buildscript {
|
||||||
|
ext.safeExtGet = { prop, fallback ->
|
||||||
|
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project.android {
|
||||||
|
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion safeExtGet("minSdkVersion", 21)
|
||||||
|
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "expo.modules.keyboardlanguage"
|
||||||
|
defaultConfig {
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0.0"
|
||||||
|
}
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package expo.modules.keyboardlanguage
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import expo.modules.kotlin.modules.Module
|
||||||
|
import expo.modules.kotlin.modules.ModuleDefinition
|
||||||
|
|
||||||
|
class ExpoKeyboardLanguageModule : Module() {
|
||||||
|
override fun definition() =
|
||||||
|
ModuleDefinition {
|
||||||
|
Name("ExpoKeyboardLanguage")
|
||||||
|
|
||||||
|
Function("getCurrentKeyboardLanguage") {
|
||||||
|
val context = appContext.reactContext ?: return@Function null
|
||||||
|
val imm = context.getSystemService<InputMethodManager>() ?: return@Function null
|
||||||
|
val subtype = imm.currentInputMethodSubtype ?: return@Function null
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
val tag = subtype.languageTag
|
||||||
|
if (tag.isNotEmpty()) return@Function tag
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val locale = subtype.locale
|
||||||
|
if (locale.isNotEmpty()) return@Function locale
|
||||||
|
|
||||||
|
return@Function null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"platforms": ["ios", "android"],
|
||||||
|
"ios": {
|
||||||
|
"modules": ["ExpoKeyboardLanguageModule"]
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"modules": ["expo.modules.keyboardlanguage.ExpoKeyboardLanguageModule"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export {
|
||||||
|
addKeyboardLanguageListener,
|
||||||
|
getCurrentKeyboardLanguage,
|
||||||
|
} from './src/index'
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
Pod::Spec.new do |s|
|
||||||
|
s.name = 'ExpoKeyboardLanguage'
|
||||||
|
s.version = '1.0.0'
|
||||||
|
s.summary = 'Expo module to detect the current keyboard language'
|
||||||
|
s.description = 'Expo module to detect the current keyboard language'
|
||||||
|
s.author = ''
|
||||||
|
s.homepage = 'https://github.com/bluesky-social/social-app'
|
||||||
|
s.platforms = { :ios => '13.4' }
|
||||||
|
s.source = { git: '' }
|
||||||
|
s.static_framework = true
|
||||||
|
|
||||||
|
s.dependency 'ExpoModulesCore'
|
||||||
|
|
||||||
|
s.pod_target_xcconfig = {
|
||||||
|
'DEFINES_MODULE' => 'YES',
|
||||||
|
'SWIFT_COMPILATION_MODE' => 'wholemodule'
|
||||||
|
}
|
||||||
|
|
||||||
|
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
||||||
|
end
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import ExpoModulesCore
|
||||||
|
|
||||||
|
public class ExpoKeyboardLanguageModule: Module {
|
||||||
|
public func definition() -> ModuleDefinition {
|
||||||
|
Name("ExpoKeyboardLanguage")
|
||||||
|
|
||||||
|
Events("onKeyboardLanguageChange")
|
||||||
|
|
||||||
|
Function("getCurrentKeyboardLanguage") {
|
||||||
|
return self.currentKeyboardLanguage()
|
||||||
|
}
|
||||||
|
|
||||||
|
OnStartObserving {
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(self.onInputModeOrResponderChange),
|
||||||
|
name: UITextInputMode.currentInputModeDidChangeNotification,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(self.onInputModeOrResponderChange),
|
||||||
|
name: UIResponder.keyboardDidShowNotification,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
OnStopObserving {
|
||||||
|
NotificationCenter.default.removeObserver(
|
||||||
|
self,
|
||||||
|
name: UITextInputMode.currentInputModeDidChangeNotification,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
NotificationCenter.default.removeObserver(
|
||||||
|
self,
|
||||||
|
name: UIResponder.keyboardDidShowNotification,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var lastLanguage: String?
|
||||||
|
|
||||||
|
@objc
|
||||||
|
private func onInputModeOrResponderChange() {
|
||||||
|
let language = currentKeyboardLanguage()
|
||||||
|
if language != lastLanguage {
|
||||||
|
lastLanguage = language
|
||||||
|
sendEvent("onKeyboardLanguageChange", [
|
||||||
|
"language": language as Any
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func currentKeyboardLanguage() -> String? {
|
||||||
|
let keyWindow = UIApplication.shared.connectedScenes
|
||||||
|
.compactMap { $0 as? UIWindowScene }
|
||||||
|
.flatMap { $0.windows }
|
||||||
|
.first { $0.isKeyWindow }
|
||||||
|
|
||||||
|
return keyWindow?.findFirstResponder()?.textInputMode?.primaryLanguage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UIView {
|
||||||
|
func findFirstResponder() -> UIView? {
|
||||||
|
if isFirstResponder { return self }
|
||||||
|
for subview in subviews {
|
||||||
|
if let found = subview.findFirstResponder() {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import {type EventSubscription} from 'expo-modules-core'
|
||||||
|
import {requireNativeModule} from 'expo-modules-core'
|
||||||
|
|
||||||
|
type ExpoKeyboardLanguageModule = {
|
||||||
|
getCurrentKeyboardLanguage(): string | null
|
||||||
|
addListener(
|
||||||
|
eventName: 'onKeyboardLanguageChange',
|
||||||
|
listener: (event: {language: string | null}) => void,
|
||||||
|
): EventSubscription
|
||||||
|
}
|
||||||
|
|
||||||
|
const NativeModule = requireNativeModule<ExpoKeyboardLanguageModule>(
|
||||||
|
'ExpoKeyboardLanguage',
|
||||||
|
)
|
||||||
|
|
||||||
|
export function getCurrentKeyboardLanguage(): string | null {
|
||||||
|
return NativeModule.getCurrentKeyboardLanguage()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addKeyboardLanguageListener(
|
||||||
|
cb: (language: string | null) => void,
|
||||||
|
): EventSubscription {
|
||||||
|
return NativeModule.addListener('onKeyboardLanguageChange', event => {
|
||||||
|
cb(event.language)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import {type EventSubscription} from 'expo-modules-core'
|
||||||
|
|
||||||
|
export function getCurrentKeyboardLanguage(): string | null {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addKeyboardLanguageListener(
|
||||||
|
_cb: (language: string | null) => void,
|
||||||
|
): EventSubscription {
|
||||||
|
return {remove() {}}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import {useEffect, useState} from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
addKeyboardLanguageListener,
|
||||||
|
getCurrentKeyboardLanguage,
|
||||||
|
} from '../../../modules/expo-keyboard-language'
|
||||||
|
|
||||||
|
function toBcp2(tag: string | null): string | null {
|
||||||
|
if (!tag) return null
|
||||||
|
return tag.split('-')[0].toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current keyboard language as a 2-letter code (e.g. "en"),
|
||||||
|
* updating in real-time on iOS.
|
||||||
|
*/
|
||||||
|
export function useKeyboardLanguage(): string | null {
|
||||||
|
const [language, setLanguage] = useState(() =>
|
||||||
|
toBcp2(getCurrentKeyboardLanguage()),
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sub = addKeyboardLanguageListener(lang => {
|
||||||
|
setLanguage(toBcp2(lang))
|
||||||
|
})
|
||||||
|
return () => sub.remove()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return language
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Web stub — keyboard language detection is not available on web.
|
||||||
|
*/
|
||||||
|
export function useKeyboardLanguage(): null {
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||||
import {APP_LANGUAGES, LANGUAGES} from '#/locale/languages'
|
import {APP_LANGUAGES, LANGUAGES} from '#/locale/languages'
|
||||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||||
import {atoms as a, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {Admonition} from '#/components/Admonition'
|
import {Admonition} from '#/components/Admonition'
|
||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
@@ -21,6 +21,8 @@ import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/
|
|||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import * as Select from '#/components/Select'
|
import * as Select from '#/components/Select'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import {IS_NATIVE} from '#/env'
|
||||||
|
import {device, useStorage} from '#/storage'
|
||||||
import * as SettingsList from './components/SettingsList'
|
import * as SettingsList from './components/SettingsList'
|
||||||
|
|
||||||
const DEDUPED_LANGUAGES = LANGUAGES.filter(
|
const DEDUPED_LANGUAGES = LANGUAGES.filter(
|
||||||
@@ -31,8 +33,12 @@ const DEDUPED_LANGUAGES = LANGUAGES.filter(
|
|||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'>
|
||||||
export function LanguageSettingsScreen({}: Props) {
|
export function LanguageSettingsScreen({}: Props) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
const setLangPrefs = useLanguagePrefsApi()
|
const setLangPrefs = useLanguagePrefsApi()
|
||||||
|
const [useKbLang = false, setUseKbLang] = useStorage(device, [
|
||||||
|
'useKeyboardLanguage',
|
||||||
|
])
|
||||||
|
|
||||||
const contentLanguagePrefsControl = useDialogControl()
|
const contentLanguagePrefsControl = useDialogControl()
|
||||||
|
|
||||||
@@ -234,6 +240,43 @@ export function LanguageSettingsScreen({}: Props) {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</SettingsList.Group>
|
</SettingsList.Group>
|
||||||
|
{IS_NATIVE && (
|
||||||
|
<>
|
||||||
|
<SettingsList.Divider />
|
||||||
|
<SettingsList.Group iconInset={false}>
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Post language</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
<Toggle.Item
|
||||||
|
name="use_keyboard_language"
|
||||||
|
label={_(
|
||||||
|
msg`Automatically detect post language from keyboard`,
|
||||||
|
)}
|
||||||
|
value={useKbLang}
|
||||||
|
onChange={value => setUseKbLang(value)}
|
||||||
|
style={[a.w_full, a.gap_md]}>
|
||||||
|
<Text style={[a.flex_1]}>
|
||||||
|
<Trans>
|
||||||
|
Automatically detect post language from keyboard
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Toggle.Platform />
|
||||||
|
</Toggle.Item>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_xs,
|
||||||
|
a.leading_snug,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
a.mt_sm,
|
||||||
|
]}>
|
||||||
|
<Trans>
|
||||||
|
Experimental. When enabled, the composer will default to
|
||||||
|
your keyboard language.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</SettingsList.Group>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</SettingsList.Container>
|
</SettingsList.Container>
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type ApiContext = {
|
|||||||
setPrimaryLanguage: (code2: string) => void
|
setPrimaryLanguage: (code2: string) => void
|
||||||
setPostLanguage: (commaSeparatedLangCodes: string) => void
|
setPostLanguage: (commaSeparatedLangCodes: string) => void
|
||||||
setContentLanguages: (code2s: string[]) => void
|
setContentLanguages: (code2s: string[]) => void
|
||||||
savePostLanguageToHistory: () => void
|
savePostLanguageToHistory: (resolvedLanguage?: string) => void
|
||||||
setAppLanguage: (code2: AppLanguage) => void
|
setAppLanguage: (code2: AppLanguage) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,21 +61,28 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
/**
|
/**
|
||||||
* Saves whatever language codes are currently selected into a history array,
|
* Saves whatever language codes are currently selected into a history array,
|
||||||
* which is then used to populate the language selector menu.
|
* which is then used to populate the language selector menu.
|
||||||
|
*
|
||||||
|
* If `resolvedLanguage` is provided (e.g. from auto-detected keyboard
|
||||||
|
* language), it will be saved to history instead of the current
|
||||||
|
* `postLanguage` preference.
|
||||||
*/
|
*/
|
||||||
savePostLanguageToHistory() {
|
savePostLanguageToHistory(resolvedLanguage?: string) {
|
||||||
// filter out duplicate `this.postLanguage` if exists, and prepend
|
// filter out duplicate `this.postLanguage` if exists, and prepend
|
||||||
// value to start of array
|
// value to start of array
|
||||||
setStateWrapped(s => ({
|
setStateWrapped(s => {
|
||||||
...s,
|
const langToSave = resolvedLanguage ?? s.postLanguage
|
||||||
postLanguageHistory: [s.postLanguage]
|
return {
|
||||||
.concat(
|
...s,
|
||||||
s.postLanguageHistory.filter(
|
postLanguageHistory: [langToSave]
|
||||||
commaSeparatedLangCodes =>
|
.concat(
|
||||||
commaSeparatedLangCodes !== s.postLanguage,
|
s.postLanguageHistory.filter(
|
||||||
),
|
commaSeparatedLangCodes =>
|
||||||
)
|
commaSeparatedLangCodes !== langToSave,
|
||||||
.slice(0, 6),
|
),
|
||||||
}))
|
)
|
||||||
|
.slice(0, 6),
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
setAppLanguage(code2: AppLanguage) {
|
setAppLanguage(code2: AppLanguage) {
|
||||||
setStateWrapped(s => ({...s, appLanguage: code2}))
|
setStateWrapped(s => ({...s, appLanguage: code2}))
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ export type Device = {
|
|||||||
activitySubscriptionsNudged?: boolean
|
activitySubscriptionsNudged?: boolean
|
||||||
threadgateNudged?: boolean
|
threadgateNudged?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to automatically detect post language from the keyboard language.
|
||||||
|
* Experimental feature, opt-in.
|
||||||
|
*/
|
||||||
|
useKeyboardLanguage?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Policy update overlays. New IDs are required for each new announcement.
|
* Policy update overlays. New IDs are required for each new announcement.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ import {
|
|||||||
type SupportedMimeTypes,
|
type SupportedMimeTypes,
|
||||||
} from '#/lib/constants'
|
} from '#/lib/constants'
|
||||||
import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
|
import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
|
||||||
|
import {useKeyboardLanguage} from '#/lib/hooks/useKeyboardLanguage'
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {mimeToExt} from '#/lib/media/video/util'
|
import {mimeToExt} from '#/lib/media/video/util'
|
||||||
@@ -134,6 +135,7 @@ import * as Toast from '#/components/Toast'
|
|||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
|
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
|
||||||
|
import {device, useStorage} from '#/storage'
|
||||||
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
|
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
|
||||||
import {
|
import {
|
||||||
draftToComposerPosts,
|
draftToComposerPosts,
|
||||||
@@ -243,26 +245,52 @@ export const ComposePost = ({
|
|||||||
replyTo?.langs || [],
|
replyTo?.langs || [],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatic keyboard language detection.
|
||||||
|
*/
|
||||||
|
const keyboardLanguage = useKeyboardLanguage()
|
||||||
|
const [autoLangEnabled = false] = useStorage(device, ['useKeyboardLanguage'])
|
||||||
|
const [manualOverride, setManualOverride] = useState(false)
|
||||||
|
const isAutoMode = !!autoLangEnabled && !manualOverride
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently selected languages of the post. Prefer local temporary
|
* The currently selected languages of the post. Prefer local temporary
|
||||||
* language suggestion over global lang prefs, if available.
|
* language suggestion, then auto-detected keyboard language, then global
|
||||||
|
* lang prefs.
|
||||||
*/
|
*/
|
||||||
const currentLanguages = useMemo(
|
const currentLanguages = useMemo(
|
||||||
() =>
|
() =>
|
||||||
acceptedLanguageSuggestion
|
acceptedLanguageSuggestion
|
||||||
? [acceptedLanguageSuggestion]
|
? [acceptedLanguageSuggestion]
|
||||||
: toPostLanguages(langPrefs.postLanguage),
|
: isAutoMode && keyboardLanguage
|
||||||
[acceptedLanguageSuggestion, langPrefs.postLanguage],
|
? [keyboardLanguage]
|
||||||
|
: toPostLanguages(langPrefs.postLanguage),
|
||||||
|
[
|
||||||
|
acceptedLanguageSuggestion,
|
||||||
|
isAutoMode,
|
||||||
|
keyboardLanguage,
|
||||||
|
langPrefs.postLanguage,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the user selects a language from the composer language selector,
|
* When the user selects a language from the composer language selector,
|
||||||
* clear any temporary language suggestions they may have selected
|
* clear any temporary language suggestions they may have selected
|
||||||
* previously, and any we might try to suggest to them.
|
* previously, and any we might try to suggest to them. Also locks out
|
||||||
|
* auto mode for this session.
|
||||||
*/
|
*/
|
||||||
const onSelectLanguage = () => {
|
const onSelectLanguage = () => {
|
||||||
setAcceptedLanguageSuggestion(null)
|
setAcceptedLanguageSuggestion(null)
|
||||||
setReplyToLanguages([])
|
setReplyToLanguages([])
|
||||||
|
setManualOverride(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-enable auto mode when the user taps "Automatic" in the language menu.
|
||||||
|
*/
|
||||||
|
const onSelectAutomatic = () => {
|
||||||
|
setAcceptedLanguageSuggestion(null)
|
||||||
|
setManualOverride(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [composerState, composerDispatch] = useReducer(
|
const [composerState, composerDispatch] = useReducer(
|
||||||
@@ -939,7 +967,7 @@ export const ComposePost = ({
|
|||||||
originalLocalRefs: composerState.originalLocalRefs,
|
originalLocalRefs: composerState.originalLocalRefs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setLangPrefs.savePostLanguageToHistory()
|
setLangPrefs.savePostLanguageToHistory(fromPostLanguages(currentLanguages))
|
||||||
if (initQuote) {
|
if (initQuote) {
|
||||||
// We want to wait for the quote count to update before we call `onPost`, which will refetch data
|
// We want to wait for the quote count to update before we call `onPost`, which will refetch data
|
||||||
whenAppViewReady(agent, initQuote.uri, res => {
|
whenAppViewReady(agent, initQuote.uri, res => {
|
||||||
@@ -1122,6 +1150,10 @@ export const ComposePost = ({
|
|||||||
}}
|
}}
|
||||||
currentLanguages={currentLanguages}
|
currentLanguages={currentLanguages}
|
||||||
onSelectLanguage={onSelectLanguage}
|
onSelectLanguage={onSelectLanguage}
|
||||||
|
onSelectAutomatic={onSelectAutomatic}
|
||||||
|
keyboardLanguage={keyboardLanguage}
|
||||||
|
isAutoMode={isAutoMode}
|
||||||
|
autoLangEnabled={autoLangEnabled}
|
||||||
openGallery={openGallery}
|
openGallery={openGallery}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -1823,6 +1855,10 @@ function ComposerFooter({
|
|||||||
onAddPost,
|
onAddPost,
|
||||||
currentLanguages,
|
currentLanguages,
|
||||||
onSelectLanguage,
|
onSelectLanguage,
|
||||||
|
onSelectAutomatic,
|
||||||
|
keyboardLanguage,
|
||||||
|
isAutoMode,
|
||||||
|
autoLangEnabled,
|
||||||
openGallery,
|
openGallery,
|
||||||
}: {
|
}: {
|
||||||
post: PostDraft
|
post: PostDraft
|
||||||
@@ -1834,6 +1870,10 @@ function ComposerFooter({
|
|||||||
onAddPost: () => void
|
onAddPost: () => void
|
||||||
currentLanguages: string[]
|
currentLanguages: string[]
|
||||||
onSelectLanguage?: (language: string) => void
|
onSelectLanguage?: (language: string) => void
|
||||||
|
onSelectAutomatic?: () => void
|
||||||
|
keyboardLanguage: string | null
|
||||||
|
isAutoMode: boolean
|
||||||
|
autoLangEnabled: boolean
|
||||||
openGallery?: boolean
|
openGallery?: boolean
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -1992,6 +2032,10 @@ function ComposerFooter({
|
|||||||
<PostLanguageSelect
|
<PostLanguageSelect
|
||||||
currentLanguages={currentLanguages}
|
currentLanguages={currentLanguages}
|
||||||
onSelectLanguage={onSelectLanguage}
|
onSelectLanguage={onSelectLanguage}
|
||||||
|
onSelectAutomatic={onSelectAutomatic}
|
||||||
|
keyboardLanguage={keyboardLanguage}
|
||||||
|
isAutoMode={isAutoMode}
|
||||||
|
autoLangEnabled={autoLangEnabled}
|
||||||
/>
|
/>
|
||||||
<CharProgress
|
<CharProgress
|
||||||
count={post.shortenedGraphemeLength}
|
count={post.shortenedGraphemeLength}
|
||||||
|
|||||||
@@ -21,9 +21,17 @@ import {Text} from '#/components/Typography'
|
|||||||
export function PostLanguageSelect({
|
export function PostLanguageSelect({
|
||||||
currentLanguages: currentLanguagesProp,
|
currentLanguages: currentLanguagesProp,
|
||||||
onSelectLanguage,
|
onSelectLanguage,
|
||||||
|
onSelectAutomatic,
|
||||||
|
keyboardLanguage,
|
||||||
|
isAutoMode,
|
||||||
|
autoLangEnabled,
|
||||||
}: {
|
}: {
|
||||||
currentLanguages?: string[]
|
currentLanguages?: string[]
|
||||||
onSelectLanguage?: (language: string) => void
|
onSelectLanguage?: (language: string) => void
|
||||||
|
onSelectAutomatic?: () => void
|
||||||
|
keyboardLanguage?: string | null
|
||||||
|
isAutoMode?: boolean
|
||||||
|
autoLangEnabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
@@ -46,26 +54,7 @@ export function PostLanguageSelect({
|
|||||||
onSelectLanguage?.(langsString)
|
onSelectLanguage?.(langsString)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const showAutoItem = !!autoLangEnabled && !!keyboardLanguage
|
||||||
dedupedHistory.length === 1 &&
|
|
||||||
dedupedHistory[0] === langPrefs.postLanguage
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LanguageBtn onPress={languageDialogControl.open} />
|
|
||||||
<LanguageSelectDialog
|
|
||||||
titleText={<Trans>Choose post languages</Trans>}
|
|
||||||
subtitleText={
|
|
||||||
<Trans>Select up to 3 languages used in this post</Trans>
|
|
||||||
}
|
|
||||||
control={languageDialogControl}
|
|
||||||
currentLanguages={currentLanguages}
|
|
||||||
onSelectLanguages={onSelectLanguages}
|
|
||||||
maxLanguages={3}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -77,6 +66,19 @@ export function PostLanguageSelect({
|
|||||||
</Menu.Trigger>
|
</Menu.Trigger>
|
||||||
<Menu.Outer>
|
<Menu.Outer>
|
||||||
<Menu.Group>
|
<Menu.Group>
|
||||||
|
{showAutoItem && (
|
||||||
|
<Menu.Item
|
||||||
|
label={_(msg`Automatic`)}
|
||||||
|
onPress={() => onSelectAutomatic?.()}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
<Trans>Automatic</Trans>
|
||||||
|
{' ('}
|
||||||
|
{codeToLanguageName(keyboardLanguage, langPrefs.appLanguage)}
|
||||||
|
{')'}
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemRadio selected={!!isAutoMode} />
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
{dedupedHistory.map(historyItem => {
|
{dedupedHistory.map(historyItem => {
|
||||||
const langCodes = historyItem.split(',')
|
const langCodes = historyItem.split(',')
|
||||||
const langName = langCodes
|
const langName = langCodes
|
||||||
@@ -92,7 +94,9 @@ export function PostLanguageSelect({
|
|||||||
}}>
|
}}>
|
||||||
<Menu.ItemText>{langName}</Menu.ItemText>
|
<Menu.ItemText>{langName}</Menu.ItemText>
|
||||||
<Menu.ItemRadio
|
<Menu.ItemRadio
|
||||||
selected={currentLanguages.includes(historyItem)}
|
selected={
|
||||||
|
!isAutoMode && currentLanguages.includes(historyItem)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user