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:
Samuel Newman
2026-02-28 16:00:45 +02:00
parent 6cdc1fe2b5
commit d1666345ae
15 changed files with 395 additions and 40 deletions
@@ -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
}
}