From 3ca5a865a16d9e5b3ed176834ecd43e24099b2d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Feb 2026 14:49:15 +0000 Subject: [PATCH] Add expo-keyboard-language native module New Expo module that exposes keyboard language detection via a node handle. On iOS it resolves the React Native view tag to the underlying UITextField/UITextView and returns textInputMode.primaryLanguage (BCP 47 tag). Android and web return null for now. https://claude.ai/code/session_01AMCp4M56WtFPz1pZtwgcvx --- .../android/build.gradle | 39 +++++++++++++++++++ .../android/src/main/AndroidManifest.xml | 1 + .../ExpoKeyboardLanguageModule.kt | 15 +++++++ .../expo-module.config.json | 9 +++++ modules/expo-keyboard-language/index.ts | 1 + .../ios/ExpoKeyboardLanguage.podspec | 20 ++++++++++ .../ios/ExpoKeyboardLanguageModule.swift | 33 ++++++++++++++++ .../src/ExpoKeyboardLanguage.ts | 11 ++++++ .../src/ExpoKeyboardLanguage.web.ts | 3 ++ 9 files changed, 132 insertions(+) create mode 100644 modules/expo-keyboard-language/android/build.gradle create mode 100644 modules/expo-keyboard-language/android/src/main/AndroidManifest.xml create mode 100644 modules/expo-keyboard-language/android/src/main/java/expo/modules/keyboardlanguage/ExpoKeyboardLanguageModule.kt create mode 100644 modules/expo-keyboard-language/expo-module.config.json create mode 100644 modules/expo-keyboard-language/index.ts create mode 100644 modules/expo-keyboard-language/ios/ExpoKeyboardLanguage.podspec create mode 100644 modules/expo-keyboard-language/ios/ExpoKeyboardLanguageModule.swift create mode 100644 modules/expo-keyboard-language/src/ExpoKeyboardLanguage.ts create mode 100644 modules/expo-keyboard-language/src/ExpoKeyboardLanguage.web.ts diff --git a/modules/expo-keyboard-language/android/build.gradle b/modules/expo-keyboard-language/android/build.gradle new file mode 100644 index 0000000000..76d1a4a344 --- /dev/null +++ b/modules/expo-keyboard-language/android/build.gradle @@ -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 + } +} diff --git a/modules/expo-keyboard-language/android/src/main/AndroidManifest.xml b/modules/expo-keyboard-language/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..9b65eb06cf --- /dev/null +++ b/modules/expo-keyboard-language/android/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/modules/expo-keyboard-language/android/src/main/java/expo/modules/keyboardlanguage/ExpoKeyboardLanguageModule.kt b/modules/expo-keyboard-language/android/src/main/java/expo/modules/keyboardlanguage/ExpoKeyboardLanguageModule.kt new file mode 100644 index 0000000000..fbd9571b99 --- /dev/null +++ b/modules/expo-keyboard-language/android/src/main/java/expo/modules/keyboardlanguage/ExpoKeyboardLanguageModule.kt @@ -0,0 +1,15 @@ +package expo.modules.keyboardlanguage + +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +class ExpoKeyboardLanguageModule : Module() { + override fun definition() = + ModuleDefinition { + Name("ExpoKeyboardLanguage") + + Function("getKeyboardLanguage") { _: Int -> + return@Function null as String? + } + } +} diff --git a/modules/expo-keyboard-language/expo-module.config.json b/modules/expo-keyboard-language/expo-module.config.json new file mode 100644 index 0000000000..8fffb703d0 --- /dev/null +++ b/modules/expo-keyboard-language/expo-module.config.json @@ -0,0 +1,9 @@ +{ + "platforms": ["ios", "android"], + "ios": { + "modules": ["ExpoKeyboardLanguageModule"] + }, + "android": { + "modules": ["expo.modules.keyboardlanguage.ExpoKeyboardLanguageModule"] + } +} diff --git a/modules/expo-keyboard-language/index.ts b/modules/expo-keyboard-language/index.ts new file mode 100644 index 0000000000..7469e28d15 --- /dev/null +++ b/modules/expo-keyboard-language/index.ts @@ -0,0 +1 @@ +export {getKeyboardLanguage} from './src/ExpoKeyboardLanguage' diff --git a/modules/expo-keyboard-language/ios/ExpoKeyboardLanguage.podspec b/modules/expo-keyboard-language/ios/ExpoKeyboardLanguage.podspec new file mode 100644 index 0000000000..f503221ea7 --- /dev/null +++ b/modules/expo-keyboard-language/ios/ExpoKeyboardLanguage.podspec @@ -0,0 +1,20 @@ +Pod::Spec.new do |s| + s.name = 'ExpoKeyboardLanguage' + s.version = '1.0.0' + s.summary = 'Get keyboard language for a text input' + s.description = 'Get the keyboard language (textInputMode) for a specific text input by node handle' + s.author = 'bluesky-social' + s.homepage = 'https://github.com/bluesky-social/social-app' + s.platforms = { :ios => '13.4', :tvos => '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 diff --git a/modules/expo-keyboard-language/ios/ExpoKeyboardLanguageModule.swift b/modules/expo-keyboard-language/ios/ExpoKeyboardLanguageModule.swift new file mode 100644 index 0000000000..d7ec613f61 --- /dev/null +++ b/modules/expo-keyboard-language/ios/ExpoKeyboardLanguageModule.swift @@ -0,0 +1,33 @@ +import ExpoModulesCore + +public class ExpoKeyboardLanguageModule: Module { + public func definition() -> ModuleDefinition { + Name("ExpoKeyboardLanguage") + + Function("getKeyboardLanguage") { (nodeHandle: Int) -> String? in + guard let view = self.appContext?.findView(withTag: nodeHandle, ofType: UIView.self) else { + return nil + } + + if let textInput = self.findTextInput(in: view) { + return textInput.textInputMode?.primaryLanguage + } + + return view.textInputMode?.primaryLanguage + } + } + + private func findTextInput(in view: UIView) -> UIView? { + if view is UITextField || view is UITextView { + return view + } + + for subview in view.subviews { + if let found = findTextInput(in: subview) { + return found + } + } + + return nil + } +} diff --git a/modules/expo-keyboard-language/src/ExpoKeyboardLanguage.ts b/modules/expo-keyboard-language/src/ExpoKeyboardLanguage.ts new file mode 100644 index 0000000000..e21f80813e --- /dev/null +++ b/modules/expo-keyboard-language/src/ExpoKeyboardLanguage.ts @@ -0,0 +1,11 @@ +import {requireNativeModule} from 'expo-modules-core' + +const NativeModule = requireNativeModule('ExpoKeyboardLanguage') + +/** + * Gets the keyboard language (BCP 47 tag) for the text input identified + * by the given node handle. Returns `null` when unavailable. + */ +export function getKeyboardLanguage(nodeHandle: number): string | null { + return NativeModule.getKeyboardLanguage(nodeHandle) +} diff --git a/modules/expo-keyboard-language/src/ExpoKeyboardLanguage.web.ts b/modules/expo-keyboard-language/src/ExpoKeyboardLanguage.web.ts new file mode 100644 index 0000000000..ffa96b4f19 --- /dev/null +++ b/modules/expo-keyboard-language/src/ExpoKeyboardLanguage.web.ts @@ -0,0 +1,3 @@ +export function getKeyboardLanguage(_nodeHandle: number): string | null { + return null +}