@nitro-mlkit/translation 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NitroMLKitTranslate.podspec +34 -0
- package/README.md +66 -0
- package/android/CMakeLists.txt +12 -0
- package/android/build.gradle +77 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromlkit/translate/HybridTranslator.kt +67 -0
- package/android/src/main/kotlin/com/nitromlkit/translate/NitroMLKitTranslatePackage.kt +19 -0
- package/expo-module.config.json +9 -0
- package/ios/HybridTranslator.swift +160 -0
- package/nitro.json +16 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroMLKitTranslate+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroMLKitTranslate+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroMLKitTranslateOnLoad.cpp +54 -0
- package/nitrogen/generated/android/NitroMLKitTranslateOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JHybridTranslatorSpec.cpp +144 -0
- package/nitrogen/generated/android/c++/JHybridTranslatorSpec.hpp +68 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromlkit/translate/HybridTranslatorSpec.kt +75 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromlkit/translate/NitroMLKitTranslateOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroMLKitTranslate+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NitroMLKitTranslate-Swift-Cxx-Bridge.cpp +73 -0
- package/nitrogen/generated/ios/NitroMLKitTranslate-Swift-Cxx-Bridge.hpp +261 -0
- package/nitrogen/generated/ios/NitroMLKitTranslate-Swift-Cxx-Umbrella.hpp +46 -0
- package/nitrogen/generated/ios/NitroMLKitTranslateAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroMLKitTranslateAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridTranslatorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridTranslatorSpecSwift.hpp +124 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTranslatorSpec.swift +60 -0
- package/nitrogen/generated/ios/swift/HybridTranslatorSpec_cxx.swift +239 -0
- package/nitrogen/generated/shared/c++/HybridTranslatorSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridTranslatorSpec.hpp +69 -0
- package/package.json +54 -0
- package/src/index.ts +14 -0
- package/src/specs/Translator.nitro.ts +23 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gonzalo Polo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "NitroMLKitTranslate"
|
|
7
|
+
# Must match nitro.json's `ios.iosModuleName` — Nitrogen's generated bridge
|
|
8
|
+
# imports "NitroMLKitTranslate-Swift.h", so the compiled Swift module name has to be NitroMLKitTranslate.
|
|
9
|
+
s.module_name = "NitroMLKitTranslate"
|
|
10
|
+
s.version = package["version"]
|
|
11
|
+
s.summary = package["description"]
|
|
12
|
+
s.homepage = package["repository"]["url"]
|
|
13
|
+
s.license = package["license"]
|
|
14
|
+
s.authors = package["author"]
|
|
15
|
+
s.source = { :git => "https://github.com/pologonzalo/react-native-nitro-mlkit.git", :tag => s.version }
|
|
16
|
+
|
|
17
|
+
s.platforms = { :ios => "15.5" }
|
|
18
|
+
|
|
19
|
+
s.source_files = [
|
|
20
|
+
"ios/**/*.{swift,h,m,mm,cpp}"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
s.dependency "GoogleMLKit/Translate", "~> 7.0"
|
|
24
|
+
|
|
25
|
+
s.pod_target_xcconfig = {
|
|
26
|
+
"SWIFT_VERSION" => "5.9"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Add all files generated by Nitrogen (generated sources + NitroModules dep +
|
|
30
|
+
# the C++/Swift-interop build flags — same setup as the working
|
|
31
|
+
# NitroMLKitFaceDetection podspec).
|
|
32
|
+
load 'nitrogen/generated/ios/NitroMLKitTranslate+autolinking.rb'
|
|
33
|
+
add_nitrogen_files(s)
|
|
34
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# React Native ML Kit — Translation
|
|
2
|
+
|
|
3
|
+
**`@nitro-mlkit/translation`** · on-device Google ML Kit via [Nitro Modules](https://github.com/mrousavy/nitro) — JSI, no bridge.
|
|
4
|
+
|
|
5
|
+
> ⚠️ **Beta (`0.1.0-beta.x`).** Android verified on-device. iOS builds & links
|
|
6
|
+
> but device runtime is pending — see [Platform status](#platform-status).
|
|
7
|
+
|
|
8
|
+
High-performance, on-device **translation** for React Native, built with
|
|
9
|
+
[Nitro Modules](https://github.com/mrousavy/nitro) (JSI, no bridge).
|
|
10
|
+
|
|
11
|
+
Powered by **Google ML Kit**. Translate between 50+ languages, entirely on-device
|
|
12
|
+
once the language model is downloaded. Language codes are BCP-47 (e.g. `"en"`,
|
|
13
|
+
`"es"`, `"fr"`, `"de"`, `"ja"`).
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @nitro-mlkit/translation@beta react-native-nitro-modules
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
No config plugin (autolinked Expo module). Just install and `npx expo prebuild`.
|
|
22
|
+
Not available in Expo Go.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { NitroTranslate } from "@nitro-mlkit/translation";
|
|
28
|
+
|
|
29
|
+
// Translate (downloads the required model on first use).
|
|
30
|
+
const out = await NitroTranslate.translate("Hello, world", "en", "es");
|
|
31
|
+
// → "Hola, mundo"
|
|
32
|
+
|
|
33
|
+
// Manage models explicitly.
|
|
34
|
+
await NitroTranslate.downloadModel("es", /* requireWifi */ true);
|
|
35
|
+
await NitroTranslate.isModelDownloaded("es"); // → true
|
|
36
|
+
await NitroTranslate.getDownloadedModels(); // → ["en", "es", ...]
|
|
37
|
+
await NitroTranslate.deleteModel("es");
|
|
38
|
+
|
|
39
|
+
NitroTranslate.isAvailable(); // boolean
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Models
|
|
43
|
+
|
|
44
|
+
Translation runs on-device, but each language pairs against a downloadable model
|
|
45
|
+
(~30 MB per language). The first `translate()` call for a language downloads its
|
|
46
|
+
model (needs network and may take a few seconds); subsequent calls are fully
|
|
47
|
+
offline. Pre-download with `downloadModel()` and gate large downloads behind
|
|
48
|
+
Wi-Fi via the `requireWifi` flag.
|
|
49
|
+
|
|
50
|
+
## Platform status
|
|
51
|
+
|
|
52
|
+
| Platform | Min | Status |
|
|
53
|
+
| -------- | --- | ------ |
|
|
54
|
+
| Android | API 26+ | ✅ Verified on-device (Pixel 9, API 36): en→es in ~2.5 s (incl. first-run model download) |
|
|
55
|
+
| iOS | 15.5+ | ⚠️ Swift impl written; on-device build & run pending¹ |
|
|
56
|
+
| tvOS/macOS | — | 🔜 Planned |
|
|
57
|
+
|
|
58
|
+
¹ ML Kit's iOS pods ship no `arm64` Simulator slice; validate on a physical device.
|
|
59
|
+
|
|
60
|
+
## Part of `nitro-mlkit`
|
|
61
|
+
|
|
62
|
+
The full ML Kit suite on Nitro — see the other `@nitro-mlkit/*` packages.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT © Gonzalo Polo
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
project(NitroMLKitTranslate)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
|
|
7
|
+
add_library(NitroMLKitTranslate SHARED
|
|
8
|
+
src/main/cpp/cpp-adapter.cpp
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# Add all files generated by Nitrogen
|
|
12
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroMLKitTranslate+autolinking.cmake)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.safeExtGet = { prop, fallback ->
|
|
3
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
def reactNativeArchitectures() {
|
|
8
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
9
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
apply plugin: "com.android.library"
|
|
13
|
+
apply plugin: "kotlin-android"
|
|
14
|
+
|
|
15
|
+
android {
|
|
16
|
+
namespace "com.nitromlkit.translate"
|
|
17
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 35)
|
|
18
|
+
ndkVersion safeExtGet("ndkVersion", "27.1.12297006")
|
|
19
|
+
|
|
20
|
+
defaultConfig {
|
|
21
|
+
minSdkVersion safeExtGet("minSdkVersion", 26)
|
|
22
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 35)
|
|
23
|
+
|
|
24
|
+
externalNativeBuild {
|
|
25
|
+
cmake {
|
|
26
|
+
arguments "-DANDROID_STL=c++_shared"
|
|
27
|
+
abiFilters (*reactNativeArchitectures())
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
externalNativeBuild {
|
|
33
|
+
cmake {
|
|
34
|
+
path "CMakeLists.txt"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
buildFeatures {
|
|
39
|
+
prefab true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
compileOptions {
|
|
43
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
44
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
kotlinOptions {
|
|
48
|
+
jvmTarget = "17"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
sourceSets {
|
|
52
|
+
main {
|
|
53
|
+
java.srcDirs += [
|
|
54
|
+
"src/main/kotlin",
|
|
55
|
+
"../nitrogen/generated/android/kotlin"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
dependencies {
|
|
62
|
+
// MLKit
|
|
63
|
+
implementation "com.google.mlkit:translate:17.0.3"
|
|
64
|
+
|
|
65
|
+
// Nitro Modules
|
|
66
|
+
implementation project(":react-native-nitro-modules")
|
|
67
|
+
|
|
68
|
+
// Expo Modules (only used so autolinking calls our OnLoad at startup)
|
|
69
|
+
implementation project(":expo-modules-core")
|
|
70
|
+
|
|
71
|
+
// Kotlin coroutines
|
|
72
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
|
|
73
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.9.0"
|
|
74
|
+
|
|
75
|
+
// React Native
|
|
76
|
+
implementation "com.facebook.react:react-android"
|
|
77
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package com.margelo.nitro.nitromlkit.translate
|
|
2
|
+
|
|
3
|
+
import com.google.mlkit.common.model.DownloadConditions
|
|
4
|
+
import com.google.mlkit.common.model.RemoteModelManager
|
|
5
|
+
import com.google.mlkit.nl.translate.TranslateLanguage
|
|
6
|
+
import com.google.mlkit.nl.translate.TranslateRemoteModel
|
|
7
|
+
import com.google.mlkit.nl.translate.Translation
|
|
8
|
+
import com.google.mlkit.nl.translate.TranslatorOptions
|
|
9
|
+
import com.margelo.nitro.core.Promise
|
|
10
|
+
import kotlinx.coroutines.tasks.await
|
|
11
|
+
|
|
12
|
+
class HybridTranslator : HybridTranslatorSpec() {
|
|
13
|
+
private val modelManager by lazy { RemoteModelManager.getInstance() }
|
|
14
|
+
|
|
15
|
+
private fun lang(tag: String): String =
|
|
16
|
+
TranslateLanguage.fromLanguageTag(tag)
|
|
17
|
+
?: throw IllegalArgumentException("Unsupported language: $tag")
|
|
18
|
+
|
|
19
|
+
override fun translate(text: String, sourceLanguage: String, targetLanguage: String): Promise<String> {
|
|
20
|
+
return Promise.async {
|
|
21
|
+
val options = TranslatorOptions.Builder()
|
|
22
|
+
.setSourceLanguage(lang(sourceLanguage))
|
|
23
|
+
.setTargetLanguage(lang(targetLanguage))
|
|
24
|
+
.build()
|
|
25
|
+
val client = Translation.getClient(options)
|
|
26
|
+
try {
|
|
27
|
+
client.downloadModelIfNeeded().await()
|
|
28
|
+
client.translate(text).await()
|
|
29
|
+
} finally {
|
|
30
|
+
client.close()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun downloadModel(language: String, requireWifi: Boolean): Promise<Unit> {
|
|
36
|
+
return Promise.async {
|
|
37
|
+
val model = TranslateRemoteModel.Builder(lang(language)).build()
|
|
38
|
+
val conditions = DownloadConditions.Builder().apply { if (requireWifi) requireWifi() }.build()
|
|
39
|
+
modelManager.download(model, conditions).await()
|
|
40
|
+
Unit
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun isModelDownloaded(language: String): Promise<Boolean> {
|
|
45
|
+
return Promise.async {
|
|
46
|
+
val model = TranslateRemoteModel.Builder(lang(language)).build()
|
|
47
|
+
modelManager.isModelDownloaded(model).await()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun deleteModel(language: String): Promise<Unit> {
|
|
52
|
+
return Promise.async {
|
|
53
|
+
val model = TranslateRemoteModel.Builder(lang(language)).build()
|
|
54
|
+
modelManager.deleteDownloadedModel(model).await()
|
|
55
|
+
Unit
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override fun getDownloadedModels(): Promise<Array<String>> {
|
|
60
|
+
return Promise.async {
|
|
61
|
+
modelManager.getDownloadedModels(TranslateRemoteModel::class.java).await()
|
|
62
|
+
.map { it.language }.toTypedArray()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun isAvailable(): Boolean = true
|
|
67
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package com.nitromlkit.translate
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.modules.Module
|
|
4
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
5
|
+
import com.margelo.nitro.nitromlkit.translate.NitroMLKitTranslateOnLoad
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Loads the native library and registers the Translator HybridObject at startup via
|
|
9
|
+
* Expo autolinking. Exposes no JS API of its own.
|
|
10
|
+
*/
|
|
11
|
+
class NitroMLKitTranslatePackage : Module() {
|
|
12
|
+
init {
|
|
13
|
+
NitroMLKitTranslateOnLoad.initializeNative()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override fun definition() = ModuleDefinition {
|
|
17
|
+
Name("NitroMLKitTranslate")
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
import MLKitCommon
|
|
4
|
+
import MLKitTranslate
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Native iOS implementation of Translator (MLKit on-device Translation).
|
|
8
|
+
* Conforms to the Nitrogen-generated HybridTranslatorSpec protocol.
|
|
9
|
+
*
|
|
10
|
+
* Language codes are BCP-47 (e.g. "en", "es", "fr"). On iOS a BCP-47 tag maps
|
|
11
|
+
* to a `TranslateLanguage` via its `rawValue`. Model downloads go through
|
|
12
|
+
* `ModelManager`, which is notification-based on iOS (unlike the completion /
|
|
13
|
+
* Task-based Android API), so `download(_:conditions:)` is bridged to async via
|
|
14
|
+
* a NotificationCenter observer wrapped in a continuation.
|
|
15
|
+
*/
|
|
16
|
+
class HybridTranslator: HybridTranslatorSpec {
|
|
17
|
+
|
|
18
|
+
// MARK: - HybridObject boilerplate
|
|
19
|
+
var memorySize: Int { MemoryLayout<HybridTranslator>.size }
|
|
20
|
+
|
|
21
|
+
// MARK: - Lazy MLKit model manager
|
|
22
|
+
private lazy var modelManager: ModelManager = ModelManager.modelManager()
|
|
23
|
+
|
|
24
|
+
// MARK: - Helpers
|
|
25
|
+
|
|
26
|
+
/// Convert a BCP-47 tag to a validated `TranslateLanguage`, or throw.
|
|
27
|
+
private func language(for tag: String) throws -> TranslateLanguage {
|
|
28
|
+
let lang = TranslateLanguage(rawValue: tag)
|
|
29
|
+
guard TranslateLanguage.allLanguages().contains(lang) else {
|
|
30
|
+
throw RuntimeError.error(withMessage: "Unsupported language: \(tag)")
|
|
31
|
+
}
|
|
32
|
+
return lang
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Bridge the notification-based ML Kit model download to async/await.
|
|
36
|
+
private func awaitModelDownload(_ model: TranslateRemoteModel,
|
|
37
|
+
conditions: ModelDownloadConditions) async throws {
|
|
38
|
+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
|
39
|
+
let center = NotificationCenter.default
|
|
40
|
+
var didResume = false
|
|
41
|
+
var successObserver: NSObjectProtocol?
|
|
42
|
+
var failObserver: NSObjectProtocol?
|
|
43
|
+
|
|
44
|
+
func cleanup() {
|
|
45
|
+
if let successObserver = successObserver { center.removeObserver(successObserver) }
|
|
46
|
+
if let failObserver = failObserver { center.removeObserver(failObserver) }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func isForThisModel(_ note: Notification) -> Bool {
|
|
50
|
+
guard let downloaded = note.userInfo?[ModelDownloadUserInfoKey.remoteModel.rawValue] as? NSObject else {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
return downloaded.isEqual(model)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
successObserver = center.addObserver(forName: .mlkitModelDownloadDidSucceed,
|
|
57
|
+
object: nil, queue: .main) { note in
|
|
58
|
+
guard !didResume, isForThisModel(note) else { return }
|
|
59
|
+
didResume = true
|
|
60
|
+
cleanup()
|
|
61
|
+
continuation.resume()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
failObserver = center.addObserver(forName: .mlkitModelDownloadDidFail,
|
|
65
|
+
object: nil, queue: .main) { note in
|
|
66
|
+
guard !didResume, isForThisModel(note) else { return }
|
|
67
|
+
didResume = true
|
|
68
|
+
cleanup()
|
|
69
|
+
let err = note.userInfo?[ModelDownloadUserInfoKey.error.rawValue] as? Error
|
|
70
|
+
continuation.resume(throwing: err ?? RuntimeError.error(
|
|
71
|
+
withMessage: "Failed to download translation model: \(model.language.rawValue)"))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Kicks off the download; progress is reported via the observed notifications.
|
|
75
|
+
_ = self.modelManager.download(model, conditions: conditions)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// MARK: - Protocol methods
|
|
80
|
+
|
|
81
|
+
func translate(text: String, sourceLanguage: String, targetLanguage: String) throws -> Promise<String> {
|
|
82
|
+
return Promise.async {
|
|
83
|
+
let source = try self.language(for: sourceLanguage)
|
|
84
|
+
let target = try self.language(for: targetLanguage)
|
|
85
|
+
let options = TranslatorOptions(sourceLanguage: source, targetLanguage: target)
|
|
86
|
+
let translator = Translator.translator(options: options)
|
|
87
|
+
|
|
88
|
+
// Download the model(s) for this language pair if needed.
|
|
89
|
+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
|
90
|
+
translator.downloadModelIfNeeded(with: ModelDownloadConditions()) { error in
|
|
91
|
+
if let error = error {
|
|
92
|
+
continuation.resume(throwing: error)
|
|
93
|
+
} else {
|
|
94
|
+
continuation.resume()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Translate.
|
|
100
|
+
return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<String, Error>) in
|
|
101
|
+
translator.translate(text) { translatedText, error in
|
|
102
|
+
if let error = error {
|
|
103
|
+
continuation.resume(throwing: error)
|
|
104
|
+
} else if let translatedText = translatedText {
|
|
105
|
+
continuation.resume(returning: translatedText)
|
|
106
|
+
} else {
|
|
107
|
+
continuation.resume(throwing: RuntimeError.error(withMessage: "Translation returned no result"))
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func downloadModel(language: String, requireWifi: Bool) throws -> Promise<Void> {
|
|
115
|
+
return Promise.async {
|
|
116
|
+
let lang = try self.language(for: language)
|
|
117
|
+
let model = TranslateRemoteModel.translateRemoteModel(language: lang)
|
|
118
|
+
let conditions = ModelDownloadConditions(allowsCellularAccess: !requireWifi,
|
|
119
|
+
allowsBackgroundDownloading: false)
|
|
120
|
+
try await self.awaitModelDownload(model, conditions: conditions)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func isModelDownloaded(language: String) throws -> Promise<Bool> {
|
|
125
|
+
return Promise.async {
|
|
126
|
+
let lang = try self.language(for: language)
|
|
127
|
+
let model = TranslateRemoteModel.translateRemoteModel(language: lang)
|
|
128
|
+
// `isModelDownloaded(_:)` is synchronous on iOS.
|
|
129
|
+
return self.modelManager.isModelDownloaded(model)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func deleteModel(language: String) throws -> Promise<Void> {
|
|
134
|
+
return Promise.async {
|
|
135
|
+
let lang = try self.language(for: language)
|
|
136
|
+
let model = TranslateRemoteModel.translateRemoteModel(language: lang)
|
|
137
|
+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
|
138
|
+
self.modelManager.deleteDownloadedModel(model) { error in
|
|
139
|
+
if let error = error {
|
|
140
|
+
continuation.resume(throwing: error)
|
|
141
|
+
} else {
|
|
142
|
+
continuation.resume()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func getDownloadedModels() throws -> Promise<[String]> {
|
|
150
|
+
return Promise.async {
|
|
151
|
+
// `downloadedTranslateModels` is a Set<TranslateRemoteModel> exposed by
|
|
152
|
+
// MLKitTranslate as a category on ModelManager.
|
|
153
|
+
return self.modelManager.downloadedTranslateModels.map { $0.language.rawValue }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
func isAvailable() throws -> Bool {
|
|
158
|
+
return true
|
|
159
|
+
}
|
|
160
|
+
}
|
package/nitro.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cxxNamespace": ["mlkit", "translate"],
|
|
3
|
+
"ios": {
|
|
4
|
+
"iosModuleName": "NitroMLKitTranslate"
|
|
5
|
+
},
|
|
6
|
+
"android": {
|
|
7
|
+
"androidNamespace": ["nitromlkit", "translate"],
|
|
8
|
+
"androidCxxLibName": "NitroMLKitTranslate"
|
|
9
|
+
},
|
|
10
|
+
"autolinking": {
|
|
11
|
+
"Translator": {
|
|
12
|
+
"ios": { "language": "swift", "implementationClassName": "HybridTranslator" },
|
|
13
|
+
"android": { "language": "kotlin", "implementationClassName": "HybridTranslator" }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
** linguist-generated=true
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#
|
|
2
|
+
# NitroMLKitTranslate+autolinking.cmake
|
|
3
|
+
# This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
# https://github.com/mrousavy/nitro
|
|
5
|
+
# Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# This is a CMake file that adds all files generated by Nitrogen
|
|
9
|
+
# to the current CMake project.
|
|
10
|
+
#
|
|
11
|
+
# To use it, add this to your CMakeLists.txt:
|
|
12
|
+
# ```cmake
|
|
13
|
+
# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroMLKitTranslate+autolinking.cmake)
|
|
14
|
+
# ```
|
|
15
|
+
|
|
16
|
+
# Define a flag to check if we are building properly
|
|
17
|
+
add_definitions(-DBUILDING_NITROMLKITTRANSLATE_WITH_GENERATED_CMAKE_PROJECT)
|
|
18
|
+
|
|
19
|
+
# Enable Raw Props parsing in react-native (for Nitro Views)
|
|
20
|
+
add_definitions(-DRN_SERIALIZABLE_STATE)
|
|
21
|
+
|
|
22
|
+
# Add all headers that were generated by Nitrogen
|
|
23
|
+
include_directories(
|
|
24
|
+
"../nitrogen/generated/shared/c++"
|
|
25
|
+
"../nitrogen/generated/android/c++"
|
|
26
|
+
"../nitrogen/generated/android/"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Add all .cpp sources that were generated by Nitrogen
|
|
30
|
+
target_sources(
|
|
31
|
+
# CMake project name (Android C++ library name)
|
|
32
|
+
NitroMLKitTranslate PRIVATE
|
|
33
|
+
# Autolinking Setup
|
|
34
|
+
../nitrogen/generated/android/NitroMLKitTranslateOnLoad.cpp
|
|
35
|
+
# Shared Nitrogen C++ sources
|
|
36
|
+
../nitrogen/generated/shared/c++/HybridTranslatorSpec.cpp
|
|
37
|
+
# Android-specific Nitrogen C++ sources
|
|
38
|
+
../nitrogen/generated/android/c++/JHybridTranslatorSpec.cpp
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake
|
|
42
|
+
# Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake
|
|
43
|
+
target_compile_definitions(
|
|
44
|
+
NitroMLKitTranslate PRIVATE
|
|
45
|
+
-DFOLLY_NO_CONFIG=1
|
|
46
|
+
-DFOLLY_HAVE_CLOCK_GETTIME=1
|
|
47
|
+
-DFOLLY_USE_LIBCPP=1
|
|
48
|
+
-DFOLLY_CFG_NO_COROUTINES=1
|
|
49
|
+
-DFOLLY_MOBILE=1
|
|
50
|
+
-DFOLLY_HAVE_RECVMMSG=1
|
|
51
|
+
-DFOLLY_HAVE_PTHREAD=1
|
|
52
|
+
# Once we target android-23 above, we can comment
|
|
53
|
+
# the following line. NDK uses GNU style stderror_r() after API 23.
|
|
54
|
+
-DFOLLY_HAVE_XSI_STRERROR_R=1
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Add all libraries required by the generated specs
|
|
58
|
+
find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++
|
|
59
|
+
find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule)
|
|
60
|
+
find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library
|
|
61
|
+
|
|
62
|
+
# Link all libraries together
|
|
63
|
+
target_link_libraries(
|
|
64
|
+
NitroMLKitTranslate
|
|
65
|
+
fbjni::fbjni # <-- Facebook C++ JNI helpers
|
|
66
|
+
ReactAndroid::jsi # <-- RN: JSI
|
|
67
|
+
react-native-nitro-modules::NitroModules # <-- NitroModules Core :)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Link react-native (different prefab between RN 0.75 and RN 0.76)
|
|
71
|
+
if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
|
|
72
|
+
target_link_libraries(
|
|
73
|
+
NitroMLKitTranslate
|
|
74
|
+
ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
|
|
75
|
+
)
|
|
76
|
+
else()
|
|
77
|
+
target_link_libraries(
|
|
78
|
+
NitroMLKitTranslate
|
|
79
|
+
ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core
|
|
80
|
+
)
|
|
81
|
+
endif()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// NitroMLKitTranslate+autolinking.gradle
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
/// This is a Gradle file that adds all files generated by Nitrogen
|
|
9
|
+
/// to the current Gradle project.
|
|
10
|
+
///
|
|
11
|
+
/// To use it, add this to your build.gradle:
|
|
12
|
+
/// ```gradle
|
|
13
|
+
/// apply from: '../nitrogen/generated/android/NitroMLKitTranslate+autolinking.gradle'
|
|
14
|
+
/// ```
|
|
15
|
+
|
|
16
|
+
logger.warn("[NitroModules] 🔥 NitroMLKitTranslate is boosted by nitro!")
|
|
17
|
+
|
|
18
|
+
android {
|
|
19
|
+
sourceSets {
|
|
20
|
+
main {
|
|
21
|
+
java.srcDirs += [
|
|
22
|
+
// Nitrogen files
|
|
23
|
+
"${project.projectDir}/../nitrogen/generated/android/kotlin"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// NitroMLKitTranslateOnLoad.cpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#ifndef BUILDING_NITROMLKITTRANSLATE_WITH_GENERATED_CMAKE_PROJECT
|
|
9
|
+
#error NitroMLKitTranslateOnLoad.cpp is not being built with the autogenerated CMakeLists.txt project. Is a different CMakeLists.txt building this?
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
#include "NitroMLKitTranslateOnLoad.hpp"
|
|
13
|
+
|
|
14
|
+
#include <jni.h>
|
|
15
|
+
#include <fbjni/fbjni.h>
|
|
16
|
+
#include <NitroModules/HybridObjectRegistry.hpp>
|
|
17
|
+
|
|
18
|
+
#include "JHybridTranslatorSpec.hpp"
|
|
19
|
+
#include <NitroModules/DefaultConstructableObject.hpp>
|
|
20
|
+
|
|
21
|
+
namespace margelo::nitro::mlkit::translate {
|
|
22
|
+
|
|
23
|
+
int initialize(JavaVM* vm) {
|
|
24
|
+
return facebook::jni::initialize(vm, []() {
|
|
25
|
+
::margelo::nitro::mlkit::translate::registerAllNatives();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
struct JHybridTranslatorSpecImpl: public jni::JavaClass<JHybridTranslatorSpecImpl, JHybridTranslatorSpec::JavaPart> {
|
|
30
|
+
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/nitromlkit/translate/HybridTranslator;";
|
|
31
|
+
static std::shared_ptr<JHybridTranslatorSpec> create() {
|
|
32
|
+
static const auto constructorFn = javaClassStatic()->getConstructor<JHybridTranslatorSpecImpl::javaobject()>();
|
|
33
|
+
jni::local_ref<JHybridTranslatorSpec::JavaPart> javaPart = javaClassStatic()->newObject(constructorFn);
|
|
34
|
+
return javaPart->getJHybridTranslatorSpec();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
void registerAllNatives() {
|
|
39
|
+
using namespace margelo::nitro;
|
|
40
|
+
using namespace margelo::nitro::mlkit::translate;
|
|
41
|
+
|
|
42
|
+
// Register native JNI methods
|
|
43
|
+
margelo::nitro::mlkit::translate::JHybridTranslatorSpec::CxxPart::registerNatives();
|
|
44
|
+
|
|
45
|
+
// Register Nitro Hybrid Objects
|
|
46
|
+
HybridObjectRegistry::registerHybridObjectConstructor(
|
|
47
|
+
"Translator",
|
|
48
|
+
[]() -> std::shared_ptr<HybridObject> {
|
|
49
|
+
return JHybridTranslatorSpecImpl::create();
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} // namespace margelo::nitro::mlkit::translate
|